diff --git a/Package.swift b/Package.swift index b2ee735..d899eda 100644 --- a/Package.swift +++ b/Package.swift @@ -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), ] ) diff --git a/README.md b/README.md index 6f59d47..9639390 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/Sources/AWSSignatureV4/AWSSignatureV4.swift b/Sources/AWSSignatureV4/AWSSignatureV4.swift index 22f6fd6..fd746a3 100644 --- a/Sources/AWSSignatureV4/AWSSignatureV4.swift +++ b/Sources/AWSSignatureV4/AWSSignatureV4.swift @@ -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) @@ -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) @@ -82,7 +83,7 @@ public struct AWSSignatureV4 { "aws4_request" ].joined(separator: "/") } - + func getCanonicalRequest( payloadHash: String, method: Method, @@ -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, @@ -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) } @@ -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, @@ -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, @@ -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, @@ -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 } } diff --git a/Sources/AWSSignatureV4/ErrorParser/ErrorParser+Grammar.swift b/Sources/AWSSignatureV4/ErrorParser/ErrorParser+Grammar.swift index 3032b45..86254ad 100644 --- a/Sources/AWSSignatureV4/ErrorParser/ErrorParser+Grammar.swift +++ b/Sources/AWSSignatureV4/ErrorParser/ErrorParser+Grammar.swift @@ -3,7 +3,7 @@ import Core extension ErrorParser { static let awsGrammar: Trie = { let trie = Trie() - + insert(into: trie, .accessDenied) insert(into: trie, .accountProblem) insert(into: trie, .ambiguousGrantByEmailAddress) @@ -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, _ error: AWSError) { trie.insert(error, for: error.rawValue.makeBytes()) } diff --git a/Sources/AWSSignatureV4/ErrorParser/ErrorParser.swift b/Sources/AWSSignatureV4/ErrorParser/ErrorParser.swift index 8e8d2f5..b7cc6f7 100644 --- a/Sources/AWSSignatureV4/ErrorParser/ErrorParser.swift +++ b/Sources/AWSSignatureV4/ErrorParser/ErrorParser.swift @@ -6,9 +6,9 @@ public struct ErrorParser { case unknownError(String) case couldNotFindErrorTag } - + var scanner: Scanner - + init(scanner: Scanner) { self.scanner = scanner } @@ -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 `` 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), @@ -56,9 +56,9 @@ extension ErrorParser { return false } } - + scanner.pop(ErrorParser.codeBytes.count) - + return true } } @@ -66,22 +66,22 @@ extension ErrorParser { 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 } } @@ -89,16 +89,16 @@ extension ErrorParser { 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 } diff --git a/Sources/AWSSignatureV4/ErrorParser/Scanner.swift b/Sources/AWSSignatureV4/ErrorParser/Scanner.swift index 4092b73..a79dc35 100644 --- a/Sources/AWSSignatureV4/ErrorParser/Scanner.swift +++ b/Sources/AWSSignatureV4/ErrorParser/Scanner.swift @@ -9,7 +9,7 @@ extension Scanner { init(_ data: [Element]) { self.elementsCopy = data self.elements = elementsCopy.withUnsafeBufferPointer { $0 } - + self.pointer = elements.baseAddress! } } @@ -19,7 +19,7 @@ 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 { @@ -27,7 +27,7 @@ extension Scanner { 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 { @@ -35,7 +35,7 @@ extension Scanner { defer { pointer = pointer.advanced(by: 1) } return pointer.pointee } - + mutating func pop(_ n: Int) { for _ in 0.. { var key: UInt8 var value: ValueType? - + var children: [Trie] = [] - + var isLeaf: Bool { return children.count == 0 } - + convenience init() { self.init(key: 0x00) } - + init(key: UInt8, value: ValueType? = nil) { self.key = key self.value = value @@ -27,58 +27,58 @@ extension Trie { children.append(newValue) return } - + guard let newValue = newValue else { children.remove(at: index) return } - + let child = children[index] guard child.value == nil else { print("warning: inserted duplicate tokens into Trie.") return } - + child.value = newValue.value } } - + func insert(_ keypath: [UInt8], value: ValueType) { insert(value, for: keypath) } - + func insert(_ value: ValueType, for keypath: [UInt8]) { var current = self - + for (index, key) in keypath.enumerated() { guard let next = current[key] else { let next = Trie(key: key) current[key] = next current = next - + if index == keypath.endIndex - 1 { next.value = value } - + continue } - + if index == keypath.endIndex - 1 && next.value == nil { next.value = value } - + current = next } } - + func contains(_ keypath: [UInt8]) -> ValueType? { var current = self - + for key in keypath { guard let next = current[key] else { return nil } current = next } - + return current.value } } diff --git a/Sources/AWSSignatureV4/Payload.swift b/Sources/AWSSignatureV4/Payload.swift index 9c082c1..402fe88 100644 --- a/Sources/AWSSignatureV4/Payload.swift +++ b/Sources/AWSSignatureV4/Payload.swift @@ -12,10 +12,10 @@ extension Payload { switch self { case .bytes(let bytes): return try Hash.make(.sha256, bytes).hexString - + case .unsigned: return "UNSIGNED-PAYLOAD" - + case .none: return "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } @@ -27,7 +27,7 @@ extension Payload { switch self { case .bytes(let bytes): return bytes - + default: return [] } @@ -39,7 +39,7 @@ extension Payload: Equatable { switch (lhs, rhs) { case (.bytes, .bytes), (.unsigned, .unsigned), (.none, .none): return true - + default: return false } diff --git a/Sources/AWSSignatureV4/PercentEncoder.swift b/Sources/AWSSignatureV4/PercentEncoder.swift index 693c62f..8f84408 100644 --- a/Sources/AWSSignatureV4/PercentEncoder.swift +++ b/Sources/AWSSignatureV4/PercentEncoder.swift @@ -1,8 +1,8 @@ import Core extension Byte { - public static let awsQueryAllowed = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~=".makeBytes() - + public static let awsQueryAllowed = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~=&".makeBytes() + public static let awsPathAllowed = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~/".makeBytes() } diff --git a/Sources/AWSSignatureV4/Region.swift b/Sources/AWSSignatureV4/Region.swift index 3afea9c..0cf7a98 100644 --- a/Sources/AWSSignatureV4/Region.swift +++ b/Sources/AWSSignatureV4/Region.swift @@ -11,7 +11,7 @@ public enum Region: String { case apNortheast1 = "ap-northeast-1" case apNortheast2 = "ap-northeast-2" case saEast1 = "sa-east-1" - + public var host: String { switch self { case .usEast1: return "s3.amazonaws.com" diff --git a/Sources/AutoScaling/AutoScaling.swift b/Sources/AutoScaling/AutoScaling.swift new file mode 100644 index 0000000..d748f54 --- /dev/null +++ b/Sources/AutoScaling/AutoScaling.swift @@ -0,0 +1,103 @@ +import HTTP +import Vapor +import Core +import Transport +import AWSSignatureV4 +import TLS +import Node +import SWXMLHash + +@_exported import enum AWSSignatureV4.AWSError +@_exported import enum AWSSignatureV4.AccessControlList + +public enum LifeCycleState { + case InService +} + +public enum HealthStatus { + case Healthy +} + +public struct Instance { + public let state: LifeCycleState + public let instanceID: String + public let status: HealthStatus + public let protectedFromScaleIn: Bool + public let availabilityZone: String +} + +public struct AutoScaling { + public enum Error: Swift.Error { + case InvalidNextToken // The NextToken value is not valid. + case ResourceContention // You already have a pending update to an Auto Scaling resource (for example, a group, instance, or load balancer). + case invalidResponse(Status) + } + + let accessKey: String + let secretKey: String + let region: Region + let service: String + let host: String + let baseURL: String + let signer: AWSSignatureV4 + + public init(accessKey: String, secretKey: String, region: String) { + self.accessKey = accessKey + self.secretKey = secretKey + self.region = Region(rawValue: region)! + self.service = "autoscaling" + self.host = "\(self.service).amazonaws.com" + self.baseURL = "https://\(self.host)" + self.signer = AWSSignatureV4( + service: self.service, + host: self.host, + region: self.region, + accessKey: accessKey, + secretKey: secretKey + ) + } + + func generateQuery(for action: String, name: String) -> String { + return "Action=\(action)&AutoScalingGroupNames.member.1=\(name)&Version=2011-01-01" + } + + /* + * http://docs.aws.amazon.com/AutoScaling/latest/APIReference/API_DescribeAutoScalingGroups.html + */ + public func describeAutoScalingGroups(name: String) throws -> [Instance] { + let query = generateQuery(for: "DescribeAutoScalingGroups", name: name) + + let headers = try signer.sign(path: "/", query: query) + + let client = try EngineClientFactory.init().makeClient(hostname: host, port: 443, securityLayer: .tls(Context.init(.client)), proxy: nil) + + let version = HTTP.Version(major: 1, minor: 1) + let request = HTTP.Request(method: Method.get, uri: "\(baseURL)/?\(query)", version: version, headers: headers, body: Body.data(Bytes([]))) + let response = try client.respond(to: request) + + guard response.status == .ok else { + print("Response error: \(response)") + guard let bytes = response.body.bytes else { + throw Error.invalidResponse(response.status) + } + + throw try ErrorParser.parse(bytes) + } + + guard let bytes = response.body.bytes else { + throw Error.invalidResponse(.internalServerError) + } + + let output = bytes.makeString() + let xml = SWXMLHash.parse(output) + let autoscalingGroupXML = xml["DescribeAutoScalingGroupsResponse"]["DescribeAutoScalingGroupsResult"]["AutoScalingGroups"]["member"] + + var autoscalingGroup = [Instance]() + for member in autoscalingGroupXML["Instances"].children { + if let instanceId = member["InstanceId"].element?.text, let availabilityZone = member["AvailabilityZone"].element?.text { + autoscalingGroup.append(Instance(state: .InService, instanceID: instanceId, status: .Healthy, protectedFromScaleIn: false, availabilityZone: availabilityZone)) + } + } + return autoscalingGroup + } +} diff --git a/Sources/S3/S3.swift b/Sources/S3/S3.swift index 959c920..0015580 100644 --- a/Sources/S3/S3.swift +++ b/Sources/S3/S3.swift @@ -12,10 +12,10 @@ public struct S3 { case unimplemented case invalidResponse(Status) } - + let signer: AWSSignatureV4 public var host: String - + public init( host: String, accessKey: String, @@ -46,7 +46,7 @@ public struct S3 { guard let bytes = response.body.bytes else { throw Error.invalidResponse(response.status) } - + throw try ErrorParser.parse(bytes) } } @@ -54,20 +54,20 @@ public struct S3 { public func get(path: String) throws -> Bytes { let url = generateURL(for: path) let headers = try signer.sign(path: path) - + let response = try EngineClient.factory.get(url, headers) guard response.status == .ok else { guard let bytes = response.body.bytes else { throw Error.invalidResponse(response.status) } - + throw try ErrorParser.parse(bytes) } - + guard let bytes = response.body.bytes else { throw Error.invalidResponse(.internalServerError) } - + return bytes } @@ -89,7 +89,7 @@ extension Dictionary where Key: CustomStringConvertible, Value: CustomStringConv self.forEach { result.updateValue($0.value.description, forKey: HeaderKey($0.key.description)) } - + return result } } diff --git a/Tests/AWSTests/AutoscalingTests.swift b/Tests/AWSTests/AutoscalingTests.swift new file mode 100644 index 0000000..190b586 --- /dev/null +++ b/Tests/AWSTests/AutoscalingTests.swift @@ -0,0 +1,18 @@ +import XCTest + +import HTTP +import Foundation + +@testable import AutoScaling + +class AutoscalingTests: XCTestCase { + static var allTests = [ + "testGenerateQuery" + ] + + func testGenerateQuery() { + let autoscaling = AutoScaling(accessKey: "fake", secretKey: "secret", region: "us-east-1") + let query = autoscaling.generateQuery(for: "Action", name: "autoscaling-name") + XCTAssertEqual(query, "Action=Action&AutoScalingGroupNames.member.1=autoscaling-name&Version=2011-01-01") + } +} diff --git a/Tests/AWSTests/SignatureTestSuite.swift b/Tests/AWSTests/SignatureTestSuite.swift index 07232dc..06f36b0 100644 --- a/Tests/AWSTests/SignatureTestSuite.swift +++ b/Tests/AWSTests/SignatureTestSuite.swift @@ -1,6 +1,8 @@ /** All tests are based off of Amazon's Signature Test Suite See: http://docs.aws.amazon.com/general/latest/gr/signature-v4-test-suite.html + + They also include the [`x-amz-content-sha256` header](http://docs.aws.amazon.com/AmazonS3/latest/API/bucket-policy-s3-sigv4-conditions.html). */ import XCTest @@ -51,7 +53,7 @@ class SignatureTestSuite: XCTestCase { canonicalHeaders: expectedCanonicalHeaders ) } - + func testGetUTF8() { let expectedCanonicalRequest = "GET\n/%E1%88%B4\n\nhost:example.amazonaws.com\nx-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:20150830T123600Z\n\nhost;x-amz-content-sha256;x-amz-date\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" @@ -201,15 +203,18 @@ class SignatureTestSuite: XCTestCase { canonicalHeaders: expectedCanonicalHeaders ) } - + + /** + This test isn't based on the test suite, but tracks handling of special characters. + */ func testPostVanillaQueryNonunreserved() { - let expectedCanonicalRequest = "POST\n/\n%40%23%24%25%5E%26%2B=%2F%2C%3F%3E%3C%60%22%3B%3A%5C%7C%5D%5B%7B%7D\nhost:example.amazonaws.com\nx-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:20150830T123600Z\n\nhost;x-amz-content-sha256;x-amz-date\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + let expectedCanonicalRequest = "POST\n/\n%40%23%24%25%5E&%2B=%2F%2C%3F%3E%3C%60%22%3B%3A%5C%7C%5D%5B%7B%7D\nhost:example.amazonaws.com\nx-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:20150830T123600Z\n\nhost;x-amz-content-sha256;x-amz-date\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" let expectedCredentialScope = "20150830/us-east-1/service/aws4_request" let expectedCanonicalHeaders: [HeaderKey : String] = [ "X-Amz-Date": "20150830T123600Z", - "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=0912082dcab8c740fe2d9397fd399854d1f08e34a7279bc1e569d74b0e613996" + "Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=3db24d76713a5ccb9afe4a26acb83ae4cfa3e67d9e10f165bdf99bda199c625d" ] let result = sign(method: .post, path: "/", query: "@#$%^&+=/,?><`\";:\\|][{}") @@ -226,23 +231,50 @@ extension SignatureTestSuite { var testDate: Date { return SignatureTestSuite.dateFormatter.date(from: "20150830T123600Z")! } - + + + /** + Preparation of data to sign a canonical request. + + Intended to handle the preparation in the AWSSignatureV4's `sign` function + + - returns: + Hash value and multiple versions of headers + + - parameters: + - auth: Signature struct to use for calculations + - host: Hostname to sign for + */ + func prepCanonicalRequest(auth: AWSSignatureV4, host: String) -> (String, String, String) { + let payloadHash = try! Payload.none.hashed() + var headers = [String:String]() + auth.generateHeadersToSign(headers: &headers, host: host, hash: payloadHash) + + let sortedHeaders = auth.alphabetize(headers) + let signedHeaders = sortedHeaders.map { $0.key.lowercased() }.joined(separator: ";") + let canonicalHeaders = auth.createCanonicalHeaders(sortedHeaders) + return (payloadHash, signedHeaders, canonicalHeaders) + } + func sign( method: AWSSignatureV4.Method, path: String, query: String = "" ) -> SignerResult { + let host = "example.amazonaws.com" var auth = AWSSignatureV4( service: "service", - host: "example.amazonaws.com", + host: host, region: .usEast1, accessKey: "AKIDEXAMPLE", secretKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY" ) auth.unitTestDate = testDate + let (payloadHash, signedHeaders, preppedCanonicalHeaders) = prepCanonicalRequest(auth: auth, host: host) + let canonicalRequest = try! auth.getCanonicalRequest(payloadHash: payloadHash, method: method, path: path, query: query, canonicalHeaders: preppedCanonicalHeaders, signedHeaders: signedHeaders) + - let canonicalRequest = "" let credentialScope = auth.getCredentialScope() //FIXME(Brett): handle throwing diff --git a/Tests/AWSTests/Utilities/SignerResult.swift b/Tests/AWSTests/Utilities/SignerResult.swift index e07a9f9..e9c0f3d 100644 --- a/Tests/AWSTests/Utilities/SignerResult.swift +++ b/Tests/AWSTests/Utilities/SignerResult.swift @@ -16,11 +16,17 @@ extension SignerResult { file: StaticString = #file, line: UInt = #line ) { - //XCTAssertEqual(self.canonicalRequest, canonicalRequest, file: file, line: line) + XCTAssertEqual(self.canonicalRequest, canonicalRequest, file: file, line: line) XCTAssertEqual(self.credentialScope, credentialScope, file: file, line: line) canonicalHeaders.forEach { - XCTAssertEqual(self.canonicalHeaders[$0.key], $0.value, file: file, line: line) + if $0.key == "Authorization" { + for (givenLine, expectedLine) in zip(self.canonicalHeaders[$0.key]!.components(separatedBy: " "), $0.value.components(separatedBy: " ")) { + XCTAssertEqual(givenLine, expectedLine) + } + } else { + XCTAssertEqual(self.canonicalHeaders[$0.key], $0.value, file: file, line: line) + } } } } diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index e609e1c..4e77f1a 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -5,4 +5,5 @@ XCTMain([ testCase(AWSTests.allTests), testCase(SignatureTestSuite.allTests), testCase(ErrorParserTests.allTests), + testCase(AutoscalingTests.allTests), ])