Skip to content

Commit

Permalink
Allow custom region names (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
dpgao authored Jan 9, 2019
1 parent f280769 commit 528112a
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 132 deletions.
15 changes: 5 additions & 10 deletions Sources/S3/Extensions/S3+Bucket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,12 @@ public extension S3 {
return Region.usEast1
} else {
// Split bucket.s3.region.amazonaws.com into parts
var parts = endpoint.split(separator: ".")
// Remove .com
parts.removeLast()
// Remove .amazonaws
parts.removeLast()
// Get region (lat part)
let regionString = String(parts.removeLast()).lowercased()
guard let region = Region(rawValue: regionString) else {
// Drop .com and .amazonaws
// Get region (last part)
guard let regionString = endpoint.split(separator: ".").dropLast(2).last?.lowercased() else {
throw Error.badResponse(response)
}
return region
return Region(name: .init(regionString))
}
} else {
throw Error.badResponse(response)
Expand Down Expand Up @@ -74,7 +69,7 @@ public extension S3 {

let content = """
<CreateBucketConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<LocationConstraint>\(region.name.rawValue)</LocationConstraint>
<LocationConstraint>\(region.name)</LocationConstraint>
</CreateBucketConfiguration>
"""

Expand Down
188 changes: 71 additions & 117 deletions Sources/S3Signer/Region.swift
Original file line number Diff line number Diff line change
@@ -1,71 +1,79 @@
import Foundation


/// AWS Region
public struct Region {

/// name of the region, see RegionName
public let name: RegionName

/// name of the region, see Name
public let name: Name
/// name of the custom host, can contain IP and/or port (e.g. 127.0.0.1:9000)
public let hostName: String?

/// use TLS/https (defaults to true)
public let useTLS: Bool

public enum RegionName : String, Codable {
public struct Name: ExpressibleByStringLiteral, LosslessStringConvertible {

/// US East (N. Virginia)
case usEast1 = "us-east-1"
public static let usEast1: Name = "us-east-1"

/// US East (Ohio)
case usEast2 = "us-east-2"
public static let usEast2: Name = "us-east-2"

/// US West (N. California)
case usWest1 = "us-west-1"
public static let usWest1: Name = "us-west-1"

/// US West (Oregon)
case usWest2 = "us-west-2"
public static let usWest2: Name = "us-west-2"

/// Canada (Central)
case caCentral1 = "ca-central-1"
public static let caCentral1: Name = "ca-central-1"

/// EU (Frankfurt)
case euCentral1 = "eu-central-1"
public static let euCentral1: Name = "eu-central-1"

/// EU (Ireland)
case euWest1 = "eu-west-1"
public static let euWest1: Name = "eu-west-1"

/// EU (London)
case euWest2 = "eu-west-2"
public static let euWest2: Name = "eu-west-2"

/// EU (Paris)
case euWest3 = "eu-west-3"
public static let euWest3: Name = "eu-west-3"

/// Asia Pacific (Tokyo)
case apNortheast1 = "ap-northeast-1"
public static let apNortheast1: Name = "ap-northeast-1"

/// Asia Pacific (Seoul)
case apNortheast2 = "ap-northeast-2"
public static let apNortheast2: Name = "ap-northeast-2"

/// Asia Pacific (Osaka-Local)
case apNortheast3 = "ap-northeast-3"
public static let apNortheast3: Name = "ap-northeast-3"

/// Asia Pacific (Singapore)
case apSoutheast1 = "ap-southeast-1"
public static let apSoutheast1: Name = "ap-southeast-1"

/// Asia Pacific (Sydney)
case apSoutheast2 = "ap-southeast-2"
public static let apSoutheast2: Name = "ap-southeast-2"

/// Asia Pacific (Mumbai)
case apSouth1 = "ap-south-1"
public static let apSouth1: Name = "ap-south-1"

/// South America (São Paulo)
case saEast1 = "sa-east-1"
public static let saEast1: Name = "sa-east-1"

public let description: String

public init(_ value: String) {
self.description = value
}

public init(stringLiteral value: String) {
self.init(value)
}
}

/// initializer for a (custom) region. If you use a custom hostName, you
/// still need a region (e.g. use usEast1 for Minio)
public init(name: RegionName, hostName: String? = nil, useTLS: Bool = true) {
public init(name: Name, hostName: String? = nil, useTLS: Bool = true) {
self.name = name
self.hostName = hostName
self.useTLS = useTLS
Expand All @@ -77,125 +85,71 @@ extension Region {

/// Base URL / Host
public var host: String {
if let host = hostName {
return host
}
return "s3.\(name.rawValue).amazonaws.com"
return hostName ?? "s3.\(name).amazonaws.com"
}
}

extension Region {
public init?(rawValue: String) {
guard let name = RegionName(rawValue: rawValue) else {
return nil
}

self.init(name: name)
}

/// convenience var for US East (N. Virginia)
public static var usEast1: Region {
return Region(name: RegionName.usEast1)
}

public static let usEast1 = Region(name: .usEast1)

/// convenience var for US East (Ohio)
public static var usEast2: Region {
return Region(name: RegionName.usEast2)
}

public static let usEast2 = Region(name: .usEast2)

/// convenience var for US West (N. California)
public static var usWest1: Region {
return Region(name: RegionName.usWest1)
}

public static let usWest1 = Region(name: .usWest1)

/// convenience var for US West (Oregon)
public static var usWest2: Region {
return Region(name: RegionName.usWest2)
}

public static let usWest2 = Region(name: .usWest2)

/// convenience var for Canada (Central)
public static var caCentral1: Region {
return Region(name: RegionName.caCentral1)
}

public static let caCentral1 = Region(name: .caCentral1)

/// convenience var for EU (Frankfurt)
public static var euCentral1: Region {
return Region(name: RegionName.euCentral1)
}

public static let euCentral1 = Region(name: .euCentral1)

/// convenience var for EU (Ireland)
public static var euWest1: Region {
return Region(name: RegionName.euWest1)
}

public static let euWest1 = Region(name: .euWest1)

/// convenience var for EU (London)
public static var euWest2: Region {
return Region(name: RegionName.euWest2)
}

public static let euWest2 = Region(name: .euWest2)

/// convenience var for EU (Paris)
public static var euWest3: Region {
return Region(name: RegionName.euWest3)
}

public static let euWest3 = Region(name: .euWest3)

/// convenience var for Asia Pacific (Tokyo)
public static var apNortheast1: Region {
return Region(name: RegionName.apNortheast1)
}

public static let apNortheast1 = Region(name: .apNortheast1)

/// convenience var for Asia Pacific (Seoul)
public static var apNortheast2: Region {
return Region(name: RegionName.apNortheast2)
}

public static let apNortheast2 = Region(name: .apNortheast2)

/// convenience var for Asia Pacific (Osaka-Local)
public static var apNortheast3: Region {
return Region(name: RegionName.apNortheast3)
}

public static let apNortheast3 = Region(name: .apNortheast3)

/// convenience var for Asia Pacific (Singapore)
public static var apSoutheast1: Region {
return Region(name: RegionName.apSoutheast1)
}

public static let apSoutheast1 = Region(name: .apSoutheast1)

/// convenience var for Asia Pacific (Sydney)
public static var apSoutheast2: Region {
return Region(name: RegionName.apSoutheast2)
}

public static let apSoutheast2 = Region(name: .apSoutheast2)

/// convenience var for Asia Pacific (Mumbai)
public static var apSouth1: Region {
return Region(name: RegionName.apSouth1)
}

public static let apSouth1 = Region(name: .apSouth1)

/// convenience var for South America (São Paulo)
public static var saEast1: Region {
return Region(name: RegionName.saEast1)
}
public static let saEast1 = Region(name: .saEast1)
}

/// Codable support for Region
extension Region: Codable {

/// decodes a string (see RegionName) to a Region (does not support custom hosts)
/// decodes a string (see Name) to a Region (does not support custom hosts)
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()

let name = try container.decode(String.self)

guard let regionName = RegionName(rawValue: name) else {
throw DecodingError.typeMismatch(String.self, DecodingError.Context(codingPath: [],
debugDescription: "Could not find region for \(name)"))
}

self.name = regionName
self.hostName = nil
self.useTLS = true
try self.init(name: .init(.init(from: decoder)))
}

/// encodes the name (see RegionName, does not support custom hosts)
/// encodes the name (see Name, does not support custom hosts)
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.name.rawValue)
try name.description.encode(to: encoder)
}
}
7 changes: 2 additions & 5 deletions Sources/S3Signer/S3Signer+Private.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ extension S3Signer {

func createSignature(_ stringToSign: String, timeStampShort: String, region: Region) throws -> String {
let dateKey = try HMAC.SHA256.authenticate(timeStampShort.convertToData(), key: "AWS4\(config.secretKey)".convertToData())
let dateRegionKey = try HMAC.SHA256.authenticate(region.name.rawValue.convertToData(), key: dateKey)
let dateRegionKey = try HMAC.SHA256.authenticate(region.name.description.convertToData(), key: dateKey)
let dateRegionServiceKey = try HMAC.SHA256.authenticate(config.service.convertToData(), key: dateRegionKey)
let signingKey = try HMAC.SHA256.authenticate("aws4_request".convertToData(), key: dateRegionServiceKey)
let signature = try HMAC.SHA256.authenticate(stringToSign.convertToData(), key: signingKey)
Expand All @@ -44,10 +44,7 @@ extension S3Signer {
}

func credentialScope(_ timeStampShort: String, region: Region) -> String {
var arr = [timeStampShort, region.name.rawValue, config.service, "aws4_request"]
if region.name == .none {
arr.remove(at: 1)
}
let arr = [timeStampShort, region.name.description, config.service, "aws4_request"]
return arr.joined(separator: "/")
}

Expand Down

0 comments on commit 528112a

Please sign in to comment.