From 7a7ff10e75f249acf1dcb544e9be6e234f9fbd87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Wed, 9 Jul 2025 23:46:25 +0100 Subject: [PATCH 01/10] temporary disable API Keys until https://github.com/awslabs/aws-sdk-swift/issues/1979 is fixed --- .gitignore | 2 +- Sources/BedrockAuthentication.swift | 6 ++++-- Sources/BedrockService.swift | 15 ++++++++------- Sources/Protocols/BedrockConfigProtocol.swift | 2 -- Tests/AuthenticationTests.swift | 8 ++++++-- 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index f4868035..ac3515cc 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ Package.resolved .vscode .env Makefile - +**/temp node_modules # **/backend diff --git a/Sources/BedrockAuthentication.swift b/Sources/BedrockAuthentication.swift index f5f1d49a..5260b1dc 100644 --- a/Sources/BedrockAuthentication.swift +++ b/Sources/BedrockAuthentication.swift @@ -63,9 +63,11 @@ public enum BedrockAuthentication: Sendable, CustomStringConvertible { ) async throws -> (any SmithyIdentity.AWSCredentialIdentityResolver)? { switch self { - case .default, - .apiKey(_): + case .default: return nil + case .apiKey(_): + fatalError("API Key authentication is not supported at the moment. Please check https://github.com/awslabs/aws-sdk-swift/issues/1979 for status.") + // return nil case .profile(let profileName): return try? ProfileAWSCredentialIdentityResolver(profileName: profileName) case .sso(let profileName): diff --git a/Sources/BedrockService.swift b/Sources/BedrockService.swift index b63b4d05..d545a193 100644 --- a/Sources/BedrockService.swift +++ b/Sources/BedrockService.swift @@ -52,7 +52,7 @@ public struct BedrockService: Sendable { ) async throws { self.logger = logger ?? BedrockService.createLogger("bedrock.service") self.logger.trace( - "Initializing SwiftBedrock", + "Initializing BedrockService", metadata: ["region": .string(region.rawValue)] ) self.region = region @@ -121,7 +121,7 @@ public struct BedrockService: Sendable { -> BedrockClient { let config: BedrockClient.BedrockClientConfiguration = try await prepareConfig( - region: region, + initialConfig: BedrockClient.BedrockClientConfiguration(region: region.rawValue), authentication: authentication, logger: logger ) @@ -143,7 +143,9 @@ public struct BedrockService: Sendable { -> BedrockRuntimeClient { let config: BedrockRuntimeClient.BedrockRuntimeClientConfiguration = try await prepareConfig( - region: region, + initialConfig: BedrockRuntimeClient.BedrockRuntimeClientConfiguration( + region: region.rawValue + ), authentication: authentication, logger: logger ) @@ -152,13 +154,12 @@ public struct BedrockService: Sendable { /// Generic function to create client configuration and avoid duplication code. internal static func prepareConfig( - region: Region, + initialConfig: C, authentication: BedrockAuthentication, logger: Logging.Logger ) async throws -> C { - var config: C = try await .init() - - config.region = region.rawValue + + var config = initialConfig // support profile, SSO, web identity and static authentication if let awsCredentialIdentityResolver = try? await authentication.getAWSCredentialIdentityResolver( diff --git a/Sources/Protocols/BedrockConfigProtocol.swift b/Sources/Protocols/BedrockConfigProtocol.swift index 89d9dc9b..bb2004c8 100644 --- a/Sources/Protocols/BedrockConfigProtocol.swift +++ b/Sources/Protocols/BedrockConfigProtocol.swift @@ -19,10 +19,8 @@ import ClientRuntime import SmithyIdentity protocol BedrockConfigProtocol { - init() async throws var awsCredentialIdentityResolver: any SmithyIdentity.AWSCredentialIdentityResolver { get set } var httpClientConfiguration: ClientRuntime.HttpClientConfiguration { get set } - var region: String? { get set } } extension BedrockClient.BedrockClientConfiguration: @retroactive @unchecked Sendable, BedrockConfigProtocol {} extension BedrockRuntimeClient.BedrockRuntimeClientConfiguration: @retroactive @unchecked Sendable, diff --git a/Tests/AuthenticationTests.swift b/Tests/AuthenticationTests.swift index ea4bd89d..2e3195ee 100644 --- a/Tests/AuthenticationTests.swift +++ b/Tests/AuthenticationTests.swift @@ -88,7 +88,9 @@ extension BedrockServiceTests { // when // create bedrock configuration with API Key authentication let config: BedrockClient.BedrockClientConfiguration = try await BedrockService.prepareConfig( - region: .useast1, + initialConfig: BedrockClient.BedrockClientConfiguration( + region: "us-east-1" // default region + ), authentication: auth, logger: Logger(label: "test.logger"), ) @@ -147,7 +149,9 @@ extension BedrockServiceTests { // when let _: BedrockClient.BedrockClientConfiguration = try await BedrockService.prepareConfig( - region: .useast1, + initialConfig: BedrockClient.BedrockClientConfiguration( + region: "us-east-1" // default region + ), authentication: auth, logger: logger ) From 407ee8e3dc469bd9d6a3dbe6c1b3f0710de2982f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Wed, 9 Jul 2025 23:49:13 +0100 Subject: [PATCH 02/10] swift format --- Sources/BedrockAuthentication.swift | 6 ++++-- Sources/BedrockService.swift | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Sources/BedrockAuthentication.swift b/Sources/BedrockAuthentication.swift index 5260b1dc..9456656f 100644 --- a/Sources/BedrockAuthentication.swift +++ b/Sources/BedrockAuthentication.swift @@ -66,8 +66,10 @@ public enum BedrockAuthentication: Sendable, CustomStringConvertible { case .default: return nil case .apiKey(_): - fatalError("API Key authentication is not supported at the moment. Please check https://github.com/awslabs/aws-sdk-swift/issues/1979 for status.") - // return nil + fatalError( + "API Key authentication is not supported at the moment. Please check https://github.com/awslabs/aws-sdk-swift/issues/1979 for status." + ) + // return nil case .profile(let profileName): return try? ProfileAWSCredentialIdentityResolver(profileName: profileName) case .sso(let profileName): diff --git a/Sources/BedrockService.swift b/Sources/BedrockService.swift index d545a193..62daad8b 100644 --- a/Sources/BedrockService.swift +++ b/Sources/BedrockService.swift @@ -158,7 +158,7 @@ public struct BedrockService: Sendable { authentication: BedrockAuthentication, logger: Logging.Logger ) async throws -> C { - + var config = initialConfig // support profile, SSO, web identity and static authentication From 39bdb0001d87de1d163c6b5bc287f3159d49508f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Wed, 9 Jul 2025 23:52:13 +0100 Subject: [PATCH 03/10] add a note in the README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b0c0f1d..24153562 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,9 @@ let bedrock = try await BedrockService( ) ``` -### API Key Authentication +### API Key Authentication (temporary unavailable) + +This capability will be available when issue [#1979](https://github.com/awslabs/aws-sdk-swift/issues/1979) from the AWS SDK for Swift will be fixed or a workaround provided. Use an API key for authentication. API keys are generated in the AWS console and provide a simpler authentication method for specific use cases. From 359e4706712f9b32a321c20d28047f837287705b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Thu, 10 Jul 2025 09:19:56 +0100 Subject: [PATCH 04/10] pave the road towards using a bearer token provider --- Sources/BedrockAuthentication.swift | 25 +++++++++++++------ Sources/BedrockService.swift | 9 +++++++ Sources/Protocols/BedrockConfigProtocol.swift | 1 + 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/Sources/BedrockAuthentication.swift b/Sources/BedrockAuthentication.swift index 9456656f..7a68ef22 100644 --- a/Sources/BedrockAuthentication.swift +++ b/Sources/BedrockAuthentication.swift @@ -56,20 +56,15 @@ public enum BedrockAuthentication: Sendable, CustomStringConvertible { /// Creates an AWS credential identity resolver depending on the authentication parameter. /// - Parameters: /// - authentication: The authentication type to use - /// - Returns: An optional AWS credential identity resolver. A nil return value means that the default AWS credential provider chain will be used. + /// - Returns: An optional AWS credential identity resolver. A nil return value means that the default AWS credential provider chain will be used or that the authentication type does not require a specific resolver (like `apiKey`). /// func getAWSCredentialIdentityResolver( logger: Logger ) async throws -> (any SmithyIdentity.AWSCredentialIdentityResolver)? { switch self { - case .default: - return nil - case .apiKey(_): - fatalError( - "API Key authentication is not supported at the moment. Please check https://github.com/awslabs/aws-sdk-swift/issues/1979 for status." - ) - // return nil + case .default, .apiKey(_): + return nil //TODO should we throw an error when apiKey is used ? case .profile(let profileName): return try? ProfileAWSCredentialIdentityResolver(profileName: profileName) case .sso(let profileName): @@ -88,4 +83,18 @@ public enum BedrockAuthentication: Sendable, CustomStringConvertible { return StaticAWSCredentialIdentityResolver(creds) } } + + /// Creates a BearerTokenIdentityResolver depending on the authentication parameter. + /// - Returns: An optional BearerTokenIdentityResolver. A nil return value means that the authentication type requires an AWSCredentialsProvider instead (like `default`, `profile`, `sso`, `webIdentity`, or `static`). + /// - Note: Only `apiKey` authentication uses BearerTokenIdentityResolver. + func getBearerTokenIdentityResolver(logger: Logger) -> (any SmithyIdentity.BearerTokenIdentityResolver)? { + guard case .apiKey(let key) = self else { + return nil // Only apiKey authentication uses BearerTokenIdentityResolver + } + + // Create a StaticBearerTokenIdentityResolver with the provided API key + let identity = BearerTokenIdentity(token: key) + return StaticBearerTokenIdentityResolver(token: identity) + } + } diff --git a/Sources/BedrockService.swift b/Sources/BedrockService.swift index 62daad8b..ee89e012 100644 --- a/Sources/BedrockService.swift +++ b/Sources/BedrockService.swift @@ -174,6 +174,15 @@ public struct BedrockService: Sendable { name: "Authorization", value: "Bearer \(key)" ) + if let bearerTokenIdentityresolver = authentication.getBearerTokenIdentityResolver(logger: logger) { + config.bearerTokenIdentityResolver = bearerTokenIdentityresolver + print(bearerTokenIdentityresolver) + } else { + // TODO: should we throw an error here ? + logger.error( + "API Key authentication is used but no BearerTokenIdentityResolver is provided. This will lead to issues." + ) + } logger.trace("Using API Key for authentication") } else { logger.trace("Using AWS credentials for authentication") diff --git a/Sources/Protocols/BedrockConfigProtocol.swift b/Sources/Protocols/BedrockConfigProtocol.swift index bb2004c8..ace95093 100644 --- a/Sources/Protocols/BedrockConfigProtocol.swift +++ b/Sources/Protocols/BedrockConfigProtocol.swift @@ -20,6 +20,7 @@ import SmithyIdentity protocol BedrockConfigProtocol { var awsCredentialIdentityResolver: any SmithyIdentity.AWSCredentialIdentityResolver { get set } + var bearerTokenIdentityResolver: any SmithyIdentity.BearerTokenIdentityResolver { get set } var httpClientConfiguration: ClientRuntime.HttpClientConfiguration { get set } } extension BedrockClient.BedrockClientConfiguration: @retroactive @unchecked Sendable, BedrockConfigProtocol {} From c5b50479a0f4baea47822bc306f2820da5c02169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Thu, 10 Jul 2025 09:20:38 +0100 Subject: [PATCH 05/10] remove debugging statements --- Sources/BedrockService.swift | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Sources/BedrockService.swift b/Sources/BedrockService.swift index ee89e012..c5373d0c 100644 --- a/Sources/BedrockService.swift +++ b/Sources/BedrockService.swift @@ -169,14 +169,13 @@ public struct BedrockService: Sendable { } // support API keys - if case .apiKey(let key) = authentication { - config.httpClientConfiguration.defaultHeaders.add( - name: "Authorization", - value: "Bearer \(key)" - ) + if case .apiKey(_) = authentication { + // config.httpClientConfiguration.defaultHeaders.add( + // name: "Authorization", + // value: "Bearer \(key)" + // ) if let bearerTokenIdentityresolver = authentication.getBearerTokenIdentityResolver(logger: logger) { config.bearerTokenIdentityResolver = bearerTokenIdentityresolver - print(bearerTokenIdentityresolver) } else { // TODO: should we throw an error here ? logger.error( From 1c492182c7a324176be5054314bb15304a6a6ba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Thu, 10 Jul 2025 09:29:31 +0100 Subject: [PATCH 06/10] swift-format --- Sources/BedrockAuthentication.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/BedrockAuthentication.swift b/Sources/BedrockAuthentication.swift index 7a68ef22..a884ccff 100644 --- a/Sources/BedrockAuthentication.swift +++ b/Sources/BedrockAuthentication.swift @@ -64,7 +64,7 @@ public enum BedrockAuthentication: Sendable, CustomStringConvertible { switch self { case .default, .apiKey(_): - return nil //TODO should we throw an error when apiKey is used ? + return nil //TODO should we throw an error when apiKey is used ? case .profile(let profileName): return try? ProfileAWSCredentialIdentityResolver(profileName: profileName) case .sso(let profileName): @@ -89,9 +89,9 @@ public enum BedrockAuthentication: Sendable, CustomStringConvertible { /// - Note: Only `apiKey` authentication uses BearerTokenIdentityResolver. func getBearerTokenIdentityResolver(logger: Logger) -> (any SmithyIdentity.BearerTokenIdentityResolver)? { guard case .apiKey(let key) = self else { - return nil // Only apiKey authentication uses BearerTokenIdentityResolver + return nil // Only apiKey authentication uses BearerTokenIdentityResolver } - + // Create a StaticBearerTokenIdentityResolver with the provided API key let identity = BearerTokenIdentity(token: key) return StaticBearerTokenIdentityResolver(token: identity) From 5edd8934214839b94e6530d8c236887bd77b635b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Fri, 11 Jul 2025 10:18:46 +0200 Subject: [PATCH 07/10] fix bearer token authentication --- Sources/BedrockAuthentication.swift | 1 - Sources/BedrockService.swift | 12 ++++++++++++ Sources/Protocols/BedrockConfigProtocol.swift | 13 ++++++++++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Sources/BedrockAuthentication.swift b/Sources/BedrockAuthentication.swift index a884ccff..21a40677 100644 --- a/Sources/BedrockAuthentication.swift +++ b/Sources/BedrockAuthentication.swift @@ -96,5 +96,4 @@ public enum BedrockAuthentication: Sendable, CustomStringConvertible { let identity = BearerTokenIdentity(token: key) return StaticBearerTokenIdentityResolver(token: identity) } - } diff --git a/Sources/BedrockService.swift b/Sources/BedrockService.swift index c5373d0c..7011dd20 100644 --- a/Sources/BedrockService.swift +++ b/Sources/BedrockService.swift @@ -161,6 +161,12 @@ public struct BedrockService: Sendable { var config = initialConfig + if logger.logLevel == .trace { + // enable trace HTTP requests and responses for the SDK + // see https://github.com/smithy-lang/smithy-swift/blob/main/Sources/ClientRuntime/Telemetry/Logging/ClientLogMode.swift + config.clientLogMode = .requestAndResponse + } + // support profile, SSO, web identity and static authentication if let awsCredentialIdentityResolver = try? await authentication.getAWSCredentialIdentityResolver( logger: logger @@ -176,6 +182,12 @@ public struct BedrockService: Sendable { // ) if let bearerTokenIdentityresolver = authentication.getBearerTokenIdentityResolver(logger: logger) { config.bearerTokenIdentityResolver = bearerTokenIdentityresolver + + // force utilisation of a bearer token instead of AWS credentials + Signv4 + // see https://github.com/awslabs/aws-sdk-swift/blob/15b8951d108968f767f4199a3c011e27ac519d61/Sources/Services/AWSBedrockRuntime/Sources/AWSBedrockRuntime/AuthSchemeResolver.swift#L58 + config.authSchemeResolver = DefaultBedrockRuntimeAuthSchemeResolver(authSchemePreference: [ + "httpBearerAuth" + ]) } else { // TODO: should we throw an error here ? logger.error( diff --git a/Sources/Protocols/BedrockConfigProtocol.swift b/Sources/Protocols/BedrockConfigProtocol.swift index ace95093..8e3e811e 100644 --- a/Sources/Protocols/BedrockConfigProtocol.swift +++ b/Sources/Protocols/BedrockConfigProtocol.swift @@ -16,12 +16,23 @@ import AWSBedrock import AWSBedrockRuntime import ClientRuntime +import SmithyHTTPAuthAPI import SmithyIdentity protocol BedrockConfigProtocol { + // support regular AWS Credentials + Sigv4 authentication var awsCredentialIdentityResolver: any SmithyIdentity.AWSCredentialIdentityResolver { get set } + + // support bearer token authentication (for API Keys) var bearerTokenIdentityResolver: any SmithyIdentity.BearerTokenIdentityResolver { get set } - var httpClientConfiguration: ClientRuntime.HttpClientConfiguration { get set } + var authSchemeResolver: SmithyHTTPAuthAPI.AuthSchemeResolver { get set } + + // not used at the moment, we use the bearer token instead + //var httpClientConfiguration: ClientRuntime.HttpClientConfiguration { get set } + + // for debugging + var clientLogMode: ClientRuntime.ClientLogMode { get set } + } extension BedrockClient.BedrockClientConfiguration: @retroactive @unchecked Sendable, BedrockConfigProtocol {} extension BedrockRuntimeClient.BedrockRuntimeClientConfiguration: @retroactive @unchecked Sendable, From 4c3261de47ae1a02a843ec1a8cc6064ab243fa0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Fri, 11 Jul 2025 10:30:41 +0200 Subject: [PATCH 08/10] fix tests --- Tests/AuthenticationTests.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Tests/AuthenticationTests.swift b/Tests/AuthenticationTests.swift index 2e3195ee..b159403f 100644 --- a/Tests/AuthenticationTests.swift +++ b/Tests/AuthenticationTests.swift @@ -14,8 +14,10 @@ //===----------------------------------------------------------------------===// import AWSBedrock +import AWSBedrockRuntime import AwsCommonRuntimeKit import Logging +import SmithyIdentity import Testing @testable import BedrockService @@ -97,9 +99,15 @@ extension BedrockServiceTests { // then #expect(config.region == Region.useast1.rawValue) // default region - #expect( - config.httpClientConfiguration.defaultHeaders.value(for: "Authorization") == "Bearer test-api-key-12345" - ) + + // check token + let resolver = config.bearerTokenIdentityResolver as? StaticBearerTokenIdentityResolver + let token = try await resolver?.getIdentity(identityProperties: nil).token + #expect(token == testApiKey, "Expected token to match the API key") + + // check bearer auth scheme + let authScheme = (config.authSchemeResolver as? DefaultBedrockRuntimeAuthSchemeResolver)?.authSchemePreference + #expect(authScheme?.contains("httpBearerAuth") == true, "Expected auth scheme to be HTTP Bearer") } From 4392a2d4f5250cabc295189fe1149cab379e4d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Fri, 11 Jul 2025 10:49:00 +0200 Subject: [PATCH 09/10] fix format --- Tests/AuthenticationTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/AuthenticationTests.swift b/Tests/AuthenticationTests.swift index b159403f..38789ff6 100644 --- a/Tests/AuthenticationTests.swift +++ b/Tests/AuthenticationTests.swift @@ -100,12 +100,12 @@ extension BedrockServiceTests { // then #expect(config.region == Region.useast1.rawValue) // default region - // check token + // check token let resolver = config.bearerTokenIdentityResolver as? StaticBearerTokenIdentityResolver let token = try await resolver?.getIdentity(identityProperties: nil).token #expect(token == testApiKey, "Expected token to match the API key") - // check bearer auth scheme + // check bearer auth scheme let authScheme = (config.authSchemeResolver as? DefaultBedrockRuntimeAuthSchemeResolver)?.authSchemePreference #expect(authScheme?.contains("httpBearerAuth") == true, "Expected auth scheme to be HTTP Bearer") From 389db9f0f0c51e4f80649d17524a29aeed203fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Fri, 11 Jul 2025 10:50:41 +0200 Subject: [PATCH 10/10] add Examples/api-key in the list of integration tests --- .github/workflows/pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b82e2dd6..1258b7f8 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -35,7 +35,7 @@ jobs: # We pass the list of examples here, but we can't pass an array as argument # Instead, we pass a String with a valid JSON array. # The workaround is mentioned here https://github.com/orgs/community/discussions/11692 - examples: "[ 'converse', 'converse-stream', 'text_chat' ]" + examples: "[ 'api-key', 'converse', 'converse-stream', 'text_chat' ]" swift-6-language-mode: name: Swift 6 Language Mode