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

Commit 12bed39

Browse files
authored
Merge pull request #25 from Yasumoto/autoscaling_apis
Add support for AutoScaling API
2 parents 46fdc6e + c9657d9 commit 12bed39

16 files changed

+284
-94
lines changed

Package.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import PackageDescription
33
let package = Package(
44
name: "AWS",
55
targets: [
6-
Target(name: "AWS", dependencies: ["EC2", "S3", "AWSSignatureV4"]),
6+
Target(name: "AWS", dependencies: ["AutoScaling", "EC2", "S3", "AWSSignatureV4"]),
77
Target(name: "EC2", dependencies: ["AWSSignatureV4"]),
8+
Target(name: "AutoScaling", dependencies: ["AWSSignatureV4"]),
89
Target(name: "S3", dependencies: ["AWSSignatureV4"]),
910
Target(name: "VaporS3", dependencies: ["S3"]),
1011
],
1112
dependencies: [
1213
.Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2),
14+
.Package(url: "https://github.com/drmohundro/SWXMLHash", majorVersion: 3),
1315
]
1416
)

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ do {
8383
}
8484
```
8585

86+
## 📃 Development
87+
88+
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:
89+
90+
```
91+
swift package -Xswiftc -I/usr/local/Cellar/openssl/1.0.2j/include -Xlinker -L/usr/local/Cellar/openssl/1.0.2j/lib/ generate-xcodeproj
92+
```
8693

8794
## 🏆 Credits
8895

Sources/AWSSignatureV4/AWSSignatureV4.swift

+45-24
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,16 @@ public struct AWSSignatureV4 {
2020
case post = "POST"
2121
case put = "PUT"
2222
}
23-
23+
2424
let service: String
2525
let host: String
2626
let region: String
2727
let accessKey: String
2828
let secretKey: String
29-
30-
var unitTestDate: Date?
31-
29+
let contentType = "application/x-www-form-urlencoded; charset=utf-8"
30+
31+
internal var unitTestDate: Date?
32+
3233
var amzDate: String {
3334
let dateFormatter = DateFormatter()
3435
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
@@ -63,7 +64,7 @@ public struct AWSSignatureV4 {
6364
canonicalHash
6465
].joined(separator: "\n")
6566
}
66-
67+
6768
func getSignature(_ stringToSign: String) throws -> String {
6869
let dateHMAC = try HMAC(.sha256, dateStamp()).authenticate(key: "AWS4\(secretKey)")
6970
let regionHMAC = try HMAC(.sha256, region).authenticate(key: dateHMAC)
@@ -82,7 +83,7 @@ public struct AWSSignatureV4 {
8283
"aws4_request"
8384
].joined(separator: "/")
8485
}
85-
86+
8687
func getCanonicalRequest(
8788
payloadHash: String,
8889
method: Method,
@@ -93,7 +94,7 @@ public struct AWSSignatureV4 {
9394
) throws -> String {
9495
let path = try path.percentEncode(allowing: Byte.awsPathAllowed)
9596
let query = try query.percentEncode(allowing: Byte.awsQueryAllowed)
96-
97+
9798
return [
9899
method.rawValue,
99100
path,
@@ -108,6 +109,7 @@ public struct AWSSignatureV4 {
108109
func dateStamp() -> String {
109110
let date = unitTestDate ?? Date()
110111
let dateFormatter = DateFormatter()
112+
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
111113
dateFormatter.dateFormat = "YYYYMMdd"
112114
return dateFormatter.string(from: date)
113115
}
@@ -119,24 +121,24 @@ extension AWSSignatureV4 {
119121
host: String,
120122
hash: String
121123
) {
122-
headers["host"] = host
124+
headers["Host"] = host
123125
headers["X-Amz-Date"] = amzDate
124-
125-
if hash != "UNSIGNED-PAYLOAD" {
126+
127+
if hash != "UNSIGNED-PAYLOAD" {
126128
headers["x-amz-content-sha256"] = hash
127129
}
128130
}
129-
131+
130132
func alphabetize(_ dict: [String : String]) -> [(key: String, value: String)] {
131133
return dict.sorted(by: { $0.0.lowercased() < $1.0.lowercased() })
132134
}
133-
135+
134136
func createCanonicalHeaders(_ headers: [(key: String, value: String)]) -> String {
135137
return headers.map {
136138
"\($0.key.lowercased()):\($0.value)"
137139
}.joined(separator: "\n")
138140
}
139-
141+
140142
func createAuthorizationHeader(
141143
algorithm: String,
142144
credentialScope: String,
@@ -148,6 +150,19 @@ extension AWSSignatureV4 {
148150
}
149151

150152
extension AWSSignatureV4 {
153+
/**
154+
Sign a request to be sent to an AWS API.
155+
156+
- returns:
157+
A dictionary with headers to attach to a request
158+
159+
- parameters:
160+
- payload: A hash of this data will be included in the headers
161+
- method: Type of HTTP request
162+
- path: API call being referenced
163+
- query: Additional querystring in key-value format ("?key=value&key2=value2")
164+
- headers: HTTP headers added to the request
165+
*/
151166
public func sign(
152167
payload: Payload = .none,
153168
method: Method = .get,
@@ -158,14 +173,16 @@ extension AWSSignatureV4 {
158173
let algorithm = "AWS4-HMAC-SHA256"
159174
let credentialScope = getCredentialScope()
160175
let payloadHash = try payload.hashed()
161-
176+
162177
var headers = headers
178+
163179
generateHeadersToSign(headers: &headers, host: host, hash: payloadHash)
164-
180+
165181
let sortedHeaders = alphabetize(headers)
166182
let signedHeaders = sortedHeaders.map { $0.key.lowercased() }.joined(separator: ";")
167183
let canonicalHeaders = createCanonicalHeaders(sortedHeaders)
168-
184+
185+
// Task 1 is the Canonical Request
169186
let canonicalRequest = try getCanonicalRequest(
170187
payloadHash: payloadHash,
171188
method: method,
@@ -176,35 +193,39 @@ extension AWSSignatureV4 {
176193
)
177194

178195
let canonicalHash = try Hash.make(.sha256, canonicalRequest).hexString
179-
196+
197+
// Task 2 is the String to Sign
180198
let stringToSign = getStringToSign(
181199
algorithm: algorithm,
182200
date: amzDate,
183201
scope: credentialScope,
184202
canonicalHash: canonicalHash
185203
)
186-
204+
205+
// Task 3 calculates Signature
187206
let signature = try getSignature(stringToSign)
188-
207+
208+
//Task 4 Add signing information to the request
189209
let authorizationHeader = createAuthorizationHeader(
190210
algorithm: algorithm,
191211
credentialScope: credentialScope,
192212
signature: signature,
193213
signedHeaders: signedHeaders
194214
)
195-
196-
215+
197216
var requestHeaders: [HeaderKey: String] = [
198217
"X-Amz-Date": amzDate,
218+
"Content-Type": contentType,
199219
"x-amz-content-sha256": payloadHash,
200-
"Authorization": authorizationHeader
220+
"Authorization": authorizationHeader,
221+
"Host": self.host
201222
]
202-
223+
203224
headers.forEach { key, value in
204225
let headerKey = HeaderKey(stringLiteral: key)
205226
requestHeaders[headerKey] = value
206227
}
207-
228+
208229
return requestHeaders
209230
}
210231
}

Sources/AWSSignatureV4/ErrorParser/ErrorParser+Grammar.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Core
33
extension ErrorParser {
44
static let awsGrammar: Trie<AWSError> = {
55
let trie = Trie<AWSError>()
6-
6+
77
insert(into: trie, .accessDenied)
88
insert(into: trie, .accountProblem)
99
insert(into: trie, .ambiguousGrantByEmailAddress)
@@ -82,10 +82,10 @@ extension ErrorParser {
8282
insert(into: trie, .unexpectedContent)
8383
insert(into: trie, .unresolvableGrantByEmailAddress)
8484
insert(into: trie, .userKeyMustBeSpecified)
85-
85+
8686
return trie
8787
}()
88-
88+
8989
static func insert(into trie: Trie<AWSError>, _ error: AWSError) {
9090
trie.insert(error, for: error.rawValue.makeBytes())
9191
}

Sources/AWSSignatureV4/ErrorParser/ErrorParser.swift

+20-20
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ public struct ErrorParser {
66
case unknownError(String)
77
case couldNotFindErrorTag
88
}
9-
9+
1010
var scanner: Scanner<Byte>
11-
11+
1212
init(scanner: Scanner<Byte>) {
1313
self.scanner = scanner
1414
}
@@ -25,29 +25,29 @@ extension ErrorParser {
2525
mutating func extractError() throws -> AWSError {
2626
while true {
2727
skip(until: .lessThan)
28-
28+
2929
guard scanner.peek() != nil else {
3030
throw Error.couldNotFindErrorTag
3131
}
32-
32+
3333
// check for `<Code>`
3434
guard checkForCodeTag() else {
3535
continue
3636
}
37-
37+
3838
let errorBytes = consume(until: .lessThan)
39-
39+
4040
guard let error = ErrorParser.awsGrammar.contains(errorBytes) else {
4141
throw Error.unknownError(errorBytes.makeString())
4242
}
43-
43+
4444
return error
4545
}
4646
}
47-
47+
4848
mutating func checkForCodeTag() -> Bool {
4949
scanner.pop()
50-
50+
5151
for (index, byte) in ErrorParser.codeBytes.enumerated() {
5252
guard
5353
let preview = scanner.peek(aheadBy: index),
@@ -56,49 +56,49 @@ extension ErrorParser {
5656
return false
5757
}
5858
}
59-
59+
6060
scanner.pop(ErrorParser.codeBytes.count)
61-
61+
6262
return true
6363
}
6464
}
6565

6666
extension ErrorParser {
6767
mutating func skip(until terminator: Byte) {
6868
var count = 0
69-
69+
7070
while let byte = scanner.peek(aheadBy: count), byte != terminator {
7171
count += 1
7272
}
73-
73+
7474
scanner.pop(count)
7575
}
76-
76+
7777
mutating func consume(until terminator: Byte) -> Bytes {
7878
var bytes: [Byte] = []
79-
79+
8080
while let byte = scanner.peek(), byte != terminator {
8181
scanner.pop()
8282
bytes.append(byte)
8383
}
84-
84+
8585
return bytes
8686
}
8787
}
8888

8989
extension Byte {
9090
/// <
9191
static let lessThan: Byte = 0x3C
92-
92+
9393
/// >
9494
static let greaterThan: Byte = 0x3E
95-
95+
9696
/// lowercase `d`
9797
static let d: Byte = 0x64
98-
98+
9999
/// lowercase `e`
100100
static let e: Byte = 0x65
101-
101+
102102
/// lowercase `o`
103103
static let o: Byte = 0x6F
104104
}

Sources/AWSSignatureV4/ErrorParser/Scanner.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ extension Scanner {
99
init(_ data: [Element]) {
1010
self.elementsCopy = data
1111
self.elements = elementsCopy.withUnsafeBufferPointer { $0 }
12-
12+
1313
self.pointer = elements.baseAddress!
1414
}
1515
}
@@ -19,23 +19,23 @@ extension Scanner {
1919
guard pointer.advanced(by: n) < elements.endAddress else { return nil }
2020
return pointer.advanced(by: n).pointee
2121
}
22-
22+
2323
/// - Precondition: index != bytes.endIndex. It is assumed before calling pop that you have
2424
@discardableResult
2525
mutating func pop() -> Element {
2626
assert(pointer != elements.endAddress)
2727
defer { pointer = pointer.advanced(by: 1) }
2828
return pointer.pointee
2929
}
30-
30+
3131
/// - Precondition: index != bytes.endIndex. It is assumed before calling pop that you have
3232
@discardableResult
3333
mutating func attemptPop() throws -> Element {
3434
guard pointer < elements.endAddress else { throw ScannerError.Reason.endOfStream }
3535
defer { pointer = pointer.advanced(by: 1) }
3636
return pointer.pointee
3737
}
38-
38+
3939
mutating func pop(_ n: Int) {
4040
for _ in 0..<n {
4141
pop()
@@ -52,7 +52,7 @@ extension Scanner {
5252
struct ScannerError: Swift.Error {
5353
let position: UInt
5454
let reason: Reason
55-
55+
5656
enum Reason: Swift.Error {
5757
case endOfStream
5858
}

0 commit comments

Comments
 (0)