Polishing.

Move RegionProvider to AwsIamAuthenticationOptions. Update documentation.

Original pull request: gh-693.
See gh-253
This commit is contained in:
Mark Paluch
2022-05-20 10:15:55 +02:00
parent 8f6ada53f8
commit 04ed18f83b
5 changed files with 82 additions and 39 deletions

View File

@@ -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.
* <p>
* 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 <a href="https://www.vaultproject.io/docs/auth/aws.html">Auth Backend: aws
* (IAM)</a>
@@ -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<String, String> 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<String, String> createRequestBody(AwsIamAuthenticationOptions options,
AwsCredentials credentials) {
AwsCredentials credentials, Region region) {
Map<String, String> 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<String, List<String>> 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();

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -63,7 +63,7 @@
<netty.version>4.1.72.Final</netty.version>
<okhttp3.version>3.14.9</okhttp3.version>
<jackson-databind.version>2.13.3</jackson-databind.version>
<aws-java-sdk.version>2.17.152</aws-java-sdk.version>
<aws-java-sdk.version>2.17.195</aws-java-sdk.version>
<google-api-services-iam.version>v1-rev20210226-1.31.0</google-api-services-iam.version>
<google-cloud-iamcredentials.version>1.2.2</google-cloud-iamcredentials.version>
<google-auth-library-oauth2-http.version>0.22.2</google-auth-library-oauth2-http.version>

View File

@@ -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 <<vault.authentication.userpass,Username/Password authentication>> for Username/Password, LDAP, Okta, and RADIUS authentication.