Skip to content

Commit

Permalink
feat: add process credential provider (#1076)
Browse files Browse the repository at this point in the history
  • Loading branch information
dayaffe authored Aug 18, 2023
1 parent 14cfdb6 commit 6b4cc3a
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 43 deletions.
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

// 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"}'

0 comments on commit 6b4cc3a

Please sign in to comment.