From 04ed18f83b7e32c8d7949ec415caa894d1a33810 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 20 May 2022 10:15:55 +0200 Subject: [PATCH] Polishing. Move RegionProvider to AwsIamAuthenticationOptions. Update documentation. Original pull request: gh-693. See gh-253 --- .../authentication/AwsIamAuthentication.java | 54 ++++++++++--------- .../AwsIamAuthenticationOptions.java | 42 +++++++++++++-- .../AwsIamAuthenticationUnitTests.java | 16 +++--- spring-vault-dependencies/pom.xml | 2 +- src/main/asciidoc/new-features.adoc | 7 ++- 5 files changed, 82 insertions(+), 39 deletions(-) diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AwsIamAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AwsIamAuthentication.java index a0285eda..41310f91 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AwsIamAuthentication.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AwsIamAuthentication.java @@ -19,27 +19,26 @@ import java.io.ByteArrayInputStream; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.List; - -import software.amazon.awssdk.http.SdkHttpFullRequest; -import software.amazon.awssdk.auth.signer.Aws4Signer; -import software.amazon.awssdk.auth.signer.params.Aws4SignerParams; -import software.amazon.awssdk.auth.credentials.AwsCredentials; -import software.amazon.awssdk.http.SdkHttpMethod; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.signer.Aws4Signer; +import software.amazon.awssdk.auth.signer.params.Aws4SignerParams; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.regions.Region; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.util.Base64Utils; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.vault.VaultException; import org.springframework.vault.support.VaultResponse; @@ -53,17 +52,19 @@ import org.springframework.web.client.RestOperations; * AWS IAM authentication creates a {@link Aws4Signer signed} HTTP request that is * executed by Vault to get the identity of the signer using AWS STS * {@literal GetCallerIdentity}. A signature requires - * {@link com.amazonaws.auth.AwsCredentials} to calculate the signature. + * {@link software.amazon.awssdk.auth.credentials.AwsCredentials} to calculate the + * signature. *

