From 93a864272d4c8dfd22479a37e3bb69147229fb84 Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Mon, 2 Sep 2019 09:32:38 +0400 Subject: [PATCH 01/26] Wrap EJSON library with KMS integration --- .gitignore | 1 + Makefile | 24 +++++++++++ VERSION | 1 + actions.go | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 14 ++++++ go.sum | 41 ++++++++++++++++++ kms.go | 43 +++++++++++++++++++ main.go | 80 +++++++++++++++++++++++++++++++++++ 8 files changed, 326 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 VERSION create mode 100644 actions.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 kms.go create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84c048a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ea813ac --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +NAME=ejsonkms +PACKAGE=github.com/envato/ejsonkms +VERSION=$(shell cat VERSION) +GOFILES=$(shell find . -type f -name '*.go') + +.PHONY: default all binaries clean + +default: all +all: binaries +binaries: build/bin/linux-amd64 build/bin/darwin-amd64 + +build/bin/linux-amd64: $(GOFILES) + mkdir -p "$(@D)" + GOOS=linux GOARCH=amd64 go build \ + -ldflags '-s -w -X main.version="$(VERSION)"' \ + -o "$@" + +build/bin/darwin-amd64: $(GOFILES) + GOOS=darwin GOARCH=amd64 go build \ + -ldflags '-s -w -X main.version="$(VERSION)"' \ + -o "$@" + +clean: + rm -rf build diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0 diff --git a/actions.go b/actions.go new file mode 100644 index 0000000..f915d12 --- /dev/null +++ b/actions.go @@ -0,0 +1,122 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + + "github.com/Shopify/ejson" +) + +func encryptAction(args []string) error { + if len(args) < 1 { + return fmt.Errorf("at least one file path must be given") + } + for _, filePath := range args { + n, err := ejson.EncryptFileInPlace(filePath) + if err != nil { + return err + } + fmt.Printf("Wrote %d bytes to %s.\n", n, filePath) + } + return nil +} + +func decryptAction(args []string, awsRegion string, outFile string) error { + if len(args) != 1 { + return fmt.Errorf("exactly one file path must be given") + } + ejsonFilePath := args[0] + + privateKeyEnc, err := findPrivateKeyEnc(ejsonFilePath) + if err != nil { + return err + } + + kmsDecryptedPrivateKey, err := decryptPrivateKeyWithKMS(privateKeyEnc, awsRegion) + if err != nil { + return err + } + + decrypted, err := ejson.DecryptFile(ejsonFilePath, "", kmsDecryptedPrivateKey) + if err != nil { + return err + } + + target := os.Stdout + if outFile != "" { + target, err = os.Create(outFile) + if err != nil { + return err + } + defer func() { _ = target.Close() }() + } + + _, err = target.Write(decrypted) + return err +} + +func keygenAction(args []string, kmsKeyID string, awsRegion string, outFile string) error { + pub, priv, err := ejson.GenerateKeypair() + if err != nil { + return err + } + + privKeyEnc, err := encryptPrivateKeyWithKMS(priv, kmsKeyID, awsRegion) + if err != nil { + return err + } + + fmt.Printf("Private Key: %s\n", priv) + target := os.Stdout + if outFile != "" { + target, err = os.Create(outFile) + if err != nil { + return err + } + defer func() { _ = target.Close() }() + } else { + fmt.Printf("EJSON File:\n") + } + + _, err = fmt.Fprintf(target, "{\n \"_public_key\": \"%s\",\n \"_private_key_enc\": \"%s\"\n}", pub, privKeyEnc) + if err != nil { + return err + } + return nil +} + +func findPrivateKeyEnc(ejsonFilePath string) (key string, err error) { + var ( + obj map[string]interface{} + ks string + ) + + file, err := os.Open(ejsonFilePath) + if err != nil { + return "", err + } + defer file.Close() + + data, err := ioutil.ReadAll(file) + if err != nil { + return "", err + } + + err = json.Unmarshal(data, &obj) + if err != nil { + return "", err + } + + k, ok := obj["_private_key_enc"] + if !ok { + return "", errors.New("Missing _private_key_enc field") + } + ks, ok = k.(string) + if !ok { + return "", errors.New("Couldn't cast _private_key_enc to string") + } + return ks, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ac4a572 --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module github.com/envato/ejsonkms + +go 1.12 + +require ( + github.com/Shopify/ejson v0.0.0-20170822170800-ce9261f21575 + github.com/aws/aws-sdk-go v1.23.12 + github.com/dustin/gojson v0.0.0-20130803055424-057ac0edc14e // indirect + github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect + github.com/stretchr/testify v1.4.0 // indirect + github.com/taskcluster/shell v0.0.0-20161108133149-4039a2cd6f88 + github.com/urfave/cli v1.20.0 + golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7af9380 --- /dev/null +++ b/go.sum @@ -0,0 +1,41 @@ +github.com/Shopify/ejson v0.0.0-20170822170800-ce9261f21575 h1:Fzg3418Z1/GsFE5ds+V2+bw2vxRnrbqGMQJZMQqnyTY= +github.com/Shopify/ejson v0.0.0-20170822170800-ce9261f21575/go.mod h1:J8cw5GOA0l/aMOPp+uDfwNYVbeqIaBhzRkv1+76UCvk= +github.com/aws/aws-sdk-go v1.23.12 h1:2UnxgNO6Y5J1OrkXS8XNp0UatDxD1bWHiDT62RDPggI= +github.com/aws/aws-sdk-go v1.23.12/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/gojson v0.0.0-20130803055424-057ac0edc14e h1:6sSV6EV1MwaHjQwpyC1ZK/f1gZJ51k9E0AcCp1wHVWQ= +github.com/dustin/gojson v0.0.0-20130803055424-057ac0edc14e/go.mod h1:mPKfmRa823oBIgl2r20LeMSpTAteW5j7FLkc0vjmzyQ= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= +github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/taskcluster/shell v0.0.0-20161108133149-4039a2cd6f88 h1:GMj4PBlD/mFjCdyWKTjlzyoKyEqL74csyX76X4G5KTw= +github.com/taskcluster/shell v0.0.0-20161108133149-4039a2cd6f88/go.mod h1:yFw6QSQUNaHUGVFwQ343WbkPft5hxZIghbdnmu/Gd3E= +github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/kms.go b/kms.go new file mode 100644 index 0000000..58dd49d --- /dev/null +++ b/kms.go @@ -0,0 +1,43 @@ +package main + +import ( + "encoding/base64" + "log" + + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/kms" +) + +func decryptPrivateKeyWithKMS(privateKeyEnc string, awsRegion string) (key string, err error) { + awsSession := session.Must(session.NewSession()) + awsSession.Config.WithRegion(awsRegion) + kmsSvc := kms.New(awsSession) + + encryptedValue, err := base64.StdEncoding.DecodeString(privateKeyEnc) + + params := &kms.DecryptInput{ + CiphertextBlob: []byte(encryptedValue), + } + resp, err := kmsSvc.Decrypt(params) + if err != nil { + log.Fatalf("Unable to decrypt parameter: %v", err) + } + return string(resp.Plaintext), nil +} + +func encryptPrivateKeyWithKMS(privateKey string, kmsKeyID string, awsRegion string) (key string, err error) { + awsSession := session.Must(session.NewSession()) + awsSession.Config.WithRegion(awsRegion) + kmsSvc := kms.New(awsSession) + params := &kms.EncryptInput{ + KeyId: &kmsKeyID, + Plaintext: []byte(privateKey), + } + resp, err := kmsSvc.Encrypt(params) + if err != nil { + log.Fatalf("Unable to encrypt parameter: %v", err) + } + + encodedPrivKey := base64.StdEncoding.EncodeToString(resp.CiphertextBlob) + return encodedPrivKey, nil +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..ee77601 --- /dev/null +++ b/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +// version information. This will be overridden by the ldflags +var version = "dev" + +func main() { + app := cli.NewApp() + app.Usage = "manage encrypted secrets using EJSON & AWS KMS" + app.Version = version + app.Author = "Steve Hodgkiss" + app.Email = "steve@envato.com" + app.Commands = []cli.Command{ + { + Name: "encrypt", + Usage: "(re-)encrypt one or more EJSON files", + Action: func(c *cli.Context) { + if err := encryptAction(c.Args()); err != nil { + fmt.Fprintln(os.Stderr, "Encryption failed:", err) + os.Exit(1) + } + }, + }, + { + Name: "decrypt", + Usage: "decrypt an EJSON file", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "o", + Usage: "print output to the provided file, rather than stdout", + }, + cli.StringFlag{ + Name: "aws-region", + Usage: "AWS Region", + }, + }, + Action: func(c *cli.Context) { + if err := decryptAction(c.Args(), c.String("aws-region"), c.String("o")); err != nil { + fmt.Fprintln(os.Stderr, "Decryption failed:", err) + os.Exit(1) + } + }, + }, + { + Name: "keygen", + Usage: "generate a new EJSON keypair", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "kms-key-id", + Usage: "KMS Key ID to encrypt the private key with", + }, + cli.StringFlag{ + Name: "aws-region", + Usage: "AWS Region", + }, + cli.StringFlag{ + Name: "o", + Usage: "write EJSON file to a file rather than stdout", + }, + }, + Action: func(c *cli.Context) { + if err := keygenAction(c.Args(), c.String("kms-key-id"), c.String("aws-region"), c.String("o")); err != nil { + fmt.Fprintln(os.Stderr, "Key generation failed:", err) + os.Exit(1) + } + }, + }, + } + + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, "Unexpected failure:", err) + os.Exit(1) + } +} From e32f8fdd6b747eca1477d14e56667496f7a96291 Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Mon, 2 Sep 2019 08:06:11 +0000 Subject: [PATCH 02/26] Extract ejsonkms functions and add tests --- Dockerfile | 6 ++++++ actions.go | 19 ++----------------- docker-compose.yml | 20 ++++++++++++++++++++ ejsonkms.go | 36 ++++++++++++++++++++++++++++++++++++ ejsonkms_test.go | 29 +++++++++++++++++++++++++++++ go.mod | 2 +- kms.go | 21 +++++++++++++++------ local_kms/seed.yaml | 9 +++++++++ testdata/test.ejson | 7 +++++++ 9 files changed, 125 insertions(+), 24 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 ejsonkms.go create mode 100644 ejsonkms_test.go create mode 100644 local_kms/seed.yaml create mode 100644 testdata/test.ejson diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f6c53af --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM golang:1.12-alpine +ENV GO111MODULE=on +WORKDIR /go/src/github.com/envato/ejsonkms +COPY . . +RUN apk add git gcc musl-dev +RUN go get diff --git a/actions.go b/actions.go index f915d12..dc9c315 100644 --- a/actions.go +++ b/actions.go @@ -30,17 +30,7 @@ func decryptAction(args []string, awsRegion string, outFile string) error { } ejsonFilePath := args[0] - privateKeyEnc, err := findPrivateKeyEnc(ejsonFilePath) - if err != nil { - return err - } - - kmsDecryptedPrivateKey, err := decryptPrivateKeyWithKMS(privateKeyEnc, awsRegion) - if err != nil { - return err - } - - decrypted, err := ejson.DecryptFile(ejsonFilePath, "", kmsDecryptedPrivateKey) + decrypted, err := decrypt(ejsonFilePath, awsRegion) if err != nil { return err } @@ -59,12 +49,7 @@ func decryptAction(args []string, awsRegion string, outFile string) error { } func keygenAction(args []string, kmsKeyID string, awsRegion string, outFile string) error { - pub, priv, err := ejson.GenerateKeypair() - if err != nil { - return err - } - - privKeyEnc, err := encryptPrivateKeyWithKMS(priv, kmsKeyID, awsRegion) + pub, priv, privKeyEnc, err := keygen(kmsKeyID, awsRegion) if err != nil { return err } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..37b6059 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,20 @@ +version: "3" +services: + awskms: + image: "nsmithuk/local-kms" + environment: + REGION: us-east-1 + volumes: + - "./local_kms:/init" + expose: + - 8080 + tests: + build: . + volumes: + - "./:/go/src/github.com/envato/ejsonkms" + command: ["go", "test"] + environment: + AWS_ACCESS_KEY_ID: '123' + AWS_SECRET_ACCESS_KEY: xyz + links: + - awskms diff --git a/ejsonkms.go b/ejsonkms.go new file mode 100644 index 0000000..3e3db8f --- /dev/null +++ b/ejsonkms.go @@ -0,0 +1,36 @@ +package main + +import "github.com/Shopify/ejson" + +func keygen(kmsKeyID string, awsRegion string) (string, string, string, error) { + pub, priv, err := ejson.GenerateKeypair() + if err != nil { + return "", "", "", err + } + + privKeyEnc, err := encryptPrivateKeyWithKMS(priv, kmsKeyID, awsRegion) + if err != nil { + return "", "", "", err + } + + return pub, priv, privKeyEnc, nil +} + +func decrypt(ejsonFilePath string, awsRegion string) ([]byte, error) { + privateKeyEnc, err := findPrivateKeyEnc(ejsonFilePath) + if err != nil { + return nil, err + } + + kmsDecryptedPrivateKey, err := decryptPrivateKeyWithKMS(privateKeyEnc, awsRegion) + if err != nil { + return nil, err + } + + decrypted, err := ejson.DecryptFile(ejsonFilePath, "", kmsDecryptedPrivateKey) + if err != nil { + return nil, err + } + + return decrypted, nil +} diff --git a/ejsonkms_test.go b/ejsonkms_test.go new file mode 100644 index 0000000..028b456 --- /dev/null +++ b/ejsonkms_test.go @@ -0,0 +1,29 @@ +package main + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestKeygen(t *testing.T) { + Convey("Keygen", t, func() { + pub, priv, privEnc, err := keygen("bc436485-5092-42b8-92a3-0aa8b93536dc", "us-east-1") + Convey("should return three strings that look key-like", func() { + So(err, ShouldBeNil) + So(pub, ShouldNotEqual, priv) + So(pub, ShouldNotContainSubstring, "00000") + So(priv, ShouldNotContainSubstring, "00000") + So(privEnc, ShouldNotContainSubstring, "00000") + }) + }) +} + +func TestDecrypt(t *testing.T) { + Convey("Decrypt", t, func() { + decrypted, err := decrypt("testdata/test.ejson", "us-east-1") + So(err, ShouldBeNil) + json := string(decrypted[:]) + So(json, ShouldContainSubstring, "\"my_secret\": \"secret123\"") + }) +} diff --git a/go.mod b/go.mod index ac4a572..f28d08d 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Shopify/ejson v0.0.0-20170822170800-ce9261f21575 github.com/aws/aws-sdk-go v1.23.12 github.com/dustin/gojson v0.0.0-20130803055424-057ac0edc14e // indirect - github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect + github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 github.com/stretchr/testify v1.4.0 // indirect github.com/taskcluster/shell v0.0.0-20161108133149-4039a2cd6f88 github.com/urfave/cli v1.20.0 diff --git a/kms.go b/kms.go index 58dd49d..0114e42 100644 --- a/kms.go +++ b/kms.go @@ -2,16 +2,18 @@ package main import ( "encoding/base64" + "flag" "log" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/kms" ) +var fakeKmsEndpoint = "http://awskms:8080" + func decryptPrivateKeyWithKMS(privateKeyEnc string, awsRegion string) (key string, err error) { - awsSession := session.Must(session.NewSession()) - awsSession.Config.WithRegion(awsRegion) - kmsSvc := kms.New(awsSession) + kmsSvc := newKmsClient(awsRegion) encryptedValue, err := base64.StdEncoding.DecodeString(privateKeyEnc) @@ -26,9 +28,7 @@ func decryptPrivateKeyWithKMS(privateKeyEnc string, awsRegion string) (key strin } func encryptPrivateKeyWithKMS(privateKey string, kmsKeyID string, awsRegion string) (key string, err error) { - awsSession := session.Must(session.NewSession()) - awsSession.Config.WithRegion(awsRegion) - kmsSvc := kms.New(awsSession) + kmsSvc := newKmsClient(awsRegion) params := &kms.EncryptInput{ KeyId: &kmsKeyID, Plaintext: []byte(privateKey), @@ -41,3 +41,12 @@ func encryptPrivateKeyWithKMS(privateKey string, kmsKeyID string, awsRegion stri encodedPrivKey := base64.StdEncoding.EncodeToString(resp.CiphertextBlob) return encodedPrivKey, nil } + +func newKmsClient(awsRegion string) *kms.KMS { + awsSession := session.Must(session.NewSession()) + awsSession.Config.WithRegion(awsRegion) + if flag.Lookup("test.v") != nil { // is there a better way to do this? + return kms.New(awsSession, aws.NewConfig().WithEndpoint(fakeKmsEndpoint)) + } + return kms.New(awsSession) +} diff --git a/local_kms/seed.yaml b/local_kms/seed.yaml new file mode 100644 index 0000000..0d02a81 --- /dev/null +++ b/local_kms/seed.yaml @@ -0,0 +1,9 @@ +Keys: + - Metadata: + KeyId: bc436485-5092-42b8-92a3-0aa8b93536dc + BackingKeys: + - 5cdaead27fe7da2de47945d73cd6d79e36494e73802f3cd3869f1d2cb0b5d7a9 + +Aliases: + - AliasName: alias/testing + TargetKeyId: bc436485-5092-42b8-92a3-0aa8b93536dc diff --git a/testdata/test.ejson b/testdata/test.ejson new file mode 100644 index 0000000..ded9ba1 --- /dev/null +++ b/testdata/test.ejson @@ -0,0 +1,7 @@ +{ + "_public_key": "6b8280f86aff5f48773f63d60e655e2f3dd0dd7c14f5fecb5df22936e5a3be52", + "_private_key_enc": "S2Fybjphd3M6a21zOnVzLWVhc3QtMToxMTExMjIyMjMzMzM6a2V5L2JjNDM2NDg1LTUwOTItNDJiOC05MmEzLTBhYThiOTM1MzZkYwAAAAAycRX5OBx6xGuYOPAmDJ1FombB1lFybMP42s7PGmoa24bAesPMMZtI9V0w0p0lEgLeeSvYdsPuoPROa4bwnQxJB28eC6fHgfWgY7jgDWY9uP/tgzuWL3zuIaq+9Q==", + "environment": { + "my_secret": "EJ[1:oAT3giWcK72oUnxPv3lLy5QdC96loVTIAZ3HslNxMkw=:zjrRXdWKBLTWJN8WkmJrXKC2mTTy7XaJ:HSzpCJj5jXudq9ByKm412bRo5Rs7KKkIzg==]" + } +} From 06dee79b479258229b9a082edf1d6e8b00548c87 Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Mon, 2 Sep 2019 12:09:18 +0400 Subject: [PATCH 03/26] Move function --- actions.go | 36 ------------------------------------ ejsonkms.go | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/actions.go b/actions.go index dc9c315..1a5ab6c 100644 --- a/actions.go +++ b/actions.go @@ -1,10 +1,7 @@ package main import ( - "encoding/json" - "errors" "fmt" - "io/ioutil" "os" "github.com/Shopify/ejson" @@ -72,36 +69,3 @@ func keygenAction(args []string, kmsKeyID string, awsRegion string, outFile stri } return nil } - -func findPrivateKeyEnc(ejsonFilePath string) (key string, err error) { - var ( - obj map[string]interface{} - ks string - ) - - file, err := os.Open(ejsonFilePath) - if err != nil { - return "", err - } - defer file.Close() - - data, err := ioutil.ReadAll(file) - if err != nil { - return "", err - } - - err = json.Unmarshal(data, &obj) - if err != nil { - return "", err - } - - k, ok := obj["_private_key_enc"] - if !ok { - return "", errors.New("Missing _private_key_enc field") - } - ks, ok = k.(string) - if !ok { - return "", errors.New("Couldn't cast _private_key_enc to string") - } - return ks, nil -} diff --git a/ejsonkms.go b/ejsonkms.go index 3e3db8f..d02a9c0 100644 --- a/ejsonkms.go +++ b/ejsonkms.go @@ -1,6 +1,13 @@ package main -import "github.com/Shopify/ejson" +import ( + "encoding/json" + "errors" + "io/ioutil" + "os" + + "github.com/Shopify/ejson" +) func keygen(kmsKeyID string, awsRegion string) (string, string, string, error) { pub, priv, err := ejson.GenerateKeypair() @@ -34,3 +41,36 @@ func decrypt(ejsonFilePath string, awsRegion string) ([]byte, error) { return decrypted, nil } + +func findPrivateKeyEnc(ejsonFilePath string) (key string, err error) { + var ( + obj map[string]interface{} + ks string + ) + + file, err := os.Open(ejsonFilePath) + if err != nil { + return "", err + } + defer file.Close() + + data, err := ioutil.ReadAll(file) + if err != nil { + return "", err + } + + err = json.Unmarshal(data, &obj) + if err != nil { + return "", err + } + + k, ok := obj["_private_key_enc"] + if !ok { + return "", errors.New("Missing _private_key_enc field") + } + ks, ok = k.(string) + if !ok { + return "", errors.New("Couldn't cast _private_key_enc to string") + } + return ks, nil +} From 4774d2fc6de0d35508eaa1266799a75b59076322 Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Mon, 2 Sep 2019 12:52:18 +0400 Subject: [PATCH 04/26] Make methods public --- actions.go | 4 ++-- ejsonkms.go | 6 ++++-- ejsonkms_test.go | 4 ++-- testdata/test_no_private_key.ejson | 6 ++++++ 4 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 testdata/test_no_private_key.ejson diff --git a/actions.go b/actions.go index 1a5ab6c..c243b40 100644 --- a/actions.go +++ b/actions.go @@ -27,7 +27,7 @@ func decryptAction(args []string, awsRegion string, outFile string) error { } ejsonFilePath := args[0] - decrypted, err := decrypt(ejsonFilePath, awsRegion) + decrypted, err := Decrypt(ejsonFilePath, awsRegion) if err != nil { return err } @@ -46,7 +46,7 @@ func decryptAction(args []string, awsRegion string, outFile string) error { } func keygenAction(args []string, kmsKeyID string, awsRegion string, outFile string) error { - pub, priv, privKeyEnc, err := keygen(kmsKeyID, awsRegion) + pub, priv, privKeyEnc, err := Keygen(kmsKeyID, awsRegion) if err != nil { return err } diff --git a/ejsonkms.go b/ejsonkms.go index d02a9c0..e421fb2 100644 --- a/ejsonkms.go +++ b/ejsonkms.go @@ -9,7 +9,8 @@ import ( "github.com/Shopify/ejson" ) -func keygen(kmsKeyID string, awsRegion string) (string, string, string, error) { +// Keygen generates keys and prepares an EJSON file with them +func Keygen(kmsKeyID string, awsRegion string) (string, string, string, error) { pub, priv, err := ejson.GenerateKeypair() if err != nil { return "", "", "", err @@ -23,7 +24,8 @@ func keygen(kmsKeyID string, awsRegion string) (string, string, string, error) { return pub, priv, privKeyEnc, nil } -func decrypt(ejsonFilePath string, awsRegion string) ([]byte, error) { +// Decrypt decrypts an EJSON file +func Decrypt(ejsonFilePath string, awsRegion string) ([]byte, error) { privateKeyEnc, err := findPrivateKeyEnc(ejsonFilePath) if err != nil { return nil, err diff --git a/ejsonkms_test.go b/ejsonkms_test.go index 028b456..f90a3c4 100644 --- a/ejsonkms_test.go +++ b/ejsonkms_test.go @@ -8,7 +8,7 @@ import ( func TestKeygen(t *testing.T) { Convey("Keygen", t, func() { - pub, priv, privEnc, err := keygen("bc436485-5092-42b8-92a3-0aa8b93536dc", "us-east-1") + pub, priv, privEnc, err := Keygen("bc436485-5092-42b8-92a3-0aa8b93536dc", "us-east-1") Convey("should return three strings that look key-like", func() { So(err, ShouldBeNil) So(pub, ShouldNotEqual, priv) @@ -21,9 +21,9 @@ func TestKeygen(t *testing.T) { func TestDecrypt(t *testing.T) { Convey("Decrypt", t, func() { - decrypted, err := decrypt("testdata/test.ejson", "us-east-1") So(err, ShouldBeNil) json := string(decrypted[:]) So(json, ShouldContainSubstring, "\"my_secret\": \"secret123\"") + decrypted, err := Decrypt("testdata/test.ejson", "us-east-1") }) } diff --git a/testdata/test_no_private_key.ejson b/testdata/test_no_private_key.ejson new file mode 100644 index 0000000..03a846f --- /dev/null +++ b/testdata/test_no_private_key.ejson @@ -0,0 +1,6 @@ +{ + "_public_key": "6b8280f86aff5f48773f63d60e655e2f3dd0dd7c14f5fecb5df22936e5a3be52", + "environment": { + "my_secret": "EJ[1:oAT3giWcK72oUnxPv3lLy5QdC96loVTIAZ3HslNxMkw=:zjrRXdWKBLTWJN8WkmJrXKC2mTTy7XaJ:HSzpCJj5jXudq9ByKm412bRo5Rs7KKkIzg==]" + } +} From 7e28504e502406796c9716015ff469806d9121c5 Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Mon, 2 Sep 2019 12:52:25 +0400 Subject: [PATCH 05/26] Update tests --- ejsonkms_test.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/ejsonkms_test.go b/ejsonkms_test.go index f90a3c4..e4a2e76 100644 --- a/ejsonkms_test.go +++ b/ejsonkms_test.go @@ -21,9 +21,18 @@ func TestKeygen(t *testing.T) { func TestDecrypt(t *testing.T) { Convey("Decrypt", t, func() { - So(err, ShouldBeNil) - json := string(decrypted[:]) - So(json, ShouldContainSubstring, "\"my_secret\": \"secret123\"") decrypted, err := Decrypt("testdata/test.ejson", "us-east-1") + Convey("should return decrypted values", func() { + So(err, ShouldBeNil) + json := string(decrypted[:]) + So(json, ShouldContainSubstring, "\"my_secret\": \"secret123\"") + }) + }) + Convey("Decrypt with no private key", t, func() { + _, err := Decrypt("testdata/test_no_private_key.ejson", "us-east-1") + Convey("should fail", func() { + So(err, ShouldNotBeNil) + So(err.Error(), ShouldContainSubstring, "Missing _private_key_enc") + }) }) } From b6d02886fa725f331a4aa0ef14ae74094199890a Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Mon, 2 Sep 2019 13:27:44 +0400 Subject: [PATCH 06/26] Add env command --- actions.go | 18 ++++++++ actions_test.go | 38 +++++++++++++++++ env.go | 109 ++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 38 +++++++++++++++++ 4 files changed, 203 insertions(+) create mode 100644 actions_test.go create mode 100644 env.go diff --git a/actions.go b/actions.go index c243b40..3d821d3 100644 --- a/actions.go +++ b/actions.go @@ -69,3 +69,21 @@ func keygenAction(args []string, kmsKeyID string, awsRegion string, outFile stri } return nil } + +func envAction(ejsonFilePath string, quiet bool, awsRegion string) error { + exportFunc := ExportEnv + if quiet { + exportFunc = ExportQuiet + } + privateKeyEnc, err := findPrivateKeyEnc(ejsonFilePath) + if err != nil { + return err + } + + kmsDecryptedPrivateKey, err := decryptPrivateKeyWithKMS(privateKeyEnc, awsRegion) + if err != nil { + return err + } + + return ExportSecrets(ejsonFilePath, kmsDecryptedPrivateKey, exportFunc) +} diff --git a/actions_test.go b/actions_test.go new file mode 100644 index 0000000..d1ed465 --- /dev/null +++ b/actions_test.go @@ -0,0 +1,38 @@ +package main + +import ( + "bytes" + "os" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestEnv(t *testing.T) { + outputBuffer := new(bytes.Buffer) + output = outputBuffer + + // ensure that output returns to os.Stdout + defer func() { + output = os.Stdout + }() + + Convey("Env", t, func() { + err := envAction("testdata/test.ejson", false, "us-east-1") + + Convey("should return decrypted values as shell exports", func() { + So(err, ShouldBeNil) + actualOutput := outputBuffer.String() + So(actualOutput, ShouldContainSubstring, "export my_secret=secret123") + }) + }) + + Convey("Env with no private key", t, func() { + err := envAction("testdata/test_no_private_key.ejson", false, "us-east-1") + + Convey("should fail", func() { + So(err, ShouldNotBeNil) + So(err.Error(), ShouldContainSubstring, "Missing _private_key_enc") + }) + }) +} diff --git a/env.go b/env.go new file mode 100644 index 0000000..7470ac8 --- /dev/null +++ b/env.go @@ -0,0 +1,109 @@ +package main + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "os" + + "github.com/Shopify/ejson" + "github.com/taskcluster/shell" +) + +// Original source: github.com/Shopify/ejson2env + +var errNoEnv = errors.New("environment is not set in ejson") +var errEnvNotMap = errors.New("environment is not a map[string]interface{}") + +// ExtractEnv extracts the environment values from the map[string]interface{} +// containing all secrets, and returns a map[string]string containing the +// key value pairs. If there's an issue (the environment key doesn't exist, for +// example), returns an error. +func ExtractEnv(secrets map[string]interface{}) (map[string]string, error) { + rawEnv, ok := secrets["environment"] + if !ok { + return nil, errNoEnv + } + + envMap, ok := rawEnv.(map[string]interface{}) + if !ok { + return nil, errEnvNotMap + } + + envSecrets := make(map[string]string, len(envMap)) + + for key, rawValue := range envMap { + + // Only export values that convert to strings properly. + if value, ok := rawValue.(string); ok { + envSecrets[key] = value + } + } + + return envSecrets, nil +} + +// ExportEnv writes the passed environment values to the passed +// io.Writer. +func ExportEnv(w io.Writer, values map[string]string) { + for key, value := range values { + fmt.Fprintf(w, "export %s=%s\n", key, shell.Escape(value)) + } +} + +// ExportQuiet writes the passed environment values to the passed +// io.Writer in %s=%s format. +func ExportQuiet(w io.Writer, values map[string]string) { + for key, value := range values { + fmt.Fprintf(w, "%s=%s\n", key, shell.Escape(value)) + } +} + +// ExportFunction is implemented in exportSecrets as an easy way +// to select how secrets are exported +type ExportFunction func(io.Writer, map[string]string) + +// output is a pointer to the io.Writer to use. This allows us to override +// stdout for testing purposes. +var output io.Writer = os.Stdout + +// ExportSecrets wraps the read, extract, and export steps. Returns +// an error if any step fails. +func ExportSecrets(filename, privateKey string, exportFunc ExportFunction) error { + secrets, err := readSecrets(filename, privateKey) + if nil != err { + return fmt.Errorf("could not load ejson file: %s", err) + } + + envValues, err := ExtractEnv(secrets) + if !isFailure(err) { + exportFunc(output, envValues) + } + + // ExtractEnv does not return an error we need to handle. + return nil +} + +// ReadSecrets reads the secrets for the passed filename and +// returns them as a map[string]interface{}. +func readSecrets(filename, privateKey string) (map[string]interface{}, error) { + secrets := make(map[string]interface{}) + + decrypted, err := ejson.DecryptFile(filename, "", privateKey) + if nil != err { + return secrets, err + } + + decoder := json.NewDecoder(bytes.NewReader(decrypted)) + + err = decoder.Decode(&secrets) + return secrets, err +} + +// isFailure returns true if the passed error should prompt a +// failure. +func isFailure(err error) bool { + return (nil != err && errNoEnv != err && errEnvNotMap != err) +} diff --git a/main.go b/main.go index ee77601..df2f09d 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,12 @@ import ( // version information. This will be overridden by the ldflags var version = "dev" +// fail prints the error message to stderr, then ends execution. +func fail(err error) { + fmt.Fprintf(os.Stderr, "error: %s\n", err) + os.Exit(1) +} + func main() { app := cli.NewApp() app.Usage = "manage encrypted secrets using EJSON & AWS KMS" @@ -71,6 +77,38 @@ func main() { } }, }, + { + Name: "env", + Usage: "print shell export statements", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "quiet, q", + Usage: "Suppress export statement", + }, + cli.StringFlag{ + Name: "aws-region", + Usage: "AWS Region", + }, + }, + Action: func(c *cli.Context) { + var filename string + + quiet := c.Bool("quiet") + awsRegion := c.String("aws-region") + + if 1 <= len(c.Args()) { + filename = c.Args().Get(0) + } + + if "" == filename { + fail(fmt.Errorf("no secrets.ejson filename passed")) + } + + if err := envAction(filename, quiet, awsRegion); nil != err { + fail(err) + } + }, + }, } if err := app.Run(os.Args); err != nil { From dbb0801b43ebe12b9fa4c4436980deb807934e9b Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Mon, 2 Sep 2019 13:33:22 +0400 Subject: [PATCH 07/26] Allow specifying fake kms endpoint --- docker-compose.yml | 1 + kms.go | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 37b6059..92bd853 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,5 +16,6 @@ services: environment: AWS_ACCESS_KEY_ID: '123' AWS_SECRET_ACCESS_KEY: xyz + FAKE_AWSKMS_URL: http://awskms:8080 links: - awskms diff --git a/kms.go b/kms.go index 0114e42..e4d7ee7 100644 --- a/kms.go +++ b/kms.go @@ -4,14 +4,13 @@ import ( "encoding/base64" "flag" "log" + "os" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/kms" ) -var fakeKmsEndpoint = "http://awskms:8080" - func decryptPrivateKeyWithKMS(privateKeyEnc string, awsRegion string) (key string, err error) { kmsSvc := newKmsClient(awsRegion) @@ -46,6 +45,10 @@ func newKmsClient(awsRegion string) *kms.KMS { awsSession := session.Must(session.NewSession()) awsSession.Config.WithRegion(awsRegion) if flag.Lookup("test.v") != nil { // is there a better way to do this? + fakeKmsEndpoint := os.Getenv("FAKE_AWSKMS_URL") + if len(fakeKmsEndpoint) == 0 { + fakeKmsEndpoint = "http://localhost:8080" + } return kms.New(awsSession, aws.NewConfig().WithEndpoint(fakeKmsEndpoint)) } return kms.New(awsSession) From 957cb1a521f62e6f69b0f74878cfb825ab94798e Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Mon, 2 Sep 2019 09:50:15 +0000 Subject: [PATCH 08/26] Expand readme --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- env.go | 2 +- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 84ed123..96f2c28 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,50 @@ # ejsonkms -`ejsonkms` is a tool to based on the [ejson library](https://github.com/Shopify/ejson) with integration with AWS KMS. +`ejsonkms` combines the [ejson library](https://github.com/Shopify/ejson) with [AWS Key Management +Service](https://aws.amazon.com/kms/) to simplify deployments on AWS. The EJSON private key is encrypted with +KMS and stored inside the EJSON file as `_private_key_enc`. Access to decrypt secrets can be controlled with IAM +permissions on the KMS key. + +## Usage + +Generating an EJSON file: + +``` +$ ejsonkms keygen --aws-region us-east-1 --kms-key-id bc436485-5092-42b8-92a3-0aa8b93536dc -o secrets.ejson +Private Key: ae5969d1fb70faab76198ee554bf91d2fffc44d027ea3d804a7c7f92876d518b +$ cat secrets.ejson +{ + "_public_key": "6b8280f86aff5f48773f63d60e655e2f3dd0dd7c14f5fecb5df22936e5a3be52", + "_private_key_enc": "S2Fybjphd3M6a21zOnVzLWVhc3QtMToxMTExMjIyMjMzMzM6a2V5L2JjNDM2NDg1LTUwOTItNDJiOC05MmEzLTBhYThiOTM1MzZkYwAAAAAycRX5OBx6xGuYOPAmDJ1FombB1lFybMP42s7PGmoa24bAesPMMZtI9V0w0p0lEgLeeSvYdsPuoPROa4bwnQxJB28eC6fHgfWgY7jgDWY9uP/tgzuWL3zuIaq+9Q==" +} +``` + +Encrypting: + +``` +$ ejsonkms encrypt secrets.ejson +``` + +Decrypting: + +``` +$ ejsonkms decrypt secrets.ejson +{ + "_public_key": "6b8280f86aff5f48773f63d60e655e2f3dd0dd7c14f5fecb5df22936e5a3be52", + "_private_key_enc": "S2Fybjphd3M6a21zOnVzLWVhc3QtMToxMTExMjIyMjMzMzM6a2V5L2JjNDM2NDg1LTUwOTItNDJiOC05MmEzLTBhYThiOTM1MzZkYwAAAAAycRX5OBx6xGuYOPAmDJ1FombB1lFybMP42s7PGmoa24bAesPMMZtI9V0w0p0lEgLeeSvYdsPuoPROa4bwnQxJB28eC6fHgfWgY7jgDWY9uP/tgzuWL3zuIaq+9Q==", + "environment": { + "my_secret": "secret123" + } +} +``` + +Exporting shell variables (from [ejson2env](https://github.com/Shopify/ejson2env)): + +``` +$ exports=$(ejsonkms env secrets.ejson) +$ echo $exports +export my_secret=secret123 +$ eval $exports +$ echo my_secret +secret123 +``` diff --git a/env.go b/env.go index 7470ac8..4da5696 100644 --- a/env.go +++ b/env.go @@ -86,7 +86,7 @@ func ExportSecrets(filename, privateKey string, exportFunc ExportFunction) error return nil } -// ReadSecrets reads the secrets for the passed filename and +// readSecrets reads the secrets for the passed filename and // returns them as a map[string]interface{}. func readSecrets(filename, privateKey string) (map[string]interface{}, error) { secrets := make(map[string]interface{}) From f579d9c86d3361924e6cfde9ea41cbfe81395a89 Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Mon, 2 Sep 2019 14:56:34 +0400 Subject: [PATCH 09/26] Add note on using env command --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 96f2c28..e87d616 100644 --- a/README.md +++ b/README.md @@ -48,3 +48,5 @@ $ eval $exports $ echo my_secret secret123 ``` + +Note that only secrets under the "environment" key will be exported using the `env` command. From c4dc96a13f894f3c609245ef4ef9182cd9103d0b Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 07:39:04 +0400 Subject: [PATCH 10/26] Update actions.go Co-Authored-By: Jacob Bednarz --- actions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions.go b/actions.go index 3d821d3..3c3a44d 100644 --- a/actions.go +++ b/actions.go @@ -38,7 +38,7 @@ func decryptAction(args []string, awsRegion string, outFile string) error { if err != nil { return err } - defer func() { _ = target.Close() }() + defer target.Close() } _, err = target.Write(decrypted) From c1c9320a64316a323a676ebc8bce260af004601c Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 07:41:46 +0400 Subject: [PATCH 11/26] Use fmt.Println --- actions.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions.go b/actions.go index 3c3a44d..1f56930 100644 --- a/actions.go +++ b/actions.go @@ -51,7 +51,7 @@ func keygenAction(args []string, kmsKeyID string, awsRegion string, outFile stri return err } - fmt.Printf("Private Key: %s\n", priv) + fmt.Println("Private Key:", priv) target := os.Stdout if outFile != "" { target, err = os.Create(outFile) @@ -60,7 +60,7 @@ func keygenAction(args []string, kmsKeyID string, awsRegion string, outFile stri } defer func() { _ = target.Close() }() } else { - fmt.Printf("EJSON File:\n") + fmt.Println("EJSON File:") } _, err = fmt.Fprintf(target, "{\n \"_public_key\": \"%s\",\n \"_private_key_enc\": \"%s\"\n}", pub, privKeyEnc) From 4a8bc2fe85ed2e4875c2b272305af9a1e9c371dc Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 07:47:03 +0400 Subject: [PATCH 12/26] Tidy up params --- actions.go | 6 +++--- actions_test.go | 4 ++-- ejsonkms.go | 4 ++-- kms.go | 4 ++-- main.go | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/actions.go b/actions.go index 1f56930..3f3e8f7 100644 --- a/actions.go +++ b/actions.go @@ -21,7 +21,7 @@ func encryptAction(args []string) error { return nil } -func decryptAction(args []string, awsRegion string, outFile string) error { +func decryptAction(args []string, awsRegion, outFile string) error { if len(args) != 1 { return fmt.Errorf("exactly one file path must be given") } @@ -45,7 +45,7 @@ func decryptAction(args []string, awsRegion string, outFile string) error { return err } -func keygenAction(args []string, kmsKeyID string, awsRegion string, outFile string) error { +func keygenAction(args []string, kmsKeyID, awsRegion, outFile string) error { pub, priv, privKeyEnc, err := Keygen(kmsKeyID, awsRegion) if err != nil { return err @@ -70,7 +70,7 @@ func keygenAction(args []string, kmsKeyID string, awsRegion string, outFile stri return nil } -func envAction(ejsonFilePath string, quiet bool, awsRegion string) error { +func envAction(ejsonFilePath, awsRegion string, quiet bool) error { exportFunc := ExportEnv if quiet { exportFunc = ExportQuiet diff --git a/actions_test.go b/actions_test.go index d1ed465..5a78e11 100644 --- a/actions_test.go +++ b/actions_test.go @@ -18,7 +18,7 @@ func TestEnv(t *testing.T) { }() Convey("Env", t, func() { - err := envAction("testdata/test.ejson", false, "us-east-1") + err := envAction("testdata/test.ejson", "us-east-1", false) Convey("should return decrypted values as shell exports", func() { So(err, ShouldBeNil) @@ -28,7 +28,7 @@ func TestEnv(t *testing.T) { }) Convey("Env with no private key", t, func() { - err := envAction("testdata/test_no_private_key.ejson", false, "us-east-1") + err := envAction("testdata/test_no_private_key.ejson", "us-east-1", false) Convey("should fail", func() { So(err, ShouldNotBeNil) diff --git a/ejsonkms.go b/ejsonkms.go index e421fb2..f9fcf81 100644 --- a/ejsonkms.go +++ b/ejsonkms.go @@ -10,7 +10,7 @@ import ( ) // Keygen generates keys and prepares an EJSON file with them -func Keygen(kmsKeyID string, awsRegion string) (string, string, string, error) { +func Keygen(kmsKeyID, awsRegion string) (string, string, string, error) { pub, priv, err := ejson.GenerateKeypair() if err != nil { return "", "", "", err @@ -25,7 +25,7 @@ func Keygen(kmsKeyID string, awsRegion string) (string, string, string, error) { } // Decrypt decrypts an EJSON file -func Decrypt(ejsonFilePath string, awsRegion string) ([]byte, error) { +func Decrypt(ejsonFilePath, awsRegion string) ([]byte, error) { privateKeyEnc, err := findPrivateKeyEnc(ejsonFilePath) if err != nil { return nil, err diff --git a/kms.go b/kms.go index e4d7ee7..810a0af 100644 --- a/kms.go +++ b/kms.go @@ -11,7 +11,7 @@ import ( "github.com/aws/aws-sdk-go/service/kms" ) -func decryptPrivateKeyWithKMS(privateKeyEnc string, awsRegion string) (key string, err error) { +func decryptPrivateKeyWithKMS(privateKeyEnc, awsRegion string) (key string, err error) { kmsSvc := newKmsClient(awsRegion) encryptedValue, err := base64.StdEncoding.DecodeString(privateKeyEnc) @@ -26,7 +26,7 @@ func decryptPrivateKeyWithKMS(privateKeyEnc string, awsRegion string) (key strin return string(resp.Plaintext), nil } -func encryptPrivateKeyWithKMS(privateKey string, kmsKeyID string, awsRegion string) (key string, err error) { +func encryptPrivateKeyWithKMS(privateKey, kmsKeyID, awsRegion string) (key string, err error) { kmsSvc := newKmsClient(awsRegion) params := &kms.EncryptInput{ KeyId: &kmsKeyID, diff --git a/main.go b/main.go index df2f09d..dcf80f1 100644 --- a/main.go +++ b/main.go @@ -104,7 +104,7 @@ func main() { fail(fmt.Errorf("no secrets.ejson filename passed")) } - if err := envAction(filename, quiet, awsRegion); nil != err { + if err := envAction(filename, awsRegion, quiet); nil != err { fail(err) } }, From cc1f3d42f1c2be0fd8854e933856630add490b34 Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 07:48:00 +0400 Subject: [PATCH 13/26] Update ejsonkms_test.go Co-Authored-By: Jacob Bednarz --- ejsonkms_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ejsonkms_test.go b/ejsonkms_test.go index e4a2e76..78385cb 100644 --- a/ejsonkms_test.go +++ b/ejsonkms_test.go @@ -25,7 +25,7 @@ func TestDecrypt(t *testing.T) { Convey("should return decrypted values", func() { So(err, ShouldBeNil) json := string(decrypted[:]) - So(json, ShouldContainSubstring, "\"my_secret\": \"secret123\"") + So(json, ShouldContainSubstring, `"my_secret": "secret123"`) }) }) Convey("Decrypt with no private key", t, func() { From 96ccca6593775ef996b83fd83a365eb03051f3c3 Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 07:50:33 +0400 Subject: [PATCH 14/26] Lowercase --- kms.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kms.go b/kms.go index 810a0af..1a684ee 100644 --- a/kms.go +++ b/kms.go @@ -21,7 +21,7 @@ func decryptPrivateKeyWithKMS(privateKeyEnc, awsRegion string) (key string, err } resp, err := kmsSvc.Decrypt(params) if err != nil { - log.Fatalf("Unable to decrypt parameter: %v", err) + log.Fatalf("unable to decrypt parameter: %v", err) } return string(resp.Plaintext), nil } @@ -34,7 +34,7 @@ func encryptPrivateKeyWithKMS(privateKey, kmsKeyID, awsRegion string) (key strin } resp, err := kmsSvc.Encrypt(params) if err != nil { - log.Fatalf("Unable to encrypt parameter: %v", err) + log.Fatalf("unable to encrypt parameter: %v", err) } encodedPrivKey := base64.StdEncoding.EncodeToString(resp.CiphertextBlob) From ac0ea38ad2c6e1b215286d244e7a5aed35cbd3d9 Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 08:53:46 +0400 Subject: [PATCH 15/26] Remove test conditional hack and rely on env var being present --- kms.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/kms.go b/kms.go index 1a684ee..10250fb 100644 --- a/kms.go +++ b/kms.go @@ -44,11 +44,8 @@ func encryptPrivateKeyWithKMS(privateKey, kmsKeyID, awsRegion string) (key strin func newKmsClient(awsRegion string) *kms.KMS { awsSession := session.Must(session.NewSession()) awsSession.Config.WithRegion(awsRegion) - if flag.Lookup("test.v") != nil { // is there a better way to do this? - fakeKmsEndpoint := os.Getenv("FAKE_AWSKMS_URL") - if len(fakeKmsEndpoint) == 0 { - fakeKmsEndpoint = "http://localhost:8080" - } + fakeKmsEndpoint := os.Getenv("FAKE_AWSKMS_URL") + if len(fakeKmsEndpoint) != 0 { return kms.New(awsSession, aws.NewConfig().WithEndpoint(fakeKmsEndpoint)) } return kms.New(awsSession) From dc905c37eaf882ad4555ef79dbe7b8861901a63f Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 05:20:02 +0000 Subject: [PATCH 16/26] Unmarshal to a struct --- ejsonkms.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/ejsonkms.go b/ejsonkms.go index f9fcf81..646e003 100644 --- a/ejsonkms.go +++ b/ejsonkms.go @@ -44,10 +44,13 @@ func Decrypt(ejsonFilePath, awsRegion string) ([]byte, error) { return decrypted, nil } +type ejsonKmsFile struct { + EncryptedPrivateKey string `json:"_private_key_enc"` +} + func findPrivateKeyEnc(ejsonFilePath string) (key string, err error) { var ( - obj map[string]interface{} - ks string + obj ejsonKmsFile ) file, err := os.Open(ejsonFilePath) @@ -66,13 +69,9 @@ func findPrivateKeyEnc(ejsonFilePath string) (key string, err error) { return "", err } - k, ok := obj["_private_key_enc"] - if !ok { + if len(obj.EncryptedPrivateKey) == 0 { return "", errors.New("Missing _private_key_enc field") } - ks, ok = k.(string) - if !ok { - return "", errors.New("Couldn't cast _private_key_enc to string") - } - return ks, nil + + return obj.EncryptedPrivateKey, nil } From e1e88cd3f0dee6ed22c4c9f2455a77e0844f2316 Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 09:25:26 +0400 Subject: [PATCH 17/26] Return an error rather than fail --- kms.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/kms.go b/kms.go index 10250fb..ac3d2a7 100644 --- a/kms.go +++ b/kms.go @@ -2,8 +2,7 @@ package main import ( "encoding/base64" - "flag" - "log" + "fmt" "os" "github.com/aws/aws-sdk-go/aws" @@ -21,7 +20,7 @@ func decryptPrivateKeyWithKMS(privateKeyEnc, awsRegion string) (key string, err } resp, err := kmsSvc.Decrypt(params) if err != nil { - log.Fatalf("unable to decrypt parameter: %v", err) + return "", fmt.Errorf("unable to decrypt parameter: %v", err) } return string(resp.Plaintext), nil } @@ -34,7 +33,7 @@ func encryptPrivateKeyWithKMS(privateKey, kmsKeyID, awsRegion string) (key strin } resp, err := kmsSvc.Encrypt(params) if err != nil { - log.Fatalf("unable to encrypt parameter: %v", err) + return "", fmt.Errorf("unable to encrypt parameter: %v", err) } encodedPrivKey := base64.StdEncoding.EncodeToString(resp.CiphertextBlob) From af90c898412726aa08d9a449155c30bf17f176b7 Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 09:25:37 +0400 Subject: [PATCH 18/26] Remove unused var --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index ea813ac..2ed99c1 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,4 @@ NAME=ejsonkms -PACKAGE=github.com/envato/ejsonkms VERSION=$(shell cat VERSION) GOFILES=$(shell find . -type f -name '*.go') From 343f7099350f6bdcef11ee8411eb2ec7c0fb8f8f Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 09:26:58 +0400 Subject: [PATCH 19/26] Mount single config file in compose --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 92bd853..0390396 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: environment: REGION: us-east-1 volumes: - - "./local_kms:/init" + - "./local_kms/seed.yaml:/init/seed.yaml" expose: - 8080 tests: From fef25754b55c8e4cf0d7cdf6fff83740fba2c31a Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 09:27:20 +0400 Subject: [PATCH 20/26] Rely on kms interface --- kms.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kms.go b/kms.go index ac3d2a7..2b70d5b 100644 --- a/kms.go +++ b/kms.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/kms" + "github.com/aws/aws-sdk-go/service/kms/kmsiface" ) func decryptPrivateKeyWithKMS(privateKeyEnc, awsRegion string) (key string, err error) { @@ -40,7 +41,7 @@ func encryptPrivateKeyWithKMS(privateKey, kmsKeyID, awsRegion string) (key strin return encodedPrivKey, nil } -func newKmsClient(awsRegion string) *kms.KMS { +func newKmsClient(awsRegion string) kmsiface.KMSAPI { awsSession := session.Must(session.NewSession()) awsSession.Config.WithRegion(awsRegion) fakeKmsEndpoint := os.Getenv("FAKE_AWSKMS_URL") From 8d57158699014f963d5917e1e073d104d865b594 Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 09:46:03 +0400 Subject: [PATCH 21/26] Extract object EjsonKmsKeys --- actions.go | 9 ++++++--- ejsonkms.go | 34 ++++++++++++++++++++++------------ ejsonkms_test.go | 10 +++++----- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/actions.go b/actions.go index 3f3e8f7..ba5b1ba 100644 --- a/actions.go +++ b/actions.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "os" @@ -46,12 +47,14 @@ func decryptAction(args []string, awsRegion, outFile string) error { } func keygenAction(args []string, kmsKeyID, awsRegion, outFile string) error { - pub, priv, privKeyEnc, err := Keygen(kmsKeyID, awsRegion) + ejsonKmsKeys, err := Keygen(kmsKeyID, awsRegion) if err != nil { return err } - fmt.Println("Private Key:", priv) + ejsonFile, err := json.Marshal(ejsonKmsKeys) + + fmt.Println("Private Key:", ejsonKmsKeys.PrivateKey) target := os.Stdout if outFile != "" { target, err = os.Create(outFile) @@ -63,7 +66,7 @@ func keygenAction(args []string, kmsKeyID, awsRegion, outFile string) error { fmt.Println("EJSON File:") } - _, err = fmt.Fprintf(target, "{\n \"_public_key\": \"%s\",\n \"_private_key_enc\": \"%s\"\n}", pub, privKeyEnc) + _, err = target.Write(ejsonFile) if err != nil { return err } diff --git a/ejsonkms.go b/ejsonkms.go index 646e003..7519fc0 100644 --- a/ejsonkms.go +++ b/ejsonkms.go @@ -9,19 +9,33 @@ import ( "github.com/Shopify/ejson" ) +// EjsonKmsKeys - keys used in an EjsonKms file +type EjsonKmsKeys struct { + PublicKey string `json:"_public_key"` + PrivateKeyEnc string `json:"_private_key_enc"` + PrivateKey string +} + // Keygen generates keys and prepares an EJSON file with them -func Keygen(kmsKeyID, awsRegion string) (string, string, string, error) { +func Keygen(kmsKeyID, awsRegion string) (EjsonKmsKeys, error) { + var ejsonKmsKeys EjsonKmsKeys pub, priv, err := ejson.GenerateKeypair() if err != nil { - return "", "", "", err + return ejsonKmsKeys, err } privKeyEnc, err := encryptPrivateKeyWithKMS(priv, kmsKeyID, awsRegion) if err != nil { - return "", "", "", err + return ejsonKmsKeys, err + } + + ejsonKmsKeys = EjsonKmsKeys{ + PublicKey: pub, + PrivateKeyEnc: privKeyEnc, + PrivateKey: priv, } - return pub, priv, privKeyEnc, nil + return ejsonKmsKeys, nil } // Decrypt decrypts an EJSON file @@ -44,13 +58,9 @@ func Decrypt(ejsonFilePath, awsRegion string) ([]byte, error) { return decrypted, nil } -type ejsonKmsFile struct { - EncryptedPrivateKey string `json:"_private_key_enc"` -} - func findPrivateKeyEnc(ejsonFilePath string) (key string, err error) { var ( - obj ejsonKmsFile + ejsonKmsKeys EjsonKmsKeys ) file, err := os.Open(ejsonFilePath) @@ -64,14 +74,14 @@ func findPrivateKeyEnc(ejsonFilePath string) (key string, err error) { return "", err } - err = json.Unmarshal(data, &obj) + err = json.Unmarshal(data, &ejsonKmsKeys) if err != nil { return "", err } - if len(obj.EncryptedPrivateKey) == 0 { + if len(ejsonKmsKeys.PrivateKeyEnc) == 0 { return "", errors.New("Missing _private_key_enc field") } - return obj.EncryptedPrivateKey, nil + return ejsonKmsKeys.PrivateKeyEnc, nil } diff --git a/ejsonkms_test.go b/ejsonkms_test.go index 78385cb..2d79a9f 100644 --- a/ejsonkms_test.go +++ b/ejsonkms_test.go @@ -8,13 +8,13 @@ import ( func TestKeygen(t *testing.T) { Convey("Keygen", t, func() { - pub, priv, privEnc, err := Keygen("bc436485-5092-42b8-92a3-0aa8b93536dc", "us-east-1") + ejsonKmsKeys, err := Keygen("bc436485-5092-42b8-92a3-0aa8b93536dc", "us-east-1") Convey("should return three strings that look key-like", func() { So(err, ShouldBeNil) - So(pub, ShouldNotEqual, priv) - So(pub, ShouldNotContainSubstring, "00000") - So(priv, ShouldNotContainSubstring, "00000") - So(privEnc, ShouldNotContainSubstring, "00000") + So(ejsonKmsKeys.PublicKey, ShouldNotEqual, ejsonKmsKeys.PrivateKey) + So(ejsonKmsKeys.PublicKey, ShouldNotContainSubstring, "00000") + So(ejsonKmsKeys.PrivateKey, ShouldNotContainSubstring, "00000") + So(ejsonKmsKeys.PrivateKeyEnc, ShouldNotContainSubstring, "00000") }) }) } From c99ad37aba23069cda17c4aecc2b555313e342b2 Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 09:51:40 +0400 Subject: [PATCH 22/26] Pretty print json in keygen action --- actions.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/actions.go b/actions.go index ba5b1ba..80d34a4 100644 --- a/actions.go +++ b/actions.go @@ -52,7 +52,10 @@ func keygenAction(args []string, kmsKeyID, awsRegion, outFile string) error { return err } - ejsonFile, err := json.Marshal(ejsonKmsKeys) + ejsonFile, err := json.MarshalIndent(ejsonKmsKeys, "", " ") + if err != nil { + return err + } fmt.Println("Private Key:", ejsonKmsKeys.PrivateKey) target := os.Stdout From 0e2f7e7d3bb45109e5f9b258f77491fe66e4d09f Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 09:56:14 +0400 Subject: [PATCH 23/26] Serialize only pub and privatekeyenc for ejsonfile --- actions.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/actions.go b/actions.go index 80d34a4..de07eba 100644 --- a/actions.go +++ b/actions.go @@ -46,13 +46,24 @@ func decryptAction(args []string, awsRegion, outFile string) error { return err } +// ejsonKmsFile - an ejson file +type ejsonKmsFile struct { + PublicKey string `json:"_public_key"` + PrivateKeyEnc string `json:"_private_key_enc"` +} + func keygenAction(args []string, kmsKeyID, awsRegion, outFile string) error { ejsonKmsKeys, err := Keygen(kmsKeyID, awsRegion) if err != nil { return err } - ejsonFile, err := json.MarshalIndent(ejsonKmsKeys, "", " ") + ejsonKmsFile := ejsonKmsFile{ + PublicKey: ejsonKmsKeys.PublicKey, + PrivateKeyEnc: ejsonKmsKeys.PrivateKeyEnc, + } + + ejsonFile, err := json.MarshalIndent(ejsonKmsFile, "", " ") if err != nil { return err } From a76060263136431a90d1b76f8d33044f62c2775e Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 09:57:09 +0400 Subject: [PATCH 24/26] Add .go-version --- .go-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .go-version diff --git a/.go-version b/.go-version new file mode 100644 index 0000000..166a50f --- /dev/null +++ b/.go-version @@ -0,0 +1 @@ +1.12.9 From 233ff52ac13371dc212173aadca81aaa8a583b3f Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 10:00:57 +0400 Subject: [PATCH 25/26] Lowercase error --- ejsonkms.go | 2 +- ejsonkms_test.go | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/ejsonkms.go b/ejsonkms.go index 7519fc0..b9c5891 100644 --- a/ejsonkms.go +++ b/ejsonkms.go @@ -80,7 +80,7 @@ func findPrivateKeyEnc(ejsonFilePath string) (key string, err error) { } if len(ejsonKmsKeys.PrivateKeyEnc) == 0 { - return "", errors.New("Missing _private_key_enc field") + return "", errors.New("missing _private_key_enc field") } return ejsonKmsKeys.PrivateKeyEnc, nil diff --git a/ejsonkms_test.go b/ejsonkms_test.go index 2d79a9f..d09ef48 100644 --- a/ejsonkms_test.go +++ b/ejsonkms_test.go @@ -6,6 +6,24 @@ import ( . "github.com/smartystreets/goconvey/convey" ) +// type mockKMSClient struct { +// kmsiface.KMSAPI +// } + +// func (m *mockKMSClient) Encrypt(input *kms.EncryptInput) (*kms.EncryptOutput, error) { +// output := &kms.EncryptOutput{ +// CiphertextBlob: input.Plaintext, +// } +// return output, nil +// } + +// func (m *mockKMSClient) Decrypt(input *kms.DecryptInput) (*kms.DecryptOutput, error) { +// output := &kms.DecryptOutput{ +// Plaintext: input.CiphertextBlob, +// } +// return output, nil +// } + func TestKeygen(t *testing.T) { Convey("Keygen", t, func() { ejsonKmsKeys, err := Keygen("bc436485-5092-42b8-92a3-0aa8b93536dc", "us-east-1") @@ -32,7 +50,7 @@ func TestDecrypt(t *testing.T) { _, err := Decrypt("testdata/test_no_private_key.ejson", "us-east-1") Convey("should fail", func() { So(err, ShouldNotBeNil) - So(err.Error(), ShouldContainSubstring, "Missing _private_key_enc") + So(err.Error(), ShouldContainSubstring, "missing _private_key_enc") }) }) } From 8818ebd7c8297d90e69d611137458fa5be452d79 Mon Sep 17 00:00:00 2001 From: Steve Hodgkiss Date: Tue, 3 Sep 2019 10:10:41 +0400 Subject: [PATCH 26/26] Add MIT license --- LICENSE.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..15e8715 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Envato + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.