From 9618a50646b6d71540dd0f12a3f7cbf9e6657db4 Mon Sep 17 00:00:00 2001 From: Stefan Kalscheuer Date: Fri, 21 Oct 2016 18:08:06 +0200 Subject: [PATCH] #4 token creation implemented --- .../jvault/connector/HTTPVaultConnector.java | 67 ++++++++++++++-- .../jvault/connector/VaultConnector.java | 22 +++++- .../connector/HTTPVaultConnectorTest.java | 79 +++++++++++++++++++ 3 files changed, 159 insertions(+), 9 deletions(-) diff --git a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java index 471a8cc..004472d 100644 --- a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java +++ b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java @@ -54,7 +54,10 @@ public class HTTPVaultConnector implements VaultConnector { private static final String PATH_UNSEAL = "sys/unseal"; private static final String PATH_INIT = "sys/init"; private static final String PATH_AUTH = "sys/auth"; - private static final String PATH_TOKEN_LOOKUP = "auth/token/lookup"; + private static final String PATH_TOKEN = "auth/token"; + private static final String PATH_LOOKUP = "/lookup"; + private static final String PATH_CREATE = "/create"; + private static final String PATH_CREATE_ORPHAN = "/create-orphan"; private static final String PATH_AUTH_USERPASS = "auth/userpass/login/"; private static final String PATH_AUTH_APPID = "auth/app-id/"; private static final String PATH_SECRET = "secret"; @@ -193,7 +196,7 @@ public class HTTPVaultConnector implements VaultConnector { this.token = token; this.tokenTTL = 0; try { - String response = requestPost(PATH_TOKEN_LOOKUP, new HashMap<>()); + String response = requestPost(PATH_TOKEN + PATH_LOOKUP, new HashMap<>()); TokenResponse res = jsonMapper.readValue(response, TokenResponse.class); authorized = true; return res; @@ -305,6 +308,9 @@ public class HTTPVaultConnector implements VaultConnector { @Override public boolean writeSecret(final String key, final String value) throws VaultConnectorException { + if (!isAuthorized()) + throw new AuthorizationRequiredException(); + if (key == null || key.isEmpty()) throw new InvalidRequestException("Secret path must not be empty."); @@ -320,7 +326,12 @@ public class HTTPVaultConnector implements VaultConnector { /* Request HTTP response and expect empty result */ String response = requestDelete(PATH_SECRET + "/" + key); - return response.equals(""); + + /* Response should be code 204 without content */ + if (!response.equals("")) + throw new InvalidResponseException("Received response where non was expected."); + + return true; } @Override @@ -330,7 +341,12 @@ public class HTTPVaultConnector implements VaultConnector { /* Request HTTP response and expect empty result */ String response = requestPut(PATH_REVOKE + leaseID, new HashMap<>()); - return response.equals(""); + + /* Response should be code 204 without content */ + if (!response.equals("")) + throw new InvalidResponseException("Received response where non was expected."); + + return true; } @Override @@ -340,9 +356,44 @@ public class HTTPVaultConnector implements VaultConnector { } @Override - public TokenResponse createToken(final Token token) throws VaultConnectorException { - /* TODO */ - return null; + public AuthResponse createToken(final Token token) throws VaultConnectorException { + return createTokenInternal(token, PATH_TOKEN + PATH_CREATE); + } + + @Override + public AuthResponse createToken(final Token token, boolean orphan) throws VaultConnectorException { + return createTokenInternal(token, PATH_TOKEN + PATH_CREATE_ORPHAN); + } + + @Override + public AuthResponse createToken(final Token token, final String role) throws VaultConnectorException { + if (role == null || role.isEmpty()) + throw new InvalidRequestException("No role name specified."); + return createTokenInternal(token, PATH_TOKEN + PATH_CREATE + "/" + role); + } + + /** + * Create token. + * Centralized method to handle different token creation requests. + * + * @param token the token + * @param path request path + * @return the response + * @throws VaultConnectorException on error + */ + private AuthResponse createTokenInternal(final Token token, final String path) throws VaultConnectorException { + if (!isAuthorized()) + throw new AuthorizationRequiredException(); + + if (token == null) + throw new InvalidRequestException("Token must be provided."); + + String response = requestPost(path, token); + try { + return jsonMapper.readValue(response, AuthResponse.class); + } catch (IOException e) { + throw new InvalidResponseException("Unable to parse response", e); + } } @@ -354,7 +405,7 @@ public class HTTPVaultConnector implements VaultConnector { * @return HTTP response * @throws VaultConnectorException on connection error */ - private String requestPost(final String path, final Map payload) throws VaultConnectorException { + private String requestPost(final String path, final Object payload) throws VaultConnectorException { /* Initialize post */ HttpPost post = new HttpPost(baseURL + path); /* generate JSON from payload */ diff --git a/src/main/java/de/stklcode/jvault/connector/VaultConnector.java b/src/main/java/de/stklcode/jvault/connector/VaultConnector.java index 48f59ec..960b6e0 100644 --- a/src/main/java/de/stklcode/jvault/connector/VaultConnector.java +++ b/src/main/java/de/stklcode/jvault/connector/VaultConnector.java @@ -217,5 +217,25 @@ public interface VaultConnector { * @return the result response * @throws VaultConnectorException on error */ - TokenResponse createToken(final Token token) throws VaultConnectorException; + AuthResponse createToken(final Token token) throws VaultConnectorException; + + /** + * Create a new token. + * + * @param token the token + * @param orphan create orphan token + * @return the result response + * @throws VaultConnectorException on error + */ + AuthResponse createToken(final Token token, boolean orphan) throws VaultConnectorException; + + /** + * Create a new token for specific role. + * + * @param token the token + * @param role the role name + * @return the result response + * @throws VaultConnectorException on error + */ + AuthResponse createToken(final Token token, final String role) throws VaultConnectorException; } diff --git a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java index 920d75c..82be7d8 100644 --- a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java +++ b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorTest.java @@ -17,6 +17,8 @@ package de.stklcode.jvault.connector; import de.stklcode.jvault.connector.exception.InvalidResponseException; +import de.stklcode.jvault.connector.model.Token; +import de.stklcode.jvault.connector.model.TokenBuilder; import de.stklcode.jvault.connector.model.response.*; import de.stklcode.jvault.connector.test.Credentials; import de.stklcode.jvault.connector.test.VaultConfiguration; @@ -33,6 +35,8 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.net.ServerSocket; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import static org.hamcrest.Matchers.*; @@ -373,6 +377,81 @@ public class HTTPVaultConnectorTest { } } + /** + * Test revocation of secrets. + */ + @Test + public void createTokenTest() { + authRoot(); + assumeTrue(connector.isAuthorized()); + + /* Create token */ + Token token = new TokenBuilder() + .withId("test-id") + .withDisplayName("test name") + .build(); + + /* Create token */ + try { + AuthResponse res = connector.createToken(token); + assertThat("No result given.", res, is(notNullValue())); + assertThat("Token creation returned warnings.", res.getWarnings(), is(nullValue())); + assertThat("Invalid token ID returned.", res.getAuth().getClientToken(), is("test-id")); + assertThat("Invalid number of policies returned.", res.getAuth().getPolicies(), hasSize(1)); + assertThat("Root policy not inherited.", res.getAuth().getPolicies(), contains("root")); + assertThat("Metadata unexpected.", res.getAuth().getMetadata(), is(nullValue())); + assertThat("Root token should not be renewable", res.getAuth().isRenewable(), is(false)); + } catch (VaultConnectorException e) { + fail("Secret written to inaccessible path."); + } + + /* Create token with attributes */ + token = new TokenBuilder() + .withId("test-id2") + .withDisplayName("test name 2") + .withPolicies(Collections.singletonList("testpolicy")) + .withoutDefaultPolicy() + .withMeta("foo", "bar") + .build(); + try { + AuthResponse res = connector.createToken(token); + assertThat("Invalid token ID returned.", res.getAuth().getClientToken(), is("test-id2")); + assertThat("Invalid number of policies returned.", res.getAuth().getPolicies(), hasSize(1)); + assertThat("Root policy not inherited.", res.getAuth().getPolicies(), contains("testpolicy")); + assertThat("Metadata not given.", res.getAuth().getMetadata(), is(notNullValue())); + assertThat("Metadata not correct.", res.getAuth().getMetadata().get("foo"), is("bar")); + assertThat("Token should be renewable", res.getAuth().isRenewable(), is(true)); + } catch (VaultConnectorException e) { + fail("Secret written to inaccessible path."); + } + + /* Overwrite token */ + token = new TokenBuilder() + .withId("test-id2") + .withDisplayName("test name 3") + .withPolicies(Arrays.asList("pol1", "pol2")) + .withDefaultPolicy() + .withMeta("test", "success") + .withMeta("key", "value") + .withTtl(1234) + .build(); + try { + AuthResponse res = connector.createToken(token); + assertThat("Invalid token ID returned.", res.getAuth().getClientToken(), is("test-id2")); + assertThat("Invalid number of policies returned.", res.getAuth().getPolicies(), hasSize(3)); + assertThat("Policies not returned as expected.", res.getAuth().getPolicies(), contains("default", "pol1", "pol2")); + assertThat("Old policy not overwritten.", res.getAuth().getPolicies(), not(contains("testpolicy"))); + assertThat("Metadata not given.", res.getAuth().getMetadata(), is(notNullValue())); + assertThat("Metadata not correct.", res.getAuth().getMetadata().get("test"), is("success")); + assertThat("Metadata not correct.", res.getAuth().getMetadata().get("key"), is("value")); + assertThat("Old metadata not overwritten.", res.getAuth().getMetadata().get("foo"), is(nullValue())); + assertThat("TTL not set correctly", res.getAuth().getLeaseDuration(), is(1234)); + assertThat("Token should be renewable", res.getAuth().isRenewable(), is(true)); + } catch (VaultConnectorException e) { + fail("Secret written to inaccessible path."); + } + } + /** * Initialize Vault with resource datastore and generated configuration. * @return Vault Configuration