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: add process credential provider #1076

Merged
merged 13 commits into from
Aug 18, 2023
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation
import XCTest
import AWSS3
import AWSClientRuntime

// Please provide your-access-key and your-secret-key in Resources/credenitals
class ProcessCredentialProviderTests: XCTestCase {

var client: S3Client!

override func setUp() async throws {
// Setup ProcessCredentialsProvider
let processCredentialsProvider = try ProcessCredentialsProvider(
configFilePath: Bundle.module.path(forResource: "config", ofType: nil)!,
credentialsFilePath: Bundle.module.path(forResource: "credentials", ofType: nil)!
)

// Setup S3ClientConfiguration to use ProcessCredentialsProvider
let testConfig = try await S3Client.S3ClientConfiguration()
testConfig.credentialsProvider = processCredentialsProvider

// Initialize our S3 client with the specified configuration
client = S3Client(config: testConfig)
}

// This test calls listBuckets() and forces S3Client to use ProcessCredentialsProvider
func test_listBuckets() async throws {
_ = try await client.listBuckets(input: ListBucketsInput())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[default]
region = us-east-1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[default]
credential_process = echo '{"Version": 1, "AccessKeyId": "your-access-key", "SecretAccessKey": "your-secret-key"}'
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import AwsCommonRuntimeKit
import ClientRuntime
import Foundation

/// The process credentials provider sources credentials from running a command or process.
/// The command to run is sourced from a profile in the AWS config file, using the standard
/// profile selection rules. The profile key the command is read from is "credential_process."
/// E.g.:
/// [default]
/// credential_process=/opt/amazon/bin/my-credential-fetcher --argsA=abc
/// On successfully running the command, the output should be a json data with the following
/// format:
/// {
/// "Version": 1,
/// "AccessKeyId": "accesskey",
/// "SecretAccessKey": "secretAccessKey"
/// "SessionToken": "....",
/// "Expiration": "2019-05-29T00:21:43Z"
/// }
/// Version here identifies the command output format version.
public struct ProcessCredentialsProvider: CredentialsSourcedByCRT {
let crtCredentialsProvider: CRTCredentialsProvider

/// Creates a credentials provider that gets credentials from running a command or process.
///
/// - Parameters:
/// - profileName: The profile name to use. If not provided it will be resolved internally via the `AWS_PROFILE` environment variable or defaulted to `default` if not configured.
/// - configFilePath: The path to the configuration file to use. If not provided it will be resolved internally via the `AWS_CONFIG_FILE` environment variable or defaulted to `~/.aws/config` if not configured.
/// - credentialsFilePath: The path to the shared credentials file to use. If not provided it will be resolved internally via the `AWS_SHARED_CREDENTIALS_FILE` environment variable or defaulted `~/.aws/credentials` if not configured.
public init(
profileName: String? = nil,
configFilePath: String? = nil,
credentialsFilePath: String? = nil
) throws {
let fileBasedConfig = try CRTFileBasedConfiguration(
configFilePath: configFilePath,
credentialsFilePath: credentialsFilePath
)
self.crtCredentialsProvider = try CRTCredentialsProvider(source: .process(
fileBasedConfiguration: fileBasedConfig,
profileFileNameOverride: profileName
))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ import XCTest
@_spi(FileBasedConfig) @testable import AWSClientRuntime

class CachedCredentialsProviderTests: XCTestCase {
func testGetCredentials() async {
func testGetCredentials() async throws {
var counter: Int = 0
let coreProvider = MockCredentialsProvider {
counter += 1
return .init(accessKey: "some_access_key", secret: "some_secret")
}

let subject = try! CachedCredentialsProvider(
let subject = try CachedCredentialsProvider(
source: coreProvider,
refreshTime: 1
)

_ = try! await subject.getCredentials()
_ = try! await subject.getCredentials()
_ = try! await subject.getCredentials()
_ = try! await subject.getCredentials()
_ = try await subject.getCredentials()
_ = try await subject.getCredentials()
_ = try await subject.getCredentials()
_ = try await subject.getCredentials()

XCTAssertEqual(counter, 1)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import XCTest
@_spi(FileBasedConfig) @testable import AWSClientRuntime

class CustomCredentialsProviderTests: XCTestCase {
func testGetCredentials() async {
func testGetCredentials() async throws {
let mockProvider = MockCredentialsProvider()
let subject = try! CustomCredentialsProvider(mockProvider)
let credentials = try! await subject.getCredentials()
let subject = try CustomCredentialsProvider(mockProvider)
let credentials = try await subject.getCredentials()

XCTAssertEqual(credentials.accessKey, "some_access_key")
XCTAssertEqual(credentials.secret, "some_secret")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,19 @@ import XCTest
@_spi(FileBasedConfig) @testable import AWSClientRuntime

class DefaultChainCredentialsProviderTests: XCTestCase {
func testGetCredentials() async {
func testGetCredentials() async throws {
setenv("AWS_ACCESS_KEY_ID", "some_access_key_b", 1)
setenv("AWS_SECRET_ACCESS_KEY", "some_secret_b", 1)

defer {
unsetenv("AWS_ACCESS_KEY_ID")
unsetenv("AWS_SECRET_ACCESS_KEY")
}

do {
let subject = try DefaultChainCredentialsProvider()
let credentials = try await subject.getCredentials()

XCTAssertEqual(credentials.accessKey, "some_access_key_b")
XCTAssertEqual(credentials.secret, "some_secret_b")
} catch {
XCTFail("Failed to create credentials")
}
let subject = try DefaultChainCredentialsProvider()
let credentials = try await subject.getCredentials()

XCTAssertEqual(credentials.accessKey, "some_access_key_b")
XCTAssertEqual(credentials.secret, "some_secret_b")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,19 @@ import XCTest
@_spi(FileBasedConfig) @testable import AWSClientRuntime

class EnvironmentCredentialsProviderTests: XCTestCase {
func testGetCredentials() async {
func testGetCredentials() async throws {
setenv("AWS_ACCESS_KEY_ID", "some_access_key_a", 1)
setenv("AWS_SECRET_ACCESS_KEY", "some_secret_a", 1)

defer {
unsetenv("AWS_ACCESS_KEY_ID")
unsetenv("AWS_SECRET_ACCESS_KEY")
}

let subject = try EnvironmentCredentialsProvider()
let credentials = try await subject.getCredentials()

do {
let subject = try EnvironmentCredentialsProvider()
let credentials = try await subject.getCredentials()

XCTAssertEqual(credentials.accessKey, "some_access_key_a")
XCTAssertEqual(credentials.secret, "some_secret_a")
} catch {
XCTFail("Failed to create credentials")
}
XCTAssertEqual(credentials.accessKey, "some_access_key_a")
XCTAssertEqual(credentials.secret, "some_secret_a")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import ClientRuntime
import Foundation
import XCTest

@_spi(FileBasedConfig) @testable import AWSClientRuntime
dayaffe marked this conversation as resolved.
Show resolved Hide resolved

// Test fails on CI build with macos-11, Xcode_13.2.1, platform=iOS Simulator but not on later versions
// ProcessCredentialsProvider is not useful on iOS platform so this test will remain disabled for now
#if !os(iOS)
class ProcessCredentialsProviderTests: XCTestCase {
let configPath = Bundle.module.path(forResource: "config", ofType: nil)!
let credentialsPath = Bundle.module.path(forResource: "credentials", ofType: nil)!

func testGetCredentialsWithDefaultProfile() async throws {
let subject = try ProcessCredentialsProvider(
configFilePath: configPath,
credentialsFilePath: credentialsPath
)
let credentials = try await subject.getCredentials()

XCTAssertEqual("AccessKey123", credentials.accessKey)
XCTAssertEqual("SecretAccessKey123", credentials.secret)
XCTAssertEqual("SessionToken123", credentials.sessionToken)
}

func testGetCredentialsWithNamedProfileFromConfigFile() async throws {
let subject = try ProcessCredentialsProvider(
profileName: "credentials-process-config-tests-profile",
configFilePath: configPath,
credentialsFilePath: credentialsPath
)
let credentials = try await subject.getCredentials()

XCTAssertEqual("AccessKey123", credentials.accessKey)
XCTAssertEqual("SecretAccessKey123", credentials.secret)
XCTAssertEqual("SessionToken123", credentials.sessionToken)
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,36 @@ class ProfileCredentialsProviderTests: XCTestCase {
let configPath = Bundle.module.path(forResource: "config", ofType: nil)!
let credentialsPath = Bundle.module.path(forResource: "credentials", ofType: nil)!

func testGetCredentialsWithDefaultProfile() async {
let subject = try! ProfileCredentialsProvider(
func testGetCredentialsWithDefaultProfile() async throws {
let subject = try ProfileCredentialsProvider(
configFilePath: configPath,
credentialsFilePath: credentialsPath
)
let credentials = try! await subject.getCredentials()
let credentials = try await subject.getCredentials()

XCTAssertEqual(credentials.accessKey, "access_key_default_cred")
XCTAssertEqual(credentials.secret, "secret_default_cred")
}

func testGetCredentialsWithNamedProfileFromConfigFile() async {
let subject = try! ProfileCredentialsProvider(
func testGetCredentialsWithNamedProfileFromConfigFile() async throws {
let subject = try ProfileCredentialsProvider(
profileName: "credentials-provider-config-tests-profile",
configFilePath: configPath,
credentialsFilePath: credentialsPath
)
let credentials = try! await subject.getCredentials()
let credentials = try await subject.getCredentials()

XCTAssertEqual(credentials.accessKey, "access_key_profile_config")
XCTAssertEqual(credentials.secret, "secret_profile_config")
}

func testGetCredentialsWithNamedProfileFromCredentialsFile() async {
let subject = try! ProfileCredentialsProvider(
func testGetCredentialsWithNamedProfileFromCredentialsFile() async throws {
let subject = try ProfileCredentialsProvider(
profileName: "credentials-provider-creds-tests-profile",
configFilePath: configPath,
credentialsFilePath: credentialsPath
)
let credentials = try! await subject.getCredentials()
let credentials = try await subject.getCredentials()

XCTAssertEqual(credentials.accessKey, "access_key_profile_cred")
XCTAssertEqual(credentials.secret, "secret_profile_cred")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import XCTest
@_spi(FileBasedConfig) @testable import AWSClientRuntime

class StaticCredentialsProviderTests: XCTestCase {
func testGetCredentials() async {
let subject = try! StaticCredentialsProvider(.init(
func testGetCredentials() async throws {
let subject = try StaticCredentialsProvider(.init(
accessKey: "some_access_key",
secret: "some_secret"
))
let credentials = try! await subject.getCredentials()
let credentials = try await subject.getCredentials()

XCTAssertEqual(credentials.accessKey, "some_access_key")
XCTAssertEqual(credentials.secret, "some_secret")
Expand Down
6 changes: 6 additions & 0 deletions Tests/Core/AWSClientRuntimeTests/Resources/config
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
[default]
aws_access_key_id = access_key_default_config
aws_secret_access_key = secret_default_config
credential_process = echo '{"Version": 1, "AccessKeyId": "AccessKey123", "SecretAccessKey": "SecretAccessKey123", "SessionToken": "SessionToken123","Expiration":"2020-02-25T06:03:31Z"}'

[profile credentials-provider-config-tests-profile]
aws_access_key_id = access_key_profile_config
aws_secret_access_key = secret_profile_config

[profile credentials-process-config-tests-profile]
aws_access_key_id = access_key_profile_config
aws_secret_access_key = secret_profile_config
credential_process = echo '{"Version": 1, "AccessKeyId": "AccessKey123", "SecretAccessKey": "SecretAccessKey123", "SessionToken": "SessionToken123","Expiration":"2020-02-25T06:03:31Z"}'