Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a RdsUtilities with the ability to generate an IAM auth token #2057

Merged
merged 1 commit into from
Feb 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/next-release/feature-AWSRDS-e6fec25.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "AWS RDS",
"contributor": "abrooksv",
"description": "Add the ability to generate IAM auth tokens for RDS using `RdsUtilities`"
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public class UtilitiesMethod {
/** Fqcn of the return type of the operation */
private String returnType;

/** Fqcn of the instance type to be created */
private String instanceType;

/**
* The utilities method will call a protected create() method in the hand-written Utilities class.
* These the ordered list of parameters that needs to be passed to the create method.
Expand All @@ -50,4 +53,12 @@ public List<String> getCreateMethodParams() {
public void setCreateMethodParams(List<String> createMethodParams) {
this.createMethodParams = createMethodParams;
}

public String getInstanceType() {
return instanceType;
}

public void setInstanceType(String instanceType) {
this.instanceType = instanceType;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -383,13 +383,19 @@ private TypeName eventStreamType(ShapeModel shapeModel) {
private MethodSpec utilitiesMethod() {
UtilitiesMethod config = model.getCustomizationConfig().getUtilitiesMethod();
ClassName returnType = PoetUtils.classNameFromFqcn(config.getReturnType());
String instanceClass = config.getInstanceType();
if (instanceClass == null) {
instanceClass = config.getReturnType();
}

ClassName instanceType = PoetUtils.classNameFromFqcn(instanceClass);

return MethodSpec.methodBuilder(UtilitiesMethod.METHOD_NAME)
.returns(returnType)
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addStatement("return $T.create($L)",
returnType,
instanceType,
String.join(",", config.getCreateMethodParams()))
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,13 +310,19 @@ private MethodSpec closeMethod() {
private MethodSpec utilitiesMethod() {
UtilitiesMethod config = model.getCustomizationConfig().getUtilitiesMethod();
ClassName returnType = PoetUtils.classNameFromFqcn(config.getReturnType());
String instanceClass = config.getInstanceType();
if (instanceClass == null) {
instanceClass = config.getReturnType();
}

ClassName instanceType = PoetUtils.classNameFromFqcn(instanceClass);

return MethodSpec.methodBuilder(UtilitiesMethod.METHOD_NAME)
.returns(returnType)
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addStatement("return $T.create($L)",
returnType,
instanceType,
String.join(",", config.getCreateMethodParams()))
.build();
}
Expand Down
4 changes: 4 additions & 0 deletions docs/LaunchChangelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,10 @@ The S3 client in 2.0 is drastically different from the client in 1.11, because i

1. An SQS client may no longer access SQS queues in regions different than the one with which the client was configured.

## 4.4. RDS Changes

1. The class`RdsIamAuthTokenGenerator` has been replaced with `RdsUtilities#generateAuthenticationToken`.

# 5. Profile File Changes

The parsing of the `~/.aws/config` and `~/.aws/credentials` has changed to more closely emulate that used by the AWS CLI.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.services.rds;

import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import software.amazon.awssdk.annotations.Immutable;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.signer.Aws4Signer;
import software.amazon.awssdk.auth.signer.params.Aws4PresignerParams;
import software.amazon.awssdk.awscore.client.config.AwsClientOption;
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.rds.model.GenerateAuthenticationTokenRequest;
import software.amazon.awssdk.utils.StringUtils;

@Immutable
@SdkInternalApi
final class DefaultRdsUtilities implements RdsUtilities {
// The time the IAM token is good for. https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html
private static final Duration EXPIRATION_DURATION = Duration.ofMinutes(15);

private final Aws4Signer signer = Aws4Signer.create();
private final Region region;
private final AwsCredentialsProvider credentialsProvider;
private final Clock clock;

DefaultRdsUtilities(DefaultBuilder builder) {
this(builder, Clock.systemUTC());
}

/**
* Test Only
*/
DefaultRdsUtilities(DefaultBuilder builder, Clock clock) {
this.credentialsProvider = builder.credentialsProvider;
this.region = builder.region;
this.clock = clock;
}

/**
* Used by RDS low-level client's utilities() method
*/
@SdkInternalApi
static RdsUtilities create(SdkClientConfiguration clientConfiguration) {
return new DefaultBuilder().clientConfiguration(clientConfiguration).build();
}

@Override
public String generateAuthenticationToken(GenerateAuthenticationTokenRequest request) {
SdkHttpFullRequest httpRequest = SdkHttpFullRequest.builder()
.method(SdkHttpMethod.GET)
.protocol("https")
.host(request.hostname())
.port(request.port())
.encodedPath("/")
.putRawQueryParameter("DBUser", request.username())
.putRawQueryParameter("Action", "connect")
.build();

Instant expirationTime = Instant.now(clock).plus(EXPIRATION_DURATION);
Aws4PresignerParams presignRequest = Aws4PresignerParams.builder()
.signingClockOverride(clock)
.expirationTime(expirationTime)
.awsCredentials(resolveCredentials(request).resolveCredentials())
.signingName("rds-db")
.signingRegion(resolveRegion(request))
.build();

SdkHttpFullRequest fullRequest = signer.presign(httpRequest, presignRequest);
String signedUrl = fullRequest.getUri().toString();

// Format should be: <hostname>>:<port>>/?Action=connect&DBUser=<username>>&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expi...
// Note: This must be the real RDS hostname, not proxy or tunnels
return StringUtils.replacePrefixIgnoreCase(signedUrl, "https://", "");
}

private Region resolveRegion(GenerateAuthenticationTokenRequest request) {
if (request.region() != null) {
return request.region();
}

if (this.region != null) {
return this.region;
}

throw new IllegalArgumentException("Region should be provided either in GenerateAuthenticationTokenRequest object " +
"or RdsUtilities object");
}

private AwsCredentialsProvider resolveCredentials(GenerateAuthenticationTokenRequest request) {
if (request.credentialsProvider() != null) {
return request.credentialsProvider();
}

if (this.credentialsProvider != null) {
return this.credentialsProvider;
}

throw new IllegalArgumentException("CredentialProvider should be provided either in GenerateAuthenticationTokenRequest " +
"object or RdsUtilities object");
}

@SdkInternalApi
static final class DefaultBuilder implements Builder {
private Region region;
private AwsCredentialsProvider credentialsProvider;

DefaultBuilder() {
}

Builder clientConfiguration(SdkClientConfiguration clientConfiguration) {
this.credentialsProvider = clientConfiguration.option(AwsClientOption.CREDENTIALS_PROVIDER);
this.region = clientConfiguration.option(AwsClientOption.AWS_REGION);

return this;
}

@Override
public Builder region(Region region) {
this.region = region;
return this;
}

@Override
public Builder credentialsProvider(AwsCredentialsProvider credentialsProvider) {
this.credentialsProvider = credentialsProvider;
return this;
}

/**
* Construct a {@link RdsUtilities} object.
*/
@Override
public RdsUtilities build() {
return new DefaultRdsUtilities(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.services.rds;

import java.util.function.Consumer;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.rds.model.GenerateAuthenticationTokenRequest;

/**
* Utilities for working with RDS. An instance of this class can be created by:
* <p>
* 1) Using the low-level client {@link RdsClient#utilities()} (or {@link RdsAsyncClient#utilities()}} method. This is
* recommended as SDK will use the same configuration from the {@link RdsClient} object to create the {@link RdsUtilities} object.
*
* <pre>
* RdsClient rdsClient = RdsClient.create();
* RdsUtilities utilities = rdsClient.utilities();
* </pre>
* </p>
*
* <p>
* 2) Directly using the {@link #builder()} method.
*
* <pre>
* RdsUtilities utilities = RdsUtilities.builder()
* .credentialsProvider(DefaultCredentialsProvider.create())
* .region(Region.US_WEST_2)
* .build()
* </pre>
* </p>
*
* Note: This class does not make network calls.
*/
@SdkPublicApi
public interface RdsUtilities {
/**
* Create a builder that can be used to configure and create a {@link RdsUtilities}.
*/
static Builder builder() {
return new DefaultRdsUtilities.DefaultBuilder();
}

/**
* Generates an authorization tokens for IAM authentication to an RDS database.
*
* @param request The request used to generate the auth token
* @return String to use as the RDS auth token
* @throws IllegalArgumentException if the required parameters are not valid
*/
default String generateAuthenticationToken(Consumer<GenerateAuthenticationTokenRequest.Builder> request) {
return generateAuthenticationToken(GenerateAuthenticationTokenRequest.builder().applyMutation(request).build());
}

/**
* Generates an authorization tokens for IAM authentication to an RDS database.
*
* @param request The request used to generate the auth token
* @return String to use as the RDS auth token
* @throws IllegalArgumentException if the required parameters are not valid
*/
default String generateAuthenticationToken(GenerateAuthenticationTokenRequest request) {
zoewangg marked this conversation as resolved.
Show resolved Hide resolved
RdsUtilities.builder().region(Region.US_WEST_2).build();
throw new UnsupportedOperationException();
}

/**
* Builder for creating an instance of {@link RdsUtilities}. It can be configured using {@link RdsUtilities#builder()}.
* Once configured, the {@link RdsUtilities} can created using {@link #build()}.
*/
@SdkPublicApi
interface Builder {
/**
* The default region to use when working with the methods in {@link RdsUtilities} class.
*
* @return This object for method chaining
*/
Builder region(Region region);

/**
* The default credentials provider to use when working with the methods in {@link RdsUtilities} class.
*
* @return This object for method chaining
*/
Builder credentialsProvider(AwsCredentialsProvider credentialsProvider);

/**
* Create a {@link RdsUtilities}
*/
RdsUtilities build();
}
}
Loading