1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"). 5 * You may not use this file except in compliance with the License. 6 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.services.rds; 17 18 import java.time.Clock; 19 import java.time.Duration; 20 import java.time.Instant; 21 import software.amazon.awssdk.annotations.Immutable; 22 import software.amazon.awssdk.annotations.SdkInternalApi; 23 import software.amazon.awssdk.auth.credentials.AwsCredentials; 24 import software.amazon.awssdk.auth.credentials.CredentialUtils; 25 import software.amazon.awssdk.auth.signer.Aws4Signer; 26 import software.amazon.awssdk.auth.signer.params.Aws4PresignerParams; 27 import software.amazon.awssdk.awscore.client.config.AwsClientOption; 28 import software.amazon.awssdk.core.client.config.SdkClientConfiguration; 29 import software.amazon.awssdk.http.SdkHttpFullRequest; 30 import software.amazon.awssdk.http.SdkHttpMethod; 31 import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; 32 import software.amazon.awssdk.identity.spi.IdentityProvider; 33 import software.amazon.awssdk.regions.Region; 34 import software.amazon.awssdk.services.rds.model.GenerateAuthenticationTokenRequest; 35 import software.amazon.awssdk.utils.CompletableFutureUtils; 36 import software.amazon.awssdk.utils.Logger; 37 import software.amazon.awssdk.utils.StringUtils; 38 39 @Immutable 40 @SdkInternalApi 41 final class DefaultRdsUtilities implements RdsUtilities { 42 private static final Logger log = Logger.loggerFor(RdsUtilities.class); 43 44 // The time the IAM token is good for. https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html 45 private static final Duration EXPIRATION_DURATION = Duration.ofMinutes(15); 46 47 private final Aws4Signer signer = Aws4Signer.create(); 48 private final Region region; 49 private final IdentityProvider<? extends AwsCredentialsIdentity> credentialsProvider; 50 private final Clock clock; 51 DefaultRdsUtilities(DefaultBuilder builder)52 DefaultRdsUtilities(DefaultBuilder builder) { 53 this(builder, Clock.systemUTC()); 54 } 55 56 /** 57 * Test Only 58 */ DefaultRdsUtilities(DefaultBuilder builder, Clock clock)59 DefaultRdsUtilities(DefaultBuilder builder, Clock clock) { 60 this.credentialsProvider = builder.credentialsProvider; 61 this.region = builder.region; 62 this.clock = clock; 63 } 64 65 /** 66 * Used by RDS low-level client's utilities() method 67 */ 68 @SdkInternalApi create(SdkClientConfiguration clientConfiguration)69 static RdsUtilities create(SdkClientConfiguration clientConfiguration) { 70 return new DefaultBuilder().clientConfiguration(clientConfiguration).build(); 71 } 72 73 @Override generateAuthenticationToken(GenerateAuthenticationTokenRequest request)74 public String generateAuthenticationToken(GenerateAuthenticationTokenRequest request) { 75 SdkHttpFullRequest httpRequest = SdkHttpFullRequest.builder() 76 .method(SdkHttpMethod.GET) 77 .protocol("https") 78 .host(request.hostname()) 79 .port(request.port()) 80 .encodedPath("/") 81 .putRawQueryParameter("DBUser", request.username()) 82 .putRawQueryParameter("Action", "connect") 83 .build(); 84 85 Instant expirationTime = Instant.now(clock).plus(EXPIRATION_DURATION); 86 87 Aws4PresignerParams presignRequest = Aws4PresignerParams.builder() 88 .signingClockOverride(clock) 89 .expirationTime(expirationTime) 90 .awsCredentials(resolveCredentials(request)) 91 .signingName("rds-db") 92 .signingRegion(resolveRegion(request)) 93 .build(); 94 95 SdkHttpFullRequest fullRequest = signer.presign(httpRequest, presignRequest); 96 String signedUrl = fullRequest.getUri().toString(); 97 98 // Format should be: <hostname>>:<port>>/?Action=connect&DBUser=<username>>&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expi... 99 // Note: This must be the real RDS hostname, not proxy or tunnels 100 String result = StringUtils.replacePrefixIgnoreCase(signedUrl, "https://", ""); 101 log.debug(() -> "Generated RDS authentication token with expiration of " + expirationTime); 102 return result; 103 } 104 resolveRegion(GenerateAuthenticationTokenRequest request)105 private Region resolveRegion(GenerateAuthenticationTokenRequest request) { 106 if (request.region() != null) { 107 return request.region(); 108 } 109 110 if (this.region != null) { 111 return this.region; 112 } 113 114 throw new IllegalArgumentException("Region should be provided either in GenerateAuthenticationTokenRequest object " + 115 "or RdsUtilities object"); 116 } 117 118 // TODO: update this to use AwsCredentialsIdentity when we migrate Signers to accept the new type. resolveCredentials(GenerateAuthenticationTokenRequest request)119 private AwsCredentials resolveCredentials(GenerateAuthenticationTokenRequest request) { 120 if (request.credentialsIdentityProvider() != null) { 121 return CredentialUtils.toCredentials( 122 CompletableFutureUtils.joinLikeSync(request.credentialsIdentityProvider().resolveIdentity())); 123 } 124 125 if (this.credentialsProvider != null) { 126 return CredentialUtils.toCredentials(CompletableFutureUtils.joinLikeSync(this.credentialsProvider.resolveIdentity())); 127 } 128 129 throw new IllegalArgumentException("CredentialProvider should be provided either in GenerateAuthenticationTokenRequest " + 130 "object or RdsUtilities object"); 131 } 132 133 @SdkInternalApi 134 static final class DefaultBuilder implements Builder { 135 private Region region; 136 private IdentityProvider<? extends AwsCredentialsIdentity> credentialsProvider; 137 DefaultBuilder()138 DefaultBuilder() { 139 } 140 clientConfiguration(SdkClientConfiguration clientConfiguration)141 Builder clientConfiguration(SdkClientConfiguration clientConfiguration) { 142 this.credentialsProvider = clientConfiguration.option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER); 143 this.region = clientConfiguration.option(AwsClientOption.AWS_REGION); 144 145 return this; 146 } 147 148 @Override region(Region region)149 public Builder region(Region region) { 150 this.region = region; 151 return this; 152 } 153 154 @Override credentialsProvider(IdentityProvider<? extends AwsCredentialsIdentity> credentialsProvider)155 public Builder credentialsProvider(IdentityProvider<? extends AwsCredentialsIdentity> credentialsProvider) { 156 this.credentialsProvider = credentialsProvider; 157 return this; 158 } 159 160 /** 161 * Construct a {@link RdsUtilities} object. 162 */ 163 @Override build()164 public RdsUtilities build() { 165 return new DefaultRdsUtilities(this); 166 } 167 } 168 } 169