Skip to content

Commit

Permalink
Merge pull request #6 from noppoMan/meta-data-service
Browse files Browse the repository at this point in the history
add MetaDataService to auto retrieve instance role credentials
  • Loading branch information
noppoMan authored Jul 12, 2017
2 parents e0f34a3 + 0b33498 commit 24b9cc8
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 2 deletions.
8 changes: 8 additions & 0 deletions Sources/AWSSDKSwiftCore/AWSClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
14 changes: 13 additions & 1 deletion Sources/AWSSDKSwiftCore/Credential/Credential.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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")
Expand All @@ -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 {
Expand All @@ -49,6 +60,7 @@ struct EnvironementCredential: CredentialProvider {
}
self.accessKeyId = accessKeyId
self.secretAccessKey = secretAccessKey
self.sessionToken = ProcessInfo.processInfo.environment["AWS_SESSION_TOKEN"]
}
}

141 changes: 141 additions & 0 deletions Sources/AWSSDKSwiftCore/MetaDataService.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
6 changes: 5 additions & 1 deletion Sources/AWSSDKSwiftCore/Signers/V4.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import CLibreSSL
extension Signers {
public final class V4 {

public let credentials: CredentialProvider
public var credentials: CredentialProvider

public let region: Region

Expand Down Expand Up @@ -108,6 +108,10 @@ extension Signers {
bodyDigest: bodyDigest
)

if let token = self.credentials.sessionToken {
headersForSign["x-amz-security-token"] = token
}

return headersForSign
}

Expand Down

0 comments on commit 24b9cc8

Please sign in to comment.