Skip to content

Commit

Permalink
Add a RdsUtilities class with the ability to generate an IAM auth token
Browse files Browse the repository at this point in the history
  • Loading branch information
Austin Brooks authored and zoewangg committed Feb 18, 2021
1 parent b0bdb2d commit caab8b9
Show file tree
Hide file tree
Showing 10 changed files with 623 additions and 3 deletions.
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) {
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

0 comments on commit caab8b9

Please sign in to comment.