diff --git a/Sources/Core/AWSSDKIdentity/Sources/AWSSDKIdentity/AWSCredentialIdentityResolvers/ECSAWSCredentialIdentityResolver.swift b/Sources/Core/AWSSDKIdentity/Sources/AWSSDKIdentity/AWSCredentialIdentityResolvers/ECSAWSCredentialIdentityResolver.swift index 42be28ec103..7e811326dea 100644 --- a/Sources/Core/AWSSDKIdentity/Sources/AWSSDKIdentity/AWSCredentialIdentityResolvers/ECSAWSCredentialIdentityResolver.swift +++ b/Sources/Core/AWSSDKIdentity/Sources/AWSSDKIdentity/AWSCredentialIdentityResolvers/ECSAWSCredentialIdentityResolver.swift @@ -17,6 +17,10 @@ import struct Foundation.URLComponents /// A credential identity resolver that sources credentials from ECS container metadata public struct ECSAWSCredentialIdentityResolver: AWSCredentialIdentityResolvedByCRT { public let crtAWSCredentialIdentityResolver: AwsCommonRuntimeKit.CredentialsProvider + public let resolvedHost: String + public let resolvedPathAndQuery: String + public let resolvedAuthorizationToken: String? + /// Creates a credential identity resolver that resolves credentials from ECS container metadata. /// ECS creds provider can be used to access creds via either relative uri to a fixed endpoint http://169.254.170.2, /// or via a full uri specified by environment variables: @@ -46,6 +50,7 @@ public struct ECSAWSCredentialIdentityResolver: AWSCredentialIdentityResolvedByC let defaultHost = "169.254.170.2" var host = defaultHost var pathAndQuery = resolvedRelativeURI ?? "" + var resolvedAuthToken: String? if let relative = resolvedRelativeURI { pathAndQuery = relative @@ -53,14 +58,19 @@ public struct ECSAWSCredentialIdentityResolver: AWSCredentialIdentityResolvedByC let (absoluteHost, absolutePathAndQuery) = try retrieveHostPathAndQuery(from: absoluteURL) host = absoluteHost pathAndQuery = absolutePathAndQuery + resolvedAuthToken = try resolveToken(authorizationToken, env) } else { throw HTTPClientError.pathCreationFailed( "Failed to retrieve either relative or absolute URI! URI may be malformed." ) } + self.resolvedHost = host + self.resolvedPathAndQuery = pathAndQuery + self.resolvedAuthorizationToken = resolvedAuthToken self.crtAWSCredentialIdentityResolver = try AwsCommonRuntimeKit.CredentialsProvider(source: .ecs( bootstrap: SDKDefaultIO.shared.clientBootstrap, + authToken: resolvedAuthToken, pathAndQuery: pathAndQuery, host: host )) @@ -90,6 +100,26 @@ private func isValidAbsoluteURI(_ uri: String?) -> Bool { return true } +private func resolveToken(_ authorizationToken: String?, _ env: ProcessEnvironment) throws -> String? { + // Initialize token variable + var tokenFromFile: String? + if let tokenPath = env.environmentVariable( + key: "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE" + ) { + do { + // Load the token from the file + let tokenFilePath = URL(fileURLWithPath: tokenPath) + tokenFromFile = try String(contentsOf: tokenFilePath, encoding: .utf8) + .trimmingCharacters(in: .whitespacesAndNewlines) + } catch { + throw ClientError.dataNotFound("Error reading the token file: \(error)") + } + } + + // AWS_CONTAINER_AUTHORIZATION_TOKEN should only be used if AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE is not set + return authorizationToken ?? tokenFromFile ?? env.environmentVariable(key: "AWS_CONTAINER_AUTHORIZATION_TOKEN") +} + private struct ProcessEnvironment { public init() {} diff --git a/Sources/Core/AWSSDKIdentity/Tests/AWSSDKIdentityTests/AWSCredentialIdentityResolverTests/ECSAWSCredentialIdentityResolverTests.swift b/Sources/Core/AWSSDKIdentity/Tests/AWSSDKIdentityTests/AWSCredentialIdentityResolverTests/ECSAWSCredentialIdentityResolverTests.swift index 116c414b583..98b455b98c8 100644 --- a/Sources/Core/AWSSDKIdentity/Tests/AWSSDKIdentityTests/AWSCredentialIdentityResolverTests/ECSAWSCredentialIdentityResolverTests.swift +++ b/Sources/Core/AWSSDKIdentity/Tests/AWSSDKIdentityTests/AWSCredentialIdentityResolverTests/ECSAWSCredentialIdentityResolverTests.swift @@ -10,13 +10,44 @@ import protocol AWSClientRuntime.Environment import struct AWSSDKIdentity.ECSAWSCredentialIdentityResolver class ECSAWSCredentialIdentityResolverTests: XCTestCase { + + override func setUp() { + super.setUp() + + // Unset the environment variables before each test + unsetenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") + unsetenv("AWS_CONTAINER_CREDENTIALS_FULL_URI") + unsetenv("AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE") + unsetenv("AWS_CONTAINER_AUTHORIZATION_TOKEN") + } + + override func tearDown() { + // Unset the environment variables after each test + unsetenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") + unsetenv("AWS_CONTAINER_CREDENTIALS_FULL_URI") + unsetenv("AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE") + unsetenv("AWS_CONTAINER_AUTHORIZATION_TOKEN") + + super.tearDown() + } + func testGetCredentialsWithRelativeURI() async throws { // relative uri is preferred over absolute uri so we shouldn't get thrown an error - XCTAssertNoThrow(try ECSAWSCredentialIdentityResolver(relativeURI: "subfolder/test.txt", absoluteURI: "invalid absolute uri")) + let resolver = try ECSAWSCredentialIdentityResolver( + relativeURI: "/subfolder/test.txt", + absoluteURI: "invalid absolute uri" + ) + XCTAssertEqual(resolver.resolvedHost, "169.254.170.2") + XCTAssertEqual(resolver.resolvedPathAndQuery, "/subfolder/test.txt") } func testGetCredentialsWithAbsoluteURI() async throws { - XCTAssertNoThrow(try ECSAWSCredentialIdentityResolver(relativeURI: nil, absoluteURI: "http://www.example.com/subfolder/test.txt")) + let resolver = try ECSAWSCredentialIdentityResolver( + relativeURI: nil, + absoluteURI: "http://www.example.com/subfolder/test.txt" + ) + XCTAssertEqual(resolver.resolvedHost, "www.example.com") + XCTAssertEqual(resolver.resolvedPathAndQuery, "/subfolder/test.txt") } func testGetCredentialsWithInvalidAbsoluteURI() async throws { @@ -29,54 +60,80 @@ class ECSAWSCredentialIdentityResolverTests: XCTestCase { func testGetCredentialsWithRelativeURIEnv() async throws { // relative uri is preferred over absolute uri so we shouldn't get thrown an error - setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "subfolder/test.txt", 1) - unsetenv("AWS_CONTAINER_CREDENTIALS_FULL_URI") - XCTAssertNoThrow(try ECSAWSCredentialIdentityResolver()) + setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/subfolder/test.txt", 1) + let resolver = try ECSAWSCredentialIdentityResolver() + XCTAssertEqual(resolver.resolvedHost, "169.254.170.2") + XCTAssertEqual(resolver.resolvedPathAndQuery, "/subfolder/test.txt") } func testGetCredentialsWithAbsoluteURIEnv() async throws { - unsetenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") setenv("AWS_CONTAINER_CREDENTIALS_FULL_URI", "http://www.example.com/subfolder/test.txt", 1) - XCTAssertNoThrow(try ECSAWSCredentialIdentityResolver()) + let resolver = try ECSAWSCredentialIdentityResolver() + XCTAssertEqual(resolver.resolvedHost, "www.example.com") + XCTAssertEqual(resolver.resolvedPathAndQuery, "/subfolder/test.txt") } func testGetCredentialsWithInvalidAbsoluteURIEnv() async throws { - unsetenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") setenv("AWS_CONTAINER_CREDENTIALS_FULL_URI", "test", 1) XCTAssertThrowsError(try ECSAWSCredentialIdentityResolver()) } func testGetCredentialsWithMissingURIEnv() async throws { - unsetenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") - unsetenv("AWS_CONTAINER_CREDENTIALS_FULL_URI") XCTAssertThrowsError(try ECSAWSCredentialIdentityResolver()) } -} -protocol EnvironmentProvider { - func environmentVariable(key: String) -> String? -} + func testGetCredentialsWithTokenFile() async throws { + // Simulating a token file + + let tokenFilePath = Bundle.module.url(forResource: "test_token", withExtension: "txt")!.path + + // Set the environment variable to point to the token file + setenv("AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", tokenFilePath, 1) + setenv("AWS_CONTAINER_CREDENTIALS_FULL_URI", "http://www.example.com/subfolder/test.txt", 1) -class MockEnvironment: Environment, EnvironmentProvider { - let relativeURI: String? - let absoluteURI: String? + // Ensure the resolver correctly loads the token from the file + let resolver = try ECSAWSCredentialIdentityResolver() + XCTAssertEqual(resolver.resolvedAuthorizationToken, "sample-token") + XCTAssertEqual(resolver.resolvedHost, "www.example.com") + XCTAssertEqual(resolver.resolvedPathAndQuery, "/subfolder/test.txt") + } + + func testGetCredentialsWithTokenEnv() async throws { + // Set the environment variable directly for the token + setenv("AWS_CONTAINER_AUTHORIZATION_TOKEN", "env-token", 1) + setenv("AWS_CONTAINER_CREDENTIALS_FULL_URI", "http://www.example.com/subfolder/test.txt", 1) - init( - relativeURI: String? = nil, - absoluteURI: String? = nil - ) { - self.relativeURI = relativeURI - self.absoluteURI = absoluteURI + // Ensure the resolver correctly loads the token from the environment + let resolver = try ECSAWSCredentialIdentityResolver() + XCTAssertEqual(resolver.resolvedAuthorizationToken, "env-token") + XCTAssertEqual(resolver.resolvedHost, "www.example.com") + XCTAssertEqual(resolver.resolvedPathAndQuery, "/subfolder/test.txt") } - func environmentVariable(key: String) -> String? { - switch key { - case "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI": - return self.relativeURI - case "AWS_CONTAINER_CREDENTIALS_FULL_URI": - return self.absoluteURI - default: - return nil - } + func testGetCredentialsWithDirectToken() async throws { + // Pass the token directly to the resolver + let resolver = try ECSAWSCredentialIdentityResolver( + absoluteURI: "http://www.example.com/subfolder/test.txt", + authorizationToken: "direct-token" + ) + + // Ensure the resolver correctly uses the passed token + XCTAssertEqual(resolver.resolvedAuthorizationToken, "direct-token") + XCTAssertEqual(resolver.resolvedHost, "www.example.com") + XCTAssertEqual(resolver.resolvedPathAndQuery, "/subfolder/test.txt") + } + + func testTokenNotResolvedWithRelativeURI() async throws { + // Pass the token directly to the resolver + let resolver = try ECSAWSCredentialIdentityResolver( + relativeURI: "/test", + authorizationToken: "direct-token" + ) + + // Ensure the resolver correctly uses the passed token + // Authorization token is not used with relative URI + XCTAssertEqual(resolver.resolvedAuthorizationToken, nil) + XCTAssertEqual(resolver.resolvedHost, "169.254.170.2") + XCTAssertEqual(resolver.resolvedPathAndQuery, "/test") } } diff --git a/Sources/Core/AWSSDKIdentity/Tests/AWSSDKIdentityTests/Resources/test_token.txt b/Sources/Core/AWSSDKIdentity/Tests/AWSSDKIdentityTests/Resources/test_token.txt new file mode 100644 index 00000000000..dc95932333a --- /dev/null +++ b/Sources/Core/AWSSDKIdentity/Tests/AWSSDKIdentityTests/Resources/test_token.txt @@ -0,0 +1 @@ +sample-token