Skip to content
This repository has been archived by the owner on Apr 20, 2024. It is now read-only.

Commit

Permalink
Merge pull request #25 from Yasumoto/autoscaling_apis
Browse files Browse the repository at this point in the history
Add support for AutoScaling API
  • Loading branch information
BrettRToomey authored Aug 8, 2017
2 parents 46fdc6e + c9657d9 commit 12bed39
Show file tree
Hide file tree
Showing 16 changed files with 284 additions and 94 deletions.
4 changes: 3 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import PackageDescription
let package = Package(
name: "AWS",
targets: [
Target(name: "AWS", dependencies: ["EC2", "S3", "AWSSignatureV4"]),
Target(name: "AWS", dependencies: ["AutoScaling", "EC2", "S3", "AWSSignatureV4"]),
Target(name: "EC2", dependencies: ["AWSSignatureV4"]),
Target(name: "AutoScaling", dependencies: ["AWSSignatureV4"]),
Target(name: "S3", dependencies: ["AWSSignatureV4"]),
Target(name: "VaporS3", dependencies: ["S3"]),
],
dependencies: [
.Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2),
.Package(url: "https://github.com/drmohundro/SWXMLHash", majorVersion: 3),
]
)
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ do {
}
```

## 📃 Development

If you want to improve this, you'll need to make sure you're making a copy of OpenSSL available to `swift build` and the toolchain. If you use Xcode, something like the following after `brew install openssl` will work:

```
swift package -Xswiftc -I/usr/local/Cellar/openssl/1.0.2j/include -Xlinker -L/usr/local/Cellar/openssl/1.0.2j/lib/ generate-xcodeproj
```

## 🏆 Credits

Expand Down
69 changes: 45 additions & 24 deletions Sources/AWSSignatureV4/AWSSignatureV4.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@ public struct AWSSignatureV4 {
case post = "POST"
case put = "PUT"
}

let service: String
let host: String
let region: String
let accessKey: String
let secretKey: String

var unitTestDate: Date?

let contentType = "application/x-www-form-urlencoded; charset=utf-8"

internal var unitTestDate: Date?

var amzDate: String {
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
Expand Down Expand Up @@ -63,7 +64,7 @@ public struct AWSSignatureV4 {
canonicalHash
].joined(separator: "\n")
}

func getSignature(_ stringToSign: String) throws -> String {
let dateHMAC = try HMAC(.sha256, dateStamp()).authenticate(key: "AWS4\(secretKey)")
let regionHMAC = try HMAC(.sha256, region).authenticate(key: dateHMAC)
Expand All @@ -82,7 +83,7 @@ public struct AWSSignatureV4 {
"aws4_request"
].joined(separator: "/")
}

func getCanonicalRequest(
payloadHash: String,
method: Method,
Expand All @@ -93,7 +94,7 @@ public struct AWSSignatureV4 {
) throws -> String {
let path = try path.percentEncode(allowing: Byte.awsPathAllowed)
let query = try query.percentEncode(allowing: Byte.awsQueryAllowed)

return [
method.rawValue,
path,
Expand All @@ -108,6 +109,7 @@ public struct AWSSignatureV4 {
func dateStamp() -> String {
let date = unitTestDate ?? Date()
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
dateFormatter.dateFormat = "YYYYMMdd"
return dateFormatter.string(from: date)
}
Expand All @@ -119,24 +121,24 @@ extension AWSSignatureV4 {
host: String,
hash: String
) {
headers["host"] = host
headers["Host"] = host
headers["X-Amz-Date"] = amzDate
if hash != "UNSIGNED-PAYLOAD" {

if hash != "UNSIGNED-PAYLOAD" {
headers["x-amz-content-sha256"] = hash
}
}

func alphabetize(_ dict: [String : String]) -> [(key: String, value: String)] {
return dict.sorted(by: { $0.0.lowercased() < $1.0.lowercased() })
}

func createCanonicalHeaders(_ headers: [(key: String, value: String)]) -> String {
return headers.map {
"\($0.key.lowercased()):\($0.value)"
}.joined(separator: "\n")
}

func createAuthorizationHeader(
algorithm: String,
credentialScope: String,
Expand All @@ -148,6 +150,19 @@ extension AWSSignatureV4 {
}

extension AWSSignatureV4 {
/**
Sign a request to be sent to an AWS API.
- returns:
A dictionary with headers to attach to a request
- parameters:
- payload: A hash of this data will be included in the headers
- method: Type of HTTP request
- path: API call being referenced
- query: Additional querystring in key-value format ("?key=value&key2=value2")
- headers: HTTP headers added to the request
*/
public func sign(
payload: Payload = .none,
method: Method = .get,
Expand All @@ -158,14 +173,16 @@ extension AWSSignatureV4 {
let algorithm = "AWS4-HMAC-SHA256"
let credentialScope = getCredentialScope()
let payloadHash = try payload.hashed()

var headers = headers

generateHeadersToSign(headers: &headers, host: host, hash: payloadHash)

let sortedHeaders = alphabetize(headers)
let signedHeaders = sortedHeaders.map { $0.key.lowercased() }.joined(separator: ";")
let canonicalHeaders = createCanonicalHeaders(sortedHeaders)


// Task 1 is the Canonical Request
let canonicalRequest = try getCanonicalRequest(
payloadHash: payloadHash,
method: method,
Expand All @@ -176,35 +193,39 @@ extension AWSSignatureV4 {
)

let canonicalHash = try Hash.make(.sha256, canonicalRequest).hexString


// Task 2 is the String to Sign
let stringToSign = getStringToSign(
algorithm: algorithm,
date: amzDate,
scope: credentialScope,
canonicalHash: canonicalHash
)


// Task 3 calculates Signature
let signature = try getSignature(stringToSign)


//Task 4 Add signing information to the request
let authorizationHeader = createAuthorizationHeader(
algorithm: algorithm,
credentialScope: credentialScope,
signature: signature,
signedHeaders: signedHeaders
)



var requestHeaders: [HeaderKey: String] = [
"X-Amz-Date": amzDate,
"Content-Type": contentType,
"x-amz-content-sha256": payloadHash,
"Authorization": authorizationHeader
"Authorization": authorizationHeader,
"Host": self.host
]

headers.forEach { key, value in
let headerKey = HeaderKey(stringLiteral: key)
requestHeaders[headerKey] = value
}

return requestHeaders
}
}
6 changes: 3 additions & 3 deletions Sources/AWSSignatureV4/ErrorParser/ErrorParser+Grammar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Core
extension ErrorParser {
static let awsGrammar: Trie<AWSError> = {
let trie = Trie<AWSError>()

insert(into: trie, .accessDenied)
insert(into: trie, .accountProblem)
insert(into: trie, .ambiguousGrantByEmailAddress)
Expand Down Expand Up @@ -82,10 +82,10 @@ extension ErrorParser {
insert(into: trie, .unexpectedContent)
insert(into: trie, .unresolvableGrantByEmailAddress)
insert(into: trie, .userKeyMustBeSpecified)

return trie
}()

static func insert(into trie: Trie<AWSError>, _ error: AWSError) {
trie.insert(error, for: error.rawValue.makeBytes())
}
Expand Down
40 changes: 20 additions & 20 deletions Sources/AWSSignatureV4/ErrorParser/ErrorParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ public struct ErrorParser {
case unknownError(String)
case couldNotFindErrorTag
}

var scanner: Scanner<Byte>

init(scanner: Scanner<Byte>) {
self.scanner = scanner
}
Expand All @@ -25,29 +25,29 @@ extension ErrorParser {
mutating func extractError() throws -> AWSError {
while true {
skip(until: .lessThan)

guard scanner.peek() != nil else {
throw Error.couldNotFindErrorTag
}

// check for `<Code>`
guard checkForCodeTag() else {
continue
}

let errorBytes = consume(until: .lessThan)

guard let error = ErrorParser.awsGrammar.contains(errorBytes) else {
throw Error.unknownError(errorBytes.makeString())
}

return error
}
}

mutating func checkForCodeTag() -> Bool {
scanner.pop()

for (index, byte) in ErrorParser.codeBytes.enumerated() {
guard
let preview = scanner.peek(aheadBy: index),
Expand All @@ -56,49 +56,49 @@ extension ErrorParser {
return false
}
}

scanner.pop(ErrorParser.codeBytes.count)

return true
}
}

extension ErrorParser {
mutating func skip(until terminator: Byte) {
var count = 0

while let byte = scanner.peek(aheadBy: count), byte != terminator {
count += 1
}

scanner.pop(count)
}

mutating func consume(until terminator: Byte) -> Bytes {
var bytes: [Byte] = []

while let byte = scanner.peek(), byte != terminator {
scanner.pop()
bytes.append(byte)
}

return bytes
}
}

extension Byte {
/// <
static let lessThan: Byte = 0x3C

/// >
static let greaterThan: Byte = 0x3E

/// lowercase `d`
static let d: Byte = 0x64

/// lowercase `e`
static let e: Byte = 0x65

/// lowercase `o`
static let o: Byte = 0x6F
}
10 changes: 5 additions & 5 deletions Sources/AWSSignatureV4/ErrorParser/Scanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ extension Scanner {
init(_ data: [Element]) {
self.elementsCopy = data
self.elements = elementsCopy.withUnsafeBufferPointer { $0 }

self.pointer = elements.baseAddress!
}
}
Expand All @@ -19,23 +19,23 @@ extension Scanner {
guard pointer.advanced(by: n) < elements.endAddress else { return nil }
return pointer.advanced(by: n).pointee
}

/// - Precondition: index != bytes.endIndex. It is assumed before calling pop that you have
@discardableResult
mutating func pop() -> Element {
assert(pointer != elements.endAddress)
defer { pointer = pointer.advanced(by: 1) }
return pointer.pointee
}

/// - Precondition: index != bytes.endIndex. It is assumed before calling pop that you have
@discardableResult
mutating func attemptPop() throws -> Element {
guard pointer < elements.endAddress else { throw ScannerError.Reason.endOfStream }
defer { pointer = pointer.advanced(by: 1) }
return pointer.pointee
}

mutating func pop(_ n: Int) {
for _ in 0..<n {
pop()
Expand All @@ -52,7 +52,7 @@ extension Scanner {
struct ScannerError: Swift.Error {
let position: UInt
let reason: Reason

enum Reason: Swift.Error {
case endOfStream
}
Expand Down
Loading

0 comments on commit 12bed39

Please sign in to comment.