diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleAuthentication.java index ce2a147a..2fa11c56 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleAuthentication.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleAuthentication.java @@ -27,13 +27,22 @@ import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.vault.VaultException; +import org.springframework.vault.authentication.AppRoleAuthenticationOptions.RoleId; +import org.springframework.vault.authentication.AppRoleAuthenticationOptions.SecretId; +import org.springframework.vault.authentication.AppRoleTokens.AbsentSecretId; +import org.springframework.vault.authentication.AppRoleTokens.Provided; +import org.springframework.vault.authentication.AppRoleTokens.Pull; +import org.springframework.vault.authentication.AppRoleTokens.Wrapped; +import org.springframework.vault.authentication.AuthenticationSteps.Node; import org.springframework.vault.client.VaultResponses; import org.springframework.vault.support.VaultResponse; import org.springframework.vault.support.VaultToken; import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestOperations; +import static org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder.get; import static org.springframework.vault.authentication.AuthenticationSteps.HttpRequestBuilder.post; /** @@ -90,41 +99,104 @@ public class AppRoleAuthentication implements ClientAuthentication, Assert.notNull(options, "AppRoleAuthenticationOptions must not be null"); - if (secretIdPullRequired(options)) { + RoleId roleId = options.getRoleId(); + SecretId secretId = options.getSecretId(); - Assert.notNull(options.getRoleId(), - "RoleId must not be null for pull mode via AuthenticationSteps"); + if ((roleId instanceof Wrapped || roleId instanceof Pull) + && (secretId instanceof Wrapped || secretId instanceof Pull)) { - Assert.state(options.getInitialToken() != null || options.getUnwrappingToken() != null, - "One of InitialToken or UnwrappingToken must be set for pull mode via AuthenticationSteps"); - - AuthenticationSteps.Node secretPullRequest = null; - if (options.getInitialToken() != null) { - HttpEntity body = createHttpEntity(options.getInitialToken()); - - secretPullRequest = AuthenticationSteps - .fromHttpRequest( - post("auth/{mount}/role/{role}/secret-id", options.getPath(), - options.getAppRole()).with(body).as( - VaultResponse.class)); - } - else { - HttpEntity body = createHttpEntity(options.getUnwrappingToken()); - secretPullRequest = AuthenticationSteps - .fromHttpRequest( - post("sys/wrapping/unwrap").with(body).as( - VaultResponse.class)); - } - - return secretPullRequest - .map(vaultResponse -> (String) vaultResponse.getRequiredData().get("secret_id")) - .map(secretId -> getAppRoleLogin(options.getRoleId(), secretId)) - .login("auth/{mount}/login", options.getPath()); + throw new IllegalArgumentException( + "RoleId and SecretId are both configured to obtain their values from initial Vault request. AuthenticationSteps supports currently only fetching of a single element."); } - return AuthenticationSteps.fromSupplier( - () -> getAppRoleLogin(options.getRoleId(), options.getSecretId())) // - .login("auth/{mount}/login", options.getPath()); + return getAuthenticationSteps(options, roleId, secretId).login( + "auth/{mount}/login", options.getPath()); + } + + private static Node getAuthenticationSteps(AppRoleAuthenticationOptions options, + RoleId roleId, SecretId secretId) { + + if (roleId instanceof Pull || roleId instanceof Wrapped) { + + Node steps; + + if (roleId instanceof Pull) { + + HttpHeaders headers = createHttpHeaders(((Pull) roleId).getInitialToken()); + + steps = AuthenticationSteps.fromHttpRequest(get( + "auth/{mount}/role/{role}/role-id", options.getPath(), + options.getAppRole()).with(headers).as(VaultResponse.class)); + } + else { + steps = unwrapResponse(((Wrapped) roleId).getInitialToken()); + } + + return steps.map( + vaultResponse -> (String) vaultResponse.getRequiredData().get( + "role_id")).map( + roleIdToken -> { + + return getAppRoleLoginBody( + roleIdToken, + secretId instanceof Provided ? ((Provided) secretId) + .getValue() : null); + }); + } + + if (secretId instanceof Pull || secretId instanceof Wrapped) { + + Node steps; + + if (secretId instanceof Pull) { + HttpHeaders headers = createHttpHeaders(((Pull) secretId) + .getInitialToken()); + + steps = AuthenticationSteps.fromHttpRequest(post( + "auth/{mount}/role/{role}/secret-id", options.getPath(), + options.getAppRole()).with(headers).as(VaultResponse.class)); + } + else { + steps = unwrapResponse(((Wrapped) secretId).getInitialToken()); + } + + return steps.map( + vaultResponse -> (String) vaultResponse.getRequiredData().get( + "secret_id")).map( + secretIdToken -> { + + return getAppRoleLoginBody( + roleId instanceof Provided ? ((Provided) roleId) + .getValue() : null, secretIdToken); + }); + } + + if (roleId instanceof Provided) { + + return AuthenticationSteps.fromSupplier(() -> { + + return getAppRoleLoginBody(((Provided) roleId).getValue(), + secretId instanceof Provided ? ((Provided) secretId).getValue() + : null); + }); + } + + throw new IllegalArgumentException(String.format( + "Provided RoleId/SecretId setup not supported. RoleId: %s, SecretId: %s", + roleId, secretId)); + } + + private static Node unwrapResponse(VaultToken token) { + + return AuthenticationSteps.fromHttpRequest( + get("cubbyhole/response").with(createHttpHeaders(token)).as( + VaultResponse.class)).map( + vaultResponse -> { + + Map data = vaultResponse.getRequiredData(); + return VaultResponses.unwrap((String) data.get("response"), + VaultResponse.class); + }); } @Override @@ -139,10 +211,8 @@ public class AppRoleAuthentication implements ClientAuthentication, private VaultToken createTokenUsingAppRole() { - String roleId = getRoleId(); - String secretId = getSecretId(); - - Map login = getAppRoleLogin(roleId, secretId); + Map login = getAppRoleLoginBody(options.getRoleId(), + options.getSecretId()); try { VaultResponse response = restOperations.postForObject("auth/{mount}/login", @@ -161,16 +231,23 @@ public class AppRoleAuthentication implements ClientAuthentication, } } - private String getRoleId() { + private String getRoleId(RoleId roleId) { - if (roleIdPullRequired(options)) { + if (roleId instanceof Provided) { + return ((Provided) roleId).getValue(); + } + + if (roleId instanceof Pull) { + + VaultToken token = ((Pull) roleId).getInitialToken(); try { - ResponseEntity response = restOperations.exchange( + + ResponseEntity entity = restOperations.exchange( "auth/{mount}/role/{role}/role-id", HttpMethod.GET, - createHttpEntity(options.getInitialToken()), VaultResponse.class, - options.getPath(), options.getAppRole()); - return (String) response.getBody().getRequiredData().get("role_id"); + createHttpEntity(token), VaultResponse.class, options.getPath(), + options.getAppRole()); + return (String) entity.getBody().getRequiredData().get("role_id"); } catch (HttpStatusCodeException e) { throw new VaultException(String.format( @@ -179,66 +256,107 @@ public class AppRoleAuthentication implements ClientAuthentication, } } - return options.getRoleId(); - } + if (roleId instanceof Wrapped) { - private static boolean roleIdPullRequired(AppRoleAuthenticationOptions options) { - return options.getRoleId() == null; - } + VaultToken token = ((Wrapped) roleId).getInitialToken(); - private String getSecretId() { + try { - if (secretIdPullRequired(options)) { - // The secret ID needs to be pulled from Vault. - // Case 1: we use the initial authentication token - if (options.getInitialToken() != null) { - try { - VaultResponse response = restOperations.postForObject( - "auth/{mount}/role/{role}/secret-id", - createHttpEntity(options.getInitialToken()), VaultResponse.class, - options.getPath(), options.getAppRole()); - return (String) response.getRequiredData().get("secret_id"); - } - catch (HttpStatusCodeException e) { - throw new VaultException(String.format( - "Cannot get Secret id using AppRole: %s", - VaultResponses.getError(e.getResponseBodyAsString()))); - } + ResponseEntity entity = restOperations.exchange( + "cubbyhole/response", HttpMethod.GET, createHttpEntity(token), + VaultResponse.class); + + Map data = entity.getBody().getRequiredData(); + VaultResponse response = VaultResponses.unwrap( + (String) data.get("response"), VaultResponse.class); + + return (String) response.getRequiredData().get("role_id"); } - // Case 2: the secret ID needs to be unwrapped - else if (options.getUnwrappingToken() != null) { - try { - VaultResponse response = restOperations.postForObject( - "sys/wrapping/unwrap", - createHttpEntity(options.getUnwrappingToken()), VaultResponse.class, - options.getPath(), options.getAppRole()); - - return (String) response.getRequiredData().get("secret_id"); - } - catch (HttpStatusCodeException e) { - throw new VaultException(String.format( - "Cannot unwrap Secret id using AppRole: %s", - VaultResponses.getError(e.getResponseBodyAsString()) - )); - } + catch (HttpStatusCodeException e) { + throw new VaultException(String.format( + "Cannot unwrap Role id using AppRole: %s", + VaultResponses.getError(e.getResponseBodyAsString()))); } } - return options.getSecretId(); + throw new IllegalArgumentException("Unknown RoleId configuration: " + roleId); } - private static boolean secretIdPullRequired(AppRoleAuthenticationOptions options) { - return options.getSecretId() == null && (options.getInitialToken() != null || options.getUnwrappingToken() != null); + private String getSecretId(SecretId secretId) { + + if (secretId instanceof Provided) { + return ((Provided) secretId).getValue(); + } + + if (secretId instanceof Pull) { + + VaultToken token = ((Pull) secretId).getInitialToken(); + + try { + VaultResponse response = restOperations.postForObject( + "auth/{mount}/role/{role}/secret-id", createHttpEntity(token), + VaultResponse.class, options.getPath(), options.getAppRole()); + return (String) response.getRequiredData().get("secret_id"); + } + catch (HttpStatusCodeException e) { + throw new VaultException(String.format( + "Cannot get Secret id using AppRole: %s", + VaultResponses.getError(e.getResponseBodyAsString()))); + } + } + + if (secretId instanceof Wrapped) { + + VaultToken token = ((Wrapped) secretId).getInitialToken(); + + try { + + ResponseEntity entity = restOperations.exchange( + "cubbyhole/response", HttpMethod.GET, createHttpEntity(token), + VaultResponse.class); + + Map data = entity.getBody().getRequiredData(); + VaultResponse response = VaultResponses.unwrap( + (String) data.get("response"), VaultResponse.class); + + return (String) response.getRequiredData().get("secret_id"); + } + catch (HttpStatusCodeException e) { + throw new VaultException(String.format( + "Cannot unwrap Role id using AppRole: %s", + VaultResponses.getError(e.getResponseBodyAsString()))); + } + } + + throw new IllegalArgumentException("Unknown SecretId configuration: " + secretId); } - private static HttpEntity createHttpEntity(VaultToken token) { + private static HttpHeaders createHttpHeaders(VaultToken token) { HttpHeaders headers = new HttpHeaders(); headers.set("X-Vault-Token", token.getToken()); - return new HttpEntity(null, headers); + + return headers; } - private static Map getAppRoleLogin(String roleId, + private static HttpEntity createHttpEntity(VaultToken token) { + return new HttpEntity(null, createHttpHeaders(token)); + } + + private Map getAppRoleLoginBody(RoleId roleId, SecretId secretId) { + + Map login = new HashMap<>(); + + login.put("role_id", getRoleId(roleId)); + + if (!ClassUtils.isAssignableValue(AbsentSecretId.class, secretId)) { + login.put("secret_id", getSecretId(secretId)); + } + + return login; + } + + private static Map getAppRoleLoginBody(String roleId, @Nullable String secretId) { Map login = new HashMap<>(); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleAuthenticationOptions.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleAuthenticationOptions.java index 4b66f2e3..5a829152 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleAuthenticationOptions.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleAuthenticationOptions.java @@ -17,7 +17,9 @@ package org.springframework.vault.authentication; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; +import org.springframework.vault.authentication.AppRoleTokens.AbsentSecretId; +import org.springframework.vault.authentication.AppRoleTokens.Provided; +import org.springframework.vault.authentication.AppRoleTokens.Pull; import org.springframework.vault.support.VaultToken; /** @@ -45,14 +47,12 @@ public class AppRoleAuthenticationOptions { /** * The RoleId. */ - @Nullable - private final String roleId; + private final RoleId roleId; /** * The Bind SecretId. */ - @Nullable - private final String secretId; + private final SecretId secretId; /** * Role name used to get roleId and secretID @@ -62,26 +62,21 @@ public class AppRoleAuthenticationOptions { /** * Token associated for pull mode (retrieval of secretId/roleId). + * @deprecated since 2.0, use {@link RoleId#pull(VaultToken)}/ + * {@link SecretId#pull(VaultToken)} to configure pull mode for roleId/secretId. */ @Nullable + @Deprecated private final VaultToken initialToken; - /** - * Token for unwrapping the secretId response - */ - @Nullable - private final VaultToken unwrappingToken; - - private AppRoleAuthenticationOptions(String path, @Nullable String roleId, - @Nullable String secretId, @Nullable String appRole, - @Nullable VaultToken initialToken, @Nullable VaultToken unwrappingToken) { + private AppRoleAuthenticationOptions(String path, RoleId roleId, SecretId secretId, + @Nullable String appRole, @Nullable VaultToken initialToken) { this.path = path; this.roleId = roleId; this.secretId = secretId; this.appRole = appRole; this.initialToken = initialToken; - this.unwrappingToken = unwrappingToken; } /** @@ -101,16 +96,14 @@ public class AppRoleAuthenticationOptions { /** * @return the RoleId. */ - @Nullable - public String getRoleId() { + public RoleId getRoleId() { return roleId; } /** * @return the bound SecretId. */ - @Nullable - public String getSecretId() { + public SecretId getSecretId() { return secretId; } @@ -126,21 +119,15 @@ public class AppRoleAuthenticationOptions { /** * @return the initial token for roleId/secretId retrieval in pull mode. * @since 1.1 + * @deprecated since 2.0, use {@link #getRoleId()}/{@link #getSecretId()} to obtain + * configuration modes (pull/wrapped) for an AppRole token. */ @Nullable + @Deprecated public VaultToken getInitialToken() { return initialToken; } - /** - * @return the token used to unwrap the roleId response. - * @since 2.0 - */ - @Nullable - public VaultToken getUnwrappingToken() { - return unwrappingToken; - } - /** * Builder for {@link AppRoleAuthenticationOptions}. */ @@ -149,17 +136,24 @@ public class AppRoleAuthenticationOptions { private String path = DEFAULT_APPROLE_AUTHENTICATION_PATH; @Nullable - private String roleId; + private String providedRoleId; @Nullable - private String secretId; + private RoleId roleId; + @Nullable + private String providedSecretId; + + @Nullable + private SecretId secretId; + + @Nullable private String appRole; + @Nullable + @Deprecated private VaultToken initialToken; - private VaultToken unwrappingToken; - AppRoleAuthenticationOptionsBuilder() { } @@ -183,12 +177,29 @@ public class AppRoleAuthenticationOptions { * * @param roleId must not be empty or {@literal null}. * @return {@code this} {@link AppRoleAuthenticationOptionsBuilder}. + * @since 2.0 */ + public AppRoleAuthenticationOptionsBuilder roleId(RoleId roleId) { + + Assert.notNull(roleId, "RoleId must not be null"); + + this.roleId = roleId; + return this; + } + + /** + * Configure the RoleId. + * + * @param roleId must not be empty or {@literal null}. + * @return {@code this} {@link AppRoleAuthenticationOptionsBuilder}. + * @deprecated since 2.0, use {@link #roleId(RoleId)}. + */ + @Deprecated public AppRoleAuthenticationOptionsBuilder roleId(String roleId) { Assert.hasText(roleId, "RoleId must not be empty"); - this.roleId = roleId; + this.providedRoleId = roleId; return this; } @@ -197,12 +208,29 @@ public class AppRoleAuthenticationOptions { * * @param secretId must not be empty or {@literal null}. * @return {@code this} {@link AppRoleAuthenticationOptionsBuilder}. + * @since 2.0 */ + public AppRoleAuthenticationOptionsBuilder secretId(SecretId secretId) { + + Assert.notNull(secretId, "SecretId must not be null"); + + this.secretId = secretId; + return this; + } + + /** + * Configure a {@code secretId}. + * + * @param secretId must not be empty or {@literal null}. + * @return {@code this} {@link AppRoleAuthenticationOptionsBuilder}. + * @deprecated since 2.0, use {@link #secretId(SecretId)}. + */ + @Deprecated public AppRoleAuthenticationOptionsBuilder secretId(String secretId) { Assert.hasText(secretId, "SecretId must not be empty"); - this.secretId = secretId; + this.providedSecretId = secretId; return this; } @@ -227,7 +255,10 @@ public class AppRoleAuthenticationOptions { * @param initialToken must not be empty or {@literal null}. * @return {@code this} {@link AppRoleAuthenticationOptionsBuilder}. * @since 1.1 + * @deprecated since 2.0, use {@link #roleId(RoleId)}/{@link #secretId(SecretId)} + * to configure pull mode. */ + @Deprecated public AppRoleAuthenticationOptionsBuilder initialToken(VaultToken initialToken) { Assert.notNull(initialToken, "InitialToken must not be null"); @@ -236,21 +267,6 @@ public class AppRoleAuthenticationOptions { return this; } - /** - * Configure a {@code unwrappingToken}. - * - * @param unwrappingToken must not be empty or {@literal null}. - * @return {@code this} {@link AppRoleAuthenticationOptionsBuilder}. - * @since 2.0 - */ - public AppRoleAuthenticationOptionsBuilder unwrappingToken(VaultToken unwrappingToken) { - - Assert.notNull(unwrappingToken, "UnwrappingToken must not be null"); - - this.unwrappingToken = unwrappingToken; - return this; - } - /** * Build a new {@link AppRoleAuthenticationOptions} instance. Requires * {@link #roleId(String)} for push mode or {@link #appRole(String)} and @@ -262,26 +278,158 @@ public class AppRoleAuthenticationOptions { Assert.hasText(path, "Path must not be empty"); - // Role ID is required in order to use push mode (no appRole and initialToken) + if (secretId == null) { - if (StringUtils.isEmpty(roleId) && StringUtils.isEmpty(appRole) - && initialToken == null) { - throw new IllegalArgumentException( - "Either roleId (push mode) or appRole/initialToken (pull mode) must be configured for AppRole authentication"); + if (providedSecretId != null) { + secretId(SecretId.provided(providedSecretId)); + } + else if (initialToken != null) { + secretId(SecretId.pull(initialToken)); + } + else { + secretId(SecretId.absent()); + } } - // AppRole and InitialToken are required in order to use pull mode (no roleId) - if (StringUtils.isEmpty(roleId)) { + if (roleId == null) { + if (providedRoleId != null) { + roleId(RoleId.provided(providedRoleId)); + } + else { + + Assert.notNull( + initialToken, + "AppRole authentication configured for pull mode. InitialToken must not be null (pull mode)"); + roleId(RoleId.pull(initialToken)); + } + } + + if (roleId instanceof Pull || secretId instanceof Pull) { Assert.notNull(appRole, "AppRole authentication configured for pull mode. AppRole must not be null."); - Assert.notNull( - initialToken, - "AppRole authentication configured for pull mode. InitialToken must not be null (pull mode)"); } return new AppRoleAuthenticationOptions(path, roleId, secretId, appRole, - initialToken, unwrappingToken); + initialToken); + } + } + + /** + * RoleId type encapsulating how the roleId is actually obtained. Provides factory + * methods to obtain a {@link RoleId} by wrapping, pull-mode or whether to use a + * string literal. + * + * @since 2.0 + */ + interface RoleId { + + /** + * Create a {@link RoleId} object that obtains its value from unwrapping a + * response using the {@link VaultToken initial token} from a Cubbyhole. + * + * @param initialToken must not be {@literal null}. + * @return {@link RoleId} object that obtains its value from unwrapping a response + * using the {@link VaultToken initial token}. + * @see org.springframework.vault.client.VaultResponses#unwrap(String, Class) + */ + static RoleId wrapped(VaultToken initialToken) { + + Assert.notNull(initialToken, "Initial token must not be null"); + + return new AppRoleTokens.Wrapped(initialToken); + } + + /** + * Create a {@link RoleId} that obtains its value using pull-mode, specifying a + * {@link VaultToken initial token}. The token policy must allow reading the + * roleId from {@code auth/approle/role/(role-name)/role-id}. + * + * @param initialToken must not be {@literal null}. + * @return {@link RoleId} that obtains its value using pull-mode. + */ + static RoleId pull(VaultToken initialToken) { + + Assert.notNull(initialToken, "Initial token must not be null"); + + return new AppRoleTokens.Pull(initialToken); + } + + /** + * Create a {@link RoleId} that encapsulates a static {@code roleId}. + * + * @param roleId must not be {@literal null} or empty. + * @return {@link RoleId} that encapsulates a static {@code roleId}. + */ + static RoleId provided(String roleId) { + + Assert.hasText(roleId, "RoleId must not be null or empty"); + + return new Provided(roleId); + } + } + + /** + * SecretId type encapsulating how the secretId is actually obtained. Provides factory + * methods to obtain a {@link SecretId} by wrapping, pull-mode or whether to use a + * string literal. + * + * @since 2.0 + */ + interface SecretId { + + /** + * Create a {@link SecretId} object that obtains its value from unwrapping a + * response using the {@link VaultToken initial token} from a Cubbyhole. + * + * @param initialToken must not be {@literal null}. + * @return {@link SecretId} object that obtains its value from unwrapping a + * response using the {@link VaultToken initial token}. + * @see org.springframework.vault.client.VaultResponses#unwrap(String, Class) + */ + static SecretId wrapped(VaultToken initialToken) { + + Assert.notNull(initialToken, "Initial token must not be null"); + + return new AppRoleTokens.Wrapped(initialToken); + } + + /** + * Create a {@link SecretId} that obtains its value using pull-mode, specifying a + * {@link VaultToken initial token}. The token policy must allow reading the + * SecretId from {@code auth/approle/role/(role-name)/secret-id}. + * + * @param initialToken must not be {@literal null}. + * @return {@link SecretId} that obtains its value using pull-mode. + */ + static SecretId pull(VaultToken initialToken) { + + Assert.notNull(initialToken, "Initial token must not be null"); + + return new AppRoleTokens.Pull(initialToken); + } + + /** + * Create a {@link SecretId} that encapsulates a static {@code secretId}. + * + * @param secretId must not be {@literal null} or empty. + * @return {@link SecretId} that encapsulates a static {@code SecretId}. + */ + static SecretId provided(String secretId) { + + Assert.hasText(secretId, "SecretId must not be null or empty"); + + return new Provided(secretId); + } + + /** + * Create a {@link SecretId} that represents an absent secretId. Using this object + * will not send a secretId during AppRole login. + * + * @return a {@link SecretId} that represents an absent secretId + */ + static SecretId absent() { + return AbsentSecretId.INSTANCE; } } } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleTokens.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleTokens.java new file mode 100644 index 00000000..f9e8ef27 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleTokens.java @@ -0,0 +1,70 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.vault.authentication; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import org.springframework.vault.authentication.AppRoleAuthenticationOptions.RoleId; +import org.springframework.vault.authentication.AppRoleAuthenticationOptions.SecretId; +import org.springframework.vault.support.VaultToken; + +/** + * Predefined {@link RoleId} token types. + * + * @author Mark Paluch + * @since 2.0 + */ +class AppRoleTokens { + + /** + * Absent secretId. + */ + enum AbsentSecretId implements SecretId { + INSTANCE; + } + + /** + * Wrapped roleId/secretId via Cubbyhole. + */ + @RequiredArgsConstructor(access = AccessLevel.PACKAGE) + @Getter + static class Wrapped implements RoleId, SecretId { + + final VaultToken initialToken; + } + + /** + * Pull-mode. + */ + @RequiredArgsConstructor(access = AccessLevel.PACKAGE) + @Getter + static class Pull implements RoleId, SecretId { + + final VaultToken initialToken; + } + + /** + * Static, provided roleId/secretId. + */ + @RequiredArgsConstructor(access = AccessLevel.PACKAGE) + @Getter + static class Provided implements RoleId, SecretId { + + final String value; + } +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationIntegrationTestBase.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationIntegrationTestBase.java new file mode 100644 index 00000000..2699c7ef --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationIntegrationTestBase.java @@ -0,0 +1,124 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.vault.authentication; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; + +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.vault.core.VaultOperations; +import org.springframework.vault.support.VaultResponse; +import org.springframework.vault.support.VaultToken; +import org.springframework.vault.util.IntegrationTestSupport; +import org.springframework.vault.util.Settings; +import org.springframework.vault.util.Version; + +import static org.junit.Assume.assumeTrue; + +/** + * Integration tests for {@link AppRoleAuthentication}. + * + * @author Mark Paluch + * @author Christophe Tafani-Dereeper + */ +public class AppRoleAuthenticationIntegrationTestBase extends IntegrationTestSupport { + + private static Version SUITABLE_FOR_APP_ROLE_TESTS = Version.parse("0.6.2"); + + @Before + public void before() { + + assumeTrue(prepare().getVersion().isGreaterThanOrEqualTo( + SUITABLE_FOR_APP_ROLE_TESTS)); + + if (!prepare().hasAuth("approle")) { + prepare().mountAuth("approle"); + } + + getVaultOperations().doWithSession(restOperations -> { + + Map withSecretId = new HashMap(); + withSecretId.put("policies", "dummy"); // policy + withSecretId.put("bound_cidr_list", "0.0.0.0/0"); + withSecretId.put("bind_secret_id", "true"); + + restOperations.postForEntity("auth/approle/role/with-secret-id", + withSecretId, Map.class); + + Map noSecretIdRole = new HashMap(); + noSecretIdRole.put("policies", "dummy"); // policy + noSecretIdRole.put("bound_cidr_list", "0.0.0.0/0"); + noSecretIdRole.put("bind_secret_id", "false"); + + restOperations.postForEntity("auth/approle/role/no-secret-id", + noSecretIdRole, Map.class); + + return null; + }); + } + + protected VaultOperations getVaultOperations() { + return prepare().getVaultOperations(); + } + + protected String getRoleId(String roleName) { + return (String) getVaultOperations() + .read(String.format("auth/approle/role/%s/role-id", roleName)).getData() + .get("role_id"); + } + + protected VaultToken generateWrappedSecretIdResponse() { + + return getVaultOperations().doWithVault( + restOperations -> { + + HttpEntity httpEntity = getWrappingHeaders(); + + VaultResponse response = restOperations.exchange( + "auth/approle/role/with-secret-id/secret-id", HttpMethod.PUT, + httpEntity, VaultResponse.class).getBody(); + + return VaultToken.of(response.getWrapInfo().get("token")); + }); + } + + protected VaultToken generateWrappedRoleIdResponse() { + + return getVaultOperations().doWithVault( + restOperations -> { + + HttpEntity httpEntity = getWrappingHeaders(); + + VaultResponse response = restOperations.exchange( + "auth/approle/role/with-secret-id/role-id", HttpMethod.GET, + httpEntity, VaultResponse.class).getBody(); + + return VaultToken.of(response.getWrapInfo().get("token")); + }); + } + + private HttpEntity getWrappingHeaders() { + + HttpHeaders headers = new HttpHeaders(); + headers.set("X-Vault-Wrap-Ttl", "3600"); + headers.set("X-Vault-Token", Settings.token().getToken()); + return new HttpEntity<>(null, headers); + } +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationIntegrationTests.java index 3d8b9141..22cf709a 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationIntegrationTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationIntegrationTests.java @@ -16,32 +16,17 @@ package org.springframework.vault.authentication; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import org.junit.Before; import org.junit.Test; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.lang.Nullable; import org.springframework.vault.VaultException; -import org.springframework.vault.core.RestOperationsCallback; -import org.springframework.vault.core.VaultOperations; +import org.springframework.vault.authentication.AppRoleAuthenticationOptions.RoleId; +import org.springframework.vault.authentication.AppRoleAuthenticationOptions.SecretId; import org.springframework.vault.support.VaultResponse; import org.springframework.vault.support.VaultToken; -import org.springframework.vault.util.IntegrationTestSupport; import org.springframework.vault.util.Settings; -import org.springframework.web.client.RestOperations; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.anyOf; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assume.assumeThat; /** * Integration tests for {@link AppRoleAuthentication}. @@ -49,41 +34,8 @@ import static org.junit.Assume.assumeThat; * @author Mark Paluch * @author Christophe Tafani-Dereeper */ -public class AppRoleAuthenticationIntegrationTests extends IntegrationTestSupport { - - @Before - public void before() { - - assumeThat( - prepare().getVaultOperations().opsForSys().health().getVersion(), - not(anyOf(nullValue(), equalTo(""), containsString("0.5"), - containsString("0.6.1")))); - - if (!prepare().hasAuth("approle")) { - prepare().mountAuth("approle"); - } - - getVaultOperations().doWithSession(restOperations -> { - - Map withSecretId = new HashMap(); - withSecretId.put("policies", "dummy"); // policy - withSecretId.put("bound_cidr_list", "0.0.0.0/0"); - withSecretId.put("bind_secret_id", "true"); - - restOperations.postForEntity("auth/approle/role/with-secret-id", - withSecretId, Map.class); - - Map noSecretIdRole = new HashMap(); - noSecretIdRole.put("policies", "dummy"); // policy - noSecretIdRole.put("bound_cidr_list", "0.0.0.0/0"); - noSecretIdRole.put("bind_secret_id", "false"); - - restOperations.postForEntity("auth/approle/role/no-secret-id", - noSecretIdRole, Map.class); - - return null; - }); - } +public class AppRoleAuthenticationIntegrationTests extends + AppRoleAuthenticationIntegrationTestBase { @Test public void shouldAuthenticateWithRoleIdOnly() { @@ -138,14 +90,29 @@ public class AppRoleAuthenticationIntegrationTests extends IntegrationTestSuppor @Test public void shouldAuthenticateWithWrappedSecretId() { - String roleId = getRoleId("no-secret-id"); - // Simulate that an operator / CM tool created a wrapped secret ID response before the application starts up - String unwrappingToken = generateWrappedSecretIdResponse(); + + String roleId = getRoleId("with-secret-id"); + VaultToken unwrappingToken = generateWrappedSecretIdResponse(); AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() - .unwrappingToken(VaultToken.of(unwrappingToken)) - .roleId(roleId) - .build(); + .secretId(SecretId.wrapped(unwrappingToken)) + .roleId(RoleId.provided(roleId)).build(); + + AppRoleAuthentication authentication = new AppRoleAuthentication(options, + prepare().getRestTemplate()); + + assertThat(authentication.login()).isNotNull(); + } + + @Test + public void shouldAuthenticateWithWrappedRoleIdAndSecretId() { + + VaultToken secretIdToken = generateWrappedSecretIdResponse(); + VaultToken roleIdToken = generateWrappedRoleIdResponse(); + + AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() + .secretId(SecretId.wrapped(secretIdToken)) + .roleId(RoleId.wrapped(roleIdToken)).build(); AppRoleAuthentication authentication = new AppRoleAuthentication(options, prepare().getRestTemplate()); @@ -155,13 +122,13 @@ public class AppRoleAuthenticationIntegrationTests extends IntegrationTestSuppor @Test(expected = VaultException.class) public void shouldAuthenticateWithWrappedSecretIdFailIfUnwrappingTokenExpired() { + String roleId = getRoleId("no-secret-id"); String unwrappingToken = "incorrect-unwrapping-token"; AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() - .unwrappingToken(VaultToken.of(unwrappingToken)) - .roleId(roleId) - .build(); + .secretId(SecretId.wrapped(VaultToken.of(unwrappingToken))) + .roleId(roleId).build(); AppRoleAuthentication authentication = new AppRoleAuthentication(options, prepare().getRestTemplate()); @@ -169,23 +136,6 @@ public class AppRoleAuthenticationIntegrationTests extends IntegrationTestSuppor authentication.login(); } - @Test - public void authenticationStepsShouldAuthenticateWithWrappedSecretId() { - String roleId = getRoleId("no-secret-id"); - String unwrappingToken = generateWrappedSecretIdResponse(); - - AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() - .unwrappingToken(VaultToken.of(unwrappingToken)) - .roleId(roleId) - .build(); - - AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor( - AppRoleAuthentication.createAuthenticationSteps(options), prepare() - .getRestTemplate()); - - assertThat(executor.login()).isNotNull(); - } - @Test(expected = VaultException.class) public void shouldAuthenticatePullModeFailsWithoutSecretId() { @@ -233,89 +183,4 @@ public class AppRoleAuthenticationIntegrationTests extends IntegrationTestSuppor "auth/approle/role/with-secret-id/secret-id-accessor/destroy", customSecretIdResponse.getData()); } - - @Test(expected = VaultException.class) - public void authenticationStepsShouldAuthenticatePullModeFailsWithWrongSecretId() { - - String roleId = getRoleId("with-secret-id"); - - AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() - .roleId(roleId).secretId("this-is-a-wrong-secret-id").build(); - - AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor( - AppRoleAuthentication.createAuthenticationSteps(options), prepare() - .getRestTemplate()); - - assertThat(executor.login()).isNotNull(); - } - - @Test - public void authenticationStepsShouldAuthenticatePushModeWithProvidedSecretId() { - - String roleId = getRoleId("with-secret-id"); - String secretId = "hello_world_two"; - - VaultResponse customSecretIdResponse = getVaultOperations().write( - "auth/approle/role/with-secret-id/custom-secret-id", - Collections.singletonMap("secret_id", secretId)); - - AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() - .roleId(roleId).secretId(secretId).build(); - - AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor( - AppRoleAuthentication.createAuthenticationSteps(options), prepare() - .getRestTemplate()); - - assertThat(executor.login()).isNotNull(); - - getVaultOperations().write( - "auth/approle/role/with-secret-id/secret-id-accessor/destroy", - customSecretIdResponse.getData()); - } - - @Test - public void authenticationStepsShouldAuthenticatePushMode() { - - String roleId = getRoleId("with-secret-id"); - - AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() - .roleId(roleId).appRole("with-secret-id").initialToken(Settings.token()) - .build(); - - AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor( - AppRoleAuthentication.createAuthenticationSteps(options), prepare() - .getRestTemplate()); - - assertThat(executor.login()).isNotNull(); - } - - private VaultOperations getVaultOperations() { - return prepare().getVaultOperations(); - } - - private String getRoleId(String roleName) { - return (String) getVaultOperations() - .read(String.format("auth/approle/role/%s/role-id", roleName)).getData() - .get("role_id"); - } - - @Nullable - private String generateWrappedSecretIdResponse() { - return getVaultOperations().doWithVault(new RestOperationsCallback() { - @Nullable - @Override - public String doWithRestOperations(RestOperations restOperations) { - HttpHeaders headers = new HttpHeaders(); - headers.set("X-Vault-Wrap-Ttl", "3600"); - headers.set("X-Vault-Token", Settings.token().getToken()); - HttpEntity httpEntity = new HttpEntity<>(null, headers); - - VaultResponse response = restOperations.exchange("auth/approle/role/with-secret-id/secret-id", - HttpMethod.PUT, httpEntity, VaultResponse.class).getBody(); - - return response.getWrapInfo().get("token"); - } - }); - - } } diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationStepsIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationStepsIntegrationTests.java new file mode 100644 index 00000000..95e8d1f4 --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationStepsIntegrationTests.java @@ -0,0 +1,165 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.vault.authentication; + +import java.util.Collections; + +import org.junit.Test; + +import org.springframework.vault.VaultException; +import org.springframework.vault.authentication.AppRoleAuthenticationOptions.RoleId; +import org.springframework.vault.authentication.AppRoleAuthenticationOptions.SecretId; +import org.springframework.vault.support.VaultResponse; +import org.springframework.vault.support.VaultToken; +import org.springframework.vault.util.Settings; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link AppRoleAuthentication} using + * {@link AuthenticationStepsExecutor}. + * + * @author Mark Paluch + * @author Christophe Tafani-Dereeper + */ +public class AppRoleAuthenticationStepsIntegrationTests extends + AppRoleAuthenticationIntegrationTestBase { + + @Test + public void authenticationStepsShouldAuthenticateWithWrappedSecretId() { + + String roleId = getRoleId("with-secret-id"); + VaultToken unwrappingToken = generateWrappedSecretIdResponse(); + + AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() + .secretId(SecretId.wrapped(unwrappingToken)).roleId(roleId).build(); + + AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor( + AppRoleAuthentication.createAuthenticationSteps(options), prepare() + .getRestTemplate()); + + assertThat(executor.login()).isNotNull(); + } + + @Test + public void authenticationStepsShouldAuthenticateWithWrappedRoleId() { + + String secretId = (String) getVaultOperations() + .write(String.format("auth/approle/role/%s/secret-id", "with-secret-id"), + null).getData().get("secret_id"); + + VaultToken roleIdToken = generateWrappedRoleIdResponse(); + + AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() + .secretId(SecretId.provided(secretId)) + .roleId(RoleId.wrapped(roleIdToken)).build(); + + AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor( + AppRoleAuthentication.createAuthenticationSteps(options), prepare() + .getRestTemplate()); + + assertThat(executor.login()).isNotNull(); + } + + @Test + public void authenticationStepsShouldAuthenticateWithPullSecretId() { + + String roleId = getRoleId("with-secret-id"); + + AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() + .appRole("with-secret-id").secretId(SecretId.pull(Settings.token())) + .roleId(roleId).build(); + + AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor( + AppRoleAuthentication.createAuthenticationSteps(options), prepare() + .getRestTemplate()); + + assertThat(executor.login()).isNotNull(); + } + + @Test + public void authenticationStepsShouldAuthenticateWithPullRoleId() { + + String secretId = (String) getVaultOperations() + .write(String.format("auth/approle/role/%s/secret-id", "with-secret-id"), + null).getData().get("secret_id"); + + AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() + .secretId(SecretId.provided(secretId)).appRole("with-secret-id") + .roleId(RoleId.pull(Settings.token())).build(); + + AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor( + AppRoleAuthentication.createAuthenticationSteps(options), prepare() + .getRestTemplate()); + + assertThat(executor.login()).isNotNull(); + } + + @Test(expected = VaultException.class) + public void authenticationStepsShouldAuthenticatePullModeFailsWithWrongSecretId() { + + String roleId = getRoleId("with-secret-id"); + + AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() + .roleId(roleId).secretId("this-is-a-wrong-secret-id").build(); + + AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor( + AppRoleAuthentication.createAuthenticationSteps(options), prepare() + .getRestTemplate()); + + assertThat(executor.login()).isNotNull(); + } + + @Test + public void authenticationStepsShouldAuthenticatePushModeWithProvidedSecretId() { + + String roleId = getRoleId("with-secret-id"); + String secretId = "hello_world_two"; + + VaultResponse customSecretIdResponse = getVaultOperations().write( + "auth/approle/role/with-secret-id/custom-secret-id", + Collections.singletonMap("secret_id", secretId)); + + AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() + .roleId(roleId).secretId(secretId).build(); + + AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor( + AppRoleAuthentication.createAuthenticationSteps(options), prepare() + .getRestTemplate()); + + assertThat(executor.login()).isNotNull(); + + getVaultOperations().write( + "auth/approle/role/with-secret-id/secret-id-accessor/destroy", + customSecretIdResponse.getData()); + } + + @Test + public void authenticationStepsShouldAuthenticatePushMode() { + + String roleId = getRoleId("with-secret-id"); + + AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() + .roleId(roleId).appRole("with-secret-id").initialToken(Settings.token()) + .build(); + + AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor( + AppRoleAuthentication.createAuthenticationSteps(options), prepare() + .getRestTemplate()); + + assertThat(executor.login()).isNotNull(); + } +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationUnitTests.java index 6e5e3044..dcfdbe71 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationUnitTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AppRoleAuthenticationUnitTests.java @@ -17,6 +17,7 @@ package org.springframework.vault.authentication; import java.time.Duration; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; @@ -24,6 +25,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.vault.VaultException; +import org.springframework.vault.authentication.AppRoleAuthenticationOptions.SecretId; import org.springframework.vault.client.VaultClients; import org.springframework.vault.client.VaultClients.PrefixAwareUriTemplateHandler; import org.springframework.vault.support.VaultToken; @@ -46,6 +48,8 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat */ public class AppRoleAuthenticationUnitTests { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private RestTemplate restTemplate; private MockRestServiceServer mockRest; @@ -177,30 +181,31 @@ public class AppRoleAuthenticationUnitTests { } @Test - public void loginShouldUnwrapSecretIdResponse() { + public void loginShouldUnwrapSecretIdResponse() throws Exception { AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() - .roleId("my_role_id").unwrappingToken(VaultToken.of("unwrapping_token")) - .build(); + .roleId("my_role_id") + .secretId(SecretId.wrapped(VaultToken.of("unwrapping_token"))).build(); + + String wrappedResponse = "{" + + " \"request_id\": \"aad6a19b-a42b-b750-cafb-51087662f53e\"," + + " \"lease_id\": \"\"," + " \"renewable\": false," + + " \"lease_duration\": 0," + " \"data\": {" + + " \"secret_id\": \"my_secret_id\"," + + " \"secret_id_accessor\": \"my_secret_id_accessor\"" + " }," + + " \"wrap_info\": null," + " \"warnings\": null," + " \"auth\": null" + + "}"; // Expect a first request to unwrap the response - mockRest.expect(requestTo("/sys/wrapping/unwrap")) + mockRest.expect(requestTo("/cubbyhole/response")) .andExpect(header("X-Vault-Token", "unwrapping_token")) - .andExpect(method(HttpMethod.POST)) + .andExpect(method(HttpMethod.GET)) .andRespond( - withSuccess() - .contentType(MediaType.APPLICATION_JSON) - .body("{" - + " \"request_id\": \"aad6a19b-a42b-b750-cafb-51087662f53e\"," - + " \"lease_id\": \"\"," - + " \"renewable\": false," - + " \"lease_duration\": 0," - + " \"data\": {" - + " \"secret_id\": \"my_secret_id\"," - + " \"secret_id_accessor\": \"my_secret_id_accessor\"" - + " }," + " \"wrap_info\": null," - + " \"warnings\": null," + " \"auth\": null" - + "}")); + withSuccess().contentType(MediaType.APPLICATION_JSON).body( + "{\"data\":{\"response\":" + + OBJECT_MAPPER + .writeValueAsString(wrappedResponse) + + "} }")); // Also expect a second request to retrieve a token mockRest.expect(requestTo("/auth/approle/login")) diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index 6960c799..b5a0fef8 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -10,6 +10,7 @@ * Transit batch encrypt and decrypt support. * Policy management for policies stored as JSON. * Support CSR signing, certificate revocation and CRL retrieval. +* RoleId/SecretId unwrapping for <>. [[new-features.1-1-0]] === What's new in Spring Vault 1.1.0 diff --git a/src/main/asciidoc/reference/authentication.adoc b/src/main/asciidoc/reference/authentication.adoc index 80129900..e5897389 100644 --- a/src/main/asciidoc/reference/authentication.adoc +++ b/src/main/asciidoc/reference/authentication.adoc @@ -197,7 +197,7 @@ AppRole authentication consists of two hard to guess (secret) tokens: RoleId and Spring Vault supports AppRole authentication by providing either RoleId only or together with a provided SecretId and fetching RoleId/SecretId from Vault -(push and pull modes). +(push and pull modes with response unwrapping). ==== [source,java] @@ -211,8 +211,8 @@ class AppConfig extends AbstractVaultConfiguration { public ClientAuthentication clientAuthentication() { AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() - .roleId("…") - .secretId("…") + .roleId(RoleId.provided("…")) + .secretId(SecretId.wrapped(VaultToken.of("…"))) .build(); return new AppRoleAuthentication(options, restOperations()); @@ -238,9 +238,11 @@ class AppConfig extends AbstractVaultConfiguration { @Override public ClientAuthentication clientAuthentication() { + VaultToken initialToken = VaultToken.of("…"); AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() .appRole("…") - .initialToken(VaultToken.of("…")) + .roleId(RoleId.pull(initialToken)) + .secretId(SecretId.pull(initialToken)) .build(); return new AppRoleAuthentication(options, restOperations());