diff --git a/client/sign_url_provider.go b/client/sign_url_provider.go new file mode 100644 index 00000000..51cdf0a7 --- /dev/null +++ b/client/sign_url_provider.go @@ -0,0 +1,19 @@ +package client + +import ( + "github.com/cloudfoundry/bosh-s3cli/config" + "time" +) + +type SignURLProvider interface { + Sign(action string, objectID string, expiration time.Duration) (string, error) +} + +func NewSignURLProvider(s3BlobstoreClient S3Blobstore, s3cliConfig *config.S3Cli) (SignURLProvider, error) { + if s3cliConfig.SwiftAuthAccount != "" { + client := NewSwiftClient(s3cliConfig) + return &client, nil + } else { + return &s3BlobstoreClient, nil + } +} diff --git a/client/swift_client.go b/client/swift_client.go new file mode 100644 index 00000000..76ce3bc1 --- /dev/null +++ b/client/swift_client.go @@ -0,0 +1,46 @@ +package client + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "fmt" + "strconv" + "strings" + "time" + + "github.com/cloudfoundry/bosh-s3cli/config" +) + +type SwiftBlobstore struct { + s3cliConfig *config.S3Cli +} + +func NewSwiftClient(s3cliConfig *config.S3Cli) SwiftBlobstore { + return SwiftBlobstore{s3cliConfig: s3cliConfig} +} + +func (client *SwiftBlobstore) Sign(objectID string, action string, expiration time.Duration) (string, error) { + action = strings.ToUpper(action) + switch action { + case "GET", "PUT": + return client.SignedURL(action, objectID, expiration) + default: + return "", fmt.Errorf("action not implemented: %s", action) + } +} + +func (client *SwiftBlobstore) SignedURL(action string, objectID string, expiration time.Duration) (string, error) { + path := fmt.Sprintf("/v1/%s/%s/%s", client.s3cliConfig.SwiftAuthAccount, client.s3cliConfig.BucketName, objectID) + + expires := time.Now().Add(expiration).Unix() + hmacBody := action + "\n" + strconv.FormatInt(expires, 10) + "\n" + path + + h := hmac.New(sha256.New, []byte(client.s3cliConfig.SwiftTempURLKey)) + h.Write([]byte(hmacBody)) + signature := hex.EncodeToString(h.Sum(nil)) + + url := fmt.Sprintf("https://%s%s?temp_url_sig=%s&temp_url_expires=%d", client.s3cliConfig.Host, path, signature, expires) + + return url, nil +} diff --git a/config/config.go b/config/config.go index 009c23d1..6d6292ea 100644 --- a/config/config.go +++ b/config/config.go @@ -26,7 +26,9 @@ type S3Cli struct { AssumeRoleArn string `json:"assume_role_arn"` MultipartUpload bool `json:"multipart_upload"` UseV2SigningMethod bool - HostStyle bool `json:"host_style"` + HostStyle bool `json:"host_style"` + SwiftAuthAccount string `json:"swift_auth_account"` + SwiftTempURLKey string `json:"swift_temp_url_key"` } // EmptyRegion is required to allow us to use the AWS SDK against S3 compatible blobstores which do not have diff --git a/main.go b/main.go index e9c7f284..ad2739d2 100644 --- a/main.go +++ b/main.go @@ -43,6 +43,11 @@ func main() { log.Fatalln(err) } + signURLProvider, err := client.NewSignURLProvider(blobstoreClient, &s3Config) + if err != nil { + log.Fatalln(err) + } + nonFlagArgs := flag.Args() if len(nonFlagArgs) < 2 { log.Fatalf("Expected at least two arguments got %d\n", len(nonFlagArgs)) @@ -114,7 +119,7 @@ func main() { log.Fatalf("Expiration should be in the format of a duration i.e. 1h, 60m, 3600s. Got: %s", nonFlagArgs[3]) } - signedURL, err := blobstoreClient.Sign(objectID, action, expiration) + signedURL, err := signURLProvider.Sign(objectID, action, expiration) if err != nil { log.Fatalf("Failed to sign request: %s", err)