* This authentication requires AWS' Java SDK to sign request parameters and calculate the - * signature key. Using an appropriate {@link com.amazonaws.auth.AwsCredentialsProvider} - * allows authentication within AWS-EC2 instances with an assigned profile, within ECS and - * Lambda instances. + * signature key. Using an appropriate + * {@link software.amazon.awssdk.auth.credentials.AwsCredentialsProvider} allows + * authentication within AWS-EC2 instances with an assigned profile, within ECS and Lambda + * instances. * * @author Mark Paluch * @since 1.1 * @see AwsIamAuthenticationOptions - * @see com.amazonaws.auth.AwsCredentialsProvider + * @see software.amazon.awssdk.auth.credentials.AwsCredentialsProvider * @see RestOperations * @see Auth Backend: aws * (IAM) @@ -115,14 +116,15 @@ public class AwsIamAuthentication implements ClientAuthentication, Authenticatio Assert.notNull(options, "AwsIamAuthenticationOptions must not be null"); AwsCredentials credentials = options.getCredentialsProvider().resolveCredentials(); + Region region = options.getRegionProvider().getRegion(); - return createAuthenticationSteps(options, credentials); + return createAuthenticationSteps(options, credentials, region); } protected static AuthenticationSteps createAuthenticationSteps(AwsIamAuthenticationOptions options, - AwsCredentials credentials) { + AwsCredentials credentials, Region region) { - return AuthenticationSteps.fromSupplier(() -> createRequestBody(options, credentials)) // + return AuthenticationSteps.fromSupplier(() -> createRequestBody(options, credentials, region)) // .login(AuthenticationUtil.getLoginPath(options.getPath())); } @@ -133,7 +135,8 @@ public class AwsIamAuthentication implements ClientAuthentication, Authenticatio @Override public AuthenticationSteps getAuthenticationSteps() { - return createAuthenticationSteps(this.options, this.options.getCredentialsProvider().resolveCredentials()); + return createAuthenticationSteps(this.options, this.options.getCredentialsProvider().resolveCredentials(), + this.options.getRegionProvider().getRegion()); } @SuppressWarnings("unchecked") @@ -174,17 +177,20 @@ public class AwsIamAuthentication implements ClientAuthentication, Authenticatio * @return the map containing body key-value pairs. */ protected static Map createRequestBody(AwsIamAuthenticationOptions options) { - return createRequestBody(options, options.getCredentialsProvider().resolveCredentials()); + return createRequestBody(options, options.getCredentialsProvider().resolveCredentials(), + options.getRegionProvider().getRegion()); } /** * Create the request body to perform a Vault login using the AWS-IAM authentication * method. * @param options must not be {@literal null}. + * @param credentials must not be {@literal null}. + * @param region must not be {@literal null}. * @return the map containing body key-value pairs. */ private static Map createRequestBody(AwsIamAuthenticationOptions options, - AwsCredentials credentials) { + AwsCredentials credentials, Region region) { Map login = new HashMap<>(); @@ -192,17 +198,18 @@ public class AwsIamAuthentication implements ClientAuthentication, Authenticatio login.put("iam_request_url", Base64Utils.encodeToString(options.getEndpointUri().toString().getBytes())); login.put("iam_request_body", REQUEST_BODY_BASE64_ENCODED); - String headerJson = getSignedHeaders(options, credentials); + String headerJson = getSignedHeaders(options, credentials, region); login.put("iam_request_headers", Base64Utils.encodeToString(headerJson.getBytes())); - if (!StringUtils.isEmpty(options.getRole())) { + if (!ObjectUtils.isEmpty(options.getRole())) { login.put("role", options.getRole()); } return login; } - private static String getSignedHeaders(AwsIamAuthenticationOptions options, AwsCredentials credentials) { + private static String getSignedHeaders(AwsIamAuthenticationOptions options, AwsCredentials credentials, + Region region) { Map> headers = createIamRequestHeaders(options); @@ -211,7 +218,6 @@ public class AwsIamAuthentication implements ClientAuthentication, Authenticatio .method(SdkHttpMethod.POST).uri(options.getEndpointUri()); SdkHttpFullRequest request = builder.build(); - Region region = DefaultAwsRegionProviderChain.builder().build().getRegion(); Aws4Signer signer = Aws4Signer.create(); Aws4SignerParams signerParams = Aws4SignerParams.builder().awsCredentials(credentials).signingName("sts") .signingRegion(region).build(); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AwsIamAuthenticationOptions.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AwsIamAuthenticationOptions.java index 7da8d7cd..426b195f 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AwsIamAuthenticationOptions.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AwsIamAuthenticationOptions.java @@ -20,6 +20,8 @@ import java.net.URI; import software.amazon.awssdk.auth.credentials.AwsCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.providers.AwsRegionProvider; +import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -51,6 +53,11 @@ public class AwsIamAuthenticationOptions { */ private final AwsCredentialsProvider credentialsProvider; + /** + * Region provider. + */ + private final AwsRegionProvider regionProvider; + /** * Name of the role against which the login is being attempted. If role is not * specified, the friendly name (i.e., role name or username) of the IAM principal @@ -71,11 +78,12 @@ public class AwsIamAuthenticationOptions { */ private final URI endpointUri; - private AwsIamAuthenticationOptions(String path, AwsCredentialsProvider credentialsProvider, @Nullable String role, - @Nullable String serverId, URI endpointUri) { + private AwsIamAuthenticationOptions(String path, AwsCredentialsProvider credentialsProvider, + AwsRegionProvider regionProvider, @Nullable String role, @Nullable String serverId, URI endpointUri) { this.path = path; this.credentialsProvider = credentialsProvider; + this.regionProvider = regionProvider; this.role = role; this.serverId = serverId; this.endpointUri = endpointUri; @@ -102,6 +110,15 @@ public class AwsIamAuthenticationOptions { return this.credentialsProvider; } + /** + * @return the region provider to obtain the AWS region to be used for computing the + * signature. + * @since 3.0 + */ + public AwsRegionProvider getRegionProvider() { + return this.regionProvider; + } + /** * @return the role, may be {@literal null} if none. */ @@ -149,6 +166,8 @@ public class AwsIamAuthenticationOptions { @Nullable private AwsCredentialsProvider credentialsProvider; + private AwsRegionProvider regionProvider = DefaultAwsRegionProviderChain.builder().build(); + @Nullable private String role; @@ -204,6 +223,21 @@ public class AwsIamAuthenticationOptions { return this; } + /** + * Configure an {@link AwsRegionProvider}, required to calculate the region to be + * used for computing the signature. + * @param regionProvider must not be {@literal null}. + * @return {@code this} {@link AwsIamAuthenticationOptionsBuilder}. + * @since 3.0 + */ + public AwsIamAuthenticationOptionsBuilder regionProvider(AwsRegionProvider regionProvider) { + + Assert.notNull(regionProvider, "AwsRegionProvider must not be null"); + + this.regionProvider = regionProvider; + return this; + } + /** * Configure the name of the role against which the login is being attempted. If * role is not specified, the friendly name (i.e., role name or username) of the @@ -267,8 +301,8 @@ public class AwsIamAuthenticationOptions { Assert.state(this.credentialsProvider != null, "Credentials or CredentialProvider must not be null"); - return new AwsIamAuthenticationOptions(this.path, this.credentialsProvider, this.role, this.serverId, - this.endpointUri); + return new AwsIamAuthenticationOptions(this.path, this.credentialsProvider, this.regionProvider, this.role, + this.serverId, this.endpointUri); } } diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AwsIamAuthenticationUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AwsIamAuthenticationUnitTests.java index 35a7f6dd..17e99ed4 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/AwsIamAuthenticationUnitTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/AwsIamAuthenticationUnitTests.java @@ -17,10 +17,10 @@ package org.springframework.vault.authentication; import java.time.Duration; -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.regions.Region; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; @@ -29,11 +29,9 @@ import org.springframework.vault.client.VaultClients; import org.springframework.vault.support.VaultToken; import org.springframework.web.client.RestTemplate; -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.client.match.MockRestRequestMatchers.jsonPath; -import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; -import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; -import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; +import static org.springframework.test.web.client.response.MockRestResponseCreators.*; /** * Unit test for {@link AwsIamAuthentication}. @@ -68,7 +66,7 @@ class AwsIamAuthenticationUnitTests { + "}")); AwsIamAuthenticationOptions options = AwsIamAuthenticationOptions.builder().role("foo-role") - .credentials(AwsBasicCredentials.create("foo", "bar")).build(); + .regionProvider(() -> Region.US_WEST_1).credentials(AwsBasicCredentials.create("foo", "bar")).build(); AwsIamAuthentication sut = new AwsIamAuthentication(options, this.restTemplate); VaultToken login = sut.login(); @@ -91,7 +89,7 @@ class AwsIamAuthenticationUnitTests { + "}")); AwsIamAuthenticationOptions options = AwsIamAuthenticationOptions.builder().role("foo-role") - .credentials(AwsBasicCredentials.create("foo", "bar")).build(); + .regionProvider(() -> Region.US_WEST_1).credentials(AwsBasicCredentials.create("foo", "bar")).build(); AuthenticationSteps steps = AwsIamAuthentication.createAuthenticationSteps(options); AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(steps, this.restTemplate); diff --git a/spring-vault-dependencies/pom.xml b/spring-vault-dependencies/pom.xml index 5c96d114..73851c3d 100644 --- a/spring-vault-dependencies/pom.xml +++ b/spring-vault-dependencies/pom.xml @@ -63,7 +63,7 @@ 4.1.72.Final 3.14.9 2.13.3 - 2.17.152 + 2.17.195 v1-rev20210226-1.31.0 1.2.2 0.22.2 diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index ed3fedec..43bfca86 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -1,7 +1,12 @@ [[new-features]] == New & Noteworthy -[[new-features.2-4-0]] +[[new-features.3-0-0]] +=== What's new in Spring Vault 3.0 + +* Upgrade to Spring Framework 6 and Java 17 baseline +* Upgrade to AWS SDK 2. + === What's new in Spring Vault 2.4 * Support for <> for Username/Password, LDAP, Okta, and RADIUS authentication.