diff --git a/Sources/AWSSDKSwiftCore/AWSClient.swift b/Sources/AWSSDKSwiftCore/AWSClient.swift index c3a00501c..9450e97af 100644 --- a/Sources/AWSSDKSwiftCore/AWSClient.swift +++ b/Sources/AWSSDKSwiftCore/AWSClient.swift @@ -179,6 +179,14 @@ extension AWSClient { headers[key.description] = value } + if self.signer.credentials.isEmpty() { + do { + signer.credentials = try MetaDataService().getCredential() + } catch { + // should not be crash + } + } + let signedHeaders = signer.signedHeaders( url: prorsumRequest.url, headers: headers, diff --git a/Sources/AWSSDKSwiftCore/Credential/Credential.swift b/Sources/AWSSDKSwiftCore/Credential/Credential.swift index e794ae546..47e76520c 100644 --- a/Sources/AWSSDKSwiftCore/Credential/Credential.swift +++ b/Sources/AWSSDKSwiftCore/Credential/Credential.swift @@ -11,6 +11,13 @@ import Foundation public protocol CredentialProvider { var accessKeyId: String { get } var secretAccessKey: String { get } + var sessionToken: String? { get } +} + +extension CredentialProvider { + public func isEmpty() -> Bool { + return self.accessKeyId.isEmpty || self.secretAccessKey.isEmpty + } } public struct SharedCredential: CredentialProvider { @@ -19,6 +26,7 @@ public struct SharedCredential: CredentialProvider { public let accessKeyId: String public let secretAccessKey: String + public let sessionToken: String? public init(filename: String = "~/.aws/credentials", profile: String = "default") throws { fatalError("Umimplemented") @@ -29,16 +37,19 @@ public struct SharedCredential: CredentialProvider { public struct Credential: CredentialProvider { public let accessKeyId: String public let secretAccessKey: String + public let sessionToken: String? - public init(accessKeyId: String, secretAccessKey: String) { + public init(accessKeyId: String, secretAccessKey: String, sessionToken: String? = nil) { self.accessKeyId = accessKeyId self.secretAccessKey = secretAccessKey + self.sessionToken = sessionToken ?? ProcessInfo.processInfo.environment["AWS_SESSION_TOKEN"] } } struct EnvironementCredential: CredentialProvider { let accessKeyId: String let secretAccessKey: String + public let sessionToken: String? init?() { guard let accessKeyId = ProcessInfo.processInfo.environment["AWS_ACCESS_KEY_ID"] else { @@ -49,6 +60,7 @@ struct EnvironementCredential: CredentialProvider { } self.accessKeyId = accessKeyId self.secretAccessKey = secretAccessKey + self.sessionToken = ProcessInfo.processInfo.environment["AWS_SESSION_TOKEN"] } } diff --git a/Sources/AWSSDKSwiftCore/MetaDataService.swift b/Sources/AWSSDKSwiftCore/MetaDataService.swift new file mode 100644 index 000000000..02ff4e117 --- /dev/null +++ b/Sources/AWSSDKSwiftCore/MetaDataService.swift @@ -0,0 +1,141 @@ +// +// MetaDataService.swift +// SwiftAWSDynamodb +// +// Created by Yuki Takei on 2017/07/12. +// +// + +import Foundation +import Prorsum + +enum MetaDataServiceError: Error { + case missingRequiredParam(String) + case couldNotGetInstanceRoleName + case connectionTimeout +} + +struct MetaDataService { + struct MetaData { + let code: String + let lastUpdated: String + let type: String + let accessKeyId: String + let secretAccessKey: String + let token: String + let expiration: String + + var credential: Credential { + return Credential( + accessKeyId: accessKeyId, + secretAccessKey: secretAccessKey, + sessionToken: token + ) + } + + init(dictionary: [String: Any]) throws { + guard let code = dictionary["Code"] as? String else { + throw MetaDataServiceError.missingRequiredParam("Code") + } + + guard let lastUpdated = dictionary["LastUpdated"] as? String else { + throw MetaDataServiceError.missingRequiredParam("LastUpdated") + } + + guard let type = dictionary["Type"] as? String else { + throw MetaDataServiceError.missingRequiredParam("Type") + } + + guard let accessKeyId = dictionary["AccessKeyId"] as? String else { + throw MetaDataServiceError.missingRequiredParam("AccessKeyId") + } + + guard let secretAccessKey = dictionary["SecretAccessKey"] as? String else { + throw MetaDataServiceError.missingRequiredParam("SecretAccessKey") + } + + guard let token = dictionary["Token"] as? String else { + throw MetaDataServiceError.missingRequiredParam("Token") + } + + guard let expiration = dictionary["Expiration"] as? String else { + throw MetaDataServiceError.missingRequiredParam("Expiration") + } + + self.code = code + self.lastUpdated = lastUpdated + self.type = type + self.accessKeyId = accessKeyId + self.secretAccessKey = secretAccessKey + self.token = token + self.expiration = expiration + } + } + + let host = "169.254.169.254" + + var urlString: String { + return "http://\(host)/latest/meta-data/iam/security-credentials/" + } + + private func request(url: URL, timeout: TimeInterval) throws -> Response { + let chan = Channel<(Response?, Error?)>.make(capacity: 1) + go { + do { + let client = try HTTPClient(url: url) + try client.open() + let response = try client.request() + try chan.send((response, nil)) + } catch { + do { try chan.send((nil, error)) } catch {} + } + } + + var response: Response? + var error: Error? + let endAt = Date().addingTimeInterval(timeout) + + forSelect { done in + when(chan) { res, err in + response = res + error = err + chan.close() + done() + } + + otherwise { + if Date() > endAt { + error = MetaDataServiceError.connectionTimeout + chan.close() + done() + } + } + } + + if let e = error { + throw e + } + + return response! + } + + func getRoleName() throws -> String { + let response = try request(url: URL(string: self.urlString)!, timeout: 2) + switch response.statusCode { + case 200: + return String(data: response.body.asData(), encoding: .utf8) ?? "" + default: + throw MetaDataServiceError.couldNotGetInstanceRoleName + } + } + + func getCredential() throws -> Credential { + let roleName = try getRoleName() + let url = URL(string: "\(urlString)/\(roleName)")! + let response = try request(url: url, timeout: 2) + let body: [String: Any] = try JSONSerialization.jsonObject(with: response.body.asData(), options: []) as? [String: Any] ?? [:] + let metadata = try MetaData(dictionary: body) + + return metadata.credential + } +} diff --git a/Sources/AWSSDKSwiftCore/Signers/V4.swift b/Sources/AWSSDKSwiftCore/Signers/V4.swift index 3f336100c..e5dd3bda4 100644 --- a/Sources/AWSSDKSwiftCore/Signers/V4.swift +++ b/Sources/AWSSDKSwiftCore/Signers/V4.swift @@ -12,7 +12,7 @@ import CLibreSSL extension Signers { public final class V4 { - public let credentials: CredentialProvider + public var credentials: CredentialProvider public let region: Region @@ -108,6 +108,10 @@ extension Signers { bodyDigest: bodyDigest ) + if let token = self.credentials.sessionToken { + headersForSign["x-amz-security-token"] = token + } + return headersForSign }