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

feat: Auth token generator for AWS DSQL #1857

Merged
merged 8 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from 5 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
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import SmithyIdentity
import struct Foundation.Date
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is the underlying auth token generator in runtime library, used by RDS and DSQL service auth token generators.

import struct Foundation.TimeInterval

/// A utility class with a single utility method that generates IAM authentication token used for connecting to RDS.
/// A utility class with utility methods that generate IAM authentication token used for connecting to RDS & Aurora DSQL.
@_spi(AuthTokenGenerator)
public class AuthTokenGenerator {
public var awsCredentialIdentity: AWSCredentialIdentity
Expand All @@ -41,15 +41,15 @@ public class AuthTokenGenerator {
self.awsCredentialIdentity = try await awsCredentialIdentityResolver.getIdentity()
}

/// Generates authenetication token using given inputs to the method and credential identity instance variable.
/// Generates authenetication token for RDS using given inputs to the method and credential identity instance variable.
///
/// - Parameters:
/// - endpoint: The endpoint of the RDS instance. E.g., `rdsmysql.123456789012.us-west-2.rds.amazonaws.com`
/// - port: The port of the RDS instance to connect to. E.g., `3306`
/// - region: The region that RDS instance is located in. E.g., `us-west-2`
/// - username: The username of the RDS database user. E.g., `admin`
/// - expiration: The expiration for the token in seconds. Default is 900 seconds (15 minutes).
public func generateAuthToken(
public func generateRDSAuthToken(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please check if its backward compatible, does it breaks any existing client using it ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the underlying generator hidden from normal import unless user explicitly uses @_spi(AuthTokenGenerator) tag to import it. Users of DSQL and RDS would use the auth token generator exposed via the wrapper in respective service modules, so this function name change isn't visible to them.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean to say for clients it is impossible to import this function?

Copy link
Contributor Author

@sichanyoo sichanyoo Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is part of AWSClientRuntime module, and it's hidden unless users do @_spi(AuthTokenGenerator) import AWSClientRuntime. A normal import import AWSClientRuntime won't expose this function.

Instead of accessing the underlying generator, users can just use the wrapper generators. The respective service modules AWSRDS and AWSDSQL will have a wrapper for this hidden generator, which isn't hidden. You can see this code generation in GitHub diff of this PR. So users can simply do this:

import AWSDSQL

let token = AWSDSQL.AuthTokenGenerator(. . .).generateDBConnectAuthToken(. . .)

endpoint: String,
port: Int16,
region: String,
Expand Down Expand Up @@ -97,4 +97,51 @@ public class AuthTokenGenerator {

return rdsAuthToken
}

public func generateDSQLAuthToken(
endpoint: String,
region: String,
expiration: TimeInterval = 900,
isForAdmin: Bool
) async throws -> String {
CommonRuntimeKit.initialize()
let requestBuilder = HTTPRequestBuilder()
requestBuilder.withHost(endpoint)

// Add the Host header and the required query items for the desired presigned URL.
requestBuilder.withHeader(name: "Host", value: "\(endpoint)")
let actionQueryItemValue = isForAdmin ? "DbConnectAdmin" : "DbConnect"
requestBuilder.withQueryItem(URIQueryItem(name: "Action", value: actionQueryItemValue))

let signingConfig = AWSSigningConfig(
credentials: self.awsCredentialIdentity,
expiration: expiration,
signedBodyValue: .empty,
flags: SigningFlags(
useDoubleURIEncode: true,
shouldNormalizeURIPath: true,
omitSessionToken: false
),
date: Date(),
service: "dsql",
region: region,
signatureType: .requestQueryParams,
signingAlgorithm: .sigv4
)

let signedRequest = await AWSSigV4Signer().sigV4SignedRequest(
requestBuilder: requestBuilder,
signingConfig: signingConfig
)

guard let presignedURL = signedRequest?.destination.url else {
throw ClientError.authError("Failed to generate auth token for RDS.")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

token for DSQL*

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

}

// Remove https:// from the presigned URL to get final value for RDS auth token.
let startIndex = presignedURL.absoluteString.index(presignedURL.absoluteString.startIndex, offsetBy: 8)
let rdsAuthToken = String(presignedURL.absoluteString[startIndex...])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: dsqlToken*

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated


return rdsAuthToken
}
}
77 changes: 77 additions & 0 deletions Sources/Services/AWSDSQL/Sources/AWSDSQL/AuthTokenGenerator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the generated auth token generator for DSQL service. Included for reference.

// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

// Code generated by smithy-swift-codegen. DO NOT EDIT!



@_spi(AuthTokenGenerator) import class AWSClientRuntime.AuthTokenGenerator
import SmithyIdentity
import struct Foundation.TimeInterval

/// A utility class with methods that generate IAM authentication token used for connecting to DSQL.
public class AuthTokenGenerator {
private let generator: AWSClientRuntime.AuthTokenGenerator

/// The initializer that takes in AWSCredentialIdentity struct to use to generate the IAM authentication token.
public init(awsCredentialIdentity: AWSCredentialIdentity) {
self.generator = AWSClientRuntime.AuthTokenGenerator(awsCredentialIdentity: awsCredentialIdentity)
}

/// The initializer that takes in a specific AWSCredentialIdentityResolver, used to resolve the AWSCredentialIdentity used to generate the IAM authentication token.
public init(awsCredentialIdentityResolver: any AWSCredentialIdentityResolver) async throws {
self.generator = try await AWSClientRuntime.AuthTokenGenerator(awsCredentialIdentityResolver: awsCredentialIdentityResolver)
}

/// Updates the AWS credentials used to generate the IAM auth token.
public func updateCredentials(newAWSCredentialIdentity: AWSCredentialIdentity) {
generator.updateCredentials(newAWSCredentialIdentity: newAWSCredentialIdentity)
}

/// Updates the AWS credentials used to generate the IAM auth token by resolving credentials from passed in resolver.
public func updateCredentials(awsCredentialIdentityResolver: any AWSCredentialIdentityResolver) async throws {
try await generator.updateCredentials(awsCredentialIdentityResolver: awsCredentialIdentityResolver)
}

/// Generates authenetication token for non-admin connection using given inputs to the method and credential identity instance variable.
///
/// - Parameters:
/// - endpoint: The endpoint of the RDS instance. E.g., `peccy.dsql.us-east-1.on.aws`
/// - region: The region that RDS instance is located in. E.g., `us-east-1`
/// - expiration: The expiration for the token in seconds. Default is 900 seconds (15 minutes).
public func generateDBConnectAuthToken(
endpoint: String,
region: String,
expiration: TimeInterval = 900
) async throws -> String {
return try await generator.generateDSQLAuthToken(
endpoint: endpoint,
region: region,
expiration: expiration,
isForAdmin: false
)
}

/// Generates authenetication token for admin connection using given inputs to the method and credential identity instance variable.
///
/// - Parameters:
/// - endpoint: The endpoint of the RDS instance. E.g., `peccy.dsql.us-east-1.on.aws`
/// - region: The region that RDS instance is located in. E.g., `us-east-1`
/// - expiration: The expiration for the token in seconds. Default is 900 seconds (15 minutes).
public func generateDBConnectAdminAuthToken(
endpoint: String,
region: String,
expiration: TimeInterval = 900
) async throws -> String {
return try await generator.generateDSQLAuthToken(
endpoint: endpoint,
region: region,
expiration: expiration,
isForAdmin: true
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public class AuthTokenGenerator {
username: String,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the generated diff for RDS auth token generator

expiration: TimeInterval = 900
) async throws -> String {
return try await generator.generateAuthToken(
return try await generator.generateRDSAuthToken(
endpoint: endpoint,
port: port,
region: region,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package software.amazon.smithy.aws.swift.codegen.customization.dsql
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the codegen integration for generating auth token generator for DSQL.


import software.amazon.smithy.aws.swift.codegen.sdkId
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.swift.codegen.SwiftDelegator
import software.amazon.smithy.swift.codegen.SwiftSettings
import software.amazon.smithy.swift.codegen.core.SwiftCodegenContext
import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator
import software.amazon.smithy.swift.codegen.integration.SwiftIntegration
import software.amazon.smithy.swift.codegen.model.expectShape

class AuthTokenGeneratorIntegration : SwiftIntegration {
override fun enabledForService(model: Model, settings: SwiftSettings): Boolean =
model.expectShape<ServiceShape>(settings.service).sdkId == "DSQL"

override fun writeAdditionalFiles(
ctx: SwiftCodegenContext,
protocolGenerationContext: ProtocolGenerator.GenerationContext,
delegator: SwiftDelegator
) {
delegator.useFileWriter("Sources/${ctx.settings.moduleName}/AuthTokenGenerator.swift") { writer ->
val authTokenGeneratorWrapperClass = """
@_spi(AuthTokenGenerator) import class AWSClientRuntime.AuthTokenGenerator
import SmithyIdentity
import struct Foundation.TimeInterval

/// A utility class with methods that generate IAM authentication token used for connecting to DSQL.
public class AuthTokenGenerator {
private let generator: AWSClientRuntime.AuthTokenGenerator

/// The initializer that takes in AWSCredentialIdentity struct to use to generate the IAM authentication token.
public init(awsCredentialIdentity: AWSCredentialIdentity) {
self.generator = AWSClientRuntime.AuthTokenGenerator(awsCredentialIdentity: awsCredentialIdentity)
}

/// The initializer that takes in a specific AWSCredentialIdentityResolver, used to resolve the AWSCredentialIdentity used to generate the IAM authentication token.
public init(awsCredentialIdentityResolver: any AWSCredentialIdentityResolver) async throws {
self.generator = try await AWSClientRuntime.AuthTokenGenerator(awsCredentialIdentityResolver: awsCredentialIdentityResolver)
}

/// Updates the AWS credentials used to generate the IAM auth token.
public func updateCredentials(newAWSCredentialIdentity: AWSCredentialIdentity) {
generator.updateCredentials(newAWSCredentialIdentity: newAWSCredentialIdentity)
}

/// Updates the AWS credentials used to generate the IAM auth token by resolving credentials from passed in resolver.
public func updateCredentials(awsCredentialIdentityResolver: any AWSCredentialIdentityResolver) async throws {
try await generator.updateCredentials(awsCredentialIdentityResolver: awsCredentialIdentityResolver)
}

/// Generates authenetication token for non-admin connection using given inputs to the method and credential identity instance variable.
///
/// - Parameters:
/// - endpoint: The endpoint of the RDS instance. E.g., `peccy.dsql.us-east-1.on.aws`
/// - region: The region that RDS instance is located in. E.g., `us-east-1`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dsql*

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

/// - expiration: The expiration for the token in seconds. Default is 900 seconds (15 minutes).
public func generateDBConnectAuthToken(
endpoint: String,
region: String,
expiration: TimeInterval = 900
) async throws -> String {
return try await generator.generateDSQLAuthToken(
endpoint: endpoint,
region: region,
expiration: expiration,
isForAdmin: false
)
}

/// Generates authenetication token for admin connection using given inputs to the method and credential identity instance variable.
///
/// - Parameters:
/// - endpoint: The endpoint of the RDS instance. E.g., `peccy.dsql.us-east-1.on.aws`
/// - region: The region that RDS instance is located in. E.g., `us-east-1`
/// - expiration: The expiration for the token in seconds. Default is 900 seconds (15 minutes).
public func generateDBConnectAdminAuthToken(
endpoint: String,
region: String,
expiration: TimeInterval = 900
) async throws -> String {
return try await generator.generateDSQLAuthToken(
endpoint: endpoint,
region: region,
expiration: expiration,
isForAdmin: true
)
}
}
""".trimIndent()
writer.write(authTokenGeneratorWrapperClass)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this... ideally we would generate as little as possible. But since we already do it like this for the RDS token we can take up a refactor if one is possible later on

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class AuthTokenGeneratorIntegration : SwiftIntegration {
username: String,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modifies codegen integration for RDS auth token generation to use modified function name in runtime auth token generator

expiration: TimeInterval = 900
) async throws -> String {
return try await generator.generateAuthToken(
return try await generator.generateRDSAuthToken(
endpoint: endpoint,
port: port,
region: region,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ software.amazon.smithy.swift.codegen.swiftintegrations.InitialRequestIntegration
software.amazon.smithy.aws.swift.codegen.swiftintegrations.RegistryConfigIntegration
software.amazon.smithy.aws.swift.codegen.swiftintegrations.AmzSdkRetryHeadersIntegration
software.amazon.smithy.aws.swift.codegen.customization.rds.AuthTokenGeneratorIntegration
software.amazon.smithy.aws.swift.codegen.customization.dsql.AuthTokenGeneratorIntegration
Loading