|
| 1 | +// |
| 2 | +// Copyright Amazon.com Inc. or its affiliates. |
| 3 | +// All Rights Reserved. |
| 4 | +// |
| 5 | +// SPDX-License-Identifier: Apache-2.0 |
| 6 | +// |
| 7 | + |
| 8 | +import AwsCommonRuntimeKit |
| 9 | +import class AWSSDKHTTPAuth.AWSSigV4Signer |
| 10 | +import ClientRuntime |
| 11 | +import Smithy |
| 12 | +import SmithyHTTPAPI |
| 13 | +import SmithyHTTPAuth |
| 14 | +import SmithyHTTPAuthAPI |
| 15 | +import SmithyIdentity |
| 16 | +import struct Foundation.Date |
| 17 | +import struct Foundation.TimeInterval |
| 18 | + |
| 19 | +/// A utility class with a single utility method that generates IAM authentication token used for connecting to RDS. |
| 20 | +@_spi(AuthTokenGenerator) |
| 21 | +public class AuthTokenGenerator { |
| 22 | + public var awsCredentialIdentity: AWSCredentialIdentity |
| 23 | + |
| 24 | + /// The initializer that takes in AWSCredentialIdentity struct to use to generate the IAM authentication token. |
| 25 | + public init(awsCredentialIdentity: AWSCredentialIdentity) { |
| 26 | + self.awsCredentialIdentity = awsCredentialIdentity |
| 27 | + } |
| 28 | + |
| 29 | + /// The initializer that takes in a specific AWSCredentialIdentityResolver, used to resolve the AWSCredentialIdentity used to generate the IAM authentication token. |
| 30 | + public init(awsCredentialIdentityResolver: any AWSCredentialIdentityResolver) async throws { |
| 31 | + self.awsCredentialIdentity = try await awsCredentialIdentityResolver.getIdentity() |
| 32 | + } |
| 33 | + |
| 34 | + /// Updates the AWS credentials used to generate the IAM auth token. |
| 35 | + public func updateCredentials(newAWSCredentialIdentity: AWSCredentialIdentity) { |
| 36 | + self.awsCredentialIdentity = newAWSCredentialIdentity |
| 37 | + } |
| 38 | + |
| 39 | + /// Updates the AWS credentials used to generate the IAM auth token by resolving credentials from passed in resolver. |
| 40 | + public func updateCredentials(awsCredentialIdentityResolver: any AWSCredentialIdentityResolver) async throws { |
| 41 | + self.awsCredentialIdentity = try await awsCredentialIdentityResolver.getIdentity() |
| 42 | + } |
| 43 | + |
| 44 | + /// Generates authenetication token using given inputs to the method and credential identity instance variable. |
| 45 | + /// |
| 46 | + /// - Parameters: |
| 47 | + /// - endpoint: The endpoint of the RDS instance. E.g., `rdsmysql.123456789012.us-west-2.rds.amazonaws.com` |
| 48 | + /// - port: The port of the RDS instance to connect to. E.g., `3306` |
| 49 | + /// - region: The region that RDS instance is located in. E.g., `us-west-2` |
| 50 | + /// - username: The username of the RDS database user. E.g., `admin` |
| 51 | + /// - expiration: The expiration for the token in seconds. Default is 900 seconds (15 minutes). |
| 52 | + public func generateAuthToken( |
| 53 | + endpoint: String, |
| 54 | + port: Int16, |
| 55 | + region: String, |
| 56 | + username: String, |
| 57 | + expiration: TimeInterval = 900 |
| 58 | + ) async throws -> String { |
| 59 | + CommonRuntimeKit.initialize() |
| 60 | + let requestBuilder = HTTPRequestBuilder() |
| 61 | + requestBuilder.withHost(endpoint) |
| 62 | + requestBuilder.withPort(port) |
| 63 | + |
| 64 | + // Add the Host header and the required query items for the desired presigned URL. |
| 65 | + requestBuilder.withHeader(name: "Host", value: "\(endpoint):\(port)") |
| 66 | + requestBuilder.withQueryItem(URIQueryItem(name: "Action", value: "connect")) |
| 67 | + requestBuilder.withQueryItem(URIQueryItem(name: "DBUser", value: username)) |
| 68 | + |
| 69 | + let signingConfig = AWSSigningConfig( |
| 70 | + credentials: self.awsCredentialIdentity, |
| 71 | + expiration: expiration, |
| 72 | + signedBodyValue: .empty, |
| 73 | + flags: SigningFlags( |
| 74 | + useDoubleURIEncode: true, |
| 75 | + shouldNormalizeURIPath: true, |
| 76 | + omitSessionToken: false |
| 77 | + ), |
| 78 | + date: Date(), |
| 79 | + service: "rds-db", |
| 80 | + region: region, |
| 81 | + signatureType: .requestQueryParams, |
| 82 | + signingAlgorithm: .sigv4 |
| 83 | + ) |
| 84 | + |
| 85 | + let signedRequest = await AWSSigV4Signer().sigV4SignedRequest( |
| 86 | + requestBuilder: requestBuilder, |
| 87 | + signingConfig: signingConfig |
| 88 | + ) |
| 89 | + |
| 90 | + guard let presignedURL = signedRequest?.destination.url else { |
| 91 | + throw ClientError.authError("Failed to generate auth token for RDS.") |
| 92 | + } |
| 93 | + |
| 94 | + // Remove https:// from the presigned URL to get final value for RDS auth token. |
| 95 | + let startIndex = presignedURL.absoluteString.index(presignedURL.absoluteString.startIndex, offsetBy: 8) |
| 96 | + let rdsAuthToken = String(presignedURL.absoluteString[startIndex...]) |
| 97 | + |
| 98 | + return rdsAuthToken |
| 99 | + } |
| 100 | +} |
0 commit comments