From 557007c00f40bf4356d9122831420238953eee79 Mon Sep 17 00:00:00 2001 From: Alexander Scheel Date: Thu, 15 Aug 2024 08:12:54 -0400 Subject: [PATCH] Remove extras unused by OpenBao These subpackages are unused by OpenBao. KMS is a package built on top of go-kms-wrapping which uses a database to store encryption keys: this is unnecessary in our case as we explicitly want all keys to be backed by the underlying wrapper (which, admittedly in the case of cloud KMSes, incurs some cost that extras/kms/ could avoid). Crypto adds, along other things, a HMAC-SHA-256 implementation: if we find this useful, we could add this directly to wrappers/ as an optional type (just like signing.go is). Lastly, StructWrapping adds a way to encrypt arbitrary interfaces, through a custom marshaling format built on protobuf. extras/multi remains: it is unclear if it might potentially be useful for multi-unseal in OpenBao in the future and aead/ had tests using it. Signed-off-by: Alexander Scheel --- Makefile | 32 +- README.md | 16 - extras/crypto/derived_reader.go | 54 - extras/crypto/derived_reader_test.go | 201 -- extras/crypto/hmac_sha256.go | 126 -- extras/crypto/hmac_sha256_test.go | 169 -- extras/crypto/isnil.go | 17 - extras/crypto/options.go | 148 -- extras/crypto/sha256sum.go | 192 -- extras/crypto/sha256sum_test.go | 351 ---- extras/crypto/testing.go | 115 -- extras/kms/.gitignore | 3 - extras/kms/CHANGELOG.md | 33 - extras/kms/Makefile | 25 - extras/kms/README.md | 101 - extras/kms/data_key.go | 96 - extras/kms/data_key_test.go | 173 -- extras/kms/data_key_version.go | 138 -- extras/kms/data_key_version_test.go | 300 --- extras/kms/docker-compose.yml | 16 - extras/kms/docs.go | 81 - extras/kms/errors.go | 28 - extras/kms/examples/Dockerfile | 6 - extras/kms/examples/README.md | 9 - extras/kms/examples/cli/.gitignore | 2 - extras/kms/examples/cli/README.md | 139 -- extras/kms/examples/cli/main.go | 243 --- extras/kms/examples/config.go | 90 - extras/kms/examples/db.go | 98 - extras/kms/examples/docker-compose.yml | 21 - extras/kms/examples/fs.go | 12 - extras/kms/examples/go.mod | 118 -- extras/kms/examples/go.sum | 486 ----- extras/kms/examples/oidc.go | 24 - extras/kms/examples/run.sh | 21 - extras/kms/examples/scope.go | 17 - .../sqlite-migrations/10_scopes.up.sql | 21 - .../examples/sqlite-migrations/15_oidc.up.sql | 13 - extras/kms/go.mod | 58 - extras/kms/go.sum | 317 ---- extras/kms/id.go | 57 - extras/kms/id_test.go | 26 - extras/kms/key.go | 69 - extras/kms/key_purpose.go | 57 - extras/kms/kms.go | 870 --------- extras/kms/kms_internal_test.go | 1629 ----------------- extras/kms/kms_test.go | 1445 --------------- extras/kms/migrations/fs.go | 19 - .../postgres/01_domain_types.up.sql | 101 - .../kms/migrations/postgres/02_version.up.sql | 36 - extras/kms/migrations/postgres/03_keys.up.sql | 33 - extras/kms/migrations/postgres/04_keys.up.sql | 130 -- .../migrations/postgres/05_key_rewrap.up.sql | 27 - .../postgres/06_kms_collection_version.up.sql | 31 - .../07_mutable_root_key_version.up.sql | 15 - .../kms/migrations/sqlite/02_version.up.sql | 35 - extras/kms/migrations/sqlite/04_keys.up.sql | 178 -- .../migrations/sqlite/05_key_rewrap.up.sql | 35 - .../sqlite/06_kms_collection_version.up.sql | 38 - .../sqlite/07_mutable_root_key_version.up.sql | 18 - extras/kms/option.go | 188 -- extras/kms/option_test.go | 105 -- extras/kms/query.go | 35 - extras/kms/repository.go | 308 ---- extras/kms/repository_data_key.go | 216 --- extras/kms/repository_data_key_test.go | 626 ------- extras/kms/repository_data_key_version.go | 401 ---- .../kms/repository_data_key_version_test.go | 1234 ------------- extras/kms/repository_root_key.go | 197 -- extras/kms/repository_root_key_test.go | 612 ------- extras/kms/repository_root_key_version.go | 294 --- .../kms/repository_root_key_version_test.go | 881 --------- extras/kms/repository_test.go | 358 ---- extras/kms/root_key.go | 56 - extras/kms/root_key_test.go | 55 - extras/kms/root_key_version.go | 126 -- extras/kms/root_key_version_test.go | 271 --- extras/kms/schema.go | 30 - extras/kms/schema_test.go | 399 ---- extras/kms/testing.go | 229 --- extras/kms/testing_test.go | 83 - extras/kms/tools/tools.go | 24 - extras/structwrapping/README.md | 78 - extras/structwrapping/structwrapping.go | 212 --- extras/structwrapping/structwrapping_test.go | 198 -- go.mod | 2 - go.sum | 4 - 87 files changed, 31 insertions(+), 16150 deletions(-) delete mode 100644 extras/crypto/derived_reader.go delete mode 100644 extras/crypto/derived_reader_test.go delete mode 100644 extras/crypto/hmac_sha256.go delete mode 100644 extras/crypto/hmac_sha256_test.go delete mode 100644 extras/crypto/isnil.go delete mode 100644 extras/crypto/options.go delete mode 100644 extras/crypto/sha256sum.go delete mode 100644 extras/crypto/sha256sum_test.go delete mode 100644 extras/crypto/testing.go delete mode 100644 extras/kms/.gitignore delete mode 100644 extras/kms/CHANGELOG.md delete mode 100644 extras/kms/Makefile delete mode 100644 extras/kms/README.md delete mode 100644 extras/kms/data_key.go delete mode 100644 extras/kms/data_key_test.go delete mode 100644 extras/kms/data_key_version.go delete mode 100644 extras/kms/data_key_version_test.go delete mode 100644 extras/kms/docker-compose.yml delete mode 100644 extras/kms/docs.go delete mode 100644 extras/kms/errors.go delete mode 100644 extras/kms/examples/Dockerfile delete mode 100644 extras/kms/examples/README.md delete mode 100644 extras/kms/examples/cli/.gitignore delete mode 100644 extras/kms/examples/cli/README.md delete mode 100644 extras/kms/examples/cli/main.go delete mode 100644 extras/kms/examples/config.go delete mode 100644 extras/kms/examples/db.go delete mode 100644 extras/kms/examples/docker-compose.yml delete mode 100644 extras/kms/examples/fs.go delete mode 100644 extras/kms/examples/go.mod delete mode 100644 extras/kms/examples/go.sum delete mode 100644 extras/kms/examples/oidc.go delete mode 100755 extras/kms/examples/run.sh delete mode 100644 extras/kms/examples/scope.go delete mode 100644 extras/kms/examples/sqlite-migrations/10_scopes.up.sql delete mode 100644 extras/kms/examples/sqlite-migrations/15_oidc.up.sql delete mode 100644 extras/kms/go.mod delete mode 100644 extras/kms/go.sum delete mode 100644 extras/kms/id.go delete mode 100644 extras/kms/id_test.go delete mode 100644 extras/kms/key.go delete mode 100644 extras/kms/key_purpose.go delete mode 100644 extras/kms/kms.go delete mode 100644 extras/kms/kms_internal_test.go delete mode 100644 extras/kms/kms_test.go delete mode 100644 extras/kms/migrations/fs.go delete mode 100644 extras/kms/migrations/postgres/01_domain_types.up.sql delete mode 100644 extras/kms/migrations/postgres/02_version.up.sql delete mode 100644 extras/kms/migrations/postgres/03_keys.up.sql delete mode 100644 extras/kms/migrations/postgres/04_keys.up.sql delete mode 100644 extras/kms/migrations/postgres/05_key_rewrap.up.sql delete mode 100644 extras/kms/migrations/postgres/06_kms_collection_version.up.sql delete mode 100644 extras/kms/migrations/postgres/07_mutable_root_key_version.up.sql delete mode 100644 extras/kms/migrations/sqlite/02_version.up.sql delete mode 100644 extras/kms/migrations/sqlite/04_keys.up.sql delete mode 100644 extras/kms/migrations/sqlite/05_key_rewrap.up.sql delete mode 100644 extras/kms/migrations/sqlite/06_kms_collection_version.up.sql delete mode 100644 extras/kms/migrations/sqlite/07_mutable_root_key_version.up.sql delete mode 100644 extras/kms/option.go delete mode 100644 extras/kms/option_test.go delete mode 100644 extras/kms/query.go delete mode 100644 extras/kms/repository.go delete mode 100644 extras/kms/repository_data_key.go delete mode 100644 extras/kms/repository_data_key_test.go delete mode 100644 extras/kms/repository_data_key_version.go delete mode 100644 extras/kms/repository_data_key_version_test.go delete mode 100644 extras/kms/repository_root_key.go delete mode 100644 extras/kms/repository_root_key_test.go delete mode 100644 extras/kms/repository_root_key_version.go delete mode 100644 extras/kms/repository_root_key_version_test.go delete mode 100644 extras/kms/repository_test.go delete mode 100644 extras/kms/root_key.go delete mode 100644 extras/kms/root_key_test.go delete mode 100644 extras/kms/root_key_version.go delete mode 100644 extras/kms/root_key_version_test.go delete mode 100644 extras/kms/schema.go delete mode 100644 extras/kms/schema_test.go delete mode 100644 extras/kms/testing.go delete mode 100644 extras/kms/testing_test.go delete mode 100644 extras/kms/tools/tools.go delete mode 100644 extras/structwrapping/README.md delete mode 100644 extras/structwrapping/structwrapping.go delete mode 100644 extras/structwrapping/structwrapping_test.go diff --git a/Makefile b/Makefile index a247bbdb..42bcb3f2 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ proto: find . -type f -name "*.pb.go" -delete buf generate buf format -w - + # inject classification tags (see: https://github.com/hashicorp/go-eventlogger/tree/main/filters/encrypt) @protoc-go-inject-tag -input=./github.com.openbao.go.kms.wrapping.v2.types.pb.go @@ -25,3 +25,33 @@ proto: tools: go install github.com/favadi/protoc-go-inject-tag@v1.4.0 go install github.com/bufbuild/buf/cmd/buf@v1.15.1 + +.PHONY: tidy-all +tidy-all: + cd entropy && go mod tidy + cd plugin && go mod tidy + cd wrappers/aead && go mod tidy + cd wrappers/alicloudkms && go mod tidy + cd wrappers/awskms && go mod tidy + cd wrappers/azurekeyvault && go mod tidy + cd wrappers/gcpckms && go mod tidy + cd wrappers/huaweicloudkms && go mod tidy + cd wrappers/ocikms && go mod tidy + cd wrappers/tencentcloudkms && go mod tidy + cd wrappers/transit && go mod tidy + go mod tidy + +.PHONY: generate-all +generate-all: + cd entropy && GOARCH= GOOS= go generate ./... + cd plugin && GOARCH= GOOS= go generate ./... + cd wrappers/aead && GOARCH= GOOS= go generate ./... + cd wrappers/alicloudkms && GOARCH= GOOS= go generate ./... + cd wrappers/awskms && GOARCH= GOOS= go generate ./... + cd wrappers/azurekeyvault && GOARCH= GOOS= go generate ./... + cd wrappers/gcpckms && GOARCH= GOOS= go generate ./... + cd wrappers/huaweicloudkms && GOARCH= GOOS= go generate ./... + cd wrappers/ocikms && GOARCH= GOOS= go generate ./... + cd wrappers/tencentcloudkms && GOARCH= GOOS= go generate ./... + cd wrappers/transit && GOARCH= GOOS= go generate ./... + GOARCH= GOOS= go generate ./... diff --git a/README.md b/README.md index caddfa38..a648a15b 100644 --- a/README.md +++ b/README.md @@ -61,22 +61,6 @@ package is capable of encrypting to a specified wrapper and decrypting using one of several wrappers switched on key ID. This can allow easy key rotation for KMSes that do not natively support it. -* The -[`structwrapping`](https://github.com/hashicorp/go-kms-wrapping/tree/main/extras/structwrapping) -package allows for structs to have members encrypted and decrypted in a single -pass via a single wrapper. This can be used for workflows such as database -library callback functions to easily encrypt/decrypt data as it goes to/from -storage. - -* The [`kms`](https://github.com/hashicorp/go-kms-wrapping/tree/main/extras/kms) - package provides key management system features for wrappers - including scoped [KEKs](https://en.wikipedia.org/wiki/Glossary_of_cryptographic_keys) - and [DEKs](https://en.wikipedia.org/wiki/Glossary_of_cryptographic_keys) which - are wrapped with an external KMS when stored in sqlite or postgres. - -* The [`crypto`](https://github.com/hashicorp/go-kms-wrapping/tree/main/extras/crypto) package provides additional operations like HMAC-SHA256 and a - derived reader from which keys can be read. - ## Installation `go get github.com/hashicorp/go-kms-wrapping/v2` diff --git a/extras/crypto/derived_reader.go b/extras/crypto/derived_reader.go deleted file mode 100644 index 3f81b93c..00000000 --- a/extras/crypto/derived_reader.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package crypto - -import ( - "context" - "crypto/sha256" - "fmt" - "io" - - wrapping "github.com/openbao/go-kms-wrapping/v2" - "golang.org/x/crypto/hkdf" -) - -// DerivedReader returns a reader from which keys can be read, using the -// given wrapper, reader length limit, salt and context info. Salt and info can -// be nil. -// -// Example: -// reader, _ := crypto.NewDerivedReader(wrapper, userId, jobId) -// key := ed25519.GenerateKey(reader) -func NewDerivedReader(ctx context.Context, wrapper wrapping.Wrapper, lenLimit int64, opt ...wrapping.Option) (*io.LimitedReader, error) { - const ( - op = "reader.NewDerivedReader" - minLen = 20 - ) - if wrapper == nil { - return nil, fmt.Errorf("%s: missing wrapper: %w", op, wrapping.ErrInvalidParameter) - } - if lenLimit < minLen { - return nil, fmt.Errorf("%s: lenLimit must be >= %d: %w", op, minLen, wrapping.ErrInvalidParameter) - } - biter, ok := wrapper.(wrapping.KeyExporter) - if !ok { - return nil, fmt.Errorf("%s: wrapper does not implement required KeyBytes interface: %w", op, wrapping.ErrInvalidParameter) - } - b, err := biter.KeyBytes(ctx) - if err != nil { - return nil, fmt.Errorf("%s: unable to get current key bytes: %w", op, err) - } - if b == nil { - return nil, fmt.Errorf("%s: wrapper missing bytes: %w", op, wrapping.ErrInvalidParameter) - } - opts, err := getOpts(opt...) - if err != nil { - return nil, fmt.Errorf("%s: unable to get options %w", op, err) - } - reader := hkdf.New(sha256.New, b, opts.withSalt, opts.withInfo) - return &io.LimitedReader{ - R: reader, - N: lenLimit, - }, nil -} diff --git a/extras/crypto/derived_reader_test.go b/extras/crypto/derived_reader_test.go deleted file mode 100644 index 43458d4b..00000000 --- a/extras/crypto/derived_reader_test.go +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package crypto_test - -import ( - "context" - "crypto/sha256" - "io" - "testing" - - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/aead" - "github.com/openbao/go-kms-wrapping/v2/extras/crypto" - "github.com/openbao/go-kms-wrapping/v2/extras/multi" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "golang.org/x/crypto/hkdf" -) - -func TestNewDerivedReader(t *testing.T) { - testWrapper := wrapping.NewTestWrapper([]byte("secret")) - pooledWrapper := aead.TestPooledWrapper(t) - aeadWrapper := aead.TestWrapper(t) - ctx := context.Background() - - type args struct { - wrapper wrapping.Wrapper - lenLimit int64 - opt []wrapping.Option - } - tests := []struct { - name string - args args - want *io.LimitedReader - wantErr bool - wantErrCode error - wantErrContains string - }{ - { - name: "valid-aead-with-salt", - args: args{ - wrapper: aeadWrapper, - lenLimit: 32, - opt: []wrapping.Option{crypto.WithSalt([]byte("salt"))}, - }, - want: func() *io.LimitedReader { - b, err := aeadWrapper.(*aead.Wrapper).KeyBytes(ctx) - require.NoError(t, err) - r := &io.LimitedReader{ - R: hkdf.New(sha256.New, b, []byte("salt"), nil), - N: 32, - } - return r - }(), - }, - { - name: "valid-aead-with-salt-info", - args: args{ - wrapper: aeadWrapper, - lenLimit: 32, - opt: []wrapping.Option{crypto.WithInfo([]byte("info")), crypto.WithSalt([]byte("salt"))}, - }, - want: func() *io.LimitedReader { - b, err := aeadWrapper.(*aead.Wrapper).KeyBytes(ctx) - require.NoError(t, err) - r := &io.LimitedReader{ - R: hkdf.New(sha256.New, b, []byte("salt"), []byte("info")), - N: 32, - } - return r - }(), - }, - { - name: "valid-with-salt", - args: args{ - wrapper: testWrapper, - lenLimit: 32, - opt: []wrapping.Option{crypto.WithSalt([]byte("salt"))}, - }, - want: func() *io.LimitedReader { - b, err := testWrapper.KeyBytes(ctx) - require.NoError(t, err) - r := &io.LimitedReader{ - R: hkdf.New(sha256.New, b, []byte("salt"), nil), - N: 32, - } - return r - }(), - }, - { - name: "valid-with-salt-info", - args: args{ - wrapper: testWrapper, - lenLimit: 32, - opt: []wrapping.Option{crypto.WithInfo([]byte("info")), crypto.WithSalt([]byte("salt"))}, - }, - want: func() *io.LimitedReader { - b, err := testWrapper.KeyBytes(ctx) - require.NoError(t, err) - r := &io.LimitedReader{ - R: hkdf.New(sha256.New, b, []byte("salt"), []byte("info")), - N: 32, - } - return r - }(), - }, - { - name: "valid-multi-wrapper-with-salt", - args: args{ - wrapper: pooledWrapper, - lenLimit: 32, - opt: []wrapping.Option{crypto.WithSalt([]byte("salt"))}, - }, - want: func() *io.LimitedReader { - raw := pooledWrapper.(*multi.PooledWrapper).WrapperForKeyId("__base__") - b, err := raw.(*aead.Wrapper).KeyBytes(ctx) - require.NoError(t, err) - return &io.LimitedReader{ - R: hkdf.New(sha256.New, b, []byte("salt"), nil), - N: 32, - } - }(), - }, - { - name: "unknown-wrapper", - args: args{ - wrapper: &unknownWrapper{}, - lenLimit: 20, - opt: []wrapping.Option{crypto.WithInfo([]byte("info")), crypto.WithSalt([]byte("salt"))}, - }, - wantErr: true, - wantErrCode: wrapping.ErrInvalidParameter, - wantErrContains: "wrapper does not implement required KeyBytes interface", - }, - { - name: "nil-wrapper", - args: args{ - wrapper: nil, - lenLimit: 10, - opt: []wrapping.Option{crypto.WithInfo([]byte("info")), crypto.WithSalt([]byte("salt"))}, - }, - wantErr: true, - wantErrCode: wrapping.ErrInvalidParameter, - wantErrContains: "missing wrapper", - }, - { - name: "too-short", - args: args{ - wrapper: testWrapper, - lenLimit: 10, - opt: []wrapping.Option{crypto.WithInfo([]byte("info")), crypto.WithSalt([]byte("salt"))}, - }, - wantErr: true, - wantErrCode: wrapping.ErrInvalidParameter, - wantErrContains: "lenLimit must be >= 20", - }, - { - name: "aead-wrapper-with-no-bytes", - args: args{ - wrapper: &aead.Wrapper{}, - lenLimit: 32, - opt: []wrapping.Option{crypto.WithSalt([]byte("salt"))}, - }, - wantErr: true, - wantErrCode: wrapping.ErrInvalidParameter, - wantErrContains: "missing bytes", - }, - { - name: "test-wrapper-with-no-bytes", - args: args{ - wrapper: &wrapping.TestWrapper{}, - lenLimit: 32, - opt: []wrapping.Option{crypto.WithSalt([]byte("salt"))}, - }, - wantErr: true, - wantErrCode: wrapping.ErrInvalidParameter, - wantErrContains: "missing bytes", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - got, err := crypto.NewDerivedReader(context.Background(), tc.args.wrapper, tc.args.lenLimit, tc.args.opt...) - if tc.wantErr { - require.Error(err) - assert.ErrorIsf(err, tc.wantErrCode, "unexpected error: %s", err) - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.want, got) - }) - } -} - -type unknownWrapper struct { - wrapping.Wrapper -} diff --git a/extras/crypto/hmac_sha256.go b/extras/crypto/hmac_sha256.go deleted file mode 100644 index cef2b1ff..00000000 --- a/extras/crypto/hmac_sha256.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package crypto - -import ( - "context" - "crypto/ed25519" - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "fmt" - "io" - - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/mr-tron/base58" - "golang.org/x/crypto/blake2b" - "google.golang.org/protobuf/proto" -) - -// HmacSha256WithPrk will HmacSha256 using the provided prk. See HmacSha256 for -// options supported. -func HmacSha256WithPrk(ctx context.Context, data, prk []byte, opt ...wrapping.Option) (string, error) { - opt = append(opt, WithPrk(prk)) - return HmacSha256(ctx, data, nil, opt...) -} - -// HmacSha256 the provided data. Supports WithSalt, WithInfo, WithPrefix, -// WithEd25519, WithPrk, WithMarshaledSigInfo, WithBase64Encoding, -// WithBase58Encoding options. WithEd25519 is a "legacy" way to complete this -// operation and should not be used in new operations unless backward -// compatibility is needed. The WithPrefix option will prepend the prefix to the -// hmac-sha256 value. -func HmacSha256(ctx context.Context, data []byte, cipher wrapping.Wrapper, opt ...wrapping.Option) (string, error) { - const op = "crypto.HmacSha256" - opts, err := getOpts(opt...) - if err != nil { - return "", fmt.Errorf("%s: unable to get options: %w", op, err) - } - if data == nil { - return "", fmt.Errorf("%s: missing data: %w", op, wrapping.ErrInvalidParameter) - } - if cipher == nil && opts.withPrk == nil { - return "", fmt.Errorf("%s: you must specify either a wrapper or prk: %w", op, wrapping.ErrInvalidParameter) - } - if cipher != nil && opts.withPrk != nil { - return "", fmt.Errorf("%s: you cannot specify both a wrapper or prk: %w", op, wrapping.ErrInvalidParameter) - } - if opts.withEd25519 && opts.withPrk != nil { - return "", fmt.Errorf("%s: you cannot specify both ed25519 and a prk: %w", op, wrapping.ErrInvalidParameter) - } - if opts.withBase58Encoding && opts.withBase64Encoding { - return "", fmt.Errorf("%s: you cannot specify both WithBase58Encoding and WithBase64Encoding: %w", op, wrapping.ErrInvalidParameter) - } - var key [32]byte - switch { - case opts.withPrk != nil: - key = blake2b.Sum256(opts.withPrk) - - case opts.withEd25519: - reader, err := NewDerivedReader(ctx, cipher, 32, opt...) - if err != nil { - return "", fmt.Errorf("%s: %w", op, err) - } - edKey, _, err := ed25519.GenerateKey(reader) - if err != nil { - return "", fmt.Errorf("%s: unable to generate derived key: %w", op, wrapping.ErrInvalidParameter) - } - n := copy(key[:], edKey) - if n != 32 { - return "", fmt.Errorf("%s: expected to copy 32 bytes and got: %d", op, n) - } - - default: - reader, err := NewDerivedReader(ctx, cipher, 32, opt...) - if err != nil { - return "", fmt.Errorf("%s: %w", op, err) - } - readerKey := make([]byte, 32) - n, err := io.ReadFull(reader, readerKey) - if err != nil { - return "", fmt.Errorf("%s: %w", op, err) - } - if n != 32 { - return "", fmt.Errorf("%s: expected to read 32 bytes and got: %d", op, n) - } - key = blake2b.Sum256(readerKey) - } - mac := hmac.New(sha256.New, key[:]) - _, _ = mac.Write(data) - hmac := mac.Sum(nil) - - if opts.withMarshaledSigInfo { - keyId, err := cipher.KeyId(ctx) - if err != nil { - return "", fmt.Errorf("%s: error retrieving key id: %w", op, err) - } - si := &wrapping.SigInfo{ - Signature: hmac, - KeyInfo: &wrapping.KeyInfo{ - KeyId: keyId, - KeyPurposes: []wrapping.KeyPurpose{wrapping.KeyPurpose_Sign}, - }, - HmacType: wrapping.HmacType_Sha256.Enum(), - } - enc, err := proto.Marshal(si) - if err != nil { - return "", fmt.Errorf("%s: error encoding as sig info: %w", op, err) - } - hmac = enc - } - - var hmacString string - switch { - case opts.withBase64Encoding: - hmacString = base64.RawURLEncoding.EncodeToString(hmac) - case opts.withBase58Encoding: - hmacString = base58.Encode(hmac) - default: - hmacString = string(hmac) - } - if opts.withPrefix != "" { - return opts.withPrefix + hmacString, nil - } - return hmacString, nil -} diff --git a/extras/crypto/hmac_sha256_test.go b/extras/crypto/hmac_sha256_test.go deleted file mode 100644 index 527784ea..00000000 --- a/extras/crypto/hmac_sha256_test.go +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package crypto_test - -import ( - "context" - "testing" - - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/extras/crypto" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_HmacSha256(t *testing.T) { - testCtx := context.Background() - testWrapper := wrapping.NewTestWrapper([]byte("secret")) - tests := []struct { - name string - data []byte - wrapper wrapping.Wrapper - opts []wrapping.Option - wantHmac string - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "bad-wrapper", - data: []byte("test"), - wrapper: &wrapping.TestWrapper{}, - wantErr: true, - wantErrIs: wrapping.ErrInvalidParameter, - wantErrContains: "missing bytes", - }, - { - name: "bad-wrapper-with-ed25519", - data: []byte("test"), - wrapper: &wrapping.TestWrapper{}, - opts: []wrapping.Option{crypto.WithEd25519()}, - wantErr: true, - wantErrIs: wrapping.ErrInvalidParameter, - wantErrContains: "missing bytes", - }, - { - name: "missing data", - wrapper: testWrapper, - wantErr: true, - wantErrIs: wrapping.ErrInvalidParameter, - wantErrContains: "missing data", - }, - { - name: "missing wrapper", - data: []byte("test"), - wantErr: true, - wantErrIs: wrapping.ErrInvalidParameter, - wantErrContains: "you must specify either a wrapper or prk", - }, - { - name: "prk-and-ed25519", - data: []byte("test"), - wrapper: nil, - opts: []wrapping.Option{crypto.WithPrk([]byte("prk")), crypto.WithEd25519()}, - wantErr: true, - wantErrIs: wrapping.ErrInvalidParameter, - wantErrContains: "you cannot specify both ed25519 and a prk", - }, - { - name: "prk-and-wrapper", - data: []byte("test"), - wrapper: testWrapper, - opts: []wrapping.Option{crypto.WithPrk([]byte("prk")), crypto.WithEd25519()}, - wantErr: true, - wantErrIs: wrapping.ErrInvalidParameter, - wantErrContains: "you cannot specify both a wrapper or prk", - }, - { - name: "base58-and-base64", - data: []byte("test"), - wrapper: testWrapper, - opts: []wrapping.Option{crypto.WithBase58Encoding(), crypto.WithBase64Encoding()}, - wantErr: true, - wantErrIs: wrapping.ErrInvalidParameter, - wantErrContains: "you cannot specify both WithBase58Encoding and WithBase64Encoding", - }, - { - name: "blake2b-with-prefix", - data: []byte("test"), - wrapper: testWrapper, - opts: []wrapping.Option{crypto.WithPrefix("prefix:")}, - wantHmac: crypto.TestWithBlake2b(t, []byte("test"), testWrapper, nil, nil, crypto.WithPrefix("prefix:")), - }, - { - name: "blake2b-with-prefix-with-base64", - data: []byte("test"), - wrapper: testWrapper, - opts: []wrapping.Option{crypto.WithPrefix("prefix:"), crypto.WithBase64Encoding()}, - wantHmac: crypto.TestWithBlake2b(t, []byte("test"), testWrapper, nil, nil, crypto.WithPrefix("prefix:"), crypto.WithBase64Encoding()), - }, - { - name: "blake2b-with-prefix-with-base58", - data: []byte("test"), - wrapper: testWrapper, - opts: []wrapping.Option{crypto.WithPrefix("prefix:"), crypto.WithBase58Encoding()}, - wantHmac: crypto.TestWithBlake2b(t, []byte("test"), testWrapper, nil, nil, crypto.WithPrefix("prefix:"), crypto.WithBase58Encoding()), - }, - { - name: "with-prk", - data: []byte("test"), - opts: []wrapping.Option{crypto.WithPrk([]byte("prk-0123456789012345678901234567890"))}, - wantHmac: crypto.TestWithBlake2b(t, []byte("test"), testWrapper, nil, nil, crypto.WithPrk([]byte("prk-0123456789012345678901234567890"))), - }, - { - name: "withEd25519", - data: []byte("test"), - wrapper: testWrapper, - opts: []wrapping.Option{crypto.WithEd25519()}, - wantHmac: crypto.TestWithEd25519(t, []byte("test"), testWrapper, nil, nil), - }, - { - name: "blake2b-WithMarshaledSigInfo", - data: []byte("test"), - wrapper: testWrapper, - opts: []wrapping.Option{crypto.WithMarshaledSigInfo()}, - wantHmac: crypto.TestWithBlake2b(t, []byte("test"), testWrapper, nil, nil, crypto.WithMarshaledSigInfo()), - }, - { - name: "blake2b-WithMarshaledSigInfo-WithBase58Encoding", - data: []byte("test"), - wrapper: testWrapper, - opts: []wrapping.Option{crypto.WithMarshaledSigInfo(), crypto.WithBase58Encoding()}, - wantHmac: crypto.TestWithBlake2b(t, []byte("test"), testWrapper, nil, nil, crypto.WithMarshaledSigInfo(), crypto.WithBase58Encoding()), - }, - { - name: "blake2b-WithMarshaledSigInfo-WithBase64Encoding", - data: []byte("test"), - wrapper: testWrapper, - opts: []wrapping.Option{crypto.WithMarshaledSigInfo(), crypto.WithBase64Encoding()}, - wantHmac: crypto.TestWithBlake2b(t, []byte("test"), testWrapper, nil, nil, crypto.WithMarshaledSigInfo(), crypto.WithBase64Encoding()), - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - hm, err := crypto.HmacSha256(testCtx, tc.data, tc.wrapper, tc.opts...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.wantHmac, hm) - }) - } - - t.Run("HmacSha256WithPrk", func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - hm, err := crypto.HmacSha256WithPrk(testCtx, []byte("test"), []byte("prk-0123456789012345678901234567890")) - require.NoError(err) - want := crypto.TestWithBlake2b(t, []byte("test"), testWrapper, nil, nil, crypto.WithPrk([]byte("prk-0123456789012345678901234567890"))) - assert.Equal(want, hm) - }) -} diff --git a/extras/crypto/isnil.go b/extras/crypto/isnil.go deleted file mode 100644 index a5a06b13..00000000 --- a/extras/crypto/isnil.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package crypto - -import reflect "reflect" - -func isNil(i interface{}) bool { - if i == nil { - return true - } - switch reflect.TypeOf(i).Kind() { - case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice: - return reflect.ValueOf(i).IsNil() - } - return false -} diff --git a/extras/crypto/options.go b/extras/crypto/options.go deleted file mode 100644 index f1d19efd..00000000 --- a/extras/crypto/options.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package crypto - -import ( - "errors" - - wrapping "github.com/openbao/go-kms-wrapping/v2" -) - -// getOpts iterates the inbound Options and returns a struct -func getOpts(opt ...wrapping.Option) (*options, error) { - // First, separate out options into local and global - opts := getDefaultOptions() - for _, o := range opt { - if o == nil { - continue - } - iface := o() - switch to := iface.(type) { - case OptionFunc: - if err := to(&opts); err != nil { - return nil, err - } - default: - return nil, errors.New("option passed into util wrapping options handler" + - " that is not from this package; this is likely due to the wrapper being" + - " invoked as a plugin but options being sent from a specific wrapper package;" + - " use WithConfigMap to send options via the plugin interface") - } - - } - return &opts, nil -} - -// OptionFunc holds a function with local options -type OptionFunc func(*options) error - -// options = how options are represented -type options struct { - withPrefix string - withPrk []byte - withEd25519 bool - withBase64Encoding bool - withBase58Encoding bool - withMarshaledSigInfo bool - withSalt []byte - withInfo []byte - WithHexEncoding bool -} - -func getDefaultOptions() options { - return options{} -} - -// WithPrefix allows an optional prefix to be specified for the data returned -func WithPrefix(prefix string) wrapping.Option { - return func() interface{} { - return OptionFunc(func(o *options) error { - o.withPrefix = prefix - return nil - }) - } -} - -// WithPrk allows an optional PRK (pseudorandom key) to be specified for an -// operation. If you're using this option with HmacSha256, you might consider -// using HmacSha256WithPrk instead. -func WithPrk(prk []byte) wrapping.Option { - return func() interface{} { - return OptionFunc(func(o *options) error { - o.withPrk = prk - return nil - }) - } -} - -// WithEd25519 allows an optional request to use ed25519 during the operation -func WithEd25519() wrapping.Option { - return func() interface{} { - return OptionFunc(func(o *options) error { - o.withEd25519 = true - return nil - }) - } -} - -// WithBase64Encoding allows an optional request to base64 encode the data returned -func WithBase64Encoding() wrapping.Option { - return func() interface{} { - return OptionFunc(func(o *options) error { - o.withBase64Encoding = true - return nil - }) - } -} - -// WithBase58Encoding allows an optional request to base58 encode the data returned -func WithBase58Encoding() wrapping.Option { - return func() interface{} { - return OptionFunc(func(o *options) error { - o.withBase58Encoding = true - return nil - }) - } -} - -// WithMarshaledSigInfo allows an optional request to wrap the returned data into -// a marshaled wrapping.SigInfo protobuf -func WithMarshaledSigInfo() wrapping.Option { - return func() interface{} { - return OptionFunc(func(o *options) error { - o.withMarshaledSigInfo = true - return nil - }) - } -} - -// WithSalt allows optional salt to be specified for an operation. -func WithSalt(salt []byte) wrapping.Option { - return func() interface{} { - return OptionFunc(func(o *options) error { - o.withSalt = salt - return nil - }) - } -} - -// WithInfo allows optional info to be specified for an operation. -func WithInfo(info []byte) wrapping.Option { - return func() interface{} { - return OptionFunc(func(o *options) error { - o.withInfo = info - return nil - }) - } -} - -// WithHexEncoding allows an optional request to use hex encoding. -func WithHexEncoding(withHexEncoding bool) wrapping.Option { - return func() interface{} { - return OptionFunc(func(o *options) error { - o.WithHexEncoding = withHexEncoding - return nil - }) - } -} diff --git a/extras/crypto/sha256sum.go b/extras/crypto/sha256sum.go deleted file mode 100644 index 13f5b137..00000000 --- a/extras/crypto/sha256sum.go +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package crypto - -import ( - "context" - "crypto/sha256" - "encoding/hex" - "fmt" - "hash" - "io" - "sync" - - wrapping "github.com/openbao/go-kms-wrapping/v2" -) - -// Sha256Sum computes SHA256 message digest. Options supported: WithHexEncoding -// (which is compatible/comparable with GNU sha256sum's output) -func Sha256Sum(ctx context.Context, r io.Reader, opt ...wrapping.Option) ([]byte, error) { - const op = "crypto.Sha256Sum" - switch { - case isNil(r): - return nil, fmt.Errorf("%s: missing reader: %w", op, wrapping.ErrInvalidParameter) - } - - hasher := sha256.New() - - if _, err := io.Copy(hasher, r); err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - - hash := hasher.Sum(nil) - opts, err := getOpts(opt...) - if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - if opts.WithHexEncoding { - encodedHex := hex.EncodeToString(hash[:]) - return []byte(encodedHex), nil - } - return hash, nil -} - -// Sha256SumWriter provides multi-writer which will be used to write to a -// hash and produce a sum. It implements io.WriterCloser and io.StringWriter. -type Sha256SumWriter struct { - l sync.Mutex - hash hash.Hash - tee io.Writer - w io.Writer -} - -// NewSha256SumWriter creates a new Sha256SumWriter -func NewSha256SumWriter(ctx context.Context, w io.Writer) (*Sha256SumWriter, error) { - const op = "crypto.NewSha256SumWriter" - switch { - case isNil(w): - return nil, fmt.Errorf("%s: missing writer: %w", op, wrapping.ErrInvalidParameter) - } - h := sha256.New() - tee := io.MultiWriter(w, h) - return &Sha256SumWriter{ - hash: h, - tee: tee, - w: w, - }, nil -} - -// Write will write the bytes to the hash. Implements the required io.Writer -// func. -func (w *Sha256SumWriter) Write(b []byte) (int, error) { - const op = "crypto.(Sha256SumWriter).Write" - w.l.Lock() - defer w.l.Unlock() - n, err := w.tee.Write(b) - if err != nil { - return n, fmt.Errorf("%s: %w", op, err) - } - return n, nil -} - -// WriteString will write the string to hash. -func (w *Sha256SumWriter) WriteString(s string) (int, error) { - const op = "crypto.(Sha256SumWriter).WriteString" - n, err := w.Write([]byte(s)) - if err != nil { - return n, fmt.Errorf("%s: %w", op, err) - } - return n, nil -} - -// Close checks to see if the Sha256SumWriter implements the optional io.Closer -// and if so, then Close() is called; otherwise this is a noop -func (w *Sha256SumWriter) Close() error { - const op = "crypto.(Sha256SumWriter).Close" - var i interface{} = w.w - if v, ok := i.(io.Closer); ok { - if err := v.Close(); err != nil { - return fmt.Errorf("%s: %w", op, err) - } - } - return nil -} - -// Sum will sum the hash. Options supported: WithHexEncoding -func (w *Sha256SumWriter) Sum(_ context.Context, opt ...wrapping.Option) ([]byte, error) { - const op = "crypto.(Sha256SumWriter).Sum" - opts, err := getOpts(opt...) - if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - w.l.Lock() - defer w.l.Unlock() - h := w.hash.Sum(nil) - switch { - case opts.WithHexEncoding: - encodedHex := hex.EncodeToString(h[:]) - return []byte(encodedHex), nil - default: - return h, nil - } -} - -// Sha256SumReader provides an io.Reader which can be used to calculate a sum -// while reading a file. It implements io.ReaderCloser. -type Sha256SumReader struct { - l sync.Mutex - hash hash.Hash - tee io.Reader - r io.Reader -} - -// NewSha256SumReader creates a new Sha256Reader. -func NewSha256SumReader(_ context.Context, r io.Reader) (*Sha256SumReader, error) { - const op = "crypto.NewSha256SumReader" - switch { - case isNil(r): - return nil, fmt.Errorf("%s: missing reader: %w", op, wrapping.ErrInvalidParameter) - } - h := sha256.New() - tee := io.TeeReader(r, h) - return &Sha256SumReader{ - hash: h, - tee: tee, - r: r, - }, nil -} - -func (r *Sha256SumReader) Read(b []byte) (int, error) { - const op = "crypto.(Sha256SumReader).Read" - r.l.Lock() - defer r.l.Unlock() - n, err := r.tee.Read(b) - if err != nil { - return n, fmt.Errorf("%s: %w", op, err) - } - return n, nil -} - -// Close checks to see if the Sha256SumReader's io.Reader implements the -// optional io.Closer and if so, then Close() is called; otherwise this is a -// noop -func (r *Sha256SumReader) Close() error { - const op = "crypto.(Sha256SumReader).Close" - var i interface{} = r.r - if v, ok := i.(io.Closer); ok { - if err := v.Close(); err != nil { - return fmt.Errorf("%s: %w", op, err) - } - } - return nil -} - -// Sum will sum the hash. Options supported: WithHexEncoding -func (r *Sha256SumReader) Sum(_ context.Context, opt ...wrapping.Option) ([]byte, error) { - const op = "crypto.(Sha256SumReader).Sum" - opts, err := getOpts(opt...) - if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - r.l.Lock() - defer r.l.Unlock() - h := r.hash.Sum(nil) - switch { - case opts.WithHexEncoding: - encodedHex := hex.EncodeToString(h[:]) - return []byte(encodedHex), nil - default: - return h, nil - } -} diff --git a/extras/crypto/sha256sum_test.go b/extras/crypto/sha256sum_test.go deleted file mode 100644 index f361def3..00000000 --- a/extras/crypto/sha256sum_test.go +++ /dev/null @@ -1,351 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package crypto_test - -import ( - "bytes" - "context" - "crypto/sha256" - "encoding/hex" - "io" - "io/ioutil" - "os" - "strings" - "testing" - - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/extras/crypto" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestSha256Sum(t *testing.T) { - t.Parallel() - testCtx := context.Background() - - // testSum created via: echo -n "test-string" | sha256sum - const ( - testSum = "ffe65f1d98fafedea3514adc956c8ada5980c6c5d2552fd61f48401aefd5c00e" - testString = "test-string" - ) - - tests := []struct { - name string - r io.Reader - opt []wrapping.Option - wantSum []byte - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "string", - r: strings.NewReader(testString), - wantSum: func() []byte { - hasher := sha256.New() - _, err := hasher.Write([]byte(testString)) - require.NoError(t, err) - return hasher.Sum(nil) - }(), - }, - { - name: "string", - r: strings.NewReader(testString), - opt: []wrapping.Option{crypto.WithHexEncoding(true)}, - wantSum: []byte(testSum), - }, - { - name: "file", - r: func() io.Reader { - f, err := ioutil.TempFile(t.TempDir(), "tmp") - require.NoError(t, err) - - l, err := f.WriteString(testString) - require.NoError(t, err) - require.Equal(t, l, len(testString)) - - f.Close() - - f, err = os.Open(f.Name()) - require.NoError(t, err) - return f - }(), - opt: []wrapping.Option{crypto.WithHexEncoding(true)}, - wantSum: []byte(testSum), - }, - { - name: "missing-reader", - wantErr: true, - wantErrIs: wrapping.ErrInvalidParameter, - wantErrContains: "missing reader", - }, - { - name: "closed-reader", - r: func() io.Reader { - f, err := ioutil.TempFile(t.TempDir(), "tmp") - require.NoError(t, err) - f.Close() - return f - }(), - wantErr: true, - wantErrIs: os.ErrClosed, - wantErrContains: "file already closed", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - sum, err := crypto.Sha256Sum(testCtx, tc.r, tc.opt...) - if tc.wantErr { - require.Error(err) - assert.Empty(sum) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.wantSum, sum) - }) - } -} - -func TestSha256SumWriter_Sum(t *testing.T) { - t.Parallel() - testCtx := context.Background() - testBytes := []byte("test-bytes") - tests := []struct { - name string - data []byte - sumWriter *crypto.Sha256SumWriter - opt []wrapping.Option - wantSum []byte - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "success", - data: testBytes, - sumWriter: func() *crypto.Sha256SumWriter { - var b strings.Builder - w, err := crypto.NewSha256SumWriter(testCtx, &b) - require.NoError(t, err) - return w - }(), - wantSum: func() []byte { - hasher := sha256.New() - _, err := hasher.Write(testBytes) - require.NoError(t, err) - _, err = hasher.Write(testBytes) - require.NoError(t, err) - return hasher.Sum(nil) - }(), - }, - { - name: "success-with-hex-encoding", - data: testBytes, - sumWriter: func() *crypto.Sha256SumWriter { - var b strings.Builder - w, err := crypto.NewSha256SumWriter(testCtx, &b) - require.NoError(t, err) - return w - }(), - opt: []wrapping.Option{crypto.WithHexEncoding(true)}, - wantSum: func() []byte { - hasher := sha256.New() - _, err := hasher.Write(testBytes) - require.NoError(t, err) - _, err = hasher.Write(testBytes) - require.NoError(t, err) - h := hasher.Sum(nil) - return []byte(hex.EncodeToString(h[:])) - }(), - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - _, err := tc.sumWriter.Write(tc.data) - require.NoError(err) - _, err = tc.sumWriter.WriteString(string(tc.data)) - require.NoError(err) - sum, err := tc.sumWriter.Sum(testCtx, tc.opt...) - if tc.wantErr { - require.Error(err) - assert.Empty(sum) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.wantSum, sum) - require.NoError(tc.sumWriter.Close()) - }) - } - - t.Run("success-with-closer", func(t *testing.T) { - c := writeCloser{ - b: strings.Builder{}, - closed: false, - } - w, err := crypto.NewSha256SumWriter(testCtx, &c) - require.NoError(t, err) - - hasher := sha256.New() - _, err = hasher.Write(testBytes) - require.NoError(t, err) - _, err = hasher.Write(testBytes) - require.NoError(t, err) - wantSum := hasher.Sum(nil) - - assert, require := assert.New(t), require.New(t) - _, err = w.Write(testBytes) - require.NoError(err) - _, err = w.WriteString(string(testBytes)) - require.NoError(err) - sum, err := w.Sum(testCtx) - require.NoError(err) - assert.Equal(wantSum, sum) - require.NoError(w.Close()) - - require.True(c.closed) - }) -} - -type writeCloser struct { - b strings.Builder - closed bool -} - -func (w *writeCloser) Write(b []byte) (int, error) { - return w.b.Write(b) -} - -func (w *writeCloser) Close() error { - w.closed = true - return nil -} - -func TestSha256SumReader_Sum(t *testing.T) { - t.Parallel() - testCtx := context.Background() - testBytes := []byte("test-bytes") - tests := []struct { - name string - data []byte - sumReader *crypto.Sha256SumReader - opt []wrapping.Option - wantSum []byte - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "success", - data: testBytes, - sumReader: func() *crypto.Sha256SumReader { - b := bytes.NewBuffer(testBytes) - w, err := crypto.NewSha256SumReader(testCtx, b) - require.NoError(t, err) - return w - }(), - wantSum: func() []byte { - hasher := sha256.New() - _, err := hasher.Write(testBytes) - require.NoError(t, err) - return hasher.Sum(nil) - }(), - }, - { - name: "success-with-hex-encoding", - data: testBytes, - sumReader: func() *crypto.Sha256SumReader { - b := bytes.NewBuffer(testBytes) - w, err := crypto.NewSha256SumReader(testCtx, b) - require.NoError(t, err) - return w - }(), - opt: []wrapping.Option{crypto.WithHexEncoding(true)}, - wantSum: func() []byte { - hasher := sha256.New() - _, err := hasher.Write(testBytes) - require.NoError(t, err) - h := hasher.Sum(nil) - return []byte(hex.EncodeToString(h[:])) - }(), - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - buf := make([]byte, len(tc.data)) - _, err := tc.sumReader.Read(buf) - require.NoError(err) - require.Equal(buf, tc.data) - sum, err := tc.sumReader.Sum(testCtx, tc.opt...) - if tc.wantErr { - require.Error(err) - assert.Empty(sum) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.wantSum, sum) - require.NoError(tc.sumReader.Close()) - }) - } - - t.Run("success-with-closer", func(t *testing.T) { - c := readCloser{ - b: bytes.NewBuffer(testBytes), - closed: false, - } - w, err := crypto.NewSha256SumReader(testCtx, &c) - require.NoError(t, err) - - hasher := sha256.New() - _, err = hasher.Write(testBytes) - require.NoError(t, err) - wantSum := hasher.Sum(nil) - - assert, require := assert.New(t), require.New(t) - buf := make([]byte, len(testBytes)) - _, err = w.Read(buf) - require.NoError(err) - require.Equal(buf, testBytes) - sum, err := w.Sum(testCtx) - require.NoError(err) - assert.Equal(wantSum, sum) - require.NoError(w.Close()) - - require.True(c.closed) - }) -} - -type readCloser struct { - b *bytes.Buffer - closed bool -} - -func (w *readCloser) Read(b []byte) (int, error) { - return w.b.Read(b) -} - -func (w *readCloser) Close() error { - w.closed = true - return nil -} diff --git a/extras/crypto/testing.go b/extras/crypto/testing.go deleted file mode 100644 index 389e790f..00000000 --- a/extras/crypto/testing.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package crypto - -import ( - "context" - "crypto/ed25519" - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "io" - "testing" - - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/mr-tron/base58" - "github.com/stretchr/testify/require" - "golang.org/x/crypto/blake2b" - "google.golang.org/protobuf/proto" -) - -// TestWithEd25519 produces test hmac sha256 using a derived Ed25519 key -func TestWithEd25519(t *testing.T, data []byte, w wrapping.Wrapper, opt ...wrapping.Option) string { - t.Helper() - require := require.New(t) - reader, err := NewDerivedReader(context.Background(), w, 32, opt...) - require.NoError(err) - edKey, _, err := ed25519.GenerateKey(reader) - require.NoError(err) - var key [32]byte - n := copy(key[:], edKey) - require.Equal(n, 32) - return TestHmacSha256(t, key[:], data, opt...) -} - -// TestWithBlake2b produces a test hmac sha256 using derived blake2b. Supported -// options: WithPrk, WithBase64Encoding, WithBase58Encoding, -// WithMarshaledSigInfo -func TestWithBlake2b(t *testing.T, data []byte, w wrapping.Wrapper, opt ...wrapping.Option) string { - t.Helper() - require := require.New(t) - require.NotNil(data) - require.NotNil(w) - opts, err := getOpts(opt...) - require.NoError(err) - var key [32]byte - switch { - case opts.withPrk != nil: - key = blake2b.Sum256(opts.withPrk) - default: - reader, err := NewDerivedReader(context.Background(), w, 32, opt...) - require.NoError(err) - readerKey := make([]byte, 32) - n, err := io.ReadFull(reader, readerKey) - require.NoError(err) - require.Equal(n, 32) - key = blake2b.Sum256(readerKey) - } - - switch { - case opts.withMarshaledSigInfo: - passedOpts := []wrapping.Option{} - if opts.withPrefix != "" { - passedOpts = append(passedOpts, WithPrefix(opts.withPrefix)) - } - hmac := TestHmacSha256(t, key[:], data, passedOpts...) - keyId, err := w.KeyId(context.Background()) - require.NoError(err) - si := &wrapping.SigInfo{ - Signature: []byte(hmac), - KeyInfo: &wrapping.KeyInfo{ - KeyId: keyId, - KeyPurposes: []wrapping.KeyPurpose{wrapping.KeyPurpose_Sign}, - }, - HmacType: wrapping.HmacType_Sha256.Enum(), - } - enc, err := proto.Marshal(si) - require.NoError(err) - switch { - case opts.withBase64Encoding: - return base64.RawURLEncoding.EncodeToString(enc) - case opts.withBase58Encoding: - return base58.Encode(enc) - default: - return string(enc) - } - default: - return TestHmacSha256(t, key[:], data, opt...) - } -} - -// TestHmacSha256 produces a test hmac sha256. Supported options: -// WithBase64Encoding, WithBase58Encoding, WithPrefix -func TestHmacSha256(t *testing.T, key, data []byte, opt ...wrapping.Option) string { - t.Helper() - require := require.New(t) - mac := hmac.New(sha256.New, key) - _, _ = mac.Write(data) - hmac := mac.Sum(nil) - var hmacString string - opts, err := getOpts(opt...) - require.NoError(err) - switch { - case opts.withBase64Encoding: - hmacString = base64.RawURLEncoding.EncodeToString(hmac) - case opts.withBase58Encoding: - hmacString = base58.Encode(hmac) - default: - hmacString = string(hmac) - } - if opts.withPrefix != "" { - return opts.withPrefix + hmacString - } - return hmacString -} diff --git a/extras/kms/.gitignore b/extras/kms/.gitignore deleted file mode 100644 index 933d6b7d..00000000 --- a/extras/kms/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.vscode -.idea -cp.out diff --git a/extras/kms/CHANGELOG.md b/extras/kms/CHANGELOG.md deleted file mode 100644 index 50f5205f..00000000 --- a/extras/kms/CHANGELOG.md +++ /dev/null @@ -1,33 +0,0 @@ -# kms package CHANGELOG - -Canonical reference for changes, improvements, and bugfixes for the kms package. - -## Bug fixes -* Explicitly naming FK and unique constraints so they can be referenced by name - in the future if any changes are required. Add the `kms` prefix to the - `update_time_column()` function. - ([PR](https://github.com/hashicorp/go-kms-wrapping/pull/88)). - - The decision was made to make these changes by modifying existing migrations, - so if you've already installed this package, you'll need to review the PR and - make the changes by hand in a new migration. - -## Breaking changes -* Publicly expose key versions. - The `Key` type now contains a `Versions` slice which holds the versions of the key. - A version of a key is what holds the key material. When a key is rotated, a new - version is created, and the old version (now deactivated) is only used for - decrypting existing data encrypted with it. The new version is used to encrypt - new data. A deactivated version can be revoked (destroyed), but care should be - taken to ensure that no existing data is encrypted with the version before doing so. - Using the foreign key relationships recommended in the [README](./README.md) will - prevent this from happening. - The `KeyVersion` type holds metadata about the key version. - -## Enhancements -* `WithKeyId` has been deprecated in favor of the new `WithKeyVersionId`. -* `RevokeKey` has been deprecated in favor of the new `RevokeKeyVersion`. -* Added `ListDataKeyReferencers` to allow listing the names of tables referencing the - data key version table's private_id column. This can be useful when finding what - data needs to be re-encrypted before destroying a key version. - diff --git a/extras/kms/Makefile b/extras/kms/Makefile deleted file mode 100644 index 1861be55..00000000 --- a/extras/kms/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -.PHONY: tools -tools: - go generate -tags tools tools/tools.go - -.PHONY: fmt -fmt: - gofumpt -w $$(find . -name '*.go') - -.PHONY: test -test: - go test -race -count=1 ./... - -.PHONY: test-all -test-all: test-sqlite test-postgres - -.PHONY: test-sqlite -test-sqlite: - DB_DIALECT=sqlite go test -race -count=1 ./... - -.PHONY: test-postgres -test-postgres: - ############################################################## - # this test is dependent on first running: docker-compose up - ############################################################## - DB_DIALECT=postgres DB_DSN="postgresql://go_db:go_db@localhost:9920/go_db?sslmode=disable" go test -race -count=1 ./... diff --git a/extras/kms/README.md b/extras/kms/README.md deleted file mode 100644 index 3b150b5d..00000000 --- a/extras/kms/README.md +++ /dev/null @@ -1,101 +0,0 @@ -# kms package - -[![Go Reference](https://pkg.go.dev/badge/github.com/hashicorp/go-kms-wrapping/extras/kms/kms.svg)](https://pkg.go.dev/github.com/hashicorp/go-kms-wrapping/extras/kms) - -kms is a package that provides key management system features for -go-kms-wrapping `Wrappers`. - -The following domain terms are key to understanding the system and how to use -it: - -- `wrapper`: all keys within the system are a `Wrapper` from go-kms-wrapping. - -- `root external wrapper`: the external wrapper that will serve as the root of - trust for the kms system. Typically you'd get this root wrapper via - go-kms-wrapper from a KMS provider. See the go-kms-wrapper docs for further - details. - -- `scope`: a scope defines a rotational boundary for a set of keys. The system - tracks only the scope identifier and which is used to find keys with a - specific scope. - - **IMPORTANT**: You should define a FK from `kms_root_key.scope_id` with - cascading deletes and updates to the PK of the table within your domain that - contains scopes. This FK will prevent orphaned kms keys. - - For example, you could assign organizations and projects - scope IDs and then associate a set of keys with each org and project within - your domain. - -- `root key`: the KEKs (key-encryption-key) of the system. - -- `data key`: the DEKs (data-encryption-key) of the system and must have a - parent root key and a purpose. - -- `data key version`: versions of DEKs (data-encryption-key) which are used to - encrypt/decrypt data. - - **IMPORTANT**: You should define a FK with a restricted delete from any - application table that stores encrypted data to - `kms_data_key_version(private_id)`. This FK will prevent kms keys from being - deleted that are currently being used to encrypt/decrypt data. - - For example, you have a table named `oidc` which contains the app's encrypted - oidc client_secret. The `oidc` table should have a `key_version_id` column with a - restricted FK to `kms_data_key_version(private_id)` which prevents in use DEKs - from being deleted. - -- `purpose`: Each data key (DEK) must have a one purpose. For - example, you could define a purpose of `client-secrets` for a DEK that will be - used encrypt/decrypt wrapper operations on `client-secrets` - - -
- -### Database Schema - -You'll find the database schema within the migrations directory. -Currently postgres and sqlite are supported. The implementation does make some -use of triggers to ensure some of its data integrity. - -The migrations are intended to be incorporated into your existing go-migrate -migrations. Feel free to change the migration file names, as long as they are -applied in the same order as defined here. FYI, the migrations include -`kms_version` table which is used to ensure that the schema and module are -compatible. - -``` -High-level ERD - - - ┌───────────────────────────────┐ - │ ○ - ┼ ┼ -┌────────────────────────┐ ┌────────────────────────┐ -│ kms_root_key │ │ kms_data_key │ -├────────────────────────┤ ├────────────────────────┤ -│private_id │ │private_id │ -│scope_id │ │root_key_id │ -│ │ │purpose │ -└────────────────────────┘ │ │ - ┼ └────────────────────────┘ - │ ┼ - │ │ - │ │ - │ │ - ┼ ┼ - ╱│╲ ╱│╲ -┌────────────────────────┐ ┌────────────────────────┐ -│ kms_root_key_version │ │ kms_data_key_version │ -├────────────────────────┤ ├────────────────────────┤ -│private_id │ │private_id │ -│root_key_id │ │data_key_id │ -│key │ │root_key_id │ -│version │ │key │ -│ │ │version │ -└────────────────────────┘ └────────────────────────┘ - ┼ ┼ - │ ○ - └───────────────────────────────┘ - -``` diff --git a/extras/kms/data_key.go b/extras/kms/data_key.go deleted file mode 100644 index 23508801..00000000 --- a/extras/kms/data_key.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "fmt" - "time" - - "github.com/hashicorp/go-dbw" -) - -// dataKey represents the DEKs (keys to encrypt data) of the system and must -// have a parent root key and a purpose. -type dataKey struct { - // PrivateId is used to access the key - PrivateId string `json:"private_id,omitempty" gorm:"primary_key"` - // RootKeyId for the key - RootKeyId string `json:"root_key_id,omitempty" gorm:"default:null"` - // Purpose of the the key - Purpose KeyPurpose `json:"purpose,omitempty" gorm:"default:null"` - // CreateTime from the RDBMS - CreateTime time.Time `json:"create_time,omitempty" gorm:"default:current_timestamp"` - - // tableNamePrefix defines the prefix to use before the table name and - // allows us to support custom prefixes as well as multi KMSs within a - // single schema. - tableNamePrefix string `gorm:"-"` -} - -// newDataKey creates a new in memory data key. This key is used for wrapper -// operations. No options are currently supported. -func newDataKey(rootKeyId string, purpose KeyPurpose, _ ...Option) (*dataKey, error) { - const op = "kms.newDataKey" - if rootKeyId == "" { - return nil, fmt.Errorf("%s: missing root key id: %w", op, ErrInvalidParameter) - } - switch purpose { - case KeyPurposeUnknown: - return nil, fmt.Errorf("%s: missing purpose: %w", op, ErrInvalidParameter) - case KeyPurposeRootKey: - return nil, fmt.Errorf("%s: cannot be a purpose of %q: %w", op, purpose, ErrInvalidParameter) - } - c := &dataKey{ - RootKeyId: rootKeyId, - Purpose: purpose, - } - return c, nil -} - -// Clone creates a clone of the DataKey -func (k *dataKey) Clone() *dataKey { - return &dataKey{ - PrivateId: k.PrivateId, - RootKeyId: k.RootKeyId, - Purpose: k.Purpose, - CreateTime: k.CreateTime, - tableNamePrefix: k.tableNamePrefix, - } -} - -// VetForWrite validates the key before it's written. -func (k *dataKey) vetForWrite(ctx context.Context, opType dbw.OpType) error { - const op = "kms.(dataKey).vetForWrite" - if k.PrivateId == "" { - return fmt.Errorf("%s: missing private id: %w", op, ErrInvalidParameter) - } - switch opType { - case dbw.CreateOp: - if k.RootKeyId == "" { - return fmt.Errorf("%s: missing root key id: %w", op, ErrInvalidParameter) - } - switch k.Purpose { - case KeyPurposeUnknown: - return fmt.Errorf("%s: missing purpose: %w", op, ErrInvalidParameter) - case KeyPurposeRootKey: - return fmt.Errorf("%s: cannot be a purpose of %q: %w", op, k.Purpose, ErrInvalidParameter) - } - case dbw.UpdateOp: - return fmt.Errorf("%s: data key is immutable: %w", op, ErrInvalidParameter) - } - return nil -} - -// TableName returns the table name -func (k *dataKey) TableName() string { - const tableName = "data_key" - return fmt.Sprintf("%s_%s", k.tableNamePrefix, tableName) -} - -// GetPrivateId returns the key's private id -func (k *dataKey) GetPrivateId() string { return k.PrivateId } - -// GetRootKeyId returns the key's root key id -func (k *dataKey) GetRootKeyId() string { return k.RootKeyId } diff --git a/extras/kms/data_key_test.go b/extras/kms/data_key_test.go deleted file mode 100644 index c82bf9c0..00000000 --- a/extras/kms/data_key_test.go +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "fmt" - "testing" - - "github.com/hashicorp/go-dbw" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_NewDataKey(t *testing.T) { - t.Parallel() - tests := []struct { - name string - rootKeyId string - purpose KeyPurpose - want *dataKey - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-root-key-id", - purpose: "database", - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key id", - }, - { - name: "missing-purpose", - rootKeyId: "root-key-id", - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing purpose", - }, - { - name: "invalid-purpose", - rootKeyId: "root-key-id", - purpose: KeyPurposeRootKey, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: fmt.Sprintf("cannot be a purpose of %q", KeyPurposeRootKey), - }, - { - name: "valid", - rootKeyId: "root-key-id", - purpose: "database", - want: &dataKey{ - RootKeyId: "root-key-id", - Purpose: "database", - }, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - got, err := newDataKey(tc.rootKeyId, tc.purpose) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.want, got) - }) - } -} - -func TestDataKey_vetForWrite(t *testing.T) { - t.Parallel() - testCtx := context.Background() - tests := []struct { - name string - key *dataKey - opType dbw.OpType - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "create-missing-private-id", - key: &dataKey{ - RootKeyId: "root-key-id", - Purpose: "database", - }, - opType: dbw.CreateOp, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing private id", - }, - { - name: "create-missing-root-key", - key: &dataKey{ - PrivateId: "private-key-id", - Purpose: "database", - }, - opType: dbw.CreateOp, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key", - }, - { - name: "create-invalid-purpose", - key: &dataKey{ - PrivateId: "private-key-id", - RootKeyId: "root-key-id", - Purpose: KeyPurposeRootKey, - }, - opType: dbw.CreateOp, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: fmt.Sprintf("cannot be a purpose of %q", KeyPurposeRootKey), - }, - { - name: "create-missing-purpose", - key: &dataKey{ - PrivateId: "private-key-id", - RootKeyId: "root-key-id", - }, - opType: dbw.CreateOp, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing purpose", - }, - { - name: "invalid-update", - key: &dataKey{ - PrivateId: "private-key-id", - RootKeyId: "root-key-id", - Purpose: "database", - }, - opType: dbw.UpdateOp, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "data key is immutable", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - err := tc.key.vetForWrite(testCtx, tc.opType) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - }) - } -} - -func TestDataKey_GetRootKeyId(t *testing.T) { - t.Parallel() - k := &dataKey{ - RootKeyId: "root-key-id", - } - assert.Equal(t, "root-key-id", k.GetRootKeyId()) -} diff --git a/extras/kms/data_key_version.go b/extras/kms/data_key_version.go deleted file mode 100644 index 4e142bcb..00000000 --- a/extras/kms/data_key_version.go +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "fmt" - "time" - - "github.com/hashicorp/go-dbw" - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/extras/structwrapping" -) - -// dataKeyVersion represents a version of a DataKey -type dataKeyVersion struct { - // PrivateId is used to access the key version - PrivateId string `json:"private_id,omitempty" gorm:"primary_key"` - // DataKeyId for the key version - DataKeyId string `json:"data_key_id,omitempty" gorm:"default:null"` - // RootKeyVersionId of the version of the root key data. - RootKeyVersionId string `json:"root_key_version_id,omitempty" gorm:"default:null"` - // Key is the plain-text of the key data. We are NOT storing this plain-text key - // in the db. - Key []byte `json:"key,omitempty" gorm:"-" wrapping:"pt,key_data"` - // CtKey is the ciphertext key data stored in the database - CtKey []byte `json:"ct_key,omitempty" gorm:"column:key;not_null" wrapping:"ct,key_data"` - // Version of the key data. This is not used for optimistic locking, since - // key versions are immutable. It's just the version of the key. - Version uint32 `json:"version,omitempty" gorm:"default:null"` - // CreateTime from the RDBMS - CreateTime time.Time `json:"create_time,omitempty" gorm:"default:current_timestamp"` - - // tableNamePrefix defines the prefix to use before the table name and - // allows us to support custom prefixes as well as multi KMSs within a - // single schema. - tableNamePrefix string `gorm:"-"` -} - -// newDataKeyVersion creates a new in memory data key version. No options -// are currently supported. -func newDataKeyVersion(dataKeyId string, key []byte, rootKeyVersionId string, _ ...Option) (*dataKeyVersion, error) { - const op = "kms.newDataKeyVersion" - if dataKeyId == "" { - return nil, fmt.Errorf("%s: missing data key id: %w", op, ErrInvalidParameter) - } - if len(key) == 0 { - return nil, fmt.Errorf("%s: missing key: %w", op, ErrInvalidParameter) - } - if rootKeyVersionId == "" { - return nil, fmt.Errorf("%s: missing root key version id: %w", op, ErrInvalidParameter) - } - - k := &dataKeyVersion{ - DataKeyId: dataKeyId, - RootKeyVersionId: rootKeyVersionId, - Key: key, - } - return k, nil -} - -// Clone creates a clone of the DataKeyVersion -func (k *dataKeyVersion) Clone() *dataKeyVersion { - clone := &dataKeyVersion{ - PrivateId: k.PrivateId, - DataKeyId: k.DataKeyId, - RootKeyVersionId: k.RootKeyVersionId, - Version: k.Version, - CreateTime: k.CreateTime, - tableNamePrefix: k.tableNamePrefix, - } - clone.Key = make([]byte, len(k.Key)) - copy(clone.Key, k.Key) - - clone.CtKey = make([]byte, len(k.CtKey)) - copy(clone.CtKey, k.CtKey) - return clone -} - -// vetForWrite validates the data key version before it's written. -func (k *dataKeyVersion) vetForWrite(ctx context.Context, opType dbw.OpType) error { - const op = "kms.(dataKeyVersion).vetForWrite" - if k.PrivateId == "" { - return fmt.Errorf("%s: missing private id: %w", op, ErrInvalidParameter) - } - switch opType { - case dbw.CreateOp: - if k.CtKey == nil { - return fmt.Errorf("%s: missing key: %w", op, ErrInvalidParameter) - } - if k.DataKeyId == "" { - return fmt.Errorf("%s: missing data key id: %w", op, ErrInvalidParameter) - } - if k.RootKeyVersionId == "" { - return fmt.Errorf("%s: missing root key version id: %w", op, ErrInvalidParameter) - } - case dbw.UpdateOp: - return fmt.Errorf("%s: key is immutable: %w", op, ErrInvalidParameter) - } - return nil -} - -// TableName returns the table name -func (k *dataKeyVersion) TableName() string { - const tableName = "data_key_version" - return fmt.Sprintf("%s_%s", k.tableNamePrefix, tableName) -} - -// Encrypt will encrypt the data key version's key -func (k *dataKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error { - const op = "kms.(dataKeyVersion).Encrypt" - if cipher == nil { - return fmt.Errorf("%s: missing cipher: %w", op, ErrInvalidParameter) - } - if err := structwrapping.WrapStruct(ctx, cipher, k, nil); err != nil { - return fmt.Errorf("%s: %w", op, err) - } - return nil -} - -// Decrypt will decrypt the data key version's key -func (k *dataKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error { - const op = "kms.(dataKeyVersion).Decrypt" - if cipher == nil { - return fmt.Errorf("%s: missing cipher: %w", op, ErrInvalidParameter) - } - if err := structwrapping.UnwrapStruct(ctx, cipher, k, nil); err != nil { - return fmt.Errorf("%s: %w", op, err) - } - return nil -} - -// GetPrivateId returns the key's private id -func (k *dataKeyVersion) GetPrivateId() string { return k.PrivateId } - -// GetKey returns the key bytes -func (k *dataKeyVersion) GetKey() []byte { return k.Key } diff --git a/extras/kms/data_key_version_test.go b/extras/kms/data_key_version_test.go deleted file mode 100644 index 00aa6029..00000000 --- a/extras/kms/data_key_version_test.go +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "testing" - - "github.com/hashicorp/go-dbw" - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/aead" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_NewDataKeyVersion(t *testing.T) { - t.Parallel() - tests := []struct { - name string - dataKeyId string - rootKeyVersionId string - key []byte - want *dataKeyVersion - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-data-key-id", - rootKeyVersionId: "root-key-version-id", - key: []byte("key"), - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing data key id", - }, - { - name: "missing-root-key-version-id", - dataKeyId: "data-key-id", - key: []byte("key"), - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key version id", - }, - { - name: "missing-key", - dataKeyId: "data-key-id", - rootKeyVersionId: "root-key-version-id", - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key", - }, - { - name: "valid", - dataKeyId: "data-key-id", - rootKeyVersionId: "root-key-version-id", - key: []byte("key"), - want: &dataKeyVersion{ - DataKeyId: "data-key-id", - RootKeyVersionId: "root-key-version-id", - Key: []byte("key"), - }, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - got, err := newDataKeyVersion(tc.dataKeyId, tc.key, tc.rootKeyVersionId) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.want, got) - }) - } -} - -func TestDataKeyVersion_vetForWrite(t *testing.T) { - t.Parallel() - testCtx := context.Background() - tests := []struct { - name string - key *dataKeyVersion - opType dbw.OpType - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "create-missing-private-id", - key: &dataKeyVersion{ - DataKeyId: "data-key-id", - RootKeyVersionId: "root-key-version-id", - CtKey: []byte("key"), - }, - opType: dbw.CreateOp, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing private id", - }, - { - name: "create-missing-ct-key", - key: &dataKeyVersion{ - PrivateId: "private-id", - DataKeyId: "data-key-id", - RootKeyVersionId: "root-key-version-id", - }, - opType: dbw.CreateOp, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key", - }, - { - name: "create-missing-data-key-id", - key: &dataKeyVersion{ - PrivateId: "private-id", - CtKey: []byte("key"), - RootKeyVersionId: "root-key-version-id", - }, - opType: dbw.CreateOp, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing data key id", - }, - { - name: "create-missing-root-key-version-id", - key: &dataKeyVersion{ - PrivateId: "private-id", - CtKey: []byte("key"), - DataKeyId: "data-key-id", - }, - opType: dbw.CreateOp, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key version id", - }, - { - name: "update-immutable", - key: &dataKeyVersion{ - PrivateId: "private-id", - }, - opType: dbw.UpdateOp, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "key is immutable", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - err := tc.key.vetForWrite(testCtx, tc.opType) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - }) - } -} - -func TestDataKeyVersion_Encrypt(t *testing.T) { - t.Parallel() - const ( - testKey = "test-key" - ) - testCtx := context.Background() - testWrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - tests := []struct { - name string - key *dataKeyVersion - wrapper wrapping.Wrapper - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "bad-cipher", - key: &dataKeyVersion{ - Key: []byte(testKey), - }, - wrapper: aead.NewWrapper(), - wantErr: true, - wantErrContains: "error wrapping value", - }, - { - name: "missing-cipher", - key: &dataKeyVersion{ - Key: []byte(testKey), - }, - wantErr: true, - wantErrContains: "missing cipher", - }, - { - name: "success", - key: &dataKeyVersion{ - Key: []byte(testKey), - }, - wrapper: testWrapper, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - require.Empty(tc.key.CtKey) - require.NotEmpty(tc.key.Key) - err := tc.key.Encrypt(testCtx, tc.wrapper) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.NotEqual(tc.key.CtKey, tc.key.Key) - assert.NotEmpty(tc.key.CtKey) - tc.key.Key = nil - err = tc.key.Decrypt(testCtx, tc.wrapper) - require.NoError(err) - assert.Equal(testKey, string(tc.key.Key)) - }) - } -} - -func TestDataKeyVersion_Decrypt(t *testing.T) { - t.Parallel() - const ( - testKey = "test-key" - ) - testCtx := context.Background() - testWrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testDataKey := &dataKeyVersion{ - Key: []byte(testKey), - } - err := testDataKey.Encrypt(testCtx, testWrapper) - require.NoError(t, err) - tests := []struct { - name string - key *dataKeyVersion - wrapper wrapping.Wrapper - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "bad-cipher", - key: testDataKey, - wrapper: aead.NewWrapper(), - wantErr: true, - wantErrContains: "error unwrapping value", - }, - { - name: "missing-cipher", - key: testDataKey, - wantErr: true, - wantErrContains: "missing cipher", - }, - { - name: "success", - key: testDataKey, - wrapper: testWrapper, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - require.NotEmpty(tc.key.CtKey) - require.NotEmpty(tc.key.Key) - err := tc.key.Decrypt(testCtx, tc.wrapper) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(testKey, string(tc.key.Key)) - }) - } -} diff --git a/extras/kms/docker-compose.yml b/extras/kms/docker-compose.yml deleted file mode 100644 index 04f364ea..00000000 --- a/extras/kms/docker-compose.yml +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -# this is used to bring up a postgres database for when you want to run tests -# that depend on postgres. For example, when running: `make test-postgres` -version: '3' - -services: - postgres: - image: 'postgres:latest' - ports: - - 9920:5432 - environment: - - POSTGRES_DB=go_db - - POSTGRES_USER=go_db - - POSTGRES_PASSWORD=go_db diff --git a/extras/kms/docs.go b/extras/kms/docs.go deleted file mode 100644 index e2171389..00000000 --- a/extras/kms/docs.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -// kms is a package that provides key management system features for -// go-kms-wrapping Wrappers. -// -// The following domain terms are key to understanding the system and how to use -// it: -// -// - wrapper: all keys within the system are a Wrapper from go-kms-wrapping. -// -// - root external wrapper: the external wrapper that will serve as the root of -// trust for the kms system. Typically you'd get this root wrapper via -// go-kms-wrapper from a KMS provider. See the go-kms-wrapper docs for further -// details. -// -// - scope: a scope defines a rotational boundary for a set of keys. The system -// tracks only the scope identifier and which is used to find keys with a -// specific scope. -// -// **IMPORTANT**: You should define a FK from kms_root_key.scope_id with -// cascading deletes and updates to the PK of the table within your domain that -// tracks scopes. This FK will prevent orphaned kms keys. -// -// For example, you could assign organizations and projects -// scope IDs and then associate a set of keys with each org and project within -// your domain. -// -// - root key: the KEKs (keys to encrypt keys) of the system. -// -// - data key: the DEKs (keys to encrypt data) of the system and must have a -// parent root key and a purpose. -// -// - purpose: Each data key (DEK) must have a one purpose. For -// example, you could define a purpose of client-secrets for a DEK that will be -// used encrypt/decrypt wrapper operations on `client-secrets` -// -// # Database Schema -// -// You'll find the database schema within the migrations directory. -// Currently postgres and sqlite are supported. The implementation does make some -// use of triggers to ensure some of its data integrity. -// -// The migrations are intended to be incorporated into your existing go-migrate -// migrations. Feel free to change the migration file names, as long as they are -// applied in the same order as defined here. FYI, the migrations include -// `kms_version` table which is used to ensure that the schema and module are -// compatible. -// -// # High-level ERD -// -// ┌───────────────────────────────┐ -// │ ○ -// ┼ ┼ -// ┌────────────────────────┐ ┌────────────────────────┐ -// │ kms_root_key │ │ kms_data_key │ -// ├────────────────────────┤ ├────────────────────────┤ -// │private_id │ │private_id │ -// │scope_id │ │root_key_id │ -// │ │ │purpose │ -// └────────────────────────┘ │ │ -// ┼ └────────────────────────┘ -// │ ┼ -// │ │ -// │ │ -// │ │ -// ┼ ┼ -// ╱│╲ ╱│╲ -// ┌────────────────────────┐ ┌────────────────────────┐ -// │ kms_root_key_version │ │ kms_data_key_version │ -// ├────────────────────────┤ ├────────────────────────┤ -// │private_id │ │private_id │ -// │root_key_id │ │data_key_id │ -// │key │ │root_key_id │ -// │version │ │key │ -// │ │ │version │ -// └────────────────────────┘ └────────────────────────┘ -// ┼ ┼ -// │ ○ -// └───────────────────────────────┘ -package kms diff --git a/extras/kms/errors.go b/extras/kms/errors.go deleted file mode 100644 index e10e14dd..00000000 --- a/extras/kms/errors.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import "errors" - -var ( - // ErrInvalidVersion represents a runtime error when the database version - // doesn't match the require version of the module. - ErrInvalidVersion = errors.New("invalid version") - - // ErrInvalidParameter represents an invalid parameter error condition. - ErrInvalidParameter = errors.New("invalid parameter") - - // ErrMultipleRecords represents multiple records were affected when only - // one was expected - ErrMultipleRecords = errors.New("multiple records") - - // ErrRecordNotFound represents that no record was found - ErrRecordNotFound = errors.New("record not found") - - // ErrKeyNotFound represents that no key was found - ErrKeyNotFound = errors.New("key not found") - - // ErrInternal represents an internal error/issue - ErrInternal = errors.New("internal issue") -) diff --git a/extras/kms/examples/Dockerfile b/extras/kms/examples/Dockerfile deleted file mode 100644 index 146d029f..00000000 --- a/extras/kms/examples/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -FROM vault:1.10.0 -COPY run.sh ./ -ENTRYPOINT ["sh","./run.sh"] \ No newline at end of file diff --git a/extras/kms/examples/README.md b/extras/kms/examples/README.md deleted file mode 100644 index d79dc225..00000000 --- a/extras/kms/examples/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Examples - -A set of examples which demonstrate various aspects of the -`go-kms-wrapping extras/kms` package. - -### [`cli`](./cli) - -An example go-kms-wrapping extras/kms CLI that demonstrates how to incorporate a -Kms into a simple CLI application. diff --git a/extras/kms/examples/cli/.gitignore b/extras/kms/examples/cli/.gitignore deleted file mode 100644 index bed3124a..00000000 --- a/extras/kms/examples/cli/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ - -cli diff --git a/extras/kms/examples/cli/README.md b/extras/kms/examples/cli/README.md deleted file mode 100644 index 43be5b47..00000000 --- a/extras/kms/examples/cli/README.md +++ /dev/null @@ -1,139 +0,0 @@ -# cli -An example go-kms-wrapping extras/kms CLI that demonstrates how to incorporate a -Kms into an application. - -The application defines a `scope` table and the migrations for the CLI define a -FK between the `kms_root_key` and the `scope` table. - -Just a reminder, that a `scope` defines ownership for a set of kms DEKs (data -encryption keys). For example an application could choose to only have a global -scope or perhaps it could decide to have scopes for each organization and -project represented in application. It's completely up to the app to decide -what sort of model it wants to use for scopes, but the kms requires an app to -define at least one scope. - -Running the cli will: -- Initialize a root wrapper using either a vault transit wrapper or a - self-generated key wrapper -- Create a global scope with a database DEK -- Encrypt a plaintext secret using the global scope database DEK and store - that secret in an oidc entry in the database. -- Retrieve the oidc entry and decrypt the cipher text secret using the global scope database DEK. -- Validate that the decrypted secret matches the original secret. -- Delete the oidc entry. -- Before exiting, it will delete the global scope and all the DEKs associated - with it. NOTE: typically you won't do this delete, but it's included in the - example to demonstrate why it's important to declare a FK between your scope - table and the kms_root_key table in order to prevent orphan wrappers when your - app deletes an unneeded scope. - -Expected output from a successful execution: -``` -❯ ./cli --use-transit --plaintext "test secret" -using a vault transit root wrapper from: http://localhost:8200 -using the structwrapping pkg to wrap (encrypt) the new oidc record... -writing the oidc record to the db... -reading the oidc record from the db... -using the structwrapping pkg to unwrap (decrypt) the oidc record read from the db... -successfully encrypted/decrypted "test secret" using the kms -attempting to delete scope with its associated DEKs... -attempting to first delete the oidc record, then delete scope with its associated DEKs... -deleted the global scope and its related key wrappers -done! -``` - -
- -Build the example: -``` -go build -``` -Usage: -``` -./cli -h -Usage of ./cli: - -debug - enable debug - -plaintext string - plaintext you'd like to use for encrypt/decrypt ops with a wrapper (default "default plaintext secret") - -use-transit - use vault transit as the root wrapper source - run "docker-compose up" first -``` -To Use [Vault's Transit Secrets -Engine](https://www.vaultproject.io/docs/secrets/transit) as your root wrapper -you must first start vault with docker-compose which is located in the parent -directory. -``` -cd .. -docker-compose up -``` -Then in a separate terminal, run the cli passing the `use-transit` flag: -``` -./cli --use-transit -``` - -
- -### High-level ERD -The example CLI extends the existing kms schema by adding a `scope` table and -declares a cascading FK between scopes and kms_root_keys. If a `scope` is -deleted, then all of its associated wrappers will be deleted. With that said, -the schema also includes an `oidc` entity and declares a restricted FK between -`kms_data_key_version` and `oidc`. Given this restricted FK, you can't deleted -a `kms_data_key_version` if there's an existing `oidc` entry that uses it. - -This schema with its FKs ensures that a wrapper can't be deleted if it's -currently in use. As a result, you'll always be able to decrypt an `oidc` entry -that's stored in the database. - -``` - ┌────────────────────────┐ - │ scope │ - ├────────────────────────┤ - ┌─┼│private_id │ - │ │ │ - │ │ │ - │ └────────────────────────┘ - │ - │ - │ ┌───────────────────────────────┐ - │ │ ○ - │ ┼ ┼ - │ ┌────────────────────────┐ ┌────────────────────────┐ - │ │ kms_root_key │ │ kms_data_key │ - │ ├────────────────────────┤ ├────────────────────────┤ - └○┼│private_id │ │private_id │ - │scope_id │ │root_key_id │ - │ │ │purpose │ - └────────────────────────┘ │ │ - ┼ └────────────────────────┘ - │ ┼ - │ │ - │ │ - │ │ - ┼ ┼ - ╱│╲ ╱│╲ - ┌────────────────────────┐ ┌────────────────────────┐ - │ kms_root_key_version │ │ kms_data_key_version │ - ├────────────────────────┤ ├────────────────────────┤ - │private_id │ │private_id │ - │root_key_id │ │data_key_id │┼─┐ - │key │ │root_key_id │ │ - │version │ │key │ │ - │ │ │version │ │ - └────────────────────────┘ └────────────────────────┘ │ - ┼ ┼ │ - │ ○ │ - └───────────────────────────────┘ │ - │ - │ - ┌────────────────────────┐ │ - │ oidc │ │ - ├────────────────────────┤ │ - │private_id │╲ │ - │client_id │─○┘ - │client_secret │╱ - │key_version_id │ - │ │ - └────────────────────────┘ -``` diff --git a/extras/kms/examples/cli/main.go b/extras/kms/examples/cli/main.go deleted file mode 100644 index eccd1240..00000000 --- a/extras/kms/examples/cli/main.go +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package main - -import ( - "context" - "errors" - "flag" - "fmt" - "os" - - "github.com/hashicorp/go-dbw" - "github.com/openbao/go-kms-wrapping/extras/kms/examples/v2" - "github.com/openbao/go-kms-wrapping/extras/kms/v2" - "github.com/openbao/go-kms-wrapping/v2/extras/structwrapping" -) - -const globalScope = "global" - -// default to an aead root kms -const rootKmsAeadTemplate = ` -kms "aead" { - purpose = "root" - aead_type = "aes-gcm" - key = "%s" - key_id = "global_root" -}` - -// support the --use-transit flag which requires the caller to run: -// "docker-compose up" before executing the example -const rootKmsTransitHcl = ` -kms "transit" { - purpose = "root" - address = "http://localhost:8200" - token = "vault-plaintext-root-token" - disable_renewal = "false" - key_name = "examplekey" - mount_path = "transit/" - namespace = "ns1/" -}` - -func main() { - mainCtx := context.Background() - - debug := flag.Bool("debug", false, "enable debug") - pt := flag.String("plaintext", "default plaintext secret", "plaintext you'd like to use for encrypt/decrypt ops with a wrapper") - useTransit := flag.Bool("use-transit", false, `use vault transit as the root wrapper source - run "docker-compose up" first`) - flag.Parse() - - var kmsHcl string - switch { - case *useTransit: - kmsHcl = rootKmsTransitHcl - default: - key := examples.GenerateKey() - kmsHcl = fmt.Sprintf(rootKmsAeadTemplate, key) - } - - // get the root wrapper from the provided configuration hcl - rootWrapper, err := examples.RootWrapperFromConfig(mainCtx, kmsHcl, *useTransit) - if err != nil { - fmt.Fprintf(os.Stderr, "unable to get root wrapper from config: %s\n\n", err) - return - } - // open the db and run migrations for both for the kms and this cli app. - // the cli app migration adds a scope table and a fk to the kms_root_key - // table. - rw, err := examples.OpenDB(mainCtx, *debug) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to open db: %s\n\n", err) - return - } - // create a kms that supports both the default kms.KeyPurposeRootKey KEK and - // a "database" DEK. - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database"}) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to init kms: %s\n\n", err) - return - } - - // add the external root key wrapper. - if err := k.AddExternalWrapper(mainCtx, kms.KeyPurposeRootKey, rootWrapper); err != nil { - fmt.Fprintf(os.Stderr, "failed to add root key: %s\n\n", err) - return - } - - // Create the global scope and it's related kms wrappers - if _, err := rw.DoTx(mainCtx, func(error) bool { return false }, 10, dbw.ExpBackoff{}, - func(r dbw.Reader, w dbw.Writer) error { - if err := w.Create(mainCtx, &examples.Scope{PrivateId: globalScope}, dbw.WithLookup(true)); err != nil { - return err - } - if err := k.CreateKeys(mainCtx, globalScope, []kms.KeyPurpose{"database"}, kms.WithReaderWriter(r, w)); err != nil { - return err - } - return nil - }); err != nil { - fmt.Fprintf(os.Stderr, "failed to create scope and its keys: %s\n\n", err) - return - } - - // get the db wrapper (DEK) - dbWrapper, err := k.GetWrapper(mainCtx, globalScope, "database") - if err != nil { - fmt.Fprintf(os.Stderr, "failed to get wrapper for database: %s\n\n", err) - return - } - - // get the DEK's version id so we can save it with the oidc row (since it will - // be used to encrypt/decrypt ciphertext in the row) - dbWrapperKeyVersionId, err := dbWrapper.KeyId(mainCtx) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to get key id: %s\n\n", err) - return - } - - oidcId, err := dbw.NewId("oidc") - if err != nil { - fmt.Fprintf(os.Stderr, "failed to get oidc id: %s\n\n", err) - return - } - // init a new OIDC record and initialize it's client_secret with our - // plaintext secret (pt) - o := &examples.OIDC{ - PrivateId: oidcId, - ClientId: "example-client-id", - ClientSecret: *pt, - KeyVersionId: dbWrapperKeyVersionId, - } - - fmt.Fprintf(os.Stderr, "using the structwrapping pkg to wrap (encrypt) the new oidc record...\n") - if err := structwrapping.WrapStruct(mainCtx, dbWrapper, o); err != nil { - fmt.Fprintf(os.Stderr, "failed to wrap: %s\n\n", err) - return - } - if o.CtClientSecret == nil { - fmt.Fprintf(os.Stderr, "failed to encrypt the client_secret: %s\n\n", err) - return - } - - fmt.Fprintf(os.Stderr, "writing the oidc record to the db...\n") - if _, err := rw.DoTx(mainCtx, func(error) bool { return false }, 10, dbw.ExpBackoff{}, - func(r dbw.Reader, w dbw.Writer) error { - if err := w.Create(mainCtx, o, dbw.WithLookup(true)); err != nil { - return err - } - return nil - }); err != nil { - fmt.Fprintf(os.Stderr, "failed to create oidc resource: %s\n\n", err) - return - } - - fmt.Fprintf(os.Stderr, "reading the oidc record from the db...\n") - found := examples.OIDC{ - PrivateId: o.PrivateId, - } - if err := rw.LookupBy(mainCtx, &found); err != nil { - fmt.Fprintf(os.Stderr, "failed to lookup oidc: %s\n\n", err) - return - } - - // Rotate and rewrap the keys for the scope - if err := k.RotateKeys(mainCtx, globalScope, kms.WithRewrap(true)); err != nil { - fmt.Fprintf(os.Stderr, "failed to rotate scope's keys: %s\n\n", err) - return - } - - fmt.Fprintf(os.Stderr, "successfully rotated keys\n") - - // get the rotated db wrapper (DEK) - rotateDbWrapper, err := k.GetWrapper(mainCtx, globalScope, "database") - if err != nil { - fmt.Fprintf(os.Stderr, "failed to get rotated wrapper for database: %s\n\n", err) - return - } - rotatedDbWrapperKeyVersionId, err := rotateDbWrapper.KeyId(mainCtx) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to get key id for rotated wrapper: %s\n\n", err) - return - } - if rotatedDbWrapperKeyVersionId == dbWrapperKeyVersionId { - fmt.Fprintf(os.Stderr, "rotated key version id %q should not equal original key version id %q\n\n", rotatedDbWrapperKeyVersionId, dbWrapperKeyVersionId) - return - } - - fmt.Fprintf(os.Stderr, "using the structwrapping pkg to unwrap (decrypt) the oidc record read from the db...\n") - if err := structwrapping.UnwrapStruct(mainCtx, rotateDbWrapper, &found); err != nil { - fmt.Fprintf(os.Stderr, "failed to unwrap: %s\n\n", err) - return - } - - if string(found.ClientSecret) != *pt { - fmt.Fprintf(os.Stderr, "%q doesn't equal %q\n\n", string(found.ClientSecret), *pt) - return - } - - fmt.Fprintf(os.Stderr, "successfully encrypted/decrypted %q using a rotated kms key\n", *pt) - - fmt.Fprintf(os.Stderr, "attempting to delete scope with its associated DEKs...\n") - if _, err := rw.DoTx(mainCtx, func(error) bool { return false }, 10, dbw.ExpBackoff{}, - func(r dbw.Reader, w dbw.Writer) error { - rowsDeleted, err := w.Delete(mainCtx, &examples.Scope{PrivateId: globalScope}) - if err != nil { - return err - } - if rowsDeleted != 1 { - return fmt.Errorf("%q rows delete and only wanted 1", rowsDeleted) - } - return nil - }); err == nil { - fmt.Fprintf(os.Stderr, "whoa... we should have failed to delete scope and its keys, since there's a FK to the oidc record\n\n") - return - } - - fmt.Fprintf(os.Stderr, "attempting to first delete the oidc record, then delete scope with its associated DEKs...\n") - if _, err := rw.DoTx(mainCtx, func(error) bool { return false }, 10, dbw.ExpBackoff{}, - func(r dbw.Reader, w dbw.Writer) error { - _, err := w.Delete(mainCtx, &found) - if err != nil { - return err - } - - _, err = w.Delete(mainCtx, &examples.Scope{PrivateId: globalScope}) - if err != nil { - return err - } - return nil - }); err != nil { - fmt.Fprintf(os.Stderr, "failed to delete scope and its keys: %s\n\n", err) - return - } - - // getting a wrapper for that scope should fail. - dbWrapper, err = k.GetWrapper(mainCtx, globalScope, "database") - if err != nil && !errors.Is(err, kms.ErrKeyNotFound) { - fmt.Fprintf(os.Stderr, "failed to delete keys for scope: %s\n\n", err) - return - } - - fmt.Fprintf(os.Stderr, "deleted the global scope and its related key wrappers\n") - fmt.Fprintf(os.Stderr, "done!\n") -} diff --git a/extras/kms/examples/config.go b/extras/kms/examples/config.go deleted file mode 100644 index 09555158..00000000 --- a/extras/kms/examples/config.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package examples - -import ( - "bytes" - "context" - "crypto/rand" - "encoding/base64" - "fmt" - "io" - "os" - - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/aead" - "github.com/openbao/go-kms-wrapping/wrappers/transit/v2" - "github.com/hashicorp/go-secure-stdlib/configutil/v2" -) - -// RootWrapperFromConfig returns the root wrapper from the provided kms hcl -func RootWrapperFromConfig(ctx context.Context, kmsHcl string, useTransit bool) (wrapping.Wrapper, error) { - parsedKmsConfig, err := parseConfig(kmsHcl) - if err != nil { - return nil, err - } - var rootWrapper wrapping.Wrapper - switch { - case useTransit: - fmt.Fprintf(os.Stderr, "using a vault transit root wrapper from: %s\n", parsedKmsConfig.Config["address"]) - w := transit.NewWrapper() - _, err := w.SetConfig(ctx, - transit.WithAddress(parsedKmsConfig.Config["address"]), - transit.WithToken(parsedKmsConfig.Config["token"]), - transit.WithKeyName(parsedKmsConfig.Config["key_name"]), - transit.WithMountPath(parsedKmsConfig.Config["mount_path"]), - transit.WithNamespace(parsedKmsConfig.Config["namespace"]), - ) - if err != nil { - fmt.Fprintf(os.Stderr, "error configuring the vault transit root wrapper.\n") - fmt.Fprintf(os.Stderr, "did you start vault via with docker-compose in the parent directory?\n") - return nil, err - } - rootWrapper = w - default: - fmt.Fprintf(os.Stderr, "using a generated aead key root wrapper...\n") - w := aead.NewWrapper() - if _, err := w.SetConfig(ctx, wrapping.WithKeyId(parsedKmsConfig.Config["key_id"])); err != nil { - return nil, err - } - decodedKey, err := base64.StdEncoding.DecodeString(parsedKmsConfig.Config["key"]) - if err != nil { - return nil, err - } - if err := w.SetAesGcmKeyBytes([]byte(decodedKey)); err != nil { - return nil, err - } - rootWrapper = w - } - - return rootWrapper, nil -} - -// GenerateKey will generate an example key -func GenerateKey() string { - var numBytes int64 = 32 - randBuf := new(bytes.Buffer) - n, err := randBuf.ReadFrom(&io.LimitedReader{ - R: rand.Reader, - N: numBytes, - }) - if err != nil { - panic(err) - } - if n != numBytes { - panic(fmt.Errorf("expected to read 64 bytes, read %d", n)) - } - return base64.StdEncoding.EncodeToString(randBuf.Bytes()[0:32]) -} - -func parseConfig(d string) (*configutil.KMS, error) { - sharedConfig, err := configutil.ParseConfig(d) - if err != nil { - return nil, err - } - if len(sharedConfig.Seals) != 1 { - return nil, fmt.Errorf("expected 1 seal and got %d", len(sharedConfig.Seals)) - } - return sharedConfig.Seals[0], nil -} diff --git a/extras/kms/examples/db.go b/extras/kms/examples/db.go deleted file mode 100644 index f0df8dda..00000000 --- a/extras/kms/examples/db.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package examples - -import ( - "context" - "fmt" - "io/fs" - "io/ioutil" - "net/http" - "os" - - "github.com/golang-migrate/migrate/v4" - sqliteMigrator "github.com/golang-migrate/migrate/v4/database/sqlite" - "github.com/golang-migrate/migrate/v4/source/httpfs" - "github.com/hashicorp/go-dbw" - "github.com/hashicorp/go-hclog" - "github.com/openbao/go-kms-wrapping/extras/kms/v2/migrations" - "gorm.io/driver/sqlite" -) - -// OpenDB returns an open db connection with it's migrations already run -func OpenDB(ctx context.Context, debug bool) (*dbw.RW, error) { - const ( - dialect = "sqlite" - inMemorySqlite = "file::memory:?cache=shared" - cliMigrationsDir = "sqlite-migrations" - tempDirPrefix = "migration-" - ) - - dialector := sqlite.Open(inMemorySqlite) - var dbOpts []dbw.Option - if !debug { - dbOpts = append(dbOpts, dbw.WithLogger(hclog.NewNullLogger())) - } - db, err := dbw.OpenWith(dialector, dbOpts...) - if err != nil { - return nil, err - } - sqlDB, err := db.SqlDB(ctx) - if err != nil { - return nil, err - } - driver, err := sqliteMigrator.WithInstance(sqlDB, &sqliteMigrator.Config{}) - if err != nil { - return nil, err - } - - dir, err := ioutil.TempDir(".", tempDirPrefix) - if err != nil { - return nil, err - } - defer os.RemoveAll(dir) - - baseMigrations, _ := fs.ReadDir(migrations.SqliteFS, dialect) - for _, m := range baseMigrations { - sql, err := fs.ReadFile(migrations.SqliteFS, fmt.Sprintf("%s/%s", dialect, m.Name())) - if err != nil { - return nil, err - } - if err := os.WriteFile(fmt.Sprintf("%s/%s", dir, m.Name()), sql, 0o666); err != nil { - return nil, err - } - } - cliMigrations, _ := fs.ReadDir(LocalSqliteFS, cliMigrationsDir) - for _, m := range cliMigrations { - sql, err := fs.ReadFile(LocalSqliteFS, fmt.Sprintf("%s/%s", cliMigrationsDir, m.Name())) - if err != nil { - return nil, err - } - if err := os.WriteFile(fmt.Sprintf("%s/%s", dir, m.Name()), sql, 0o666); err != nil { - return nil, err - } - } - - source, err := httpfs.New(http.Dir("."), dir) - if err != nil { - return nil, fmt.Errorf("failed to open migrations: %w", err) - } - m, err := migrate.NewWithInstance( - dialect, - source, - dialect, - driver) - if err != nil { - return nil, err - } - err = m.Up() - if err != nil { - return nil, err - } - rw := dbw.New(db) - if debug { - rw.DB().Debug(true) - } - return rw, nil -} diff --git a/extras/kms/examples/docker-compose.yml b/extras/kms/examples/docker-compose.yml deleted file mode 100644 index a1ee6752..00000000 --- a/extras/kms/examples/docker-compose.yml +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -version: "3.8" - -services: - - vault-server: - image: vault:latest - ports: - - "8200:8200" - environment: - VAULT_ADDR: "http://0.0.0.0:8200" - VAULT_DEV_ROOT_TOKEN_ID: "vault-plaintext-root-token" - cap_add: - - IPC_LOCK - - vault-client: - build: . - environment: - VAULT_ADDR: "http://vault-server:8200" diff --git a/extras/kms/examples/fs.go b/extras/kms/examples/fs.go deleted file mode 100644 index 3220b4a2..00000000 --- a/extras/kms/examples/fs.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package examples - -import "embed" - -// LocalSqliteFS contains the sql for creating additional sqlite tables for -// examples. -// -//go:embed sqlite-migrations -var LocalSqliteFS embed.FS diff --git a/extras/kms/examples/go.mod b/extras/kms/examples/go.mod deleted file mode 100644 index c7daeddc..00000000 --- a/extras/kms/examples/go.mod +++ /dev/null @@ -1,118 +0,0 @@ -module github.com/openbao/go-kms-wrapping/extras/kms/examples/v2 - -go 1.22.1 - -toolchain go1.22.7 - -replace github.com/openbao/go-kms-wrapping/v2 => ../../../ - -replace github.com/openbao/go-kms-wrapping/extras/kms/v2 => ../ - -replace github.com/openbao/go-kms-wrapping/plugin/v2 => ../../../plugin - -replace github.com/openbao/go-kms-wrapping/wrappers/transit/v2 => ../../../wrappers/transit - -require ( - github.com/golang-migrate/migrate/v4 v4.16.2 - github.com/hashicorp/go-dbw v0.1.1-0.20231011231112-ed00db42814c - github.com/hashicorp/go-hclog v1.6.3 - github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.11 - github.com/openbao/go-kms-wrapping/extras/kms/v2 v2.0.0-20231027204625-466117c39bed - github.com/openbao/go-kms-wrapping/v2 v2.1.0 - github.com/openbao/go-kms-wrapping/wrappers/transit/v2 v2.0.8 - gorm.io/driver/sqlite v1.5.4 -) - -require ( - github.com/DATA-DOG/go-sqlmock v1.5.0 // indirect - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.0 // indirect - github.com/Masterminds/sprig/v3 v3.2.3 // indirect - github.com/armon/go-radix v1.0.0 // indirect - github.com/bgentry/speakeasy v0.1.0 // indirect - github.com/cenkalti/backoff/v3 v3.2.2 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/fatih/color v1.16.0 // indirect - github.com/go-jose/go-jose/v3 v3.0.1 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/uuid v1.3.1 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.5 // indirect - github.com/hashicorp/go-kms-wrapping/v2 v2.0.14 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.5.2 // indirect - github.com/hashicorp/go-retryablehttp v0.7.7 // indirect - github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 // indirect - github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect - github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6 // indirect - github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 // indirect - github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect - github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3 // indirect - github.com/hashicorp/go-sockaddr v1.0.6 // indirect - github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/hashicorp/hcl v1.0.1-vault-5 // indirect - github.com/hashicorp/yamux v0.1.1 // indirect - github.com/huandu/xstrings v1.4.0 // indirect - github.com/imdario/mergo v0.3.13 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.14.1 // indirect - github.com/jackc/pgio v1.0.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.2 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgtype v1.14.0 // indirect - github.com/jackc/pgx/v4 v4.18.1 // indirect - github.com/jackc/pgx/v5 v5.4.3 // indirect - github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.5 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/lib/pq v1.10.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.17 // indirect - github.com/mitchellh/cli v1.1.5 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/oklog/run v1.1.0 // indirect - github.com/openbao/openbao/api/v2 v2.0.1 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/posener/complete v1.2.3 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/shopspring/decimal v1.3.1 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect - github.com/xo/dburl v0.16.0 // indirect - go.uber.org/atomic v1.10.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.22.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.19.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/grpc v1.59.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/postgres v1.5.2 // indirect - gorm.io/gorm v1.25.4 // indirect - lukechampine.com/uint128 v1.2.0 // indirect - modernc.org/cc/v3 v3.40.0 // indirect - modernc.org/ccgo/v3 v3.16.13 // indirect - modernc.org/libc v1.22.3 // indirect - modernc.org/mathutil v1.5.0 // indirect - modernc.org/memory v1.5.0 // indirect - modernc.org/opt v0.1.3 // indirect - modernc.org/sqlite v1.21.0 // indirect - modernc.org/strutil v1.1.3 // indirect - modernc.org/token v1.1.0 // indirect -) diff --git a/extras/kms/examples/go.sum b/extras/kms/examples/go.sum deleted file mode 100644 index 483db7d3..00000000 --- a/extras/kms/examples/go.sum +++ /dev/null @@ -1,486 +0,0 @@ -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= -github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= -github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= -github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= -github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= -github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw= -github.com/containerd/containerd v1.7.11/go.mod h1:5UluHxHTX2rdvYuZ5OJTC5m/KJNs0Zs9wVoJm9zf5ZE= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dhui/dktest v0.3.16 h1:i6gq2YQEtcrjKbeJpBkWjE8MmLZPYllcjOFbTZuPDnw= -github.com/dhui/dktest v0.3.16/go.mod h1:gYaA3LRmM8Z4vJl2MA0THIigJoZrwOansEOsp+kqxp0= -github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= -github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= -github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= -github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= -github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-dbw v0.1.1-0.20231011231112-ed00db42814c h1:G4eMV0LjAh9X5mx90i0D7//7yXa8HcRvx9S3WoyOJdY= -github.com/hashicorp/go-dbw v0.1.1-0.20231011231112-ed00db42814c/go.mod h1:mlttLFmV3/UDHYeaFGdE6t8hANJ4ADpWCylBL8ZbYGU= -github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= -github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.5 h1:jrnDfQm2hCQ0/hEselgqzV4fK16gpZoY0OWGZpVPNHM= -github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.5/go.mod h1:psh1qKep5ukvuNobFY/hCybuudlkkACpmazOsCgX5Rg= -github.com/hashicorp/go-kms-wrapping/v2 v2.0.14 h1:1ZuhfnZgRnLK8S0KovJkoTCRIQId5pv3sDR7pG5VQBw= -github.com/hashicorp/go-kms-wrapping/v2 v2.0.14/go.mod h1:0dWtzl2ilqKpavgM3id/kFK9L3tjo6fS4OhbVPSYpnQ= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.5.2 h1:aWv8eimFqWlsEiMrYZdPYl+FdHaBJSN4AWwGWfT1G2Y= -github.com/hashicorp/go-plugin v1.5.2/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= -github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= -github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng= -github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= -github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.11 h1:uPW2Wn0YlmI9RGSkZpcIplnVRwJ7BCiGpk1vnF2TMw4= -github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.11/go.mod h1:uis9dCmOzXuOaRyXq+1Foh31kcvXKoWogjNnhfjHfW8= -github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9 h1:0S0ctJ7Ra8O7ap+/3fZUnzJ3VzJyirWS/WnNCuOYtZY= -github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9/go.mod h1:TNNdgtjLgVDbrgFcyCKrlAicIl3dZF94swJltyGUX2M= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0= -github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6 h1:ZYv2XA+tEfFXIToR2jmBgVqQU9gERt0APbWqmUoNGnY= -github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6/go.mod h1:ggFN8dlaLWS2R1gymBbCrvXM/bkZP7hEAa4seqDwhyg= -github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI= -github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1/go.mod h1:Ch/bf00Qnx77MZd49JRgHYqHQjtEmTgGU2faufpVZb0= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= -github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3 h1:xbrxd0U9XQW8qL1BAz2XrAjAF/P2vcqUTAues9c24B8= -github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3/go.mod h1:LWq2Sy8UoKKuK4lFuCNWSjJj57MhNNf2zzBWMtkAIX4= -github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= -github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= -github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= -github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= -github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= -github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= -github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= -github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= -github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= -github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= -github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= -github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= -github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= -github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4= -github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00= -github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= -github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= -github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= -github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= -github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng= -github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= -github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo= -github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= -github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= -github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= -github.com/openbao/openbao/api/v2 v2.0.1 h1:oyDqLa8m+XY3YBbgQ4YnX5o+/4/ybShiDPMC/7WomtE= -github.com/openbao/openbao/api/v2 v2.0.1/go.mod h1:qIp3G8D5vaW+r7TG2YoCCEo/5HxTvidwZA0GiwA1iJ8= -github.com/openbao/openbao/sdk/v2 v2.0.1 h1:to7KnJSf3Guz082ZJvnCqRrsdrVO2Hd4V1tQRhxd9jw= -github.com/openbao/openbao/sdk/v2 v2.0.1/go.mod h1:JmVNJPvv1g1043F1dXWkqpBPkgxjArXy/oK8Iu82wN0= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= -github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= -github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= -github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= -github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/xo/dburl v0.16.0 h1:jlBeGe8fnsW+vBYemte903WHQbJnZx7OpJZy2ofq+5g= -github.com/xo/dburl v0.16.0/go.mod h1:B7/G9FGungw6ighV8xJNwWYQPMfn3gsi2sn5SE8Bzco= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= -gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= -gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= -gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= -gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw= -gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= -lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= -modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= -modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= -modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= -modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= -modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= -modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= -modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= -modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow= -modernc.org/sqlite v1.21.0/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI= -modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= -modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws= -modernc.org/tcl v1.15.1/go.mod h1:aEjeGJX2gz1oWKOLDVZ2tnEWLUrIn8H+GFu+akoDhqs= -modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= -modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= diff --git a/extras/kms/examples/oidc.go b/extras/kms/examples/oidc.go deleted file mode 100644 index e1288408..00000000 --- a/extras/kms/examples/oidc.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package examples - -import "time" - -// OIDC represents the client info for an oidc conn -type OIDC struct { - // PrivateId is used to access the root key - PrivateId string `json:"private_id,omitempty" gorm:"primary_key"` - // ClientId is the oidc client id - ClientId string `json:"client_id,omitempty"` - // CtClientSecret is the ciphertext of the client_secret - CtClientSecret []byte `json:"-" gorm:"column:client_secret" wrapping:"ct,client_secret"` - // ClientSecret is the oidc client secret (plaintext) - ClientSecret string `json:"client_secret,omitempty" wrapping:"pt,client_secret"` - // KeyVersionId is the key's version id used to encrypt/decrypt the client secret - KeyVersionId string `json:"key_version_id,omitempty" gorm:"not_null"` - // CreateTime from the db - CreateTime time.Time `json:"create_time,omitempty" gorm:"default:current_timestamp"` -} - -func (_ *OIDC) TableName() string { return "oidc" } diff --git a/extras/kms/examples/run.sh b/extras/kms/examples/run.sh deleted file mode 100755 index 0d81bf73..00000000 --- a/extras/kms/examples/run.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - - -VAULT_RETRIES=5 -echo "Vault is starting..." -until vault status > /dev/null 2>&1 || [ "$VAULT_RETRIES" -eq 0 ]; do - echo "Waiting for vault to start...: $((VAULT_RETRIES--))" - sleep 1 -done - -echo "Authenticating to vault..." -vault login token=vault-plaintext-root-token - -echo "Initialize transit..." -vault secrets enable transit -echo "Adding examplekey..." -vault write -f transit/keys/examplekey - -echo "Complete..." \ No newline at end of file diff --git a/extras/kms/examples/scope.go b/extras/kms/examples/scope.go deleted file mode 100644 index a07b6dd5..00000000 --- a/extras/kms/examples/scope.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package examples - -import "time" - -// Scope represents an application scope like "global" or some unique id for an -// org or proj. -type Scope struct { - // PrivateId is used to access the root key - PrivateId string `json:"private_id,omitempty" gorm:"primary_key"` - // CreateTime from the db - CreateTime time.Time `json:"create_time,omitempty" gorm:"default:current_timestamp"` -} - -func (_ *Scope) TableName() string { return "scope" } diff --git a/extras/kms/examples/sqlite-migrations/10_scopes.up.sql b/extras/kms/examples/sqlite-migrations/10_scopes.up.sql deleted file mode 100644 index 4fc85398..00000000 --- a/extras/kms/examples/sqlite-migrations/10_scopes.up.sql +++ /dev/null @@ -1,21 +0,0 @@ --- Copyright (c) HashiCorp, Inc. --- SPDX-License-Identifier: MPL-2.0 - -create table scope ( - private_id text not null primary key, - create_time timestamp not null default current_timestamp -); - -drop table kms_root_key; - -create table kms_root_key ( - private_id text not null primary key, - scope_id text not null unique - references scope(private_id) - on delete cascade - on update cascade - check( - scope_id > 0 - ), - create_time timestamp not null default current_timestamp -); diff --git a/extras/kms/examples/sqlite-migrations/15_oidc.up.sql b/extras/kms/examples/sqlite-migrations/15_oidc.up.sql deleted file mode 100644 index e3affe49..00000000 --- a/extras/kms/examples/sqlite-migrations/15_oidc.up.sql +++ /dev/null @@ -1,13 +0,0 @@ --- Copyright (c) HashiCorp, Inc. --- SPDX-License-Identifier: MPL-2.0 - -create table oidc ( - private_id text not null primary key, - client_id text not null, - client_secret blob not null, - key_version_id text not null - references kms_data_key_version(private_id) - on delete restrict -- keep a data key from being deleted while it's in use - on update cascade, - create_time timestamp not null default current_timestamp -); diff --git a/extras/kms/go.mod b/extras/kms/go.mod deleted file mode 100644 index e8ac6070..00000000 --- a/extras/kms/go.mod +++ /dev/null @@ -1,58 +0,0 @@ -module github.com/openbao/go-kms-wrapping/extras/kms/v2 - -go 1.22.1 - -replace github.com/openbao/go-kms-wrapping/v2 => ../../ - -require ( - github.com/DATA-DOG/go-sqlmock v1.5.0 - github.com/golang-migrate/migrate/v4 v4.16.2 - github.com/google/go-cmp v0.6.0 - github.com/hashicorp/go-dbw v0.1.1-0.20231011231112-ed00db42814c - github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 - github.com/hashicorp/go-uuid v1.0.3 - github.com/openbao/go-kms-wrapping/v2 v2.0.0-00010101000000-000000000000 - github.com/stretchr/testify v1.8.4 - google.golang.org/protobuf v1.31.0 - mvdan.cc/gofumpt v0.5.0 -) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fatih/color v1.15.0 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-hclog v1.5.0 // indirect - github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.14.1 // indirect - github.com/jackc/pgio v1.0.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.2 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgtype v1.14.0 // indirect - github.com/jackc/pgx/v4 v4.18.1 // indirect - github.com/jackc/pgx/v5 v5.4.3 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.5 // indirect - github.com/lib/pq v1.10.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mattn/go-sqlite3 v1.14.17 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/xo/dburl v0.16.0 // indirect - go.uber.org/atomic v1.10.0 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/mod v0.10.0 // indirect - golang.org/x/sync v0.2.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/postgres v1.5.2 // indirect - gorm.io/driver/sqlite v1.5.3 // indirect - gorm.io/gorm v1.25.4 // indirect -) - -retract [v2.0.0, v2.0.15] diff --git a/extras/kms/go.sum b/extras/kms/go.sum deleted file mode 100644 index 34ab3f66..00000000 --- a/extras/kms/go.sum +++ /dev/null @@ -1,317 +0,0 @@ -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dhui/dktest v0.3.16 h1:i6gq2YQEtcrjKbeJpBkWjE8MmLZPYllcjOFbTZuPDnw= -github.com/dhui/dktest v0.3.16/go.mod h1:gYaA3LRmM8Z4vJl2MA0THIigJoZrwOansEOsp+kqxp0= -github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= -github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= -github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-dbw v0.1.1-0.20231011231112-ed00db42814c h1:G4eMV0LjAh9X5mx90i0D7//7yXa8HcRvx9S3WoyOJdY= -github.com/hashicorp/go-dbw v0.1.1-0.20231011231112-ed00db42814c/go.mod h1:mlttLFmV3/UDHYeaFGdE6t8hANJ4ADpWCylBL8ZbYGU= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng= -github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= -github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= -github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= -github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= -github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= -github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= -github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= -github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= -github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= -github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= -github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= -github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/xo/dburl v0.16.0 h1:jlBeGe8fnsW+vBYemte903WHQbJnZx7OpJZy2ofq+5g= -github.com/xo/dburl v0.16.0/go.mod h1:B7/G9FGungw6ighV8xJNwWYQPMfn3gsi2sn5SE8Bzco= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= -gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= -gorm.io/driver/sqlite v1.5.3 h1:7/0dUgX28KAcopdfbRWWl68Rflh6osa4rDh+m51KL2g= -gorm.io/driver/sqlite v1.5.3/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= -gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw= -gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E= -mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js= diff --git a/extras/kms/id.go b/extras/kms/id.go deleted file mode 100644 index 2199427c..00000000 --- a/extras/kms/id.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "fmt" - - "github.com/hashicorp/go-dbw" -) - -const ( - // rootKeyPrefix is a prefix used with RootKey IDs - rootKeyPrefix = "krk" - // rootKeyVersionPrefix is a prefix used with RootKeyVersion IDs - rootKeyVersionPrefix = "krkv" - // dataKeyPrefix is a prefix used with RootKey IDs - dataKeyPrefix = "kdk" - // dataKeyVersionPrefix is a prefix used with DataKeyVersion IDs - dataKeyVersionPrefix = "kdkv" -) - -func newRootKeyId() (string, error) { - const op = "kms.newRootKeyId" - id, err := dbw.NewId(rootKeyPrefix) - if err != nil { - return "", fmt.Errorf("%s: %w", op, err) - } - return id, nil -} - -func newRootKeyVersionId() (string, error) { - const op = "kms.newRootKeyVersionId" - id, err := dbw.NewId(rootKeyVersionPrefix) - if err != nil { - return "", fmt.Errorf("%s: %w", op, err) - } - return id, nil -} - -func newDataKeyId() (string, error) { - const op = "kms.newDataKeyId" - id, err := dbw.NewId(dataKeyPrefix) - if err != nil { - return "", fmt.Errorf("%s: %w", op, err) - } - return id, nil -} - -func newDataKeyVersionId() (string, error) { - const op = "kms.newDataKeyVersionId" - id, err := dbw.NewId(dataKeyVersionPrefix) - if err != nil { - return "", fmt.Errorf("%s: %w", op, err) - } - return id, nil -} diff --git a/extras/kms/id_test.go b/extras/kms/id_test.go deleted file mode 100644 index ac2674ce..00000000 --- a/extras/kms/id_test.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_Ids(t *testing.T) { - t.Parallel() - t.Run("krk", func(t *testing.T) { - id, err := newRootKeyId() - require.NoError(t, err) - assert.True(t, strings.HasPrefix(id, rootKeyPrefix+"_")) - }) - t.Run("krkv", func(t *testing.T) { - id, err := newRootKeyVersionId() - require.NoError(t, err) - assert.True(t, strings.HasPrefix(id, rootKeyVersionPrefix+"_")) - }) -} diff --git a/extras/kms/key.go b/extras/kms/key.go deleted file mode 100644 index caca04fe..00000000 --- a/extras/kms/key.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import "time" - -type KeyType string - -const ( - // KeyTypeDek defines a KEK (key encryption key) - KeyTypeKek KeyType = "kek" - - // KeyTypeDek defines a DEK (data encryption key) - KeyTypeDek = "dek" -) - -// KeyVersion is a key's version (the construct containing the key material) -type KeyVersion struct { - // Id is the key version's id - Id string `json:"id"` - - // Version is the key version's version - Version uint `json:"version"` - - // CreateTime is the key version's create time. - CreateTime time.Time `json:"create_time"` -} - -// Key is the permanent construct representing ephemeral key versions -type Key struct { - // Id is the key's id - Id string `json:"id"` - - // Scope is the scope of the key - Scope string `json:"scope"` - - // Type is the key's KeyType. - Type KeyType `json:"type"` - - // CreateTime is the time this key was created in the db - CreateTime time.Time `json:"create_time"` - - // Purpose is the key's purpose - Purpose KeyPurpose `json:"key_purpose"` - - // Versions is a list of key versions for this key - Versions []KeyVersion `json:"versions"` -} - -func newKeyFromRootKey(key *rootKey) Key { - return Key{ - Id: key.PrivateId, - Scope: key.ScopeId, - CreateTime: key.CreateTime, - Type: KeyTypeKek, - Purpose: KeyPurposeRootKey, - } -} - -func newKeyFromDataKey(key *dataKey, scope string) Key { - return Key{ - Id: key.PrivateId, - Scope: scope, - CreateTime: key.CreateTime, - Type: KeyTypeDek, - Purpose: key.Purpose, - } -} diff --git a/extras/kms/key_purpose.go b/extras/kms/key_purpose.go deleted file mode 100644 index f8a1b5fb..00000000 --- a/extras/kms/key_purpose.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "strings" -) - -// KeyPurpose allows an application to specify the reason they need a key; this -// is used to select which DEK to return -type KeyPurpose string - -const ( - // KeyPurposeUnknown is the default, and indicates that a correct purpose - // wasn't specified - KeyPurposeUnknown KeyPurpose = "" - - // KeyPurposeRootKey defines a root key purpose - KeyPurposeRootKey = "rootKey" -) - -func reservedKeyPurpose() []string { - return []string{ - string(KeyPurposeRootKey), - } -} - -func (kp KeyPurpose) trimSpace() KeyPurpose { - return KeyPurpose(strings.TrimSpace(string(kp))) -} - -// removeDuplicatePurposes will de-dup a set of key purposes -func removeDuplicatePurposes(purposes []KeyPurpose) []KeyPurpose { - purposesMap := make(map[KeyPurpose]struct{}, len(purposes)) - for _, purpose := range purposes { - purpose = purpose.trimSpace() - if purpose == "" { - continue - } - purposesMap[purpose] = struct{}{} - } - purposes = make([]KeyPurpose, 0, len(purposesMap)) - for purpose := range purposesMap { - purposes = append(purposes, purpose) - } - return purposes -} - -func purposeListContains(haystack []KeyPurpose, needle KeyPurpose) bool { - for _, item := range haystack { - if item == needle { - return true - } - } - return false -} diff --git a/extras/kms/kms.go b/extras/kms/kms.go deleted file mode 100644 index 623891dd..00000000 --- a/extras/kms/kms.go +++ /dev/null @@ -1,870 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "errors" - "fmt" - "reflect" - "strings" - "sync" - "sync/atomic" - - "github.com/hashicorp/go-dbw" - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/aead" - "github.com/openbao/go-kms-wrapping/v2/extras/multi" - "github.com/hashicorp/go-multierror" -) - -// cachePurpose defines an enum for wrapper cache purposes -type cachePurpose int - -const ( - // unknownWrapperCache is the default, and indicates that a purpose wasn't - // specified - unknownWrapperCache cachePurpose = iota - - // externalWrapperCache defines an external wrapper cache - externalWrapperCache - - // scopeWrapperCache defines an scope wrapper cache - scopeWrapperCache - - // DefaultTableNamePrefix defines the default prefix that will be add to - // every table name when an optional WithTableNamePrefix is not used when - // calling New(...). For example the root table name might be "data_key" and - // then the default is added to form "kms_data_key" - DefaultTableNamePrefix = "kms" -) - -// Kms is a way to access wrappers for a given scope and purpose. Since keys can -// never change, only be added or (eventually) removed, it opportunistically -// caches, going to the database as needed. -type Kms struct { - // scopedWrapperCache holds a per-scope-purpose multiwrapper containing the - // current encrypting key and all previous key versions, for decryption - scopedWrapperCache sync.Map - - externalWrapperCache sync.Map - - collectionVersion uint64 - - purposes []KeyPurpose - repo *repository - - // tableNamePrefix defines the prefix to use before the table name and - // allows us to support custom prefixes as well as multi KMSs within a - // single schema. - tableNamePrefix string -} - -// New takes in a reader, writer and a list of key purposes it will support. -// Every kms will support a KeyPurposeRootKey by default and it doesn't need to -// be passed in as one of the supported purposes. -// -// Supported options: WithTableNamePrefix. -func New(r dbw.Reader, w dbw.Writer, purposes []KeyPurpose, opt ...Option) (*Kms, error) { - const op = "kms.New" - repo, err := newRepository(r, w, opt...) - if err != nil { - return nil, fmt.Errorf("%s: unable to initialize repository: %w", op, err) - } - purposes = append(purposes, KeyPurposeRootKey) - removeDuplicatePurposes(purposes) - - opts := getOpts(opt...) - return &Kms{ - purposes: purposes, - repo: repo, - tableNamePrefix: opts.withTableNamePrefix, - }, nil -} - -// clearCache clears the cached DEK wrappers and by default the DEK wrappers for all -// scopes are cleared from the cache. The WithScopeIds(...) allows the caller -// to limit which scoped DEK wrappers are cleared from the cache. -func (k *Kms) clearCache(ctx context.Context, opt ...Option) error { - const op = "kms.(Kms).ResetCache" - opts := getOpts(opt...) - switch { - case len(opts.withScopeIds) > 0: - for _, p := range k.purposes { - for _, id := range opts.withScopeIds { - k.scopedWrapperCache.Delete(scopedPurpose(id, p)) - } - } - return nil - default: - k.scopedWrapperCache = sync.Map{} - return nil - } -} - -// addKey will add a key to the appropriate cache. This is a no-op, when the cachePurpose is -// scopeWrapperCache and k.withCache is false -func (k *Kms) addKey(ctx context.Context, cPurpose cachePurpose, kPurpose KeyPurpose, wrapper wrapping.Wrapper, opt ...Option) error { - const ( - op = "kms.addKey" - missingId = "" - ) - if kPurpose == KeyPurposeUnknown { - return fmt.Errorf("%s: missing purpose: %w", op, ErrInvalidParameter) - } - if isNil(wrapper) { - return fmt.Errorf("%s: missing wrapper: %w", op, ErrInvalidParameter) - } - if !purposeListContains(k.purposes, kPurpose) { - return fmt.Errorf("%s: not a supported key purpose %q: %w", op, kPurpose, ErrInvalidParameter) - } - - opts := getOpts(opt...) - - switch cPurpose { - case externalWrapperCache: - k.externalWrapperCache.Store(kPurpose, wrapper) - case scopeWrapperCache: - if opts.withKeyVersionId == "" { - return fmt.Errorf("%s: missing key version id for scoped wrapper cache: %w", op, ErrInvalidParameter) - } - k.scopedWrapperCache.Store(opts.withKeyVersionId, wrapper) - default: - return fmt.Errorf("%s: unsupported cache purpose %q: %w", op, cPurpose, ErrInvalidParameter) - } - return nil -} - -// Purposes returns a copy of the key purposes for the kms -func (k *Kms) Purposes() []KeyPurpose { - cp := make([]KeyPurpose, len(k.purposes)) - copy(cp, k.purposes) - return cp -} - -// AddExternalWrapper allows setting the external keys which are defined outside -// of the kms (e.g. in a configuration file). -func (k *Kms) AddExternalWrapper(ctx context.Context, purpose KeyPurpose, wrapper wrapping.Wrapper) error { - // TODO: If we support more than one, e.g. for encrypting against many in case - // of a key loss, there will need to be some refactoring here to have the values - // being stored in the struct be a multiwrapper, but that's for a later project. - return k.addKey(ctx, externalWrapperCache, purpose, wrapper) -} - -// GetExternalWrapper returns the external wrapper for a given purpose and -// returns ErrKeyNotFound when a key for the given purpose is not found. -func (k *Kms) GetExternalWrapper(_ context.Context, purpose KeyPurpose) (wrapping.Wrapper, error) { - const op = "kms.(Kms).GetExternalWrapper" - if purpose == KeyPurposeUnknown { - return nil, fmt.Errorf("%s: missing purpose: %w", op, ErrInvalidParameter) - } - if !purposeListContains(k.purposes, purpose) { - return nil, fmt.Errorf("%s: not a supported key purpose %q: %w", op, purpose, ErrInvalidParameter) - } - if k, ok := k.externalWrapperCache.Load(purpose); ok { - w, ok := k.(wrapping.Wrapper) - if !ok { - return nil, fmt.Errorf("%s: external wrapper is not a wrapping.Wrapper: %w", op, ErrInternal) - } - return w, nil - } - return nil, fmt.Errorf("%s: missing external wrapper for %q: %w", op, purpose, ErrKeyNotFound) -} - -// GetExternalRootWrapper returns the external wrapper for KeyPurposeRootKey is -// is just a convenience function for GetExternalWrapper(...) and returns -// ErrKeyNotFound when a root key is not found. -func (k *Kms) GetExternalRootWrapper() (wrapping.Wrapper, error) { - const op = "kms.(Kms).GetRootWrapper" - if k, err := k.GetExternalWrapper(context.Background(), KeyPurposeRootKey); err == nil { - return k, nil - } - return nil, fmt.Errorf("%s: missing external root wrapper: %w", op, ErrKeyNotFound) -} - -// GetWrapper returns a wrapper for the given scope and purpose. The -// WithReader(...) option is supported for getting a wrapper. -// -// If an optional WithKeyVersionId(...) or WithKeyId(...) is -// passed, it will ensure that the returning wrapper has that key version ID in the -// multiwrapper. This is not necessary for encryption but should be supplied for -// decryption. WithKeyVersionId(...) and WithKeyId(...) are equivalent options, -// describing the key version ID requested in the wrapper. -// -// Note: getting a wrapper for KeyPurposeRootKey is supported, but a root -// wrapper is a KEK and should never be used for data encryption. -func (k *Kms) GetWrapper(ctx context.Context, scopeId string, purpose KeyPurpose, opt ...Option) (wrapping.Wrapper, error) { - const op = "kms.(Kms).GetWrapper" - if scopeId == "" { - return nil, fmt.Errorf("%s: missing scope id: %w", op, ErrInvalidParameter) - } - if purpose == KeyPurposeUnknown { - return nil, fmt.Errorf("%s: missing purpose: %w", op, ErrInvalidParameter) - } - if !purposeListContains(k.purposes, purpose) { - return nil, fmt.Errorf("%s: not a supported key purpose %q: %w", op, purpose, ErrInvalidParameter) - } - opts := getOpts(opt...) - // Fast-path: we have a valid key at the scope/purpose. Verify the key with - // that ID is in the multiwrapper; if not, fall through to reload from the - // DB. - currVersion, err := currentCollectionVersion(ctx, k.repo.reader, k.tableNamePrefix) - if err != nil { - return nil, fmt.Errorf("%s: unable to determine current version of the kms collection: %w", op, err) - } - switch { - case currVersion != atomic.LoadUint64(&k.collectionVersion): - k.clearCache(ctx) - atomic.StoreUint64(&k.collectionVersion, currVersion) - default: - val, ok := k.scopedWrapperCache.Load(scopedPurpose(scopeId, purpose)) - if ok { - wrapper, ok := val.(*multi.PooledWrapper) - if !ok { - return nil, fmt.Errorf("%s: scoped wrapper is not a multi.PooledWrapper: %w", op, ErrInternal) - } - if opts.withKeyVersionId == "" { - return wrapper, nil - } - if keyIdVersionWrapper := wrapper.WrapperForKeyId(opts.withKeyVersionId); keyIdVersionWrapper != nil { - return keyIdVersionWrapper, nil - } - // Fall through to refresh our multiwrapper for this scope/purpose from the DB - } - } - - // We don't have it cached, so we'll need to read from the database. Get the - // root for the scope as we'll need it to decrypt the value coming from the - // DB. We don't cache the roots as we expect that after a few calls the - // scope-purpose cache will catch everything in steady-state. - rootWrapper, rootKeyId, err := k.loadRoot(ctx, scopeId, WithReader(opts.withReader)) - if err != nil { - return nil, fmt.Errorf("%s: error loading root key for scope %q: %w", op, scopeId, err) - } - if isNil(rootWrapper) { - return nil, fmt.Errorf("%s: got nil root wrapper for scope %q: %w", op, scopeId, ErrInvalidParameter) - } - - if purpose == KeyPurposeRootKey { - return rootWrapper, nil - } - - wrapper, err := k.loadDek(ctx, scopeId, purpose, rootWrapper, rootKeyId, WithReader(opts.withReader)) - if err != nil { - return nil, fmt.Errorf("%s: error loading %q for scope %q: %w", op, purpose, scopeId, err) - } - if err := k.addKey(ctx, scopeWrapperCache, purpose, wrapper, WithKeyVersionId(scopeId+string(purpose))); err != nil { - return nil, fmt.Errorf("%s: error adding key to cache: %w", op, err) - } - - if opts.withKeyVersionId != "" { - if keyIdVersionWrapper := wrapper.WrapperForKeyId(opts.withKeyVersionId); keyIdVersionWrapper != nil { - return keyIdVersionWrapper, nil - } - return nil, fmt.Errorf("%s: unable to find specified key version ID: %w", op, ErrKeyNotFound) - } - - return wrapper, nil -} - -// ListKeys returns the current list of kms keys. -func (k *Kms) ListKeys(ctx context.Context, scopeId string) ([]Key, error) { - const op = "kms.(Kms).ListKeys" - switch { - case scopeId == "": - return nil, fmt.Errorf("%s: missing scope id: %w", op, ErrInvalidParameter) - } - dbKey, err := k.repo.LookupRootKeyByScope(ctx, scopeId) - if err != nil { - return nil, fmt.Errorf("%s: unable to lookup root key: %w", op, err) - } - rkv := rootKeyVersion{ - tableNamePrefix: k.tableNamePrefix, - } - var rkVersions []*rootKeyVersion - // we don't need to decrypt their keys, so we'll get them directly from the repo.list(...) - if err := k.repo.list(ctx, &rkVersions, "root_key_id = ?", []interface{}{dbKey.PrivateId}, withOrderByVersion(ascendingOrderBy), withTableName(rkv.TableName())); err != nil { - return nil, fmt.Errorf("%s: unable to list root key versions: %w", op, err) - } - rk := newKeyFromRootKey(dbKey) - for _, rkv := range rkVersions { - rk.Versions = append(rk.Versions, KeyVersion{ - Id: rkv.PrivateId, - Version: uint(rkv.Version), - CreateTime: rkv.CreateTime, - }) - } - - dataKeys, err := k.repo.ListDataKeys(ctx, withRootKeyId(rk.Id)) - if err != nil { - return nil, fmt.Errorf("%s: unable to list data keys: %w", op, err) - } - dkv := dataKeyVersion{ - tableNamePrefix: k.tableNamePrefix, - } - keys := []Key{rk} - for _, dk := range dataKeys { - dataKey := newKeyFromDataKey(dk, rk.Scope) - var versions []*dataKeyVersion - // we don't need to decrypt their keys, so we'll get them directly from the repo.list(...) - err := k.repo.list(ctx, &versions, "data_key_id = ?", []interface{}{dk.GetPrivateId()}, withOrderByVersion(ascendingOrderBy), withTableName(dkv.TableName())) - if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - for _, dkv := range versions { - dataKey.Versions = append(dataKey.Versions, KeyVersion{ - Id: dkv.PrivateId, - Version: uint(dkv.Version), - CreateTime: dkv.CreateTime, - }) - } - keys = append(keys, dataKey) - } - return keys, nil -} - -// CreateKeys creates the root key and DEKs for the given scope id. By -// default, CreateKeys manages its own transaction (begin/rollback/commit). -// -// It's valid to provide no KeyPurposes (nil or empty), which means that the -// scope will only end up with a root key (and one rk version) with no DEKs. -// -// CreateKeys also supports the WithTx(...) and WithReaderWriter(...) options -// which allows the caller to pass an inflight transaction to be used for all -// database operations. If WithTx(...) or WithReaderWriter(...) are used, then -// the caller is responsible for managing the transaction. The purpose of the -// WithTx(...) and WithReaderWriter(...) options are to allow the caller to -// create the scope and all of its keys in the same transaction. -// -// The WithRandomReader(...) option is supported as well. If no optional -// random reader is provided, then the reader from crypto/rand will be used as -// a default. -func (k *Kms) CreateKeys(ctx context.Context, scopeId string, purposes []KeyPurpose, opt ...Option) error { - const op = "kms.(Kms).CreateKeys" - if scopeId == "" { - return fmt.Errorf("%s: missing scope id: %w", op, ErrInvalidParameter) - } - for _, p := range purposes { - if !purposeListContains(k.purposes, p) { - return fmt.Errorf("%s: not a supported key purpose %q: %w", op, p, ErrInvalidParameter) - } - } - - rootWrapper, err := k.GetExternalRootWrapper() - if err != nil { - return fmt.Errorf("%s: %w", op, err) - } - - r, w, localTx, err := k.txFromOpts(ctx, opt...) - if err != nil { - return fmt.Errorf("%s: %w", op, err) - } - - if err := updateKeyCollectionVersion(ctx, w, k.tableNamePrefix); err != nil { - return fmt.Errorf("%s: %w", op, err) - } - - opts := getOpts(opt...) - if _, err := createKeysTx(ctx, r, w, rootWrapper, opts.withRandomReader, k.tableNamePrefix, scopeId, purposes...); err != nil { - if localTx != nil { - if rollBackErr := localTx.Rollback(ctx); rollBackErr != nil { - err = multierror.Append(err, rollBackErr) - } - } - return fmt.Errorf("%s: %w", op, err) - } - if localTx != nil { - if err := localTx.Commit(ctx); err != nil { - return fmt.Errorf("%s: unable to commit transaction: %w", op, err) - } - } - - return nil -} - -// RevokeKey will revoke (remove) a key version. -// Deprecated: use RevokeKeyVersion instead. -func (k *Kms) RevokeKey(ctx context.Context, keyVersionId string) error { - return k.RevokeKeyVersion(ctx, keyVersionId) -} - -// RevokeKeyVersion will revoke (remove) a key version. Be sure to rotate and rewrap KEK -// versions before revoking them. If it's a DEK, then you need to re-encrypt all -// data that was encrypted with the key version before revoking it. You must have -// foreign key restrictions between DEK key version IDs -// (kms_data_key_version.private_id) and columns in your tables which store the -// wrapper key ID used for encrypt/decrypt operations, otherwise you could lose -// access to your encrypted data when you revoke a DEK version that's still being used. -func (k *Kms) RevokeKeyVersion(ctx context.Context, keyVersionId string) error { - const op = "kms.(Kms).RevokeKeyVersion" - switch { - case keyVersionId == "": - return fmt.Errorf("%s: missing key version id: %w", op, ErrInvalidParameter) - case strings.HasPrefix(keyVersionId, rootKeyVersionPrefix): - if err := k.revokeRootKeyVersion(ctx, keyVersionId); err != nil { - return fmt.Errorf("%s: %w", op, err) - } - return nil - case strings.HasPrefix(keyVersionId, dataKeyVersionPrefix): - if err := k.revokeDataKeyVersion(ctx, keyVersionId); err != nil { - return fmt.Errorf("%s: %w", op, err) - } - return nil - default: - return fmt.Errorf("%s: not a valid key version id: %w", op, ErrInvalidParameter) - } -} - -// revokeRootKeyVersion will revoke (remove) a root key version. Be sure to -// rotate and rewrap the keys before revoking a root key version. -func (k *Kms) revokeRootKeyVersion(ctx context.Context, keyVersionId string) error { - const op = "kms.(Kms).RevokeRootKeyVersion" - switch { - case keyVersionId == "": - return fmt.Errorf("%s: missing key version id: %w", op, ErrInvalidParameter) - } - rowsDeleted, err := k.repo.DeleteRootKeyVersion(ctx, keyVersionId) - switch { - case err != nil: - return fmt.Errorf("%s: unable to revoke root key version: %w", op, err) - case rowsDeleted == 0: - return fmt.Errorf("%s: unable to revoke root key version: %w", op, ErrKeyNotFound) - } - return nil -} - -// revokeDataKeyVersion will revoke (remove) a data key version (DEK). Be sure -// to rotate the DEKs and re-encrypt all data that uses a data key version (DEK) -// before revoking it. You must have foreign key restrictions between DEK key -// IDs (kms_data_key_version.private_id) and columns in your tables which store -// the wrapper key ID used for encrypt/decrypt operations. -func (k *Kms) revokeDataKeyVersion(ctx context.Context, keyVersionId string) error { - const op = "kms.(Kms).RevokeDataKeyVersion" - switch { - case keyVersionId == "": - return fmt.Errorf("%s: missing key version id: %w", op, ErrInvalidParameter) - } - rowsDeleted, err := k.repo.DeleteDataKeyVersion(ctx, keyVersionId) - switch { - case err != nil: - return fmt.Errorf("%s: unable to revoke data key version: %w", op, err) - case rowsDeleted == 0: - return fmt.Errorf("%s: unable to revoke data key version: %w", op, ErrKeyNotFound) - } - return nil -} - -// RotateKeys will create new versions for the KEK and all DEKs -// in the scope identified by the current KeyPurpose(s) of the KMS. -// -// If an optional WithRewrap(...) is requested, then all existing KEK versions -// in the scope will be re-encrypted with the external root wrapper and -// all DEK versions in the scope will be re-encrypted with the new KEK version. -// -// WithTx(...) and WithReaderWriter(...) options are supported which allow the -// caller to pass an inflight transaction to be used for all database -// operations. If WithTx(...) or WithReaderWriter(...) are used, then the -// caller is responsible for managing the transaction. If neither WithTx or -// WithReaderWriter are specified, then RotateKeys will rotate the scope's keys -// within its own transaction, which will be managed by RotateKeys. -// -// The WithRandomReader(...) option is supported. If no optional random reader -// is provided, then the reader from crypto/rand will be used as a default. -// -// Options supported: WithRandomReader, WithTx, WithRewrap, WithReaderWriter -func (k *Kms) RotateKeys(ctx context.Context, scopeId string, opt ...Option) error { - const op = "kms.(Kms).RotateKeys" - if scopeId == "" { - return fmt.Errorf("%s: missing scope id: %w", op, ErrInvalidParameter) - } - rootWrapper, err := k.GetExternalRootWrapper() - if err != nil { - return fmt.Errorf("%s: unable to get an external root wrapper: %w", op, err) - } - - reader, writer, localTx, err := k.txFromOpts(ctx, opt...) - if err != nil { - return fmt.Errorf("%s: %w", op, err) - } - - if err := updateKeyCollectionVersion(ctx, writer, k.tableNamePrefix); err != nil { - return fmt.Errorf("%s: %w", op, err) - } - - // since we could have started a local txn, we'll use an anon function for - // all the stmts which should be managed within that possible local txn. - if err := func() error { - rk, err := k.repo.LookupRootKeyByScope(ctx, scopeId, WithReader(reader)) - if err != nil { - return fmt.Errorf("%s: unable to load the scope's root key: %w", op, err) - } - - opts := getOpts(opt...) - if opts.withRewrap { - // rewrap the root key versions with the provided rootWrapper (assuming - // it has a new wrapper) - if err := rewrapRootKeyVersionsTx(ctx, reader, writer, rootWrapper, rk.PrivateId, WithTableNamePrefix(k.tableNamePrefix)); err != nil { - return fmt.Errorf("%s: unable to rewrap root key versions: %w", op, err) - } - } - - // rotate the root key version (adding a new version) - rkv, err := rotateRootKeyVersionTx(ctx, writer, rootWrapper, rk.PrivateId, WithRandomReader(opts.withRandomReader), WithTableNamePrefix(k.tableNamePrefix)) - if err != nil { - return fmt.Errorf("%s: unable to rotate root key version: %w", op, err) - } - - rkvWrapper, _, err := k.loadRoot(ctx, scopeId, WithReader(reader)) - if err != nil { - return fmt.Errorf("%s: unable to load the root key version wrapper: %w", op, err) - } - - // we've got a new rootKeyVersion wrapper, so let's rewrap the existing DEKs. - if opts.withRewrap { - if err := rewrapDataKeyVersionsTx(ctx, reader, writer, k.tableNamePrefix, rkvWrapper, rk.PrivateId); err != nil { - return fmt.Errorf("%s: unable to rewrap data key versions: %w", op, err) - } - } - - // finally, let's rotate the scope's DEKs - for _, purpose := range k.purposes { - if purpose == KeyPurposeRootKey { - continue - } - if err := rotateDataKeyVersionTx(ctx, reader, writer, k.tableNamePrefix, rkv.PrivateId, rkvWrapper, rk.PrivateId, purpose, WithRandomReader(opts.withRandomReader)); err != nil { - return fmt.Errorf("%s: unable to rotate data key version: %w", op, err) - } - } - return nil - }(); err != nil { - if localTx != nil { - if rollBackErr := localTx.Rollback(ctx); rollBackErr != nil { - err = multierror.Append(err, rollBackErr) - } - } - return fmt.Errorf("%s: %w", op, err) - } - if localTx != nil { - if err := localTx.Commit(ctx); err != nil { - return fmt.Errorf("%s: unable to commit transaction: %w", op, err) - } - } - return nil -} - -// RewrapKeys will re-encrypt all versions of a scope's KEK with the external -// root key wrapper and re-encrypt all versions of a scopes DEKs with the latest -// KEK version. If you wish to rewrap and rotate keys, then use the RotateKeys function. -// -// WithTx(...) and WithReaderWriter(...) options are supported which allow the -// caller to pass an inflight transaction to be used for all database -// operations. If WithTx(...) or WithReaderWriter(...) are used, then the -// caller is responsible for managing the transaction. If neither WithTx or -// WithReaderWriter are specified, then RotateKeys will rotate the scope's keys -// within its own transaction, which will be managed by RewrapKeys. -// -// The WithRandomReader(...) option is supported. If no optional random reader -// is provided, then the reader from crypto/rand will be used as a default. -// -// Options supported: WithRandomReader, WithTx, WithReaderWriter -func (k *Kms) RewrapKeys(ctx context.Context, scopeId string, opt ...Option) error { - const op = "kms.(Kms).RewrapKeys" - if scopeId == "" { - return fmt.Errorf("%s: missing scope id: %w", op, ErrInvalidParameter) - } - rootWrapper, err := k.GetExternalRootWrapper() - if err != nil { - return fmt.Errorf("%s: unable to get an external root wrapper: %w", op, err) - } - - reader, writer, localTx, err := k.txFromOpts(ctx, opt...) - if err != nil { - return fmt.Errorf("%s: %w", op, err) - } - - if err := updateKeyCollectionVersion(ctx, writer, k.tableNamePrefix); err != nil { - return fmt.Errorf("%s: %w", op, err) - } - - // since we could have started a local txn, we'll use an anon function for - // all the stmts which should be managed within that possible local txn. - if err := func() error { - rk, err := k.repo.LookupRootKeyByScope(ctx, scopeId, WithReader(reader)) - if err != nil { - return fmt.Errorf("%s: unable to load the scope's root key: %w", op, err) - } - - // rewrap the root key versions with the provided rootWrapper (assuming - // it has a new wrapper) - if err := rewrapRootKeyVersionsTx(ctx, reader, writer, rootWrapper, rk.PrivateId, WithTableNamePrefix(k.tableNamePrefix)); err != nil { - return fmt.Errorf("%s: unable to rewrap root key versions: %w", op, err) - } - - rkvWrapper, _, err := k.loadRoot(ctx, scopeId, WithReader(reader)) - if err != nil { - return fmt.Errorf("%s: unable to load the root key version wrapper: %w", op, err) - } - - // we've got a new rootKeyVersion wrapper, so let's rewrap the existing DEKs. - if err := rewrapDataKeyVersionsTx(ctx, reader, writer, k.tableNamePrefix, rkvWrapper, rk.PrivateId); err != nil { - return fmt.Errorf("%s: unable to rewrap data key versions: %w", op, err) - } - - return nil - }(); err != nil { - if localTx != nil { - if rollBackErr := localTx.Rollback(ctx); rollBackErr != nil { - err = multierror.Append(err, rollBackErr) - } - } - return fmt.Errorf("%s: %w", op, err) - } - if localTx != nil { - if err := localTx.Commit(ctx); err != nil { - return fmt.Errorf("%s: unable to commit transaction: %w", op, err) - } - } - return nil -} - -// txFromOpts will determine the correct transaction strategy given the provided -// options. Options supported: WithReaderWriter, WithTx -func (k *Kms) txFromOpts(ctx context.Context, opt ...Option) (dbw.Reader, dbw.Writer, *dbw.RW, error) { - const op = "kms.(Kms).getTxFromOpt" - var localTx *dbw.RW - var r dbw.Reader - var w dbw.Writer - opts := getOpts(opt...) - switch { - case opts.withTx != nil: - if opts.withReader != nil || opts.withWriter != nil { - return nil, nil, nil, fmt.Errorf("%s: WithTx(...) and WithReaderWriter(...) options cannot be used at the same time: %w", op, ErrInvalidParameter) - } - if ok := opts.withTx.IsTx(); !ok { - return nil, nil, nil, fmt.Errorf("%s: provided transaction has no inflight transaction: %w", op, ErrInvalidParameter) - } - r = opts.withTx - w = opts.withTx - case opts.withReader != nil, opts.withWriter != nil: - if opts.withTx != nil { - return nil, nil, nil, fmt.Errorf("%s: WithTx(...) and WithReaderWriter(...) options cannot be used at the same time: %w", op, ErrInvalidParameter) - } - if opts.withReader == nil { - return nil, nil, nil, fmt.Errorf("%s: WithReaderWriter(...) option is missing the reader: %w", op, ErrInvalidParameter) - } - if opts.withWriter == nil { - return nil, nil, nil, fmt.Errorf("%s: WithReaderWriter(...) option is missing the writer: %w", op, ErrInvalidParameter) - } - r = opts.withReader - w = opts.withWriter - default: - var err error - localTx, err = k.repo.writer.Begin(ctx) - if err != nil { - return nil, nil, nil, fmt.Errorf("%s: unable to begin transaction: %w", op, err) - } - r = localTx - w = localTx - } - return r, w, localTx, nil -} - -// ValidateSchema will validate the database schema against the module's -// required migrations.Version -func (k *Kms) ValidateSchema(ctx context.Context) (string, error) { - const op = "kms.(Kms).ValidateVersion" - return k.repo.ValidateSchema(ctx) -} - -// ReconcileKeys will reconcile the keys in the kms against known possible -// issues. This function reconciles the global scope unless the -// WithScopeIds(...) option is provided -// -// The WithRandomReader(...) option is supported as well. If an optional -// random reader is not provided (is nill), then the reader from crypto/rand -// will be used as a default. -func (k *Kms) ReconcileKeys(ctx context.Context, scopeIds []string, purposes []KeyPurpose, opt ...Option) error { - const op = "kms.(Kms).ReconcileKeys" - if len(scopeIds) == 0 { - return fmt.Errorf("%s: missing scope ids: %w", op, ErrInvalidParameter) - } - for _, p := range purposes { - if !purposeListContains(k.purposes, p) { - return fmt.Errorf("%s: not a supported key purpose %q: %w", op, p, ErrInvalidParameter) - } - } - - opts := getOpts(opt...) - for _, id := range scopeIds { - var scopeRootWrapper *multi.PooledWrapper - - for _, purpose := range purposes { - if _, err := k.GetWrapper(ctx, id, purpose); err != nil { - switch { - case errors.Is(err, ErrKeyNotFound): - if scopeRootWrapper == nil { - if scopeRootWrapper, _, err = k.loadRoot(ctx, id); err != nil { - return fmt.Errorf("%s: %w", op, err) - } - } - key, err := generateKey(ctx, opts.withRandomReader) - if err != nil { - return fmt.Errorf("%s: error generating random bytes for %q key in scope %q: %w", op, purpose, id, err) - } - - if _, _, err := k.repo.CreateDataKey(ctx, scopeRootWrapper, purpose, key); err != nil { - return fmt.Errorf("%s: error creating %q key in scope %s: %w", op, purpose, id, err) - } - default: - return fmt.Errorf("%s: %w", op, err) - } - } - } - } - return nil -} - -// ListDataKeyVersionReferencers will lists the names of all tables -// referencing the private_id column of the data key version table. -// This can be useful when re-encrypting data that references a specific -// data key version by private_id. -// Supported options: -// - WithTx -// - WithReaderWriter -func (k *Kms) ListDataKeyVersionReferencers(ctx context.Context, opt ...Option) ([]string, error) { - return k.repo.ListDataKeyVersionReferencers(ctx, opt...) -} - -func (k *Kms) loadRoot(ctx context.Context, scopeId string, opt ...Option) (_ *multi.PooledWrapper, rootKeyId string, _ error) { - const op = "kms.(Kms).loadRoot" - if scopeId == "" { - return nil, "", fmt.Errorf("%s: missing scope id: %w", op, ErrInvalidParameter) - } - rk, err := k.repo.LookupRootKeyByScope(ctx, scopeId, opt...) - if err != nil { - if errors.Is(err, dbw.ErrRecordNotFound) { - return nil, "", fmt.Errorf("%s: missing root key for scope %q: %w", op, scopeId, ErrKeyNotFound) - } - return nil, "", fmt.Errorf("%s: unable to find root key for scope %q: %w", op, scopeId, err) - } - rootKeyId = rk.PrivateId - // Now: find the external KMS that can be used to decrypt the root values - // from the DB. - externalRootWrapper, err := k.GetExternalRootWrapper() - if err != nil { - return nil, "", fmt.Errorf("%s: missing root key wrapper for scope %q: %w", op, scopeId, ErrKeyNotFound) - } - opts := getOpts(opt...) - rootKeyVersions, err := k.repo.ListRootKeyVersions(ctx, externalRootWrapper, rootKeyId, withOrderByVersion(descendingOrderBy), WithReader(opts.withReader)) - if err != nil { - return nil, "", fmt.Errorf("%s: error looking up root key versions for scope %q: %w", op, scopeId, err) - } - if len(rootKeyVersions) == 0 { - return nil, "", fmt.Errorf("%s: no root key versions found for scope %q: %w", op, scopeId, ErrKeyNotFound) - } - - var pooled *multi.PooledWrapper - for i, keyVersion := range rootKeyVersions { - var err error - wrapper := aead.NewWrapper() - if _, err = wrapper.SetConfig(ctx, wrapping.WithKeyId(keyVersion.GetPrivateId())); err != nil { - return nil, "", fmt.Errorf("%s: error setting config on aead root wrapper in scope %q: %w", op, scopeId, err) - } - if err = wrapper.SetAesGcmKeyBytes(keyVersion.Key); err != nil { - return nil, "", fmt.Errorf("%s: error setting key bytes on aead root wrapper in scope %q: %w", op, scopeId, err) - } - if i == 0 { - pooled, err = multi.NewPooledWrapper(ctx, wrapper) - if err != nil { - return nil, "", fmt.Errorf("%s: error getting root pooled wrapper for key version 0 in scope %q: %w", op, scopeId, err) - } - } else { - _, err = pooled.AddWrapper(ctx, wrapper) - if err != nil { - return nil, "", fmt.Errorf("%s: error adding pooled wrapper for key version %d in scope %q: %w", op, i, scopeId, err) - } - } - } - - return pooled, rootKeyId, nil -} - -func (k *Kms) loadDek(ctx context.Context, scopeId string, purpose KeyPurpose, rootWrapper wrapping.Wrapper, rootKeyId string, opt ...Option) (*multi.PooledWrapper, error) { - const op = "kms.(Kms).loadDek" - if scopeId == "" { - return nil, fmt.Errorf("%s: missing scope id: %w", op, ErrInvalidParameter) - } - if rootWrapper == nil { - return nil, fmt.Errorf("%s: nil root wrapper for scope %q: %w", op, scopeId, ErrInvalidParameter) - } - if rootKeyId == "" { - return nil, fmt.Errorf("%s: missing root key ID for scope %q: %w", op, scopeId, ErrInvalidParameter) - } - if !purposeListContains(k.purposes, purpose) { - return nil, fmt.Errorf("%s: not a supported key purpose %q: %w", op, purpose, ErrInvalidParameter) - } - - opts := getOpts(opt...) - - keys, err := k.repo.ListDataKeys(ctx, withPurpose(purpose), withRootKeyId(rootKeyId), WithReader(opts.withReader)) - if err != nil { - return nil, fmt.Errorf("%s: error listing keys for purpose %q: %w", op, purpose, err) - } - var keyId string - switch { - case len(keys) == 0: - return nil, fmt.Errorf("%s: error finding %q key for scope %q: %w", op, purpose, scopeId, ErrKeyNotFound) - case len(keys) > 1: - return nil, fmt.Errorf("%s: found %q data keys for %q purpose: %w", op, len(keys), purpose, ErrInternal) - default: - keyId = keys[0].GetPrivateId() - } - keyVersions, err := k.repo.ListDataKeyVersions(ctx, rootWrapper, keyId, withOrderByVersion(descendingOrderBy), WithReader(opts.withReader)) - if err != nil { - return nil, fmt.Errorf("%s: error looking up %q key versions for scope %q: %w", op, purpose, scopeId, err) - } - if len(keyVersions) == 0 { - return nil, fmt.Errorf("%s: no %q key versions found for scope %q: %w", op, purpose, scopeId, ErrKeyNotFound) - } - - var pooled *multi.PooledWrapper - for i, keyVersion := range keyVersions { - var err error - wrapper := aead.NewWrapper() - if _, err = wrapper.SetConfig(ctx, wrapping.WithKeyId(keyVersion.GetPrivateId())); err != nil { - return nil, fmt.Errorf("%s: error setting config on aead %q wrapper in scope %q: %w", op, purpose, scopeId, err) - } - if err = wrapper.SetAesGcmKeyBytes(keyVersion.GetKey()); err != nil { - return nil, fmt.Errorf("%s: error setting key bytes on aead %q wrapper in scope %q: %w", op, purpose, scopeId, err) - } - if i == 0 { - pooled, err = multi.NewPooledWrapper(ctx, wrapper) - if err != nil { - return nil, fmt.Errorf("%s: error getting %q pooled wrapper for key version 0 in scope %q: %w", op, purpose, scopeId, err) - } - } else { - _, err = pooled.AddWrapper(ctx, wrapper) - if err != nil { - return nil, fmt.Errorf("%s: error getting %q pooled wrapper for key version %q in scope %q: %w", op, purpose, i, scopeId, err) - } - } - } - - return pooled, nil -} - -func isNil(i interface{}) bool { - if i == nil { - return true - } - switch reflect.TypeOf(i).Kind() { - case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice: - return reflect.ValueOf(i).IsNil() - } - return false -} - -func scopedPurpose(scopeId string, purpose KeyPurpose) string { - return scopeId + string(purpose) -} diff --git a/extras/kms/kms_internal_test.go b/extras/kms/kms_internal_test.go deleted file mode 100644 index 540bf2bd..00000000 --- a/extras/kms/kms_internal_test.go +++ /dev/null @@ -1,1629 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "crypto/rand" - "encoding/base64" - "errors" - "sort" - "testing" - "time" - - "github.com/DATA-DOG/go-sqlmock" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/go-dbw" - "github.com/openbao/go-kms-wrapping/extras/kms/v2/migrations" - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/aead" - "github.com/openbao/go-kms-wrapping/v2/extras/multi" - "github.com/hashicorp/go-uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" -) - -func TestKms_loadDek(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - rk := testRootKey(t, db, "global") - _, rkw := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - dk := testDataKey(t, db, rk.PrivateId, "database") - const ( - testKey1 = "1234567890123456" - testKey2 = "2234567890123456" - ) - _ = testDataKeyVersion(t, db, rkw, dk.PrivateId, []byte(testKey1)) - dkv := testDataKeyVersion(t, db, rkw, dk.PrivateId, []byte(testKey2)) - - tests := []struct { - name string - kms *Kms - scopeId string - purpose KeyPurpose - rootWrapper wrapping.Wrapper - rootKeyId string - want []byte - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-scope-id", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database"}) - require.NoError(t, err) - return k - }(), - purpose: "database", - rootWrapper: rkw, - rootKeyId: rk.PrivateId, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing scope id", - }, - { - name: "missing-wrapper", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database"}) - require.NoError(t, err) - return k - }(), - scopeId: "global", - purpose: "database", - rootKeyId: rk.PrivateId, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "nil root wrapper for scope", - }, - { - name: "missing-root-key-id", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database"}) - require.NoError(t, err) - return k - }(), - scopeId: "global", - purpose: "database", - rootWrapper: rkw, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key ID for scope", - }, - { - name: "invalid-purpose", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database"}) - require.NoError(t, err) - return k - }(), - scopeId: "global", - purpose: "invalid-purpose", - rootWrapper: rkw, - rootKeyId: rk.PrivateId, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "not a supported key purpose", - }, - { - name: "list-keys-error", - kms: func() *Kms { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("list-keys-error")) - k, err := New(rw, rw, []KeyPurpose{"database"}) - require.NoError(t, err) - return k - }(), - scopeId: "global", - purpose: "database", - rootWrapper: rkw, - rootKeyId: rk.PrivateId, - wantErr: true, - wantErrContains: "list-keys-error", - }, - { - name: "list-keys-not-found", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - return k - }(), - scopeId: "global", - purpose: "auth", - rootWrapper: rkw, - rootKeyId: rk.PrivateId, - wantErr: true, - wantErrIs: ErrKeyNotFound, - wantErrContains: "key not found", - }, - { - name: "list-key-version-error", - kms: func() *Kms { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"private_id", "root_key_id", "create_time"}).AddRow(dk.PrivateId, rk.PrivateId, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("list-key-version-error")) - k, err := New(rw, rw, []KeyPurpose{"database"}) - require.NoError(t, err) - return k - }(), - scopeId: "global", - purpose: "database", - rootWrapper: rkw, - rootKeyId: rk.PrivateId, - wantErr: true, - wantErrContains: "list-key-version-error", - }, - { - name: "list-key-version-no-rows", - kms: func() *Kms { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"private_id", "root_key_id", "create_time"}).AddRow(dk.PrivateId, rk.PrivateId, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"private_id", "root_key_id", "create_time"})) - k, err := New(rw, rw, []KeyPurpose{"database"}) - require.NoError(t, err) - return k - }(), - scopeId: "global", - purpose: "database", - rootWrapper: rkw, - rootKeyId: rk.PrivateId, - wantErr: true, - wantErrContains: "key not found", - }, - { - name: "success", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database"}) - require.NoError(t, err) - return k - }(), - scopeId: "global", - purpose: "database", - rootWrapper: rkw, - rootKeyId: rk.PrivateId, - want: dkv.Key, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - got, err := tc.kms.loadDek(testCtx, tc.scopeId, tc.purpose, tc.rootWrapper, tc.rootKeyId) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - gotKey, err := got.KeyBytes(testCtx) - require.NoError(err) - assert.Equal(tc.want, gotKey) - }) - } -} - -func TestKms_loadRoot(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - rk := testRootKey(t, db, "global") - - rkv1, rkw1 := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - rkv2, rkw2 := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - - tests := []struct { - name string - kms *Kms - scopeId string - opt []Option - want *multi.PooledWrapper - wantRootKeyId string - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-scope-id", - kms: func() *Kms { - k, err := New(rw, rw, nil) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing scope id", - }, - { - name: "list-keys-error", - kms: func() *Kms { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("list-keys-error")) - k, err := New(rw, rw, nil) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - scopeId: "global", - wantErr: true, - wantErrContains: "list-keys-error", - }, - { - name: "missing-root-key-for-scope", - kms: func() *Kms { - k, err := New(rw, rw, nil) - require.NoError(t, err) - return k - }(), - scopeId: "invalid-scope", - wantErr: true, - wantErrIs: ErrKeyNotFound, - wantErrContains: "missing root key for scope", - }, - { - name: "missing-root-key-wrapper-for-scope", - kms: func() *Kms { - k, err := New(rw, rw, nil) - require.NoError(t, err) - return k - }(), - scopeId: "global", - wantErr: true, - wantErrIs: ErrKeyNotFound, - wantErrContains: "missing root key wrapper for scope", - }, - { - name: "list-key-version-error", - kms: func() *Kms { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"private_id", "root_key_id", "scope_id", "create_time"}).AddRow(rk.PrivateId, rk.PrivateId, "global", time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("list-key-version-error")) - k, err := New(rw, rw, nil) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - scopeId: "global", - wantErr: true, - wantErrContains: "list-key-version-error", - }, - { - name: "list-key-version-no-rows", - kms: func() *Kms { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"private_id", "root_key_id", "scope_id", "create_time"}).AddRow(rk.PrivateId, rk.PrivateId, "global", time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"private_id", "root_key_id", "create_time"})) - k, err := New(rw, rw, nil) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - scopeId: "global", - wantErr: true, - wantErrContains: "key not found", - }, - { - name: "success", - kms: func() *Kms { - k, err := New(rw, rw, nil) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - scopeId: "global", - want: func() *multi.PooledWrapper { - var pool *multi.PooledWrapper - for _, wrapper := range []wrapping.Wrapper{rkw1, rkw2} { - w := aead.NewWrapper() - keyVersionId, err := wrapper.KeyId(testCtx) - require.NoError(t, err) - _, err = w.SetConfig(testCtx, wrapping.WithKeyId(keyVersionId)) - require.NoError(t, err) - err = w.SetAesGcmKeyBytes([]byte(testDefaultWrapperSecret)) - require.NoError(t, err) - switch pool { - case nil: - pool, err = multi.NewPooledWrapper(testCtx, w) - require.NoError(t, err) - default: - pool.AddWrapper(testCtx, w) - } - require.NoError(t, err) - } - return pool - }(), - wantRootKeyId: rkv2.RootKeyId, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - gotWrapper, gotKeyId, err := tc.kms.loadRoot(testCtx, tc.scopeId) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.want.WrapperForKeyId(rkv1.PrivateId), gotWrapper.WrapperForKeyId(rkv1.PrivateId)) - assert.Equal(tc.want.WrapperForKeyId(rkv2.PrivateId), gotWrapper.WrapperForKeyId(rkv2.PrivateId)) - blob, err := gotWrapper.Encrypt(testCtx, []byte("test")) - require.NoError(err) - decrypted, err := tc.want.Decrypt(testCtx, blob) - require.NoError(err) - assert.Equal([]byte("test"), decrypted) - assert.Equal(tc.wantRootKeyId, gotKeyId) - }) - } -} - -func TestKms_KeyId(t *testing.T) { - t.Parallel() - require := require.New(t) - ctx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - extWrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - repo, err := newRepository(rw, rw) - require.NoError(err) - - const globalScope = "global" - databaseKeyPurpose := KeyPurpose("database") - - // Get the global scope's root wrapper - kmsCache, err := New(rw, rw, []KeyPurpose{databaseKeyPurpose}) - require.NoError(err) - require.NoError(kmsCache.AddExternalWrapper(ctx, KeyPurposeRootKey, extWrapper)) - // Make the global scope base keys - err = kmsCache.CreateKeys(ctx, globalScope, []KeyPurpose{databaseKeyPurpose}) - require.NoError(err) - globalRootWrapper, _, err := kmsCache.loadRoot(ctx, globalScope) - require.NoError(err) - - dks, err := repo.ListDataKeys(ctx) - require.NoError(err) - require.Len(dks, 1) - - // Create another key version - newKeyBytes, err := uuid.GenerateRandomBytes(32) - require.NoError(err) - _, err = repo.CreateDataKeyVersion(ctx, globalRootWrapper, dks[0].GetPrivateId(), newKeyBytes) - require.NoError(err) - - dkvs, err := repo.ListDataKeyVersions(ctx, globalRootWrapper, dks[0].GetPrivateId()) - require.NoError(err) - require.Len(dkvs, 2) - - keyVersionId1 := dkvs[0].GetPrivateId() - keyVersionId2 := dkvs[1].GetPrivateId() - - // First test: just getting the key should return the latest - wrapper, err := kmsCache.GetWrapper(ctx, globalScope, databaseKeyPurpose) - require.NoError(err) - tKeyVersionId, err := wrapper.KeyId(context.Background()) - require.NoError(err) - require.Equal(keyVersionId2, tKeyVersionId) - - // Second: ask for each in turn - wrapper, err = kmsCache.GetWrapper(ctx, globalScope, databaseKeyPurpose, WithKeyVersionId(keyVersionId1)) - require.NoError(err) - tKeyVersionId, err = wrapper.KeyId(context.Background()) - require.NoError(err) - require.Equal(keyVersionId1, tKeyVersionId) - wrapper, err = kmsCache.GetWrapper(ctx, globalScope, databaseKeyPurpose, WithKeyVersionId(keyVersionId2)) - require.NoError(err) - tKeyVersionId, err = wrapper.KeyId(context.Background()) - require.NoError(err) - require.Equal(keyVersionId2, tKeyVersionId) - - // Last: verify something bogus finds nothing - _, err = kmsCache.GetWrapper(ctx, globalScope, databaseKeyPurpose, WithKeyVersionId("foo")) - require.Error(err) - - // empty cache and pull from database - kmsCache, err = New(rw, rw, []KeyPurpose{"database"}) - require.NoError(err) - require.NoError(kmsCache.AddExternalWrapper(ctx, KeyPurposeRootKey, extWrapper)) - // ask for each in turn - wrapper, err = kmsCache.GetWrapper(ctx, globalScope, databaseKeyPurpose, WithKeyVersionId(keyVersionId1)) - require.NoError(err) - tKeyVersionId, err = wrapper.KeyId(context.Background()) - require.NoError(err) - require.Equal(keyVersionId1, tKeyVersionId) - wrapper, err = kmsCache.GetWrapper(ctx, globalScope, databaseKeyPurpose, WithKeyVersionId(keyVersionId2)) - require.NoError(err) - tKeyVersionId, err = wrapper.KeyId(context.Background()) - require.NoError(err) - require.Equal(keyVersionId2, tKeyVersionId) -} - -func TestKms_ClearCache(t *testing.T) { - t.Parallel() - require := require.New(t) - ctx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - extWrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - - const ( - globalScope = "global" - orgScope = "o_1234567890" - ) - databaseKeyPurpose := KeyPurpose("database") - - // init kms with a cache - kmsCache, err := New(rw, rw, []KeyPurpose{"database"}) - require.NoError(err) - require.NoError(kmsCache.AddExternalWrapper(ctx, KeyPurposeRootKey, extWrapper)) - // Make the global scope base keys - err = kmsCache.CreateKeys(ctx, globalScope, []KeyPurpose{databaseKeyPurpose}) - require.NoError(err) - assertCacheEqual(t, 0, kmsCache) - - // First test: just getting the wrapper - _, err = kmsCache.GetWrapper(ctx, globalScope, databaseKeyPurpose) - require.NoError(err) - assertCacheEqual(t, 1, kmsCache) - - require.NoError(kmsCache.clearCache(ctx)) - assertCacheEqual(t, 0, kmsCache) - - // delete all the keys (increment version) - testDeleteWhere(t, db, &rootKey{tableNamePrefix: DefaultTableNamePrefix}, "1=1") - - // should now fail to get the wrapper. - _, err = kmsCache.GetWrapper(ctx, globalScope, databaseKeyPurpose) - require.Error(err) - - // Make the global scope base keys again - err = kmsCache.CreateKeys(ctx, globalScope, []KeyPurpose{databaseKeyPurpose}) - require.NoError(err) - // add in an org scope - err = kmsCache.CreateKeys(ctx, orgScope, []KeyPurpose{databaseKeyPurpose}) - require.NoError(err) - - // can we get them... - globalWrapper, err := kmsCache.GetWrapper(ctx, globalScope, databaseKeyPurpose) - require.NoError(err) - assertCacheEqual(t, 1, kmsCache) - - orgWrapper, err := kmsCache.GetWrapper(ctx, orgScope, databaseKeyPurpose) - require.NoError(err) - assertCacheEqual(t, 2, kmsCache) - - assert.NotEqual(t, globalWrapper, orgWrapper) - - kmsCache.clearCache(ctx, WithScopeIds(orgScope)) - assertCacheEqual(t, 1, kmsCache) - - // re-init kms - kmsCache, err = New(rw, rw, []KeyPurpose{"database"}) - require.NoError(err) - require.NoError(kmsCache.AddExternalWrapper(ctx, KeyPurposeRootKey, extWrapper)) - _, err = kmsCache.GetWrapper(ctx, orgScope, databaseKeyPurpose) - require.NoError(err) - assertCacheEqual(t, 1, kmsCache) - require.NoError(kmsCache.clearCache(ctx)) -} - -func TestKms_RotateKeys(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - - newWrapper := func(id string) wrapping.Wrapper { - tmpWrapper := aead.NewWrapper() - _, err := tmpWrapper.SetConfig(testCtx, wrapping.WithKeyId(id)) - require.NoError(t, err) - key, err := generateKey(testCtx, rand.Reader) - require.NoError(t, err) - err = tmpWrapper.SetAesGcmKeyBytes(key) - require.NoError(t, err) - return tmpWrapper - } - - wrapper, err := multi.NewPooledWrapper(testCtx, newWrapper("1")) - require.NoError(t, err) - - tests := []struct { - name string - kms *Kms - scopeId string - rewrap bool - opt []Option - setup func(k *Kms, scopeId string) - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-scope-id", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing scope id", - }, - { - name: "missing-external-root-wrapper", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - return k - }(), - scopeId: "global", - wantErr: true, - wantErrIs: ErrKeyNotFound, - wantErrContains: "missing external root wrapper", - }, - { - name: "scope-root-key-error", - kms: func() *Kms { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("rotate-key-version-error")) - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - scopeId: "global", - wantErr: true, - wantErrContains: "unable to load the scope's root key", - }, - { - name: "rewrap-root-key-version-error", - kms: func() *Kms { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"private_id", "scope_id", "create_time"}).AddRow("1", "global", time.Now())) - mock.ExpectQuery(`INSERT`).WillReturnError(errors.New("rewrap-root-key-version-error")) - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - rewrap: true, - scopeId: "global", - wantErr: true, - wantErrContains: "unable to rewrap root key versions", - }, - { - name: "rotate-root-key-version-error", - kms: func() *Kms { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"private_id", "scope_id", "create_time"}).AddRow("1", "global", time.Now())) - mock.ExpectQuery(`INSERT`).WillReturnError(errors.New("rotate-root-key-version-error")) - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - scopeId: "global", - wantErr: true, - wantErrContains: "unable to rotate root key version", - }, - { - name: "success", - scopeId: "success", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(k *Kms, scopeId string) { - require.NoError(t, k.CreateKeys(testCtx, scopeId, []KeyPurpose{"database", "auth"})) - _, err := wrapper.SetEncryptingWrapper(testCtx, newWrapper("2")) - require.NoError(t, err) - }, - }, - { - name: "success-with-rewrap", - scopeId: "success-with-rewrap", - rewrap: true, - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(k *Kms, scopeId string) { - require.NoError(t, k.CreateKeys(testCtx, scopeId, []KeyPurpose{"database", "auth"})) - _, err := wrapper.SetEncryptingWrapper(testCtx, newWrapper("3")) - require.NoError(t, err) - }, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - if tc.setup != nil { - tc.setup(tc.kms, tc.scopeId) - } - - var scopeRootKey *rootKey - var currentRootKeyVersions []*rootKeyVersion - var currentDataKeyVersions []*dataKeyVersion - if !tc.wantErr { - var err error - scopeRootKey, err = tc.kms.repo.LookupRootKeyByScope(testCtx, tc.scopeId) - require.NoError(err) - currentRootKeyVersions, err = tc.kms.repo.ListRootKeyVersions(testCtx, wrapper, scopeRootKey.PrivateId, withOrderByVersion(ascendingOrderBy)) - require.NoError(err) - - currDataKeys, err := tc.kms.repo.ListDataKeys(testCtx, withRootKeyId(scopeRootKey.PrivateId)) - require.NoError(err) - for _, dk := range currDataKeys { - var versions []*dataKeyVersion - tc.kms.repo.list(testCtx, &versions, "data_key_id = ?", []interface{}{dk.PrivateId}, withOrderByVersion(ascendingOrderBy)) - require.NoError(err) - currentDataKeyVersions = append(currentDataKeyVersions, versions...) - } - sort.Slice(currentDataKeyVersions, func(i, j int) bool { - return currentDataKeyVersions[i].PrivateId < currentDataKeyVersions[j].PrivateId - }) - } - - prevVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - - tc.opt = append(tc.opt, WithRewrap(tc.rewrap)) - - err = tc.kms.RotateKeys(testCtx, tc.scopeId, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - - newRootKeyVersions, err := tc.kms.repo.ListRootKeyVersions(testCtx, wrapper, scopeRootKey.PrivateId, withOrderByVersion(ascendingOrderBy)) - require.NoError(err) - - newDataKeys, err := tc.kms.repo.ListDataKeys(testCtx, withRootKeyId(scopeRootKey.PrivateId)) - require.NoError(err) - var newDataKeyVersions []*dataKeyVersion - for _, dk := range newDataKeys { - var versions []*dataKeyVersion - tc.kms.repo.list(testCtx, &versions, "data_key_id = ?", []interface{}{dk.PrivateId}, withOrderByVersion(ascendingOrderBy)) - require.NoError(err) - newDataKeyVersions = append(newDataKeyVersions, versions...) - } - sort.Slice(newDataKeyVersions, func(i, j int) bool { - return newDataKeyVersions[i].PrivateId < newDataKeyVersions[j].PrivateId - }) - - assert.Equal(len(newRootKeyVersions), len(currentRootKeyVersions)+1) - assert.Equal(len(newDataKeyVersions), len(currentDataKeyVersions)*2) - - if tc.rewrap { - // ensure the rootKeyVersion encrypted keys have been - // re-encrypted - for i := range currentRootKeyVersions { - assert.NotEqual(currentRootKeyVersions[i].CtKey, newRootKeyVersions[i].CtKey) - } - } - - currVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - assert.Greater(currVersion, prevVersion) - }) - } - t.Run("WithTx", func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - db, _ := TestDb(t) - rw := dbw.New(db) - purposes := []KeyPurpose{"database"} - k, err := New(rw, rw, purposes) - require.NoError(err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(err) - - err = k.CreateKeys(testCtx, "global", []KeyPurpose{"database"}) - require.NoError(err) - - tx, err := rw.Begin(testCtx) - require.NoError(err) - - err = k.RotateKeys(testCtx, "global", WithTx(tx), WithRewrap(true)) - require.NoError(err) - - assert.NoError(tx.Commit(testCtx)) - - err = k.RotateKeys(testCtx, "global", WithTx(tx), WithReaderWriter(tx, tx), WithRewrap(true)) - require.Error(err) - assert.Contains(err.Error(), "WithTx(...) and WithReaderWriter(...) options cannot be used at the same time") - }) - t.Run("WithTx-missing", func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - db, _ := TestDb(t) - rw := dbw.New(db) - purposes := []KeyPurpose{"database"} - k, err := New(rw, rw, purposes) - require.NoError(err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(err) - - err = k.RotateKeys(testCtx, "global", WithTx(rw), WithRewrap(true)) - require.Error(err) - assert.ErrorIs(err, ErrInvalidParameter) - }) - t.Run("WithReaderWriter", func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - db, _ := TestDb(t) - rw := dbw.New(db) - purposes := []KeyPurpose{"database"} - k, err := New(rw, rw, purposes) - require.NoError(err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(err) - - err = k.CreateKeys(testCtx, "global", []KeyPurpose{"database"}) - require.NoError(err) - - _, err = rw.DoTx( - context.Background(), - func(error) bool { return false }, - 3, dbw.ExpBackoff{}, - func(r dbw.Reader, w dbw.Writer) error { - return k.RotateKeys(testCtx, "global", WithReaderWriter(r, w), WithRewrap(true)) - }, - ) - require.NoError(err) - - _, err = rw.DoTx( - context.Background(), - func(error) bool { return false }, - 3, dbw.ExpBackoff{}, - func(r dbw.Reader, w dbw.Writer) error { - return k.RotateKeys(testCtx, "global", WithReaderWriter(nil, w), WithRewrap(true)) - }, - ) - require.Error(err) - assert.Contains(err.Error(), "missing the reader") - - _, err = rw.DoTx( - context.Background(), - func(error) bool { return false }, - 3, dbw.ExpBackoff{}, - func(r dbw.Reader, w dbw.Writer) error { - return k.RotateKeys(testCtx, "global", WithReaderWriter(r, nil), WithRewrap(true)) - }, - ) - require.Error(err) - assert.Contains(err.Error(), "missing the writer") - - _, err = rw.DoTx( - context.Background(), - func(error) bool { return false }, - 3, dbw.ExpBackoff{}, - func(r dbw.Reader, w dbw.Writer) error { - return k.RotateKeys(testCtx, "global", WithReaderWriter(r, w), WithTx(rw), WithRewrap(true)) - }, - ) - require.Error(err) - assert.Contains(err.Error(), "WithTx(...) and WithReaderWriter(...) options cannot be used at the same time") - }) -} - -func TestKms_RewrapKeys(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - newWrapper := func(id string) wrapping.Wrapper { - tmpWrapper := aead.NewWrapper() - _, err := tmpWrapper.SetConfig(testCtx, wrapping.WithKeyId(id)) - require.NoError(t, err) - key, err := generateKey(testCtx, rand.Reader) - require.NoError(t, err) - err = tmpWrapper.SetAesGcmKeyBytes(key) - require.NoError(t, err) - return tmpWrapper - } - - wrapper, err := multi.NewPooledWrapper(testCtx, newWrapper("1")) - require.NoError(t, err) - tests := []struct { - name string - kms *Kms - scopeId string - opt []Option - setup func(k *Kms, scopeId string) - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-scope-id", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "audit"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing scope id", - }, - { - name: "missing-root-wrapper", - scopeId: "missing-root-wrapper", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "audit"}) - require.NoError(t, err) - return k - }(), - wantErr: true, - wantErrIs: ErrKeyNotFound, - wantErrContains: "unable to get an external root wrapper", - }, - { - name: "bad-txFromOpts", - scopeId: "bad-txFromOpts", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "audit"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - opt: []Option{WithTx(&dbw.RW{})}, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "provided transaction has no inflight transaction", - }, - { - name: "lookupRootKeyByScope-error", - scopeId: "success", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "audit"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - wantErr: true, - wantErrIs: dbw.ErrRecordNotFound, - wantErrContains: "unable to load the scope's root key", - }, - { - name: "rewrapRootKeyVersionsTx-error", - scopeId: "success", - kms: func() *Kms { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"private_id", "scope_id", "create_time"}).AddRow("1", "global", time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("rewrapRootKeyVersionsTx-error")) - mock.ExpectRollback() - - k, err := New(rw, rw, []KeyPurpose{"database", "audit"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - wantErr: true, - wantErrContains: "unable to rewrap root key versions", - }, - { - name: "success", - scopeId: "success", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "audit"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(k *Kms, scopeId string) { - require.NoError(t, k.CreateKeys(testCtx, scopeId, []KeyPurpose{"database", "audit"})) - _, err := wrapper.SetEncryptingWrapper(testCtx, newWrapper("2")) - require.NoError(t, err) - }, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - if tc.setup != nil { - tc.setup(tc.kms, tc.scopeId) - } - - prevVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - - err = tc.kms.RewrapKeys(testCtx, tc.scopeId, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - - currVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - assert.Greater(currVersion, prevVersion) - }) - } -} - -func TestKms_GetWrapperCaching(t *testing.T) { - t.Parallel() - require := require.New(t) - ctx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - extWrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - - const globalScope = "global" - databaseKeyPurpose := KeyPurpose("database") - - // Get the global scope's root wrapper - kmsCache, err := New(rw, rw, []KeyPurpose{"database"}) - require.NoError(err) - require.NoError(kmsCache.AddExternalWrapper(ctx, KeyPurposeRootKey, extWrapper)) - // Make the global scope base keys - err = kmsCache.CreateKeys(ctx, globalScope, []KeyPurpose{databaseKeyPurpose}) - require.NoError(err) - - assertCacheEqual(t, 0, kmsCache) - require.Equal(0, int(kmsCache.collectionVersion)) - - gotWrapper, err := kmsCache.GetWrapper(ctx, globalScope, databaseKeyPurpose) - require.NoError(err) - require.NotEmpty(gotWrapper) - origKeyVersionId, err := gotWrapper.KeyId(ctx) - require.NoError(err) - - assertCacheEqual(t, 1, kmsCache) - require.Equal(2, int(kmsCache.collectionVersion)) // version starts as 1 in the db... so we're looking for 2 - - err = kmsCache.RotateKeys(ctx, globalScope) - require.NoError(err) - - gotWrapper, err = kmsCache.GetWrapper(ctx, globalScope, databaseKeyPurpose) - require.NoError(err) - require.NotEmpty(gotWrapper) - - assertCacheEqual(t, 1, kmsCache) - require.Equal(3, int(kmsCache.collectionVersion)) // version starts as 1 in the db... so we're looking for 3 - - currKeyVersionId, err := gotWrapper.KeyId(ctx) - require.NoError(err) - require.NotEqual(origKeyVersionId, currKeyVersionId) - - gotWrapper, err = kmsCache.GetWrapper(ctx, globalScope, databaseKeyPurpose, WithKeyVersionId(origKeyVersionId)) - require.NoError(err) - require.NotEmpty(gotWrapper) - currKeyVersionId, err = gotWrapper.KeyId(ctx) - require.NoError(err) - require.Equal(origKeyVersionId, currKeyVersionId) -} - -func assertCacheEqual(t *testing.T, want int, k *Kms) { - assert := assert.New(t) - current := 0 - k.scopedWrapperCache.Range(func(k, v interface{}) bool { - current++ - return true - }) - assert.Equal(want, current) -} - -func TestKms_RevokeRootKeyVersion(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - - setupWithRotationRewrapFn := func(t *testing.T, k *Kms) *multi.PooledWrapper { - t.Helper() - testDeleteWhere(t, db, &rootKey{tableNamePrefix: DefaultTableNamePrefix}, "1=1") - require.NoError(t, k.CreateKeys(testCtx, "global", []KeyPurpose{"database", "auth"})) - require.NoError(t, k.RotateKeys(testCtx, "global", WithRewrap(true))) - w, err := k.GetWrapper(testCtx, "global", KeyPurposeRootKey) - require.NoError(t, err) - require.Equal(t, 2, len(w.(*multi.PooledWrapper).AllKeyIds())) - return w.(*multi.PooledWrapper) - } - - tests := []struct { - name string - kms *Kms - setup func(t *testing.T, k *Kms) (keyVersionId string) - want func(t *testing.T, testKeyVersionId string, k *Kms) - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-key-id", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *Kms) string { - t.Helper() - _ = setupWithRotationRewrapFn(t, k) - return "" - }, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key version id", - }, - { - name: "invalid-key-id", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *Kms) string { - t.Helper() - _ = setupWithRotationRewrapFn(t, k) - return "invalid-key-version-id" - }, - wantErr: true, - wantErrIs: ErrRecordNotFound, - wantErrContains: "unable to revoke root key version", - }, - { - name: "key-in-use", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *Kms) string { - t.Helper() - testDeleteWhere(t, db, &rootKey{tableNamePrefix: DefaultTableNamePrefix}, "1=1") - require.NoError(t, k.CreateKeys(testCtx, "global", []KeyPurpose{"database", "auth"})) - w, err := k.GetWrapper(testCtx, "global", KeyPurposeRootKey) - require.NoError(t, err) - require.Equal(t, 1, len(w.(*multi.PooledWrapper).AllKeyIds())) - currentKeyVersionId, err := w.KeyId(testCtx) - require.NoError(t, err) - - dbWrapper, err := k.GetWrapper(testCtx, "global", "database") - require.NoError(t, err) - - testInsertEncryptedData(t, dbWrapper, rw, []byte("test-plaintext")) - t.Cleanup(func() { testDeleteWhere(t, db, &testEncryptedData{}, "1=1") }) - - return currentKeyVersionId - }, - want: func(t *testing.T, testKeyVersionId string, k *Kms) { - w, err := k.GetWrapper(testCtx, "global", KeyPurposeRootKey) - require.NoError(t, err) - for _, id := range w.(*multi.PooledWrapper).AllKeyIds() { - if testKeyVersionId == id { - return - } - } - assert.Fail(t, "did not find: %q key version id", testKeyVersionId) - }, - wantErr: true, - wantErrContains: "unable to revoke root key version", - }, - { - name: "success", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *Kms) string { - t.Helper() - w := setupWithRotationRewrapFn(t, k) - currentKeyVersionId, err := w.KeyId(testCtx) - require.NoError(t, err) - return currentKeyVersionId - }, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - var testKeyVersionId string - if tc.setup != nil { - testKeyVersionId = tc.setup(t, tc.kms) - } - - prevVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - - err = tc.kms.revokeRootKeyVersion(testCtx, testKeyVersionId) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - if tc.want != nil { - tc.want(t, testKeyVersionId, tc.kms) - } - return - } - require.NoError(err) - w, err := tc.kms.GetWrapper(testCtx, "global", KeyPurposeRootKey) - require.NoError(err) - for _, id := range w.(*multi.PooledWrapper).AllKeyIds() { - assert.NotEqual(testKeyVersionId, id) - } - if tc.want != nil { - tc.want(t, testKeyVersionId, tc.kms) - } - - currVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - assert.Greater(currVersion, prevVersion) - }) - } -} - -func TestKms_RevokeDataKeyVersion(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - - setupWithRotationRewrapFn := func(t *testing.T, k *Kms) *multi.PooledWrapper { - t.Helper() - testDeleteWhere(t, db, &rootKey{tableNamePrefix: DefaultTableNamePrefix}, "1=1") - require.NoError(t, k.CreateKeys(testCtx, "global", []KeyPurpose{"database", "auth"})) - require.NoError(t, k.RotateKeys(testCtx, "global", WithRewrap(true))) - w, err := k.GetWrapper(testCtx, "global", KeyPurposeRootKey) - require.NoError(t, err) - require.Equal(t, 2, len(w.(*multi.PooledWrapper).AllKeyIds())) - return w.(*multi.PooledWrapper) - } - - tests := []struct { - name string - kms *Kms - setup func(t *testing.T, k *Kms) (keyVersionId string) - want func(t *testing.T, testKeyVersionId string, k *Kms) - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-key-id", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *Kms) string { - t.Helper() - _ = setupWithRotationRewrapFn(t, k) - return "" - }, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key version id", - }, - { - name: "invalid-key-id", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *Kms) string { - t.Helper() - _ = setupWithRotationRewrapFn(t, k) - return "invalid-key-version-id" - }, - wantErr: true, - wantErrIs: ErrRecordNotFound, - wantErrContains: "unable to revoke data key version", - }, - { - name: "key-in-use", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *Kms) string { - t.Helper() - testDeleteWhere(t, db, &rootKey{tableNamePrefix: DefaultTableNamePrefix}, "1=1") - require.NoError(t, k.CreateKeys(testCtx, "global", []KeyPurpose{"database", "auth"})) - w, err := k.GetWrapper(testCtx, "global", KeyPurposeRootKey) - require.NoError(t, err) - require.Equal(t, 1, len(w.(*multi.PooledWrapper).AllKeyIds())) - - dbWrapper, err := k.GetWrapper(testCtx, "global", "database") - require.NoError(t, err) - testInsertEncryptedData(t, dbWrapper, rw, []byte("test-plaintext")) - t.Cleanup(func() { testDeleteWhere(t, db, &testEncryptedData{}, "1=1") }) - - dbKeyVersionId, err := dbWrapper.KeyId(testCtx) - require.NoError(t, err) - return dbKeyVersionId - }, - want: func(t *testing.T, testKeyVersionId string, k *Kms) { - w, err := k.GetWrapper(testCtx, "global", KeyPurpose("database")) - require.NoError(t, err) - for _, id := range w.(*multi.PooledWrapper).AllKeyIds() { - if testKeyVersionId == id { - return - } - } - assert.Fail(t, "did not find: %q key version id", testKeyVersionId) - }, - wantErr: true, - wantErrContains: "unable to revoke data key version", - }, - { - name: "success", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *Kms) string { - t.Helper() - _ = setupWithRotationRewrapFn(t, k) - dbWrapper, err := k.GetWrapper(testCtx, "global", KeyPurpose("database")) - require.NoError(t, err) - dbKeyVersionId, err := dbWrapper.KeyId(testCtx) - require.NoError(t, err) - return dbKeyVersionId - }, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - var testKeyVersionId string - if tc.setup != nil { - testKeyVersionId = tc.setup(t, tc.kms) - } - - prevVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - - err = tc.kms.revokeDataKeyVersion(testCtx, testKeyVersionId) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - if tc.want != nil { - tc.want(t, testKeyVersionId, tc.kms) - } - return - } - require.NoError(err) - dbWrapper, err := tc.kms.GetWrapper(testCtx, "global", KeyPurpose("database")) - require.NoError(err) - for _, keyVersionId := range dbWrapper.(*multi.PooledWrapper).AllKeyIds() { - assert.NotEqual(testKeyVersionId, keyVersionId) - } - if tc.want != nil { - tc.want(t, testKeyVersionId, tc.kms) - } - - currVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - assert.Greater(currVersion, prevVersion) - }) - } -} - -func TestKms_ListKeys(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - - setupWithRotationFn := func(t *testing.T, k *Kms) *multi.PooledWrapper { - t.Helper() - testDeleteWhere(t, db, &rootKey{tableNamePrefix: DefaultTableNamePrefix}, "1=1") - require.NoError(t, k.CreateKeys(testCtx, "global", []KeyPurpose{"database", "auth"})) - require.NoError(t, k.RotateKeys(testCtx, "global")) - w, err := k.GetWrapper(testCtx, "global", KeyPurposeRootKey) - require.NoError(t, err) - require.Equal(t, 2, len(w.(*multi.PooledWrapper).AllKeyIds())) - return w.(*multi.PooledWrapper) - } - - tests := []struct { - name string - kms *Kms - setup func(t *testing.T, k *Kms) string // returns scopeId - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-scope-id", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *Kms) string { - setupWithRotationFn(t, k) - return "" - }, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing scope id", - }, - { - name: "success", - kms: func() *Kms { - k, err := New(rw, rw, []KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *Kms) string { - setupWithRotationFn(t, k) - return "global" - }, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - var testScopeId string - if tc.setup != nil { - testScopeId = tc.setup(t, tc.kms) - } - - gotKeys, err := tc.kms.ListKeys(testCtx, testScopeId) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - var found []Key - for _, purpose := range tc.kms.Purposes() { - w, err := tc.kms.GetWrapper(testCtx, testScopeId, purpose) - require.NoError(err) - for _, keyVersionId := range w.(*multi.PooledWrapper).AllKeyIds() { - purposeSwitch: - switch purpose { - case KeyPurposeRootKey: - kv := rootKeyVersion{ - tableNamePrefix: DefaultTableNamePrefix, - } - kv.PrivateId = keyVersionId - err := tc.kms.repo.reader.LookupBy(testCtx, &kv, dbw.WithTable(kv.TableName())) - require.NoError(err) - for i := range found { - if found[i].Purpose == purpose && found[i].Id == kv.RootKeyId { - found[i].Versions = append(found[i].Versions, KeyVersion{ - Id: keyVersionId, - Version: uint(kv.Version), - CreateTime: kv.CreateTime, - }) - break purposeSwitch - } - } - k := rootKey{ - tableNamePrefix: DefaultTableNamePrefix, - } - k.PrivateId = kv.RootKeyId - err = tc.kms.repo.reader.LookupBy(testCtx, &k, dbw.WithTable(k.TableName())) - require.NoError(err) - found = append(found, Key{ - Id: k.PrivateId, - Scope: k.ScopeId, - Type: KeyTypeKek, - CreateTime: k.CreateTime, - Purpose: purpose, - Versions: []KeyVersion{ - { - Id: keyVersionId, - Version: uint(kv.Version), - CreateTime: kv.CreateTime, - }, - }, - }) - default: - kv := dataKeyVersion{tableNamePrefix: DefaultTableNamePrefix} - kv.PrivateId = keyVersionId - err := tc.kms.repo.reader.LookupBy(testCtx, &kv, dbw.WithTable(kv.TableName())) - require.NoError(err) - for i := range found { - if found[i].Purpose == purpose && found[i].Id == kv.DataKeyId { - found[i].Versions = append(found[i].Versions, KeyVersion{ - Id: keyVersionId, - Version: uint(kv.Version), - CreateTime: kv.CreateTime, - }) - break purposeSwitch - } - } - k := dataKey{tableNamePrefix: DefaultTableNamePrefix} - k.PrivateId = kv.DataKeyId - err = tc.kms.repo.reader.LookupBy(testCtx, &k, dbw.WithTable(k.TableName())) - require.NoError(err) - rk := rootKey{ - tableNamePrefix: DefaultTableNamePrefix, - } - rk.PrivateId = k.RootKeyId - err = tc.kms.repo.reader.LookupBy(testCtx, &rk, dbw.WithTable(rk.TableName())) - require.NoError(err) - found = append(found, Key{ - Id: k.PrivateId, - Scope: rk.ScopeId, - Type: KeyTypeDek, - CreateTime: k.CreateTime, - Purpose: purpose, - Versions: []KeyVersion{ - { - Id: keyVersionId, - Version: uint(kv.Version), - CreateTime: kv.CreateTime, - }, - }, - }) - } - } - } - assert.Empty(cmp.Diff(gotKeys, found, - cmpopts.SortSlices(func(i, j Key) bool { - return i.Purpose < j.Purpose - }), - cmpopts.SortSlices(func(i, j KeyVersion) bool { - return i.Version < j.Version - }), - )) - // intentionally logging during verbose testing - for i := range found { - t.Logf("%#v", found[i]) - if len(gotKeys) <= i { - t.Logf("no more got keys") - } else { - t.Logf("%#v", gotKeys[i]) - } - } - }) - } -} - -type testEncryptedData struct { - PrivateId string - KeyVersionId string - CipherText []byte -} - -func (*testEncryptedData) TableName() string { return "kms_test_encrypted_data" } - -func testInsertEncryptedData(t *testing.T, w wrapping.Wrapper, rw *dbw.RW, pt []byte) string { - t.Helper() - require := require.New(t) - ctx := context.Background() - - blob, err := w.Encrypt(ctx, pt) - require.NoError(err) - b, err := proto.Marshal(blob) - require.NoError(err) - - keyVersionId, err := w.KeyId(ctx) - require.NoError(err) - - id, err := dbw.NewId("d_") - require.NoError(err) - - d := &testEncryptedData{ - PrivateId: id, - KeyVersionId: keyVersionId, - CipherText: []byte(base64.RawStdEncoding.EncodeToString(b)), - } - err = rw.Create(ctx, d) - require.NoError(err) - return id -} diff --git a/extras/kms/kms_test.go b/extras/kms/kms_test.go deleted file mode 100644 index eed45233..00000000 --- a/extras/kms/kms_test.go +++ /dev/null @@ -1,1445 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms_test - -import ( - "context" - "crypto/rand" - "encoding/base64" - "errors" - "fmt" - "io" - "strings" - "testing" - "time" - - "github.com/DATA-DOG/go-sqlmock" - "github.com/hashicorp/go-dbw" - "github.com/openbao/go-kms-wrapping/extras/kms/v2" - "github.com/openbao/go-kms-wrapping/extras/kms/v2/migrations" - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/extras/multi" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" -) - -func TestKms_New(t *testing.T) { - t.Parallel() - db, _ := kms.TestDb(t) - rw := dbw.New(db) - tests := []struct { - name string - reader dbw.Reader - writer dbw.Writer - purposes []kms.KeyPurpose - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-reader", - purposes: []kms.KeyPurpose{kms.KeyPurposeRootKey, "database"}, - wantErr: true, - wantErrIs: kms.ErrInvalidParameter, - wantErrContains: "nil reader", - }, - { - name: "missing-writer", - purposes: []kms.KeyPurpose{kms.KeyPurposeRootKey, "database"}, - reader: rw, - wantErr: true, - wantErrIs: kms.ErrInvalidParameter, - wantErrContains: "nil writer", - }, - { - name: "nil-purpose", - reader: rw, - writer: rw, - }, - { - name: "with-purposes", - reader: rw, - writer: rw, - purposes: []kms.KeyPurpose{"database", "audit"}, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - k, err := kms.New(tc.reader, tc.writer, tc.purposes) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.NotNil(k) - tc.purposes = append(tc.purposes, kms.KeyPurposeRootKey) - removeDuplicatePurposes(tc.purposes) - assert.Equal(tc.purposes, k.Purposes()) - }) - } -} - -func TestKms_AddExternalWrapper(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := kms.TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - - tests := []struct { - name string - reader dbw.Reader - writer dbw.Writer - kmsPurposes []kms.KeyPurpose - wrapper wrapping.Wrapper - wrapperPurpose kms.KeyPurpose - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-purpose", - reader: rw, - writer: rw, - kmsPurposes: []kms.KeyPurpose{"audit"}, - wrapper: wrapper, - wantErr: true, - wantErrIs: kms.ErrInvalidParameter, - wantErrContains: "missing purpose", - }, - { - name: "nil-wrapper", - reader: rw, - writer: rw, - kmsPurposes: []kms.KeyPurpose{"recovery"}, - wrapperPurpose: "recovery", - wantErr: true, - wantErrIs: kms.ErrInvalidParameter, - wantErrContains: "missing wrapper", - }, - { - name: "unsupported-purpose", - reader: rw, - writer: rw, - kmsPurposes: []kms.KeyPurpose{"audit"}, - wrapper: wrapper, - wrapperPurpose: "recovery", - wantErr: true, - wantErrIs: kms.ErrInvalidParameter, - wantErrContains: "not a supported key purpose", - }, - { - name: "success-non-default-purpose", - reader: rw, - writer: rw, - kmsPurposes: []kms.KeyPurpose{"recovery"}, - wrapper: wrapper, - wrapperPurpose: "recovery", - }, - { - name: "success", - reader: rw, - writer: rw, - // use the default kmsPurposes - wrapper: wrapper, - wrapperPurpose: kms.KeyPurposeRootKey, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - k, err := kms.New(tc.reader, tc.writer, tc.kmsPurposes) - require.NoError(err) - - err = k.AddExternalWrapper(testCtx, tc.wrapperPurpose, tc.wrapper) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - w, err := k.GetExternalWrapper(testCtx, tc.wrapperPurpose) - require.NoError(err) - assert.Equal(tc.wrapper, w) - }) - } -} - -func TestKms_GetExternalWrapper(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := kms.TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - - tests := []struct { - name string - kms *kms.Kms - wrapperPurpose kms.KeyPurpose - want wrapping.Wrapper - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-purpose", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"recovery"}) - require.NoError(t, err) - k.AddExternalWrapper(testCtx, "recovery", wrapper) - return k - }(), - want: wrapper, - wantErr: true, - wantErrIs: kms.ErrInvalidParameter, - wantErrContains: "missing purpose", - }, - { - name: "invalid-purpose", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"recovery"}) - require.NoError(t, err) - k.AddExternalWrapper(testCtx, "recovery", wrapper) - return k - }(), - wrapperPurpose: "invalid", - want: wrapper, - wantErr: true, - wantErrIs: kms.ErrInvalidParameter, - wantErrContains: "not a supported key purpose", - }, - { - name: "missing-external-wrapper", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"recovery"}) - require.NoError(t, err) - return k - }(), - wrapperPurpose: "recovery", - want: wrapper, - wantErr: true, - wantErrIs: kms.ErrKeyNotFound, - wantErrContains: " missing external wrapper for", - }, - { - name: "success", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"recovery"}) - require.NoError(t, err) - k.AddExternalWrapper(testCtx, "recovery", wrapper) - return k - }(), - wrapperPurpose: "recovery", - want: wrapper, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - got, err := tc.kms.GetExternalWrapper(testCtx, tc.wrapperPurpose) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.want, got) - }) - } -} - -func TestKms_GetExternalRootWrapper(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := kms.TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - - tests := []struct { - name string - kms *kms.Kms - want wrapping.Wrapper - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-root-wrapper", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, nil) - require.NoError(t, err) - return k - }(), - wantErr: true, - wantErrIs: kms.ErrKeyNotFound, - wantErrContains: "missing external root wrapper", - }, - { - name: "success", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, nil) - require.NoError(t, err) - k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - return k - }(), - want: wrapper, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - got, err := tc.kms.GetExternalRootWrapper() - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.want, got) - }) - } -} - -func TestKms_GetWrapper(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := kms.TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - - tests := []struct { - name string - kms *kms.Kms - reader dbw.Reader - writer dbw.Writer - scopeId string - purpose kms.KeyPurpose - opt []kms.Option - setup func(*kms.Kms) - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-scope-id", - wantErr: true, - wantErrIs: kms.ErrInvalidParameter, - wantErrContains: "missing scope id", - }, - { - name: "missing-purpose", - scopeId: "global", - wantErr: true, - wantErrIs: kms.ErrInvalidParameter, - wantErrContains: "missing purpose", - }, - { - name: "invalid-purpose", - scopeId: "global", - purpose: "invalid", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - return k - }(), - wantErr: true, - wantErrIs: kms.ErrInvalidParameter, - wantErrContains: "not a supported key purpose", - }, - { - name: "load-root-error", - kms: func() *kms.Kms { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectQuery(`select version from kms_collection_version`).WillReturnRows(sqlmock.NewRows([]string{"version"}).AddRow(1)) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("load-root-error")) - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - scopeId: "global", - purpose: "database", - wantErr: true, - wantErrContains: "error loading root key for scope", - }, - { - name: "load-dek-error", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - return k - }(), - setup: func(k *kms.Kms) { - testDeleteWhere(t, db, &rootKey{}, "1=1") - - err := k.CreateKeys(testCtx, "global", []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - testDeleteWhere(t, db, &dataKeyVersion{}, "1=1") - require.NoError(t, err) - }, - scopeId: "global", - purpose: "database", - wantErr: true, - wantErrContains: `error loading "database" for scope`, - }, - { - name: "success-database", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - return k - }(), - setup: func(k *kms.Kms) { - testDeleteWhere(t, db, &rootKey{}, "1=1") - err := k.CreateKeys(testCtx, "global", []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - }, - scopeId: "global", - purpose: "database", - }, - { - name: "success-root-key", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - return k - }(), - setup: func(k *kms.Kms) { - testDeleteWhere(t, db, &rootKey{}, "1=1") - err := k.CreateKeys(testCtx, "o_1234567890", []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - }, - scopeId: "o_1234567890", - purpose: kms.KeyPurposeRootKey, - }, - { - name: "success-database", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - return k - }(), - setup: func(k *kms.Kms) { - testDeleteWhere(t, db, &rootKey{}, "1=1") - err := k.CreateKeys(testCtx, "global", []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - }, - scopeId: "global", - purpose: "database", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - if tc.setup != nil { - tc.setup(tc.kms) - } - got, err := tc.kms.GetWrapper(testCtx, tc.scopeId, tc.purpose, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.NotNil(got) - }) - } - t.Run("with-reader", func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - db, _ := kms.TestDb(t) - rw := dbw.New(db) - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(err) - require.NoError(k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper)) - testDeleteWhere(t, db, &rootKey{}, "1=1") - require.NoError(k.CreateKeys(testCtx, "global", []kms.KeyPurpose{"database", "auth"})) - - emptyDb, _ := kms.TestDb(t) - emptyRw := dbw.New(emptyDb) - emptyKms, err := kms.New(emptyRw, emptyRw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(err) - require.NoError(emptyKms.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper)) - - got, err := emptyKms.GetWrapper(testCtx, "global", "database") - assert.Empty(got) - assert.Error(err) - - got, err = emptyKms.GetWrapper(testCtx, "global", "database", kms.WithReader(rw)) - assert.NotEmpty(got) - assert.NoError(err) - }) -} - -func TestKms_CreateKeys(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := kms.TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - tests := []struct { - name string - kms *kms.Kms - scopeId string - purposes []kms.KeyPurpose - opt []kms.Option - setup func(*kms.Kms) - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-scope-id", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - wantErr: true, - wantErrIs: kms.ErrInvalidParameter, - wantErrContains: "missing scope id", - }, - { - name: "missing-external-root-wrapper", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - return k - }(), - scopeId: "global", - wantErr: true, - wantErrIs: kms.ErrKeyNotFound, - wantErrContains: "missing external root wrapper", - }, - { - name: "begin-error", - kms: func() *kms.Kms { - db, mock := dbw.TestSetupWithMock(t) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectBegin().WillReturnError(errors.New("begin-error")) - rw := dbw.New(db) - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - scopeId: "global", - setup: func(k *kms.Kms) { - testDeleteWhere(t, db, &rootKey{}, "1=1") - }, - wantErr: true, - wantErrContains: "begin-error", - }, - { - name: "create-error", - kms: func() *kms.Kms { - db, mock := dbw.TestSetupWithMock(t) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectQuery(`INSERT INTO "kms_root_key"`).WillReturnError(errors.New("create-error")) - mock.ExpectRollback() - rw := dbw.New(db) - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - scopeId: "global", - setup: func(k *kms.Kms) { - testDeleteWhere(t, db, &rootKey{}, "1=1") - }, - wantErr: true, - wantErrContains: "create-error", - }, - { - name: "rollback-error", - kms: func() *kms.Kms { - db, mock := dbw.TestSetupWithMock(t) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectQuery(`INSERT INTO "kms_root_key"`).WillReturnError(errors.New("create-error")) - mock.ExpectRollback().WillReturnError(errors.New("rollback-error")) - rw := dbw.New(db) - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - scopeId: "global", - setup: func(k *kms.Kms) { - testDeleteWhere(t, db, &rootKey{}, "1=1") - }, - wantErr: true, - wantErrContains: "rollback-error", - }, - { - name: "success-no-opts", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - scopeId: "global", - setup: func(k *kms.Kms) { - testDeleteWhere(t, db, &rootKey{}, "1=1") - }, - }, - { - name: "success-with-rand", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - scopeId: "global", - setup: func(k *kms.Kms) { - testDeleteWhere(t, db, &rootKey{}, "1=1") - }, - opt: []kms.Option{kms.WithRandomReader(rand.Reader)}, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - if tc.setup != nil { - tc.setup(tc.kms) - } - prevVersion, err := currentCollectionVersion(testCtx, rw) - require.NoError(err) - - err = tc.kms.CreateKeys(testCtx, tc.scopeId, tc.purposes, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - for _, kp := range tc.purposes { - w, err := tc.kms.GetWrapper(testCtx, tc.scopeId, kp) - require.NoError(err) - assert.NotNil(w) - } - - currVersion, err := currentCollectionVersion(testCtx, rw) - require.NoError(err) - assert.Greater(currVersion, prevVersion) - }) - } - t.Run("WithTx", func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - db, _ := kms.TestDb(t) - rw := dbw.New(db) - purposes := []kms.KeyPurpose{"database"} - k, err := kms.New(rw, rw, purposes) - require.NoError(err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(err) - - prevVersion, err := currentCollectionVersion(testCtx, rw) - require.NoError(err) - - tx, err := rw.Begin(testCtx) - require.NoError(err) - - err = k.CreateKeys(testCtx, "global", []kms.KeyPurpose{"database"}, kms.WithTx(tx)) - require.NoError(err) - - assert.NoError(tx.Commit(testCtx)) - - for _, kp := range purposes { - w, err := k.GetWrapper(testCtx, "global", kp) - require.NoError(err) - assert.NotNil(w) - } - - currVersion, err := currentCollectionVersion(testCtx, rw) - require.NoError(err) - assert.Greater(currVersion, prevVersion) - - err = k.CreateKeys(testCtx, "global", []kms.KeyPurpose{"database"}, kms.WithTx(tx), kms.WithReaderWriter(tx, tx)) - require.Error(err) - assert.Contains(err.Error(), "WithTx(...) and WithReaderWriter(...) options cannot be used at the same time") - }) - t.Run("WithTx-missing", func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - db, _ := kms.TestDb(t) - rw := dbw.New(db) - purposes := []kms.KeyPurpose{"database"} - k, err := kms.New(rw, rw, purposes) - require.NoError(err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(err) - - err = k.CreateKeys(testCtx, "global", []kms.KeyPurpose{"database"}, kms.WithTx(rw)) - require.Error(err) - assert.ErrorIs(err, kms.ErrInvalidParameter) - - for _, kp := range purposes { - w, err := k.GetWrapper(testCtx, "global", kp) - require.Error(err) - assert.Nil(w) - } - }) - t.Run("WithReaderWriter", func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - db, _ := kms.TestDb(t) - rw := dbw.New(db) - purposes := []kms.KeyPurpose{"database"} - k, err := kms.New(rw, rw, purposes) - require.NoError(err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(err) - - _, err = rw.DoTx( - context.Background(), - func(error) bool { return false }, - 3, dbw.ExpBackoff{}, - func(r dbw.Reader, w dbw.Writer) error { - return k.CreateKeys(testCtx, "global", []kms.KeyPurpose{"database"}, kms.WithReaderWriter(r, w)) - }, - ) - require.NoError(err) - - for _, kp := range purposes { - w, err := k.GetWrapper(testCtx, "global", kp) - require.NoError(err) - assert.NotNil(w) - } - - _, err = rw.DoTx( - context.Background(), - func(error) bool { return false }, - 3, dbw.ExpBackoff{}, - func(r dbw.Reader, w dbw.Writer) error { - return k.CreateKeys(testCtx, "global", []kms.KeyPurpose{"database"}, kms.WithReaderWriter(nil, w)) - }, - ) - require.Error(err) - assert.Contains(err.Error(), "missing the reader") - - _, err = rw.DoTx( - context.Background(), - func(error) bool { return false }, - 3, dbw.ExpBackoff{}, - func(r dbw.Reader, w dbw.Writer) error { - return k.CreateKeys(testCtx, "global", []kms.KeyPurpose{"database"}, kms.WithReaderWriter(r, nil)) - }, - ) - require.Error(err) - assert.Contains(err.Error(), "missing the writer") - - _, err = rw.DoTx( - context.Background(), - func(error) bool { return false }, - 3, dbw.ExpBackoff{}, - func(r dbw.Reader, w dbw.Writer) error { - return k.CreateKeys(testCtx, "global", []kms.KeyPurpose{"database"}, kms.WithReaderWriter(r, w), kms.WithTx(rw)) - }, - ) - require.Error(err) - assert.Contains(err.Error(), "WithTx(...) and WithReaderWriter(...) options cannot be used at the same time") - }) -} - -func TestRepository_ValidateVersion(t *testing.T) { - testCtx := context.Background() - db, _ := kms.TestDb(t) - rw := dbw.New(db) - tests := []struct { - name string - kms *kms.Kms - wantVersion string - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "valid", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, nil) - require.NoError(t, err) - return k - }(), - wantVersion: migrations.Version, - }, - { - name: "invalid-version", - kms: func() *kms.Kms { - mDb, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(mDb) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version"}).AddRow(100)) - k, err := kms.New(rw, rw, nil) - require.NoError(t, err) - return k - }(), - wantErr: true, - wantErrContains: "invalid version", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - version, err := tc.kms.ValidateSchema(testCtx) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.wantVersion, version) - }) - } -} - -func TestKms_ReconcileKeys(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := kms.TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - const ( - org = "o_1234567890" - org2 = "o_2234567890" - ) - - tests := []struct { - name string - kms *kms.Kms - scopeIds []string - opt []kms.Option - setup func(*kms.Kms) - wantPurpose []kms.KeyPurpose - wantUpdatedVersion bool - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-scope-ids", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - wantErr: true, - wantErrIs: kms.ErrInvalidParameter, - wantErrContains: "missing scope ids", - }, - { - name: "invalid-purpose", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - scopeIds: []string{"global"}, - wantPurpose: []kms.KeyPurpose{"invalid-purpose"}, - wantErr: true, - wantErrIs: kms.ErrInvalidParameter, - wantErrContains: "not a supported key purpose", - }, - { - name: "missing-root-key-in-org", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(k *kms.Kms) { - // start with no keys... - testDeleteWhere(t, db, func() interface{} { i := rootKey{}; return &i }(), "1=1") - // create initial keys for the global scope... - err := k.CreateKeys(context.Background(), "global", nil) - require.NoError(t, err) - - // make sure the kms is in the proper state for the unit test - // before proceeding. - _, err = k.GetWrapper(testCtx, org, kms.KeyPurposeRootKey) - require.Error(t, err) - }, - scopeIds: []string{org}, - wantPurpose: []kms.KeyPurpose{"database"}, - wantErr: true, - wantErrIs: kms.ErrKeyNotFound, - wantErrContains: "missing root key for scope", - }, - { - name: "rand-reader-err", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(k *kms.Kms) { - // start with no keys... - testDeleteWhere(t, db, func() interface{} { i := rootKey{}; return &i }(), "1=1") - // create initial keys for the global scope... - err := k.CreateKeys(context.Background(), "global", nil) - require.NoError(t, err) - - // make sure the kms is in the proper state for the unit test - // before proceeding. - _, err = k.GetWrapper(testCtx, "global", "database") - require.Error(t, err) - }, - opt: []kms.Option{kms.WithRandomReader(&mockReader{err: errors.New("rand-err")})}, - scopeIds: []string{"global"}, - wantPurpose: []kms.KeyPurpose{"database"}, - wantErr: true, - wantErrContains: "rand-err", - }, - - { - name: "success-nil-rand-reader-option", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - opt: []kms.Option{ - kms.WithRandomReader(func() io.Reader { var sr *strings.Reader; var r io.Reader = sr; return r }()), - }, - setup: func(k *kms.Kms) { - // start with no keys... - testDeleteWhere(t, db, func() interface{} { i := rootKey{}; return &i }(), "1=1") - // create initial keys for the global scope... - err := k.CreateKeys(context.Background(), "global", nil) - require.NoError(t, err) - - // make sure the kms is in the proper state for the unit test - // before proceeding. - _, err = k.GetWrapper(testCtx, org, "database") - require.Error(t, err) - }, - scopeIds: []string{"global"}, - wantPurpose: []kms.KeyPurpose{"database"}, - wantUpdatedVersion: true, - }, - { - name: "success-rand-reader-option", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - opt: []kms.Option{kms.WithRandomReader(rand.Reader)}, - setup: func(k *kms.Kms) { - // start with no keys... - testDeleteWhere(t, db, func() interface{} { i := rootKey{}; return &i }(), "1=1") - // create initial keys for the global scope... - err := k.CreateKeys(context.Background(), org, nil) - require.NoError(t, err) - - // make sure the kms is in the proper state for the unit test - // before proceeding. - _, err = k.GetWrapper(testCtx, org, "database") - require.Error(t, err) - }, - scopeIds: []string{org}, - wantPurpose: []kms.KeyPurpose{"database"}, - wantUpdatedVersion: true, - }, - { - name: "nothing-to-reconcile", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(k *kms.Kms) { - // start with no keys... - testDeleteWhere(t, db, func() interface{} { i := rootKey{}; return &i }(), "1=1") - // create initial keys for the global scope... - err := k.CreateKeys(context.Background(), "global", []kms.KeyPurpose{"database"}) - require.NoError(t, err) - - // make sure the kms is in the proper state for the unit test - // before proceeding. - _, err = k.GetWrapper(testCtx, "global", "database") - require.NoError(t, err) - }, - scopeIds: []string{"global"}, - wantPurpose: []kms.KeyPurpose{"database"}, - }, - { - name: "success-reconcile-database-key-in-org", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(k *kms.Kms) { - // start with no keys... - testDeleteWhere(t, db, func() interface{} { i := rootKey{}; return &i }(), "1=1") - // create initial keys for the global scope... - err := k.CreateKeys(context.Background(), org, nil) - require.NoError(t, err) - - // make sure the kms is in the proper state for the unit test - // before proceeding. - _, err = k.GetWrapper(testCtx, org, "database") - require.Error(t, err) - }, - scopeIds: []string{org}, - wantPurpose: []kms.KeyPurpose{"database"}, - wantErr: false, - wantUpdatedVersion: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - if tt.setup != nil { - tt.setup(tt.kms) - } - - prevVersion, err := currentCollectionVersion(testCtx, rw) - require.NoError(err) - - err = tt.kms.ReconcileKeys(testCtx, tt.scopeIds, tt.wantPurpose, tt.opt...) - if tt.wantErr { - require.Error(err) - if tt.wantErrIs != nil { - assert.ErrorIs(err, tt.wantErrIs) - } - if tt.wantErrContains != "" { - assert.Contains(err.Error(), tt.wantErrContains) - } - return - } - assert.NoError(err) - if len(tt.scopeIds) > 0 { - for _, id := range tt.scopeIds { - for _, p := range tt.wantPurpose { - _, err := tt.kms.GetWrapper(testCtx, id, p) - require.NoError(err) - } - } - } - - if tt.wantUpdatedVersion { - currVersion, err := currentCollectionVersion(testCtx, rw) - require.NoError(err) - assert.Greater(currVersion, prevVersion) - } - }) - } -} - -func TestKms_RevokeKey(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := kms.TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - - setupWithRotationRewrapFn := func(t *testing.T, k *kms.Kms) *multi.PooledWrapper { - t.Helper() - testDeleteWhere(t, db, &rootKey{}, "1=1") - require.NoError(t, k.CreateKeys(testCtx, "global", []kms.KeyPurpose{"database", "auth"})) - require.NoError(t, k.RotateKeys(testCtx, "global", kms.WithRewrap(true))) - w, err := k.GetWrapper(testCtx, "global", kms.KeyPurposeRootKey) - require.NoError(t, err) - require.Equal(t, 2, len(w.(*multi.PooledWrapper).AllKeyIds())) - return w.(*multi.PooledWrapper) - } - - tests := []struct { - name string - kms *kms.Kms - setup func(t *testing.T, k *kms.Kms) (keyVersionId string) - want func(t *testing.T, testKeyVersionId string, k *kms.Kms) - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-key-id", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *kms.Kms) string { - t.Helper() - _ = setupWithRotationRewrapFn(t, k) - return "" - }, - wantErr: true, - wantErrIs: kms.ErrInvalidParameter, - wantErrContains: "missing key version id", - }, - { - name: "invalid-key-id", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *kms.Kms) string { - t.Helper() - _ = setupWithRotationRewrapFn(t, k) - return "invalid-key-id" - }, - wantErr: true, - wantErrIs: kms.ErrInvalidParameter, - wantErrContains: "not a valid key version id", - }, - { - name: "root-key-version-key-not-found", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *kms.Kms) string { - t.Helper() - _ = setupWithRotationRewrapFn(t, k) - return "krkv-key-not-found" - }, - wantErr: true, - wantErrIs: kms.ErrRecordNotFound, - wantErrContains: "unable to revoke root key version", - }, - { - name: "data-key-version-key-not-found", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *kms.Kms) string { - t.Helper() - _ = setupWithRotationRewrapFn(t, k) - return "kdkv-key-not-found" - }, - wantErr: true, - wantErrIs: kms.ErrRecordNotFound, - wantErrContains: "unable to revoke data key version", - }, - { - name: "key-in-use", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *kms.Kms) string { - t.Helper() - testDeleteWhere(t, db, &rootKey{}, "1=1") - require.NoError(t, k.CreateKeys(testCtx, "global", []kms.KeyPurpose{"database", "auth"})) - w, err := k.GetWrapper(testCtx, "global", kms.KeyPurposeRootKey) - require.NoError(t, err) - require.Equal(t, 1, len(w.(*multi.PooledWrapper).AllKeyIds())) - - dbWrapper, err := k.GetWrapper(testCtx, "global", "database") - require.NoError(t, err) - testInsertEncryptedData(t, dbWrapper, rw, []byte("test-plaintext")) - t.Cleanup(func() { testDeleteWhere(t, db, &testEncryptedData{}, "1=1") }) - - dbKeyVersionId, err := dbWrapper.KeyId(testCtx) - require.NoError(t, err) - return dbKeyVersionId - }, - want: func(t *testing.T, testKeyVersionId string, k *kms.Kms) { - w, err := k.GetWrapper(testCtx, "global", kms.KeyPurpose("database")) - require.NoError(t, err) - for _, keyVersionId := range w.(*multi.PooledWrapper).AllKeyIds() { - if testKeyVersionId == keyVersionId { - return - } - } - assert.Fail(t, "did not find: %q key version id", testKeyVersionId) - }, - wantErr: true, - wantErrContains: "unable to revoke data key version", - }, - { - name: "success-dkv", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *kms.Kms) string { - t.Helper() - _ = setupWithRotationRewrapFn(t, k) - dbWrapper, err := k.GetWrapper(testCtx, "global", kms.KeyPurpose("database")) - require.NoError(t, err) - dbKeyVersionId, err := dbWrapper.KeyId(testCtx) - require.NoError(t, err) - return dbKeyVersionId - }, - want: func(t *testing.T, testKeyVersionId string, k *kms.Kms) { - dbWrapper, err := k.GetWrapper(testCtx, "global", kms.KeyPurpose("database")) - require.NoError(t, err) - for _, keyVersionId := range dbWrapper.(*multi.PooledWrapper).AllKeyIds() { - assert.NotEqual(t, testKeyVersionId, keyVersionId) - } - }, - }, - { - name: "success-rkv", - kms: func() *kms.Kms { - k, err := kms.New(rw, rw, []kms.KeyPurpose{"database", "auth"}) - require.NoError(t, err) - err = k.AddExternalWrapper(testCtx, kms.KeyPurposeRootKey, wrapper) - require.NoError(t, err) - return k - }(), - setup: func(t *testing.T, k *kms.Kms) string { - t.Helper() - w := setupWithRotationRewrapFn(t, k) - currentKeyVersionId, err := w.KeyId(testCtx) - require.NoError(t, err) - return currentKeyVersionId - }, - want: func(t *testing.T, testKeyVersionId string, k *kms.Kms) { - w, err := k.GetWrapper(testCtx, "global", kms.KeyPurposeRootKey) - require.NoError(t, err) - for _, keyVersionId := range w.(*multi.PooledWrapper).AllKeyIds() { - assert.NotEqual(t, testKeyVersionId, keyVersionId) - } - }, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - var testKeyVersionId string - if tc.setup != nil { - testKeyVersionId = tc.setup(t, tc.kms) - } - - prevVersion, err := currentCollectionVersion(testCtx, rw) - require.NoError(err) - - err = tc.kms.RevokeKeyVersion(testCtx, testKeyVersionId) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - if tc.want != nil { - tc.want(t, testKeyVersionId, tc.kms) - } - return - } - require.NoError(err) - if tc.want != nil { - tc.want(t, testKeyVersionId, tc.kms) - } - - currVersion, err := currentCollectionVersion(testCtx, rw) - require.NoError(err) - assert.Greater(currVersion, prevVersion) - }) - } -} - -type testEncryptedData struct { - PrivateId string - KeyVersionId string - CipherText []byte -} - -func (*testEncryptedData) TableName() string { return "kms_test_encrypted_data" } - -func testInsertEncryptedData(t *testing.T, w wrapping.Wrapper, rw *dbw.RW, pt []byte) string { - t.Helper() - require := require.New(t) - ctx := context.Background() - - blob, err := w.Encrypt(ctx, pt) - require.NoError(err) - b, err := proto.Marshal(blob) - require.NoError(err) - - keyVersionId, err := w.KeyId(ctx) - require.NoError(err) - - id, err := dbw.NewId("d_") - require.NoError(err) - - d := &testEncryptedData{ - PrivateId: id, - KeyVersionId: keyVersionId, - CipherText: []byte(base64.RawStdEncoding.EncodeToString(b)), - } - err = rw.Create(ctx, d) - require.NoError(err) - return id -} - -func testDeleteWhere(t *testing.T, conn *dbw.DB, i interface{}, whereClause string, args ...interface{}) { - t.Helper() - require := require.New(t) - ctx := context.Background() - tabler, ok := i.(interface { - TableName() string - }) - require.True(ok) - _, err := dbw.New(conn).Exec(ctx, fmt.Sprintf(`delete from "%s" where %s`, tabler.TableName(), whereClause), args) - require.NoError(err) -} - -const testDefaultWrapperSecret = "secret1234567890" - -type rootKey struct{} - -func (k *rootKey) TableName() string { return "kms_root_key" } - -type dataKey struct{} - -func (k *dataKey) TableName() string { return "kms_data_key" } - -type dataKeyVersion struct{} - -func (k *dataKeyVersion) TableName() string { return "kms_data_key_version" } - -func removeDuplicatePurposes(purposes []kms.KeyPurpose) []kms.KeyPurpose { - purposesMap := make(map[kms.KeyPurpose]struct{}, len(purposes)) - for _, purpose := range purposes { - purpose = kms.KeyPurpose(strings.TrimSpace(string(purpose))) - if purpose == "" { - continue - } - purposesMap[purpose] = struct{}{} - } - purposes = make([]kms.KeyPurpose, 0, len(purposesMap)) - for purpose := range purposesMap { - purposes = append(purposes, purpose) - } - return purposes -} - -type mockTestWrapper struct { - wrapping.Wrapper - err error - keyId string -} - -func (m *mockTestWrapper) KeyId(context.Context) (string, error) { - if m.err != nil { - return "", m.err - } - return m.keyId, nil -} - -type mockReader struct { - err error -} - -func (r *mockReader) Read(b []byte) (n int, err error) { - return 0, r.err -} - -func currentCollectionVersion(ctx context.Context, r dbw.Reader) (uint64, error) { - const ( - op = "kms.currentCollectionVersion" - - sql = "select version from kms_collection_version" - ) - rows, err := r.Query(ctx, sql, nil) - if err != nil { - } - v := struct { - Version uint64 - }{} - for rows.Next() { - if err := r.ScanRows(rows, &v); err != nil { - return 0, fmt.Errorf("%s: %w", op, err) - } - } - return v.Version, nil -} diff --git a/extras/kms/migrations/fs.go b/extras/kms/migrations/fs.go deleted file mode 100644 index a30f7853..00000000 --- a/extras/kms/migrations/fs.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package migrations - -import "embed" - -// Version defines the current migrations version required by the module -const Version = "v0.0.2" - -// PostgresFS contains the sql for creating the postgres tables -// -//go:embed postgres -var PostgresFS embed.FS - -// SqliteFS contains the sql for creating the sqlite tables -// -//go:embed sqlite -var SqliteFS embed.FS diff --git a/extras/kms/migrations/postgres/01_domain_types.up.sql b/extras/kms/migrations/postgres/01_domain_types.up.sql deleted file mode 100644 index 5b423351..00000000 --- a/extras/kms/migrations/postgres/01_domain_types.up.sql +++ /dev/null @@ -1,101 +0,0 @@ --- Copyright (c) HashiCorp, Inc. --- SPDX-License-Identifier: MPL-2.0 - -begin; - -create domain kms_private_id as text -not null -check( - length(trim(value)) > 0 -); -comment on domain kms_private_id is -'standard column for private id'; - -create domain kms_scope_id as text -check( - length(trim(value)) > 0 -); -comment on domain kms_scope_id is -'standard column for scope id'; - -create domain kms_timestamp as - timestamp with time zone - default current_timestamp; -comment on domain kms_timestamp is -'Standard timestamp for all create_time and update_time columns'; - -create domain kms_version as bigint - default 1 - not null - check( - value > 0 - ); -comment on domain kms_version is -'standard column for row version'; - --- kms_immutable_columns() will make the column names immutable which are passed as --- parameters when the trigger is created. It raises error code 23601 which is a --- class 23 integrity constraint violation: immutable column -create function kms_immutable_columns() - returns trigger -as $$ -declare - col_name text; - new_value text; - old_value text; -begin - foreach col_name in array tg_argv loop - execute format('SELECT $1.%I', col_name) into new_value using new; - execute format('SELECT $1.%I', col_name) into old_value using old; - if new_value is distinct from old_value then - raise exception 'immutable column: %.%', tg_table_name, col_name using - errcode = '23601', - schema = tg_table_schema, - table = tg_table_name, - column = col_name; - end if; - end loop; - return new; -end; -$$ language plpgsql; - -comment on function - kms_immutable_columns() -is - 'function used in before update triggers to make columns immutable'; - - -create function kms_default_create_time() - returns trigger -as $$ -begin - if new.create_time is distinct from now() then - new.create_time = now(); - end if; - return new; -end; -$$ language plpgsql; -comment on function - kms_default_create_time() -is - 'function used to properly set create_time columns'; - -create or replace function - kms_update_time_column() - returns trigger -as $$ -begin - if row(new.*) is distinct from row(old.*) then - new.update_time = now(); - return new; - else - return old; - end if; -end; -$$ language plpgsql; -comment on function - kms_update_time_column() -is - 'function used in before update triggers to properly set update_time columns'; - -commit; \ No newline at end of file diff --git a/extras/kms/migrations/postgres/02_version.up.sql b/extras/kms/migrations/postgres/02_version.up.sql deleted file mode 100644 index 233fca8c..00000000 --- a/extras/kms/migrations/postgres/02_version.up.sql +++ /dev/null @@ -1,36 +0,0 @@ --- Copyright (c) HashiCorp, Inc. --- SPDX-License-Identifier: MPL-2.0 - -begin; - -create table kms_schema_version( - version text not null, - create_time kms_timestamp, - update_time kms_timestamp -); -comment on table kms_schema_version is - 'kms_schema_version contains the kms schema version'; - --- ensure that it's only ever one row -create unique index kms_schema_version_one_row -ON kms_schema_version((version is not null)); - - -- define the immutable fields for kms_root_key (all of them) -create trigger kms_immutable_columns -before -update on kms_schema_version - for each row execute procedure kms_immutable_columns('create_time'); - -create trigger kms_default_create_time_column -before -insert on kms_schema_version - for each row execute procedure kms_default_create_time(); - -create trigger kms_update_time_column -before -update on kms_schema_version - for each row execute procedure kms_update_time_column(); - -insert into kms_schema_version(version) values('v0.0.1'); - -commit; \ No newline at end of file diff --git a/extras/kms/migrations/postgres/03_keys.up.sql b/extras/kms/migrations/postgres/03_keys.up.sql deleted file mode 100644 index 0c8ec08f..00000000 --- a/extras/kms/migrations/postgres/03_keys.up.sql +++ /dev/null @@ -1,33 +0,0 @@ --- Copyright (c) HashiCorp, Inc. --- SPDX-License-Identifier: MPL-2.0 - -begin; - - --- kms_version_column() will increment the version column whenever row data --- is inserted and should only be used in an before insert trigger. This --- function will overwrite any explicit values to the version column. -create function kms_version_column() - returns trigger -as $$ -declare - _key_id text; - _max bigint; -begin - execute format('SELECT $1.%I', tg_argv[0]) into _key_id using new; - execute format('select max(version) + 1 from %I where %I = $1', tg_relid::regclass, tg_argv[0]) using _key_id into _max; - if _max is null then - _max = 1; - end if; - new.version = _max; - return new; -end; -$$ language plpgsql; - -comment on function - kms_version_column() -is - 'function used in before insert triggers to properly set version columns for kms_* tables with a version column'; - - -commit; \ No newline at end of file diff --git a/extras/kms/migrations/postgres/04_keys.up.sql b/extras/kms/migrations/postgres/04_keys.up.sql deleted file mode 100644 index 1f55f676..00000000 --- a/extras/kms/migrations/postgres/04_keys.up.sql +++ /dev/null @@ -1,130 +0,0 @@ --- Copyright (c) HashiCorp, Inc. --- SPDX-License-Identifier: MPL-2.0 - -begin; - -create table kms_root_key ( - private_id kms_private_id primary key, - scope_id kms_scope_id not null unique, -- there can only be one root key for a scope. - create_time kms_timestamp -); -comment on table kms_root_key is - 'kms_root_key defines a root key for a scope'; - - -- define the immutable fields for kms_root_key (all of them) -create trigger kms_immutable_columns -before -update on kms_root_key - for each row execute procedure kms_immutable_columns('private_id', 'scope_id', 'create_time'); - -create trigger kms_default_create_time_column -before -insert on kms_root_key - for each row execute procedure kms_default_create_time(); - -create table kms_root_key_version ( - private_id kms_private_id primary key, - root_key_id kms_private_id not null - constraint kms_root_key_fkey - references kms_root_key(private_id) - on delete cascade - on update cascade, - version kms_version, - key bytea not null - constraint not_empty_key - check ( - length(key) > 0 - ), - create_time kms_timestamp, - constraint kms_root_key_version_root_key_id_version_uq - unique(root_key_id, version) -); -comment on table kms_root_key_version is - 'kms_root_key_version contains versions of a kms_root_key'; - --- define the immutable fields for kms_root_key_version (all of them) -create trigger kms_immutable_columns -before -update on kms_root_key_version - for each row execute procedure kms_immutable_columns('private_id', 'root_key_id', 'version', 'key', 'create_time'); - -create trigger kms_default_create_time_column -before -insert on kms_root_key_version - for each row execute procedure kms_default_create_time(); - - -create trigger kms_version_column -before insert on kms_root_key_version - for each row execute procedure kms_version_column('root_key_id'); - - -create table kms_data_key ( - private_id kms_private_id primary key, - root_key_id kms_private_id not null - constraint kms_root_key_fkey - references kms_root_key(private_id) - on delete cascade - on update cascade, - purpose text not null - constraint not_start_end_whitespace_purpose - check (length(trim(purpose)) = length(purpose)), - create_time kms_timestamp, - constraint kms_data_key_root_key_id_purpose_uq - unique (root_key_id, purpose) -- there can only be one dek for a specific purpose per root key -); -comment on table kms_data_key is - 'kms_data_key contains deks (data keys) for specific purposes'; - - -- define the immutable fields for kms_data_key (all of them) -create trigger kms_immutable_columns -before -update on kms_data_key - for each row execute procedure kms_immutable_columns('private_id', 'root_key_id', 'purpose', 'create_time'); - -create trigger kms_default_create_time_column -before -insert on kms_data_key - for each row execute procedure kms_default_create_time(); - -create table kms_data_key_version ( - private_id kms_private_id primary key, - data_key_id kms_private_id not null - constraint kms_data_key_fkey - references kms_data_key(private_id) - on delete cascade - on update cascade, - root_key_version_id kms_private_id not null - constraint kms_root_key_version_fkey - references kms_root_key_version(private_id) - on delete cascade - on update cascade, - version kms_version, - key bytea not null - constraint not_empty_key - check ( - length(key) > 0 - ), - create_time kms_timestamp, - constraint kms_data_key_version_data_key_id_version_uq - unique(data_key_id, version) -); -comment on table kms_data_key is - 'kms_data_key_version contains versions of a kms_data_key (dek aka data keys)'; - - -- define the immutable fields for kms_data_key_version (all of them) -create trigger kms_immutable_columns -before -update on kms_data_key_version - for each row execute procedure kms_immutable_columns('private_id', 'data_key_id', 'root_key_version_id', 'version', 'key', 'create_time'); - -create trigger kms_default_create_time_column -before -insert on kms_data_key_version - for each row execute procedure kms_default_create_time(); - -create trigger kms_version_column -before insert on kms_data_key_version - for each row execute procedure kms_version_column('data_key_id'); - -commit; \ No newline at end of file diff --git a/extras/kms/migrations/postgres/05_key_rewrap.up.sql b/extras/kms/migrations/postgres/05_key_rewrap.up.sql deleted file mode 100644 index a87c9ce5..00000000 --- a/extras/kms/migrations/postgres/05_key_rewrap.up.sql +++ /dev/null @@ -1,27 +0,0 @@ --- Copyright (c) HashiCorp, Inc. --- SPDX-License-Identifier: MPL-2.0 - -begin; - - --- we need to make the key and version columns mutable in order to support --- rewrapping the root key versions. -drop trigger kms_immutable_columns on kms_root_key_version; - -create trigger kms_immutable_columns -before -update on kms_root_key_version - for each row execute procedure kms_immutable_columns('private_id', 'root_key_id', 'create_time'); - - - --- we need to make the key and version columns mutable in order to support --- rewrapping the data key version. -drop trigger kms_immutable_columns on kms_data_key_version; - -create trigger kms_immutable_columns -before -update on kms_data_key_version - for each row execute procedure kms_immutable_columns('private_id', 'data_key_id', 'root_key_version_id', 'create_time'); - -commit; diff --git a/extras/kms/migrations/postgres/06_kms_collection_version.up.sql b/extras/kms/migrations/postgres/06_kms_collection_version.up.sql deleted file mode 100644 index 319b220f..00000000 --- a/extras/kms/migrations/postgres/06_kms_collection_version.up.sql +++ /dev/null @@ -1,31 +0,0 @@ --- Copyright (c) HashiCorp, Inc. --- SPDX-License-Identifier: MPL-2.0 - -begin; - -create table kms_collection_version ( - version kms_version, - create_time timestamp not null default current_timestamp, - update_time timestamp not null default current_timestamp -); - --- ensure that it's only ever one row -create unique index kms_collection_version_one_row -ON kms_collection_version((version is not null)); - -create trigger kms_immutable_columns -before -update on kms_collection_version - for each row execute procedure kms_immutable_columns('create_time'); - -create trigger kms_update_time_column -before -update on kms_collection_version - for each row execute procedure kms_update_time_column(); - - -insert into kms_collection_version(version) values(1); - -update kms_schema_version set version = 'v0.0.2'; - -commit; \ No newline at end of file diff --git a/extras/kms/migrations/postgres/07_mutable_root_key_version.up.sql b/extras/kms/migrations/postgres/07_mutable_root_key_version.up.sql deleted file mode 100644 index 78bae26b..00000000 --- a/extras/kms/migrations/postgres/07_mutable_root_key_version.up.sql +++ /dev/null @@ -1,15 +0,0 @@ --- Copyright (c) HashiCorp, Inc. --- SPDX-License-Identifier: MPL-2.0 - -begin; - --- we need to make the root_key_version_id mutable in order to support --- rewrapping the data key version. -drop trigger kms_immutable_columns on kms_data_key_version; - -create trigger kms_immutable_columns -before -update on kms_data_key_version - for each row execute procedure kms_immutable_columns('private_id', 'data_key_id', 'create_time'); - -commit; diff --git a/extras/kms/migrations/sqlite/02_version.up.sql b/extras/kms/migrations/sqlite/02_version.up.sql deleted file mode 100644 index 5e8e74f8..00000000 --- a/extras/kms/migrations/sqlite/02_version.up.sql +++ /dev/null @@ -1,35 +0,0 @@ --- Copyright (c) HashiCorp, Inc. --- SPDX-License-Identifier: MPL-2.0 - --- kms_version is a one row table to keep the version -create table kms_schema_version ( - version text not null, - create_time timestamp not null default current_timestamp, - update_time timestamp not null default current_timestamp -); - --- ensure that it's only ever one row -create unique index kms_schema_version_one_row -ON kms_schema_version((version is not null)); - -create trigger kms_immutable_columns_kms_schema_version -before update on kms_schema_version -for each row - when - new.create_time <> old.create_time - begin - select raise(abort, 'immutable column'); - end; - - -create trigger update_time_column_kms_version -before update on kms_schema_version -for each row -when - new.version <> old.version - begin - update kms_schema_version set update_time = datetime('now','localtime') where rowid == new.rowid; - end; - - -insert into kms_schema_version(version) values('v0.0.1') \ No newline at end of file diff --git a/extras/kms/migrations/sqlite/04_keys.up.sql b/extras/kms/migrations/sqlite/04_keys.up.sql deleted file mode 100644 index 861462c6..00000000 --- a/extras/kms/migrations/sqlite/04_keys.up.sql +++ /dev/null @@ -1,178 +0,0 @@ --- Copyright (c) HashiCorp, Inc. --- SPDX-License-Identifier: MPL-2.0 - -create table kms_root_key ( - private_id text not null primary key - check( - length(trim(private_id) > 0) - ), - scope_id text not null unique - check( - length(trim(scope_id)) > 0 - ), - create_time timestamp not null default current_timestamp -); - -create trigger kms_immutable_columns_kms_root_key -before update on kms_root_key -for each row - when - new.private_id <> old.private_id or - new.scope_id <> old.scope_id or - new.create_time <> old.create_time - begin - select raise(abort, 'immutable column'); - end; - -create trigger kms_default_create_time_column_kms_root_key -before insert on kms_root_key -for each row -begin - update kms_root_key set create_time = datetime('now','localtime') where rowid = new.rowid; -end; - -create table kms_root_key_version ( - private_id text not null primary key - check( - length(trim(private_id) > 0) - ), - root_key_id text not null - references kms_root_key(private_id) - on delete cascade - on update cascade, - version int, - key bytea not null - constraint not_empty_key - check ( - length(key) > 0 - ), - create_time timestamp not null default current_timestamp, - unique(root_key_id, version) -); - -create trigger kms_immutable_columns_kms_root_key_version -before update on kms_root_key_version -for each row - when - new.private_id <> old.private_id or - new.root_key_id <> old.root_key_id or - new.version <> old.version or - new.key <> old.key or - new.version <> old.version or - new.create_time <> old.create_time - begin - select raise(abort, 'immutable column'); - end; - -create trigger kms_default_create_time_column_kms_root_key_version -before insert on kms_root_key_version -for each row -begin - update kms_root_key_version set create_time = datetime('now','localtime') where rowid = new.rowid; -end; - -create trigger version_column_kms_root_key_version -after insert on kms_root_key_version -for each row -begin - update kms_root_key_version set version = - ( - select max(coalesce(version,0)) + 1 - from kms_root_key_version - where - root_key_id = new.root_key_id - ) - where rowid = new.rowid; -end; - -create table kms_data_key ( - private_id text not null primary key - check( - length(trim(private_id) > 0) - ), - root_key_id text not null - references kms_root_key(private_id) - on delete cascade - on update cascade, - purpose text not null - check(length(trim(purpose)) = length(purpose)), - create_time timestamp not null default current_timestamp, - unique (root_key_id, purpose) -- there can only be one dek per purpose per root key -); - -create trigger kms_immutable_columns_kms_data_key -before update on kms_data_key -for each row - when - new.private_id <> old.private_id or - new.root_key_id <> old.root_key_id or - new.purpose <> old.purpose or - new.create_time <> old.create_time - begin - select raise(abort, 'immutable column'); - end; - -create trigger kms_default_create_time_column_kms_data_key -before insert on kms_data_key -for each row -begin - update kms_data_key set create_time = datetime('now','localtime') where rowid = new.rowid; -end; - -create table kms_data_key_version ( - private_id text not null primary key - check( - length(trim(private_id) > 0) - ), - data_key_id text not null - references kms_data_key(private_id) - on delete cascade - on update cascade, - root_key_version_id text not null - references kms_root_key_version(private_id) - on delete cascade - on update cascade, - version int, - key bytea not null - constraint not_empty_key - check ( - length(key) > 0 - ), - create_time timestamp not null default current_timestamp, - unique(data_key_id, version) -); - -create trigger kms_immutable_columns_kms_data_key_version -before update on kms_data_key_version -for each row - when - new.private_id <> old.private_id or - new.data_key_id <> old.data_key_id or - new.root_key_version_id <> old.root_key_version_id or - new.version <> old.version or - new.key <> old.key or - new.create_time <> old.create_time - begin - select raise(abort, 'immutable column'); - end; - -create trigger kms_default_create_time_column_kms_data_key_version -before insert on kms_data_key_version -for each row -begin - update kms_data_key_version set create_time = datetime('now','localtime') where rowid = new.rowid; -end; - -create trigger version_column_kms_data_key_version -after insert on kms_data_key_version -for each row -begin - update kms_data_key_version set version = - ( - select max(coalesce(version,0)) + 1 - from kms_data_key_version - where - data_key_id = new.data_key_id - ) - where rowid = new.rowid; -end; \ No newline at end of file diff --git a/extras/kms/migrations/sqlite/05_key_rewrap.up.sql b/extras/kms/migrations/sqlite/05_key_rewrap.up.sql deleted file mode 100644 index f9fa244d..00000000 --- a/extras/kms/migrations/sqlite/05_key_rewrap.up.sql +++ /dev/null @@ -1,35 +0,0 @@ --- Copyright (c) HashiCorp, Inc. --- SPDX-License-Identifier: MPL-2.0 - --- we need to make the key and version columns mutable in order to support --- rewrapping the root key versions. -drop trigger kms_immutable_columns_kms_root_key_version; - -create trigger kms_immutable_columns_kms_root_key_version -before update on kms_root_key_version -for each row - when - new.private_id <> old.private_id or - new.root_key_id <> old.root_key_id or - new.create_time <> old.create_time - begin - select raise(abort, 'immutable column'); - end; - - --- we need to make the key and version columns mutable in order to support --- rewrapping the data key version. -drop trigger kms_immutable_columns_kms_data_key_version; - -create trigger kms_immutable_columns_kms_data_key_version -before update on kms_data_key_version -for each row - when - new.private_id <> old.private_id or - new.data_key_id <> old.data_key_id or - new.root_key_version_id <> old.root_key_version_id or - new.create_time <> old.create_time - begin - select raise(abort, 'immutable column'); - end; - \ No newline at end of file diff --git a/extras/kms/migrations/sqlite/06_kms_collection_version.up.sql b/extras/kms/migrations/sqlite/06_kms_collection_version.up.sql deleted file mode 100644 index 167e42f7..00000000 --- a/extras/kms/migrations/sqlite/06_kms_collection_version.up.sql +++ /dev/null @@ -1,38 +0,0 @@ --- Copyright (c) HashiCorp, Inc. --- SPDX-License-Identifier: MPL-2.0 - -create table kms_collection_version ( - version int not null, - create_time timestamp not null default current_timestamp, - update_time timestamp not null default current_timestamp -); - --- ensure that it's only ever one row -create unique index kms_collection_version_one_row -ON kms_collection_version((version is not null)); - -create trigger kms_immutable_columns_kms_collection_version -before update on kms_collection_version -for each row - when - new.create_time <> old.create_time - begin - select raise(abort, 'immutable column'); - end; - - -create trigger update_time_column_kms_collection_version -before update on kms_collection_version -for each row -when - new.version <> old.version - begin - update kms_collection_version set update_time = datetime('now','localtime') where rowid == new.rowid; - end; - - -insert into kms_collection_version(version) values(1); - -update kms_schema_version set version = 'v0.0.2'; - - diff --git a/extras/kms/migrations/sqlite/07_mutable_root_key_version.up.sql b/extras/kms/migrations/sqlite/07_mutable_root_key_version.up.sql deleted file mode 100644 index 464ae9c1..00000000 --- a/extras/kms/migrations/sqlite/07_mutable_root_key_version.up.sql +++ /dev/null @@ -1,18 +0,0 @@ --- Copyright (c) HashiCorp, Inc. --- SPDX-License-Identifier: MPL-2.0 - --- we need to make the root_key_version_id column mutable in order to support --- rewrapping the data key version. -drop trigger kms_immutable_columns_kms_data_key_version; - -create trigger kms_immutable_columns_kms_data_key_version -before update on kms_data_key_version -for each row - when - new.private_id <> old.private_id or - new.data_key_id <> old.data_key_id or - new.create_time <> old.create_time - begin - select raise(abort, 'immutable column'); - end; - \ No newline at end of file diff --git a/extras/kms/option.go b/extras/kms/option.go deleted file mode 100644 index 105449a1..00000000 --- a/extras/kms/option.go +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "crypto/rand" - "io" - - "github.com/hashicorp/go-dbw" -) - -// getOpts - iterate the inbound Options and return a struct -func getOpts(opt ...Option) options { - opts := getDefaultOptions() - for _, o := range opt { - o(&opts) - } - return opts -} - -// Option - how Options are passed as arguments -type Option func(*options) - -// options = how options are represented -type options struct { - withTableNamePrefix string - withLimit int - withKeyVersionId string - withOrderByVersion orderBy - withRetryCnt uint - withErrorsMatching func(error) bool - withPurpose KeyPurpose - withTx *dbw.RW - withRandomReader io.Reader - withReader dbw.Reader - withWriter dbw.Writer - withScopeIds []string - withRewrap bool - withRootKeyId string - withTableName string // not an exported option -} - -var noOpErrorMatchingFn = func(error) bool { return false } - -func getDefaultOptions() options { - return options{ - withTableNamePrefix: DefaultTableNamePrefix, - withErrorsMatching: noOpErrorMatchingFn, - withRetryCnt: stdRetryCnt, - withRandomReader: rand.Reader, - } -} - -// intentionally not exported -func withTableName(name string) Option { - return func(o *options) { - o.withTableName = name - } -} - -// WithTableNamePrefix specifying a prefix that should be used for schema table -// names. If not specified, the DefaultTableNamePrefix will be used. This -// optional allows us to support custom prefixes as well as multi KMSs within a -// single schema. -func WithTableNamePrefix(prefix string) Option { - return func(o *options) { - o.withTableNamePrefix = prefix - } -} - -// withLimit provides an option to provide a limit. Intentionally allowing -// negative integers. If withLimit < 0, then unlimited results are returned. If -// withLimit == 0, then default limits are used for results. -func withLimit(limit int) Option { - return func(o *options) { - o.withLimit = limit - } -} - -// WithKeyVersionId allows specifying a key version ID that should be found in a scope's -// multiwrapper; if it is not found, keys will be refreshed -func WithKeyVersionId(keyVersionId string) Option { - return func(o *options) { - o.withKeyVersionId = keyVersionId - } -} - -// WithKeyId allows specifying a key version ID that should be found in a scope's -// multiwrapper; if it is not found, keys will be refreshed. -// Deprecated: use WithKeyVersionId instead. -func WithKeyId(keyVersionId string) Option { - return WithKeyVersionId(keyVersionId) -} - -// withOrderByVersion provides an option to specify ordering by the -// CreateTime field. -func withOrderByVersion(by orderBy) Option { - return func(o *options) { - o.withOrderByVersion = by - } -} - -// withRetryCount provides an optional specified retry count, otherwise the -// StdRetryCnt is used. You must specify WithRetryErrorsMatching if you want -// any retries at all. -func withRetryCount(cnt uint) Option { - return func(o *options) { - o.withRetryCnt = cnt - } -} - -// withRetryErrorsMatching provides an optional function to match transactions -// errors which should be retried. -func withRetryErrorsMatching(matchingFn func(error) bool) Option { - return func(o *options) { - o.withErrorsMatching = matchingFn - } -} - -// withPurpose provides an optional key purpose -func withPurpose(purpose KeyPurpose) Option { - return func(o *options) { - o.withPurpose = purpose - } -} - -// WithTx allows the caller to pass an inflight transaction to be used for all -// database operations. If WithTx(...) is used, then the caller is responsible -// for managing the transaction. The purpose of the WithTx(...) option is to -// allow the caller to create the scope and all of its keys in the same -// transaction. -func WithTx(tx *dbw.RW) Option { - return func(o *options) { - o.withTx = tx - } -} - -// WithRandomReadear(...) option allows an optional random reader to be -// provided. By default the reader from crypto/rand will be used. -func WithRandomReader(randomReader io.Reader) Option { - return func(o *options) { - if !isNil(randomReader) { - o.withRandomReader = randomReader - } - } -} - -// WithReaderWriter allows the caller to pass an inflight transaction to be used -// for all database operations. If WithReaderWriter(...) is used, then the -// caller is responsible for managing the transaction. The purpose of the -// WithReaderWriter(...) option is to allow the caller to create the scope and -// all of its keys in the same transaction. -func WithReaderWriter(r dbw.Reader, w dbw.Writer) Option { - return func(o *options) { - o.withReader = r - o.withWriter = w - } -} - -// WithScopeIds allows an optional set of scope ids to be specified -func WithScopeIds(id ...string) Option { - return func(o *options) { - o.withScopeIds = id - } -} - -// WithReader provides an optional reader -func WithReader(r dbw.Reader) Option { - return func(o *options) { - o.withReader = r - } -} - -// WithRewrap allows for optionally specifying that the keys should be -// rewrapped. -func WithRewrap(enableRewrap bool) Option { - return func(o *options) { - o.withRewrap = enableRewrap - } -} - -// withRootKeyId allows specifying an optional root key ID -func withRootKeyId(keyId string) Option { - return func(o *options) { - o.withRootKeyId = keyId - } -} diff --git a/extras/kms/option_test.go b/extras/kms/option_test.go deleted file mode 100644 index 7e94ac35..00000000 --- a/extras/kms/option_test.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "reflect" - "runtime" - "testing" - - "github.com/stretchr/testify/assert" -) - -// Test_GetOpts provides unit tests for GetOpts and all the options -func Test_GetOpts(t *testing.T) { - t.Parallel() - t.Run("WithTableNamePrefix", func(t *testing.T) { - assert := assert.New(t) - opts := getOpts(WithTableNamePrefix("test")) - testOpts := getDefaultOptions() - testOpts.withTableNamePrefix = "test" - testOpts.withErrorsMatching = nil - opts.withErrorsMatching = nil - assert.Equal(opts, testOpts) - }) - t.Run("WithLimit", func(t *testing.T) { - assert := assert.New(t) - // test default of 0 - opts := getOpts() - testOpts := getDefaultOptions() - testOpts.withLimit = 0 - testOpts.withErrorsMatching = nil - opts.withErrorsMatching = nil - assert.Equal(opts, testOpts) - - opts = getOpts(withLimit(-1)) - testOpts = getDefaultOptions() - testOpts.withLimit = -1 - testOpts.withErrorsMatching = nil - opts.withErrorsMatching = nil - assert.Equal(opts, testOpts) - - opts = getOpts(withLimit(1)) - testOpts = getDefaultOptions() - testOpts.withLimit = 1 - testOpts.withErrorsMatching = nil - opts.withErrorsMatching = nil - assert.Equal(opts, testOpts) - }) - t.Run("WithKeyVersionId", func(t *testing.T) { - assert := assert.New(t) - opts := getOpts(WithKeyVersionId("id")) - testOpts := getDefaultOptions() - testOpts.withKeyVersionId = "id" - testOpts.withErrorsMatching = nil - opts.withErrorsMatching = nil - assert.Equal(opts, testOpts) - }) - t.Run("WithOrderByVersion", func(t *testing.T) { - assert := assert.New(t) - opts := getOpts(withOrderByVersion(descendingOrderBy)) - testOpts := getDefaultOptions() - testOpts.withOrderByVersion = descendingOrderBy - testOpts.withErrorsMatching = nil - opts.withErrorsMatching = nil - assert.Equal(opts, testOpts) - }) - t.Run("WithRetryErrorsMatching", func(t *testing.T) { - assert := assert.New(t) - - // testing the default first... - opts := getOpts() - // using this pattern to test for equality: https://github.com/stretchr/testify/issues/182#issuecomment-495359313 - funcName1 := runtime.FuncForPC(reflect.ValueOf(noOpErrorMatchingFn).Pointer()).Name() - funcName2 := runtime.FuncForPC(reflect.ValueOf(opts.withErrorsMatching).Pointer()).Name() - assert.Equal(funcName1, funcName2) - - // now, we'll test an optional override - fn := func(error) bool { return true } - opts = getOpts(withRetryErrorsMatching(fn)) - funcName1 = runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name() - funcName2 = runtime.FuncForPC(reflect.ValueOf(opts.withErrorsMatching).Pointer()).Name() - assert.Equal(funcName1, funcName2) - }) - t.Run("WithRetryCount", func(t *testing.T) { - const cnt = 1000 - assert := assert.New(t) - opts := getOpts(withRetryCount(cnt)) - testOpts := getDefaultOptions() - testOpts.withRetryCnt = cnt - testOpts.withErrorsMatching = nil - opts.withErrorsMatching = nil - assert.Equal(opts, testOpts) - }) - t.Run("WithPurpose", func(t *testing.T) { - const purpose = "test-purpose" - assert := assert.New(t) - opts := getOpts(withPurpose(purpose)) - testOpts := getDefaultOptions() - testOpts.withPurpose = purpose - testOpts.withErrorsMatching = nil - opts.withErrorsMatching = nil - assert.Equal(opts, testOpts) - }) -} diff --git a/extras/kms/query.go b/extras/kms/query.go deleted file mode 100644 index 20e92c40..00000000 --- a/extras/kms/query.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -const ( - postgresForeignReferencersQuery = ` -select distinct - r.table_name -from - information_schema.constraint_column_usage u - inner join information_schema.referential_constraints fk - on u.constraint_catalog = fk.unique_constraint_catalog - and u.constraint_schema = fk.unique_constraint_schema - and u.constraint_name = fk.unique_constraint_name - inner join information_schema.key_column_usage r - on r.constraint_catalog = fk.constraint_catalog - and r.constraint_schema = fk.constraint_schema - and r.constraint_name = fk.constraint_name -where - u.column_name = 'private_id' and - u.table_name = 'kms_data_key_version' -` - sqliteForeignReferencersQuery = ` -select - m.name -from - sqlite_master m - join pragma_foreign_key_list(m.name) p on m.name != p."table" -where - m.type = 'table' and - p."table" = 'kms_data_key_version' and - p."to" = 'private_id' -` -) diff --git a/extras/kms/repository.go b/extras/kms/repository.go deleted file mode 100644 index e881de36..00000000 --- a/extras/kms/repository.go +++ /dev/null @@ -1,308 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "fmt" - "io" - - "github.com/hashicorp/go-dbw" - "github.com/openbao/go-kms-wrapping/extras/kms/v2/migrations" - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/aead" - "github.com/hashicorp/go-secure-stdlib/strutil" - "github.com/hashicorp/go-uuid" -) - -const ( - // defaultLimit is the default for results: -1 signals no limit - defaultLimit = -1 - - // testDefaultWrapperSecret defines a default secret for testing - testDefaultWrapperSecret = "secret1234567890" - - // stdRetryCnt defines a standard retry count for transactions. - stdRetryCnt = 20 - - // noRowsAffected defines the returned value for no rows affected - noRowsAffected = 0 -) - -// orderBy defines an enum type for declaring a column's order by criteria. -type orderBy int - -const ( - // unknownOrderBy would designate an unknown ordering of the column, which - // is the standard ordering for any select without an order by clause. - unknownOrderBy = iota - - // ascendingOrderBy would designate ordering the column in ascending order. - ascendingOrderBy - - // descendingOrderBy would designate ordering the column in decending order. - descendingOrderBy -) - -// repository is the iam database repository -type repository struct { - reader dbw.Reader - writer dbw.Writer - // defaultLimit provides a default for limiting the number of results returned from the repo - defaultLimit int - - // tableNamePrefix defines the prefix to use before the table name and - // allows us to support custom prefixes as well as multi KMSs within a - // single schema. - tableNamePrefix string -} - -// newRepository creates a new kms Repository. Supports the options: WithLimit -// which sets a default limit on results returned by repo operations. -func newRepository(r dbw.Reader, w dbw.Writer, opt ...Option) (*repository, error) { - const op = "kms.newRepository" - if r == nil { - return nil, fmt.Errorf("%s: nil reader: %w", op, ErrInvalidParameter) - } - if w == nil { - return nil, fmt.Errorf("%s: nil writer: %w", op, ErrInvalidParameter) - } - opts := getOpts(opt...) - if opts.withLimit == 0 { - // zero signals the defaults should be used. - opts.withLimit = defaultLimit - } - if _, err := validateSchema(context.Background(), r, opts.withTableNamePrefix); err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - return &repository{ - reader: r, - writer: w, - defaultLimit: opts.withLimit, - tableNamePrefix: opts.withTableNamePrefix, - }, nil -} - -// ValidateSchema will validate the database schema against the module's -// required migrations.Version -func (r *repository) ValidateSchema(ctx context.Context) (string, error) { - const op = "kms.(repository).validateVersion" - return validateSchema(ctx, r.reader, r.tableNamePrefix) -} - -func validateSchema(ctx context.Context, r dbw.Reader, tableNamePrefix string) (string, error) { - const op = "kms.validateSchema" - s := schema{ - tableNamePrefix: tableNamePrefix, - } - if err := r.LookupWhere(ctx, &s, "1=1", nil, dbw.WithTable(s.TableName())); err != nil { - return "", fmt.Errorf("%s: unable to get version: %w", op, err) - } - if s.Version != migrations.Version { - return s.Version, fmt.Errorf("%s: invalid schema version, expected version %q and got %q: %w", op, migrations.Version, s.Version, ErrInvalidVersion) - } - return s.Version, nil -} - -// DefaultLimit returns the default limit for listing as set on the repo -func (r *repository) DefaultLimit() int { - return r.defaultLimit -} - -// list will return a listing of resources and honor the WithLimit option or the -// repo defaultLimit. WithOrderByVersion is supported for types that have a -// version column. WithReader option is supported. Non-exported withTableName -// is supported -func (r *repository) list(ctx context.Context, resources interface{}, where string, args []interface{}, opt ...Option) error { - opts := getOpts(opt...) - limit := r.defaultLimit - var dbOpts []dbw.Option - if opts.withTableName != "" { - dbOpts = append(dbOpts, dbw.WithTable(opts.withTableName)) - } - if opts.withLimit != 0 { - // non-zero signals an override of the default limit for the repo. - limit = opts.withLimit - } - dbOpts = append(dbOpts, dbw.WithLimit(limit)) - switch resources.(type) { - case *[]*rootKeyVersion, *[]*dataKeyVersion, []*rootKeyVersion, []*dataKeyVersion: - switch opts.withOrderByVersion { - case ascendingOrderBy: - dbOpts = append(dbOpts, dbw.WithOrder("version asc")) - case descendingOrderBy: - dbOpts = append(dbOpts, dbw.WithOrder("version desc")) - } - } - if opts.withReader == nil { - opts.withReader = r.reader - } - return opts.withReader.SearchWhere(ctx, resources, where, args, dbOpts...) -} - -type vetForWriter interface { - vetForWrite(ctx context.Context, opType dbw.OpType) error -} - -func updateKeyCollectionVersion(ctx context.Context, w dbw.Writer, tableNamePrefix string) error { - const ( - op = "kms.updateKeyCollectionVersion" - baseTableName = "collection_version" - sql = "update %s_%s set version = version + 1" - ) - if isNil(w) { - return fmt.Errorf("%s: missing writer: %w", op, ErrInvalidParameter) - } - - rowsUpdated, err := w.Exec(ctx, fmt.Sprintf(sql, tableNamePrefix, baseTableName), nil) - if err != nil { - return fmt.Errorf("%s: %w", op, err) - } - if rowsUpdated != 1 { - return fmt.Errorf("%s: update %q rows and expected 1: %w", op, rowsUpdated, ErrInternal) - } - return nil -} - -func currentCollectionVersion(ctx context.Context, r dbw.Reader, tableNamePrefix string) (uint64, error) { - const ( - op = "kms.currentCollectionVersion" - baseTableName = "collection_version" - sql = "select version from %s_%s" - ) - if isNil(r) { - return 0, fmt.Errorf("%s: missing reader: %w", op, ErrInvalidParameter) - } - - rows, err := r.Query(ctx, fmt.Sprintf(sql, tableNamePrefix, baseTableName), nil) - if err != nil { - return 0, fmt.Errorf("%s: %w", op, err) - } - v := struct { - Version uint64 - }{} - for rows.Next() { - if err := r.ScanRows(rows, &v); err != nil { - return 0, fmt.Errorf("%s: %w", op, err) - } - } - return v.Version, nil -} - -func create(ctx context.Context, writer dbw.Writer, i interface{}, opt ...dbw.Option) error { - const op = "kms.create" - before := func(interface{}) error { return nil } - if vetter, ok := i.(vetForWriter); ok { - before = func(i interface{}) error { - if err := vetter.vetForWrite(ctx, dbw.CreateOp); err != nil { - return err - } - return nil - } - } - if before != nil { - opt = append(opt, dbw.WithBeforeWrite(before)) - } - opt = append(opt, dbw.WithLookup(true)) - if err := writer.Create(ctx, i, opt...); err != nil { - return fmt.Errorf("%s: %w", op, err) - } - return nil -} - -// keyIder defines a common interface for all keys contained within a -// KeyWithVersion -type keyIder interface { - GetPrivateId() string -} - -// keyWithVersion encapsulates a key with its key version -type keyWithVersion struct { - Key keyIder - KeyVersion keyIder -} - -// keys defines a return type for createKeysTx so the returned keys can be -// easily accessed via their KeyPurpose -type keys map[KeyPurpose]keyWithVersion - -// createKeysTx creates the root key and DEKs and returns a map of the new keys. -// This function encapsulates all the work required within a dbw.TxHandler and -// allows this capability to be shared with other repositories or just called -// within a transaction. To be clear, this repository function doesn't include -// its own transaction and is intended to be used within a transaction provided -// by the caller. -func createKeysTx(ctx context.Context, r dbw.Reader, w dbw.Writer, rootWrapper wrapping.Wrapper, randomReader io.Reader, tableNamePrefix string, scopeId string, purpose ...KeyPurpose) (keys, error) { - const op = "kms.createKeysTx" - if rootWrapper == nil { - return nil, fmt.Errorf("%s: missing root wrapper: %w", op, ErrInvalidParameter) - } - if randomReader == nil { - return nil, fmt.Errorf("%s: missing random reader: %w", op, ErrInvalidParameter) - } - if scopeId == "" { - return nil, fmt.Errorf("%s: missing scope id: %w", op, ErrInvalidParameter) - } - if tableNamePrefix == "" { - return nil, fmt.Errorf("%s: missing table name prefix: %w", op, ErrInvalidParameter) - } - reserved := reservedKeyPurpose() - dups := map[KeyPurpose]struct{}{} - for _, p := range purpose { - if strutil.StrListContains(reserved, string(p)) { - return nil, fmt.Errorf("%s: reserved key purpose %q: %w", op, p, ErrInvalidParameter) - } - if _, ok := dups[p]; ok { - return nil, fmt.Errorf("%s: duplicate key purpose %q: %w", op, p, ErrInvalidParameter) - } - dups[p] = struct{}{} - } - k, err := generateKey(ctx, randomReader) - if err != nil { - return nil, fmt.Errorf("%s: error generating random bytes for root key in scope %q: %w", op, scopeId, err) - } - rootKey, rootKeyVersion, err := createRootKeyTx(ctx, w, rootWrapper, scopeId, k, tableNamePrefix) - if err != nil { - return nil, fmt.Errorf("%s: unable to create root key in scope %q: %w", op, scopeId, err) - } - keys := keys{ - KeyPurposeRootKey: keyWithVersion{ - rootKey, - rootKeyVersion, - }, - } - - rkvWrapper := aead.NewWrapper() - if _, err := rkvWrapper.SetConfig(ctx, wrapping.WithKeyId(rootKeyVersion.PrivateId)); err != nil { - return nil, fmt.Errorf("%s: error setting config on aead root wrapper in scope %q: %w", op, scopeId, err) - } - if err := rkvWrapper.SetAesGcmKeyBytes(rootKeyVersion.Key); err != nil { - return nil, fmt.Errorf("%s: error setting key bytes on aead root wrapper in scope %q: %w", op, scopeId, err) - } - - for _, p := range purpose { - k, err = generateKey(ctx, randomReader) - if err != nil { - return nil, fmt.Errorf("%s: error generating random bytes for data key of purpose %q in scope %q: %w", op, p, scopeId, err) - } - dataKey, dataKeyVersion, err := createDataKeyTx(ctx, r, w, rkvWrapper, tableNamePrefix, p, k) - if err != nil { - return nil, fmt.Errorf("%s: unable to create data key of purpose %q in scope %q: %w", op, p, scopeId, err) - } - keys[p] = keyWithVersion{ - Key: dataKey, - KeyVersion: dataKeyVersion, - } - } - return keys, nil -} - -func generateKey(ctx context.Context, randomReader io.Reader) ([]byte, error) { - const op = "kms.generateKey" - k, err := uuid.GenerateRandomBytesWithReader(32, randomReader) - if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - return k, nil -} diff --git a/extras/kms/repository_data_key.go b/extras/kms/repository_data_key.go deleted file mode 100644 index 382b79a0..00000000 --- a/extras/kms/repository_data_key.go +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "errors" - "fmt" - "strings" - - "github.com/hashicorp/go-dbw" - wrapping "github.com/openbao/go-kms-wrapping/v2" -) - -// CreateDataKey inserts into the repository and returns the new data key and -// data key version. Supported options: WithRetryCnt, WithRetryErrorsMatching -func (r *repository) CreateDataKey(ctx context.Context, rkvWrapper wrapping.Wrapper, purpose KeyPurpose, key []byte, opt ...Option) (*dataKey, *dataKeyVersion, error) { - const op = "kms.(Repository).CreateDataKey" - opts := getOpts(opt...) - var returnedDk *dataKey - var returnedDv *dataKeyVersion - _, err := r.writer.DoTx( - ctx, - opts.withErrorsMatching, - opts.withRetryCnt, - dbw.ExpBackoff{}, - func(reader dbw.Reader, w dbw.Writer) error { - if err := updateKeyCollectionVersion(ctx, w, r.tableNamePrefix); err != nil { - return err - } - var err error - if returnedDk, returnedDv, err = createDataKeyTx(ctx, reader, w, rkvWrapper, r.tableNamePrefix, purpose, key); err != nil { - return fmt.Errorf("%s: %w", op, err) - } - return nil - }, - ) - if err != nil { - return nil, nil, fmt.Errorf("%s: unable to create data key for purpose %q: %w", op, purpose, err) - } - return returnedDk, returnedDv, nil -} - -// createDataKeyTx inserts into the db (via dbw.Writer) and returns the new data key -// and data key version. This function encapsulates all the work required within -// a dbw.TxHandler and allows this capability to be shared within this repository -func createDataKeyTx(ctx context.Context, r dbw.Reader, w dbw.Writer, rkvWrapper wrapping.Wrapper, tableNamePrefix string, purpose KeyPurpose, key []byte) (*dataKey, *dataKeyVersion, error) { - const op = "kms.createDataKeyTx" - if rkvWrapper == nil { - return nil, nil, fmt.Errorf("%s: missing key wrapper: %w", op, ErrInvalidParameter) - } - if purpose == KeyPurposeUnknown { - return nil, nil, fmt.Errorf("%s: missing purpose: %w", op, ErrInvalidParameter) - } - if len(key) == 0 { - return nil, nil, fmt.Errorf("%s: missing key: %w", op, ErrInvalidParameter) - } - if tableNamePrefix == "" { - return nil, nil, fmt.Errorf("%s: missing table name prefix: %w", op, ErrInvalidParameter) - } - rootKeyVersionId, err := rkvWrapper.KeyId(ctx) - if err != nil { - return nil, nil, fmt.Errorf("%s: unable to lookup root key id: %w", op, err) - } - switch { - case rootKeyVersionId == "": - return nil, nil, fmt.Errorf("%s: missing root key version id: %w", op, ErrInvalidParameter) - case !strings.HasPrefix(rootKeyVersionId, rootKeyVersionPrefix): - return nil, nil, fmt.Errorf("%s: root key version id %q doesn't start with prefix %q: %w", op, rootKeyVersionId, rootKeyVersionPrefix, ErrInvalidParameter) - } - rv := rootKeyVersion{tableNamePrefix: tableNamePrefix} - rv.PrivateId = rootKeyVersionId - err = r.LookupBy(ctx, &rv, dbw.WithTable(rv.TableName())) - if err != nil { - return nil, nil, fmt.Errorf("%s: unable to lookup root key version %q: %w", op, rootKeyVersionId, err) - } - - dk := dataKey{ - Purpose: purpose, - tableNamePrefix: tableNamePrefix, - } - dv := dataKeyVersion{ - tableNamePrefix: tableNamePrefix, - } - id, err := newDataKeyId() - if err != nil { - return nil, nil, fmt.Errorf("%s: %w", op, err) - } - dk.PrivateId = id - dk.RootKeyId = rv.RootKeyId - - id, err = newDataKeyVersionId() - if err != nil { - return nil, nil, fmt.Errorf("%s: %w", op, err) - } - dv.PrivateId = id - dv.DataKeyId = dk.PrivateId - dv.RootKeyVersionId = rootKeyVersionId - dv.Key = key - if err := dv.Encrypt(ctx, rkvWrapper); err != nil { - return nil, nil, fmt.Errorf("%s: %w", op, err) - } - - // no oplog entries for keys - if err := create(ctx, w, &dk, dbw.WithTable(dk.TableName())); err != nil { - return nil, nil, fmt.Errorf("%s: keys create: %w", op, err) - } - // no oplog entries for key versions - if err := create(ctx, w, &dv, dbw.WithTable(dv.TableName())); err != nil { - return nil, nil, fmt.Errorf("%s: key versions create: %w", op, err) - } - - return &dk, &dv, nil -} - -// LookupDataKey will look up a key in the repository. If the key is not -// found then an ErrRecordNotFound will be returned. -func (r *repository) LookupDataKey(ctx context.Context, privateId string, _ ...Option) (*dataKey, error) { - const op = "kms.(Repository).LookupDataKey" - if privateId == "" { - return nil, fmt.Errorf("%s: missing private id: %w", op, ErrInvalidParameter) - } - k := dataKey{ - tableNamePrefix: r.tableNamePrefix, - } - k.PrivateId = privateId - if err := r.reader.LookupBy(ctx, &k, dbw.WithTable(k.TableName())); err != nil { - if errors.Is(err, dbw.ErrRecordNotFound) { - return nil, fmt.Errorf("%s: failed for %q: %w", op, privateId, ErrRecordNotFound) - } - return nil, fmt.Errorf("%s: failed for %q: %w", op, privateId, err) - } - return &k, nil -} - -// DeleteDataKey deletes the key for the provided id from the -// repository returning a count of the number of records deleted. Supported -// options: WithRetryCnt, WithRetryErrorsMatching -func (r *repository) DeleteDataKey(ctx context.Context, privateId string, opt ...Option) (int, error) { - const op = "kms.(Repository).DeleteDataKey" - if privateId == "" { - return noRowsAffected, fmt.Errorf("%s: missing private id: %w", op, ErrInvalidParameter) - } - k := dataKey{ - tableNamePrefix: r.tableNamePrefix, - } - k.PrivateId = privateId - if err := r.reader.LookupBy(ctx, &k, dbw.WithTable(k.TableName())); err != nil { - if errors.Is(err, dbw.ErrRecordNotFound) { - return noRowsAffected, fmt.Errorf("%s: failed for %q: %w", op, privateId, ErrRecordNotFound) - } - return noRowsAffected, fmt.Errorf("%s: failed for %q: %w", op, privateId, err) - } - opts := getOpts(opt...) - - var rowsDeleted int - _, err := r.writer.DoTx( - ctx, - opts.withErrorsMatching, - opts.withRetryCnt, - dbw.ExpBackoff{}, - func(_ dbw.Reader, w dbw.Writer) (err error) { - if err := updateKeyCollectionVersion(ctx, w, r.tableNamePrefix); err != nil { - return err - } - dk := k.Clone() - // no oplog entries for root keys - rowsDeleted, err = w.Delete(ctx, dk, dbw.WithTable(dk.TableName())) - if err != nil { - return fmt.Errorf("%s: %w", op, err) - } - if rowsDeleted > 1 { - return fmt.Errorf("%s: more than 1 resource would have been deleted: %w", op, ErrMultipleRecords) - } - return nil - }, - ) - if err != nil { - return noRowsAffected, fmt.Errorf("%s: failed for %q: %w", op, privateId, err) - } - return rowsDeleted, nil -} - -// ListDataKeys will list the keys. Supports options: WithPurpose, WithLimit, -// WithOrderByVersion, WithReader, WithRootKeyId -func (r *repository) ListDataKeys(ctx context.Context, opt ...Option) ([]*dataKey, error) { - const op = "kms.(Repository).ListDataKeys" - opts := getOpts(opt...) - - var keys []*dataKey - var where string - var whereArgs []interface{} - switch { - case opts.withRootKeyId != "": - where = "root_key_id = ?" - whereArgs = append(whereArgs, opts.withRootKeyId) - default: - where = "1=1" - } - if opts.withPurpose != KeyPurposeUnknown { - where += " and purpose = ?" - whereArgs = append(whereArgs, opts.withPurpose) - } - { - dk := dataKey{ - tableNamePrefix: r.tableNamePrefix, - } - opt = append(opt, withTableName(dk.TableName())) - } - err := r.list(ctx, &keys, where, whereArgs, opt...) - if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - return keys, nil -} diff --git a/extras/kms/repository_data_key_test.go b/extras/kms/repository_data_key_test.go deleted file mode 100644 index 2f90bd41..00000000 --- a/extras/kms/repository_data_key_test.go +++ /dev/null @@ -1,626 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "errors" - "fmt" - "testing" - "time" - - "github.com/DATA-DOG/go-sqlmock" - "github.com/hashicorp/go-dbw" - "github.com/openbao/go-kms-wrapping/extras/kms/v2/migrations" - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/aead" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type mockTestWrapper struct { - wrapping.Wrapper - decryptError bool - encryptError bool - err error - keyId string -} - -func (m *mockTestWrapper) KeyId(context.Context) (string, error) { - if m.err != nil && !m.encryptError && !m.decryptError { - return "", m.err - } - return m.keyId, nil -} - -func (m *mockTestWrapper) Encrypt(ctx context.Context, plaintext []byte, options ...wrapping.Option) (*wrapping.BlobInfo, error) { - if m.err != nil && m.encryptError { - return nil, m.err - } - panic("todo") -} - -func (m *mockTestWrapper) Decrypt(ctx context.Context, ciphertext *wrapping.BlobInfo, options ...wrapping.Option) ([]byte, error) { - if m.err != nil && m.decryptError { - return nil, m.err - } - return []byte("decrypted"), nil -} - -func TestRepository_CreateDataKey(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - testScopeId := "o_1234567890" - rk := testRootKey(t, db, testScopeId) - rkv, rkvWrapper := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - - tests := []struct { - name string - repo *repository - purpose KeyPurpose - scopeId string - key []byte - keyWrapper wrapping.Wrapper - opt []Option - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "nil-wrapper", - repo: testRepo, - purpose: "database", - scopeId: testScopeId, - key: []byte(testDefaultWrapperSecret), - keyWrapper: nil, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key wrapper", - }, - { - name: "missing-purpose", - repo: testRepo, - scopeId: testScopeId, - key: []byte(testDefaultWrapperSecret), - keyWrapper: rkvWrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing purpose", - }, - { - name: "missing-key", - repo: testRepo, - purpose: "database", - scopeId: testScopeId, - keyWrapper: rkvWrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key", - }, - { - name: "empty-key", - repo: testRepo, - purpose: "database", - scopeId: testScopeId, - key: []byte(""), - keyWrapper: rkvWrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key", - }, - { - name: "wrapper-key-id-error", - repo: testRepo, - purpose: "database", - scopeId: testScopeId, - key: []byte(testDefaultWrapperSecret), - keyWrapper: &mockTestWrapper{err: errors.New("KeyId error")}, - wantErr: true, - wantErrContains: "KeyId error", - }, - { - name: "wrapper-missing-key-id", - repo: testRepo, - purpose: "database", - scopeId: testScopeId, - key: []byte(testDefaultWrapperSecret), - keyWrapper: aead.NewWrapper(), - wantErr: true, - wantErrContains: "missing root key version id", - }, - { - name: "wrapper-invalid-key-id", - repo: testRepo, - purpose: "database", - scopeId: testScopeId, - key: []byte(testDefaultWrapperSecret), - keyWrapper: &mockTestWrapper{keyId: "invalid-key-id"}, - wantErr: true, - wantErrContains: "doesn't start with prefix", - }, - { - name: "encrypt-error", - repo: testRepo, - purpose: "database", - scopeId: testScopeId, - key: []byte(testDefaultWrapperSecret), - keyWrapper: func() wrapping.Wrapper { - w := aead.NewWrapper() - w.SetConfig(testCtx, wrapping.WithKeyId(rkv.PrivateId)) - return w - }(), - wantErr: true, - wantErrContains: "error wrapping value", - }, - { - name: "lookup-root-key-version-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectBegin() - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("lookup-root-key-version-error")) - mock.ExpectRollback() - return r - }(), - purpose: "database", - scopeId: testScopeId, - key: []byte(testDefaultWrapperSecret), - keyWrapper: rkvWrapper, - wantErr: true, - wantErrContains: "lookup-root-key-version-error", - }, - { - name: "create-data-key-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"private_id", "create_time"}).AddRow(rk.PrivateId, time.Now())) - mock.ExpectQuery(`INSERT INTO "kms_data_key"`).WillReturnError(errors.New("create-data-key-error")) - mock.ExpectRollback() - return r - }(), - purpose: "database", - scopeId: testScopeId, - key: []byte(testDefaultWrapperSecret), - keyWrapper: rkvWrapper, - wantErr: true, - wantErrContains: "create-data-key-error", - }, - { - name: "create-data-key-version-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"private_id", "root_key_id"}).AddRow(rkv.PrivateId, rkv.RootKeyId)) - mock.ExpectQuery(`INSERT INTO`).WillReturnRows(sqlmock.NewRows([]string{"scope_id", "create_time"}).AddRow(testScopeId, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"private_id", "create_time"}).AddRow(rk.PrivateId, time.Now())) - mock.ExpectQuery(`INSERT INTO`).WillReturnError(errors.New("create-data-key-version-error")) - mock.ExpectRollback() - return r - }(), - purpose: "database", - scopeId: testScopeId, - key: []byte(testDefaultWrapperSecret), - keyWrapper: rkvWrapper, - wantErr: true, - wantErrContains: "create-data-key-version-error", - }, - { - name: "success", - repo: testRepo, - purpose: "database", - scopeId: testScopeId, - key: []byte(testDefaultWrapperSecret), - keyWrapper: rkvWrapper, - wantErr: false, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - prevVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - - dk, dv, err := tc.repo.CreateDataKey(context.Background(), tc.keyWrapper, tc.purpose, tc.key, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.NotNil(dk.CreateTime) - foundKey, err := tc.repo.LookupDataKey(context.Background(), dk.PrivateId) - assert.NoError(err) - assert.Equal(dk, foundKey) - - assert.NotNil(dv.CreateTime) - foundKeyVersion, err := tc.repo.LookupDataKeyVersion(context.Background(), tc.keyWrapper, dv.PrivateId) - assert.NoError(err) - assert.Equal(dv, foundKeyVersion) - - currVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - assert.Greater(currVersion, prevVersion) - }) - } -} - -func TestRepository_DeleteDatabaseKey(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - const ( - testPurpose = "database" - testScopeId = "o_1234567890" - ) - rk := testRootKey(t, db, testScopeId) - - tests := []struct { - name string - repo *repository - key *dataKey - opt []Option - wantRowsDeleted int - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "no-private-id", - key: func() *dataKey { - k := dataKey{} - return &k - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing private id", - }, - { - name: "not-found", - repo: testRepo, - key: func() *dataKey { - id, err := dbw.NewId(rootKeyPrefix) - require.NoError(t, err) - k := dataKey{} - k.PrivateId = id - require.NoError(t, err) - return &k - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrIs: ErrRecordNotFound, - wantErrContains: "not found", - }, - { - name: "lookup-by-error", - key: func() *dataKey { - id, err := dbw.NewId(rootKeyPrefix) - require.NoError(t, err) - k := dataKey{} - k.PrivateId = id - require.NoError(t, err) - return &k - }(), - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("lookup-by-error")) - mock.ExpectRollback() - return r - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrContains: "lookup-by-error", - }, - { - name: "delete-error", - key: func() *dataKey { - id, err := dbw.NewId(rootKeyPrefix) - require.NoError(t, err) - k := dataKey{} - k.PrivateId = id - require.NoError(t, err) - return &k - }(), - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"scope_id", "create_time"}).AddRow(testScopeId, time.Now())) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(`DELETE`).WillReturnError(errors.New("delete-error")) - mock.ExpectRollback() - return r - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrContains: "delete-error", - }, - { - name: "delete-too-many-error", - key: func() *dataKey { - id, err := dbw.NewId(rootKeyPrefix) - require.NoError(t, err) - k := dataKey{} - k.PrivateId = id - require.NoError(t, err) - return &k - }(), - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"scope_id", "create_time"}).AddRow(testScopeId, time.Now())) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(`DELETE`).WillReturnResult(sqlmock.NewResult(0, 2)) - mock.ExpectRollback() - return r - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrIs: ErrMultipleRecords, - wantErrContains: "multiple records", - }, - { - name: "valid", - repo: testRepo, - key: testDataKey(t, db, rk.PrivateId, testPurpose), - wantRowsDeleted: 1, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - prevVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - - deletedRows, err := tc.repo.DeleteDataKey(testCtx, tc.key.PrivateId, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.wantRowsDeleted, deletedRows) - foundKey, err := tc.repo.LookupDataKey(context.Background(), tc.key.PrivateId) - assert.Error(err) - assert.Nil(foundKey) - assert.ErrorIs(err, ErrRecordNotFound) - - currVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - assert.Greater(currVersion, prevVersion) - }) - } -} - -func TestRepository_ListDataKeys(t *testing.T) { - const ( - testLimit = 10 - testPurpose = "database" - testScopeId = "o_1234567890" - ) - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw, withLimit(testLimit)) - require.NoError(t, err) - - tests := []struct { - name string - repo *repository - opt []Option - createCnt int - wantCnt int - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "no-limit", - repo: testRepo, - createCnt: testLimit * 2, - opt: []Option{withLimit(-1)}, - wantCnt: testLimit * 2, - wantErr: false, - }, - { - name: "default-limit", - repo: testRepo, - createCnt: testLimit + 5, - wantCnt: testLimit, - }, - { - name: "custom-limit", - repo: testRepo, - createCnt: testLimit + 1, - opt: []Option{withLimit(3)}, - wantCnt: 3, - wantErr: false, - }, - { - name: "WithOrderByVersion", - repo: testRepo, - createCnt: testLimit * 5, - opt: []Option{withOrderByVersion(ascendingOrderBy)}, - wantCnt: testLimit, - }, - { - name: "WithPurpose", - repo: testRepo, - createCnt: testLimit * 5, - opt: []Option{withPurpose("not-found")}, - wantCnt: 0, - }, - { - name: "WithPurpose", - repo: testRepo, - createCnt: testLimit * 5, - opt: []Option{withPurpose(KeyPurpose(fmt.Sprintf("%s-1", testPurpose)))}, - wantCnt: 1, - }, - { - name: "list-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("list-error")) - return r - }(), - createCnt: testLimit, - wantErr: true, - wantErrContains: "list-error", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - testDeleteWhere(t, db, func() interface{} { i := rootKey{tableNamePrefix: DefaultTableNamePrefix}; return &i }(), "1=1") - rk := testRootKey(t, db, testScopeId) - _, rkvWrapper := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - for i := 0; i < tc.createCnt; i++ { - _, _, err := testRepo.CreateDataKey(testCtx, rkvWrapper, KeyPurpose(fmt.Sprintf("%s-%d", testPurpose, i)), []byte(testDefaultWrapperSecret)) - require.NoError(err) - } - got, err := tc.repo.ListDataKeys(context.Background(), tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.wantCnt, len(got)) - }) - } -} - -func TestRepository_LookupDataKey(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - tests := []struct { - name string - repo *repository - privateKeyId string - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-private-id", - repo: testRepo, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing private id", - }, - { - name: "lookup-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("lookup-error")) - return r - }(), - privateKeyId: func() string { - id, err := dbw.NewId("o") - require.NoError(t, err) - k := testRootKey(t, db, id) - return k.PrivateId - }(), - wantErr: true, - wantErrContains: "lookup-error", - }, - { - name: "success", - repo: testRepo, - privateKeyId: func() string { - id, err := dbw.NewId("o") - require.NoError(t, err) - rk := testRootKey(t, db, id) - _, rkvWrapper := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - dk, _, err := testRepo.CreateDataKey(testCtx, rkvWrapper, "database", []byte(testDefaultWrapperSecret)) - require.NoError(t, err) - return dk.PrivateId - }(), - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - got, err := tc.repo.LookupDataKey(testCtx, tc.privateKeyId) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.privateKeyId, got.PrivateId) - }) - } -} diff --git a/extras/kms/repository_data_key_version.go b/extras/kms/repository_data_key_version.go deleted file mode 100644 index 3b49b066..00000000 --- a/extras/kms/repository_data_key_version.go +++ /dev/null @@ -1,401 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "errors" - "fmt" - "strings" - - "github.com/hashicorp/go-dbw" - wrapping "github.com/openbao/go-kms-wrapping/v2" -) - -// CreateDataKeyVersion inserts into the repository and returns the new key -// version with its PrivateId. Supported options: WithRetryCnt, -// WithRetryErrorsMatching -func (r *repository) CreateDataKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, dataKeyId string, key []byte, opt ...Option) (*dataKeyVersion, error) { - const op = "kms.(repository).CreateDataKeyVersion" - if rkvWrapper == nil { - return nil, fmt.Errorf("%s: missing root key version wrapper: %w", op, ErrInvalidParameter) - } - if dataKeyId == "" { - return nil, fmt.Errorf("%s: missing data key id: %w", op, ErrInvalidParameter) - } - if len(key) == 0 { - return nil, fmt.Errorf("%s: missing key: %w", op, ErrInvalidParameter) - } - rootKeyVersionId, err := rkvWrapper.KeyId(ctx) - if err != nil { - return nil, fmt.Errorf("%s: unable to get key id: %w", op, err) - } - switch { - case rootKeyVersionId == "": - return nil, fmt.Errorf("%s: missing root key version id: %w", op, ErrInvalidParameter) - case !strings.HasPrefix(rootKeyVersionId, rootKeyVersionPrefix): - return nil, fmt.Errorf("%s: root key version id %q doesn't start with prefix %q: %w", op, rootKeyVersionId, rootKeyVersionPrefix, ErrInvalidParameter) - } - kv := dataKeyVersion{ - tableNamePrefix: r.tableNamePrefix, - } - id, err := newDataKeyVersionId() - if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - kv.PrivateId = id - kv.RootKeyVersionId = rootKeyVersionId - kv.Key = key - kv.DataKeyId = dataKeyId - if err := kv.Encrypt(ctx, rkvWrapper); err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - opts := getOpts(opt...) - - var returnedKey interface{} - _, err = r.writer.DoTx( - ctx, - opts.withErrorsMatching, - opts.withRetryCnt, - dbw.ExpBackoff{}, - func(_ dbw.Reader, w dbw.Writer) error { - if err := updateKeyCollectionVersion(ctx, w, r.tableNamePrefix); err != nil { - return err - } - returnedKey = kv.Clone() - // no oplog entries for root key version - if err := create(ctx, w, returnedKey, dbw.WithTable(kv.TableName())); err != nil { - return fmt.Errorf("%s: %w", op, err) - } - return nil - }, - ) - if err != nil { - return nil, fmt.Errorf("%s: failed for %q data key id: %w", op, kv.DataKeyId, err) - } - k, ok := returnedKey.(*dataKeyVersion) - if !ok { - return nil, fmt.Errorf("%s: not a DataKeyVersion: %w", op, ErrInternal) - } - return k, nil -} - -// LookupDataKeyVersion will look up a key version in the repository. If -// the key version is not found then an ErrRecordNotFound will be returned. -func (r *repository) LookupDataKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, dataKeyVersionId string, _ ...Option) (*dataKeyVersion, error) { - const op = "kms.(repository).LookupDatabaseKeyVersion" - if dataKeyVersionId == "" { - return nil, fmt.Errorf("%s: missing private id: %w", op, ErrInvalidParameter) - } - if keyWrapper == nil { - return nil, fmt.Errorf("%s: missing key wrapper: %w", op, ErrInvalidParameter) - } - k := dataKeyVersion{ - tableNamePrefix: r.tableNamePrefix, - } - k.PrivateId = dataKeyVersionId - if err := r.reader.LookupBy(ctx, &k, dbw.WithTable(k.TableName())); err != nil { - if errors.Is(err, dbw.ErrRecordNotFound) { - return nil, fmt.Errorf("%s: failed for %q: %w", op, dataKeyVersionId, ErrRecordNotFound) - } - return nil, fmt.Errorf("%s: failed for %q: %w", op, dataKeyVersionId, err) - } - if err := k.Decrypt(ctx, keyWrapper); err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - return &k, nil -} - -// DeleteDataKeyVersion deletes the key version for the provided id from the -// repository returning a count of the number of records deleted. Supported -// options: WithRetryCnt, WithRetryErrorsMatching -func (r *repository) DeleteDataKeyVersion(ctx context.Context, dataKeyVersionId string, opt ...Option) (int, error) { - const op = "kms.(repository).DeleteDataKeyVersion" - if dataKeyVersionId == "" { - return noRowsAffected, fmt.Errorf("%s: missing private id: %w", op, ErrInvalidParameter) - } - k := dataKeyVersion{ - tableNamePrefix: r.tableNamePrefix, - } - k.PrivateId = dataKeyVersionId - if err := r.reader.LookupBy(ctx, &k, dbw.WithTable(k.TableName())); err != nil { - if errors.Is(err, dbw.ErrRecordNotFound) { - return noRowsAffected, fmt.Errorf("%s: failed for %q: %w", op, dataKeyVersionId, ErrRecordNotFound) - } - return noRowsAffected, fmt.Errorf("%s: failed for %q: %w", op, dataKeyVersionId, err) - } - opts := getOpts(opt...) - - var rowsDeleted int - _, err := r.writer.DoTx( - ctx, - opts.withErrorsMatching, - opts.withRetryCnt, - dbw.ExpBackoff{}, - func(_ dbw.Reader, w dbw.Writer) (err error) { - if err := updateKeyCollectionVersion(ctx, w, r.tableNamePrefix); err != nil { - return err - } - dk := k.Clone() - // no oplog entries for the key version - rowsDeleted, err = w.Delete(ctx, dk, dbw.WithTable(k.TableName())) - if err != nil { - return fmt.Errorf("%s: %w", op, err) - } - if rowsDeleted > 1 { - return fmt.Errorf("%s: more than 1 resource would have been deleted: %w", op, ErrMultipleRecords) - } - return nil - }, - ) - if err != nil { - return noRowsAffected, fmt.Errorf("%s: failed for %q: %w", op, dataKeyVersionId, err) - } - return rowsDeleted, nil -} - -// LatestDataKeyVersion searches for the key version with the highest -// version number. When no results are found, it returns nil with an -// ErrRecordNotFound error. -func (r *repository) LatestDataKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, dataKeyId string, _ ...Option) (*dataKeyVersion, error) { - const op = "kms.(repository).LatestDataKeyVersion" - if dataKeyId == "" { - return nil, fmt.Errorf("%s: missing data key id: %w", op, ErrInvalidParameter) - } - if rkvWrapper == nil { - return nil, fmt.Errorf("%s: missing root key version wrapper: %w", op, ErrInvalidParameter) - } - dkv := dataKeyVersion{ - tableNamePrefix: r.tableNamePrefix, - } - var foundKeys []*dataKeyVersion - if err := r.reader.SearchWhere(ctx, &foundKeys, "data_key_id = ?", []interface{}{dataKeyId}, dbw.WithLimit(1), dbw.WithOrder("version desc"), dbw.WithTable(dkv.TableName())); err != nil { - return nil, fmt.Errorf("%s: failed for %q: %w", op, dataKeyId, err) - } - if len(foundKeys) == 0 { - return nil, fmt.Errorf("%s: %w", op, ErrRecordNotFound) - } - if err := foundKeys[0].Decrypt(ctx, rkvWrapper); err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - return foundKeys[0], nil -} - -// ListDataKeyVersions will lists versions of a key. Supported options: -// WithLimit, WithOrderByVersion, WithReader -func (r *repository) ListDataKeyVersions(ctx context.Context, rkvWrapper wrapping.Wrapper, databaseKeyId string, opt ...Option) ([]*dataKeyVersion, error) { - const op = "kms.(repository).ListDataKeyVersions" - if databaseKeyId == "" { - return nil, fmt.Errorf("%s: missing data key id: %w", op, ErrInvalidParameter) - } - if rkvWrapper == nil { - return nil, fmt.Errorf("%s: missing root key version wrapper: %w", op, ErrInvalidParameter) - } - { - dkv := dataKeyVersion{ - tableNamePrefix: r.tableNamePrefix, - } - opt = append(opt, withTableName(dkv.TableName())) - } - var versions []*dataKeyVersion - err := r.list(ctx, &versions, "data_key_id = ?", []interface{}{databaseKeyId}, opt...) - if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - for i, k := range versions { - if err := k.Decrypt(ctx, rkvWrapper); err != nil { - return nil, fmt.Errorf("%s: error decrypting key num %q: %w", op, i, err) - } - } - return versions, nil -} - -// ListDataKeyVersionReferencers will lists the names of all tables -// referencing the private_id column of the data key version table. -// Supported options: -// - WithTx -// - WithReaderWriter -func (r *repository) ListDataKeyVersionReferencers(ctx context.Context, opt ...Option) ([]string, error) { - const op = "kms.(repository).ListDataKeyVersionReferencers" - typ, _, err := r.reader.Dialect() - if err != nil { - return nil, fmt.Errorf("%s: failed to get db dialect: %w", op, err) - } - var query string - switch typ { - case dbw.Postgres: - query = postgresForeignReferencersQuery - case dbw.Sqlite: - query = sqliteForeignReferencersQuery - default: - return nil, fmt.Errorf("unsupported DB dialect: %q", typ) - } - queryFn := r.reader.Query - opts := getOpts(opt...) - if opts.withTx != nil { - if opts.withReader != nil || opts.withWriter != nil { - return nil, fmt.Errorf("%s: WithTx(...) and WithReaderWriter(...) options cannot be used at the same time: %w", op, ErrInvalidParameter) - } - queryFn = opts.withTx.Query - } else if opts.withReader != nil { - queryFn = opts.withReader.Query - } - rows, err := queryFn(ctx, query, nil) - if err != nil { - return nil, fmt.Errorf("%s: failed to list foreign referencers: %w", op, err) - } - defer rows.Close() - var tableNames []string - for rows.Next() { - var tableName string - err := rows.Scan(&tableName) - if err != nil { - return nil, fmt.Errorf("%s: failed to scan table name into string: %w", op, err) - } - tableNames = append(tableNames, tableName) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("%s: failed to iterate rows: %w", op, err) - } - return tableNames, nil -} - -// rewrapDataKeyVersionsTx will rewrap (re-encrypt) the data key versions for a -// given rootKeyId with the latest root key version wrapper. -// This function encapsulates all the work required within a dbw.TxHandler and -// allows this capability to be shared with other repositories or just called -// within a transaction. To be clear, this repository function doesn't include -// its own transaction and is intended to be used within a transaction provided -// by the caller. -func rewrapDataKeyVersionsTx(ctx context.Context, reader dbw.Reader, writer dbw.Writer, tableNamePrefix string, rkvWrapper wrapping.Wrapper, rootKeyId string, _ ...Option) error { - const ( - op = "kms.rewrapDataKeyVersionsTx" - keyFieldName = "CtKey" - rootKeyVersionIdFieldName = "RootKeyVersionId" - ) - if isNil(reader) { - return fmt.Errorf("%s: missing reader: %w", op, ErrInvalidParameter) - } - if isNil(writer) { - return fmt.Errorf("%s: missing writer: %w", op, ErrInvalidParameter) - } - if isNil(rkvWrapper) { - return fmt.Errorf("%s: missing root key version wrapper: %w", op, ErrInvalidParameter) - } - if rootKeyId == "" { - return fmt.Errorf("%s: missing root key id: %w", op, ErrInvalidParameter) - } - if tableNamePrefix == "" { - return fmt.Errorf("%s: missing table name prefix: %w", op, ErrInvalidParameter) - } - - currentKeyVersionId, err := rkvWrapper.KeyId(ctx) - if err != nil { - return fmt.Errorf("%s: unable to get current key version ID: %w", op, err) - } - r, err := newRepository(reader, writer, WithTableNamePrefix(tableNamePrefix)) - if err != nil { - return fmt.Errorf("%s: unable to create repo: %w", op, err) - } - dks, err := r.ListDataKeys(ctx, withRootKeyId(rootKeyId), WithReader(reader)) - if err != nil { - return fmt.Errorf("%s: unable to list the current data keys: %w", op, err) - } - dkv := dataKeyVersion{ - tableNamePrefix: tableNamePrefix, - } - for _, dk := range dks { - var versions []*dataKeyVersion - if err := r.list(ctx, &versions, "data_key_id = ?", []interface{}{dk.PrivateId}, WithReader(reader), withTableName(dkv.TableName())); err != nil { - return fmt.Errorf("%s: unable to list the current data key versions: %w", op, err) - } - for _, v := range versions { - if err := v.Decrypt(ctx, rkvWrapper); err != nil { - return fmt.Errorf("%s: failed to decrypt data key version: %w", op, err) - } - if err := v.Encrypt(ctx, rkvWrapper); err != nil { - return fmt.Errorf("%s: failed to rewrap data key version: %w", op, err) - } - v.RootKeyVersionId = currentKeyVersionId - rowsAffected, err := writer.Update(ctx, v, []string{keyFieldName, rootKeyVersionIdFieldName}, nil, dbw.WithVersion(&v.Version), dbw.WithTable(dkv.TableName())) - if err != nil { - return fmt.Errorf("%s: failed to update data key version: %w", op, err) - } - if rowsAffected != 1 { - return fmt.Errorf("%s: expected to update 1 data key version and updated %d", op, rowsAffected) - } - } - } - return nil -} - -// rotateDataKeyVersionTx will rotate the key version for the given rootKeyId. -// This function encapsulates all the work required within a dbw.TxHandler and -// allows this capability to be shared with other repositories or just called -// within a transaction. To be clear, this repository function doesn't include -// its own transaction and is intended to be used within a transaction provided -// by the caller. -// Supported options: withRandomReader -func rotateDataKeyVersionTx(ctx context.Context, reader dbw.Reader, writer dbw.Writer, tableNamePrefix string, rootKeyVersionId string, rkvWrapper wrapping.Wrapper, rootKeyId string, purpose KeyPurpose, opt ...Option) error { - const op = "kms.rotateDataKeyVersionTx" - if isNil(reader) { - return fmt.Errorf("%s: missing reader: %w", op, ErrInvalidParameter) - } - if isNil(writer) { - return fmt.Errorf("%s: missing writer: %w", op, ErrInvalidParameter) - } - if rootKeyVersionId == "" { - return fmt.Errorf("%s: missing root key version id: %w", op, ErrInvalidParameter) - } - if isNil(rkvWrapper) { - return fmt.Errorf("%s: missing root key version wrapper: %w", op, ErrInvalidParameter) - } - if rootKeyId == "" { - return fmt.Errorf("%s: missing root key id: %w", op, ErrInvalidParameter) - } - if purpose == KeyPurposeUnknown { - return fmt.Errorf("%s: missing key purpose: %w", op, ErrInvalidParameter) - } - if tableNamePrefix == "" { - return fmt.Errorf("%s: missing table name prefix: %w", op, ErrInvalidParameter) - } - - r, err := newRepository(reader, writer, WithTableNamePrefix(tableNamePrefix)) - if err != nil { - return fmt.Errorf("%s: unable to create repo: %w", op, err) - } - dataKeys, err := r.ListDataKeys(ctx, withPurpose(purpose), withRootKeyId(rootKeyId), WithReader(reader)) - switch { - case err != nil: - return fmt.Errorf("%s: unable to lookup data key for %q: %w", op, purpose, err) - case len(dataKeys) == 0: - // this is NOT an error, there's just not data key to rotate for this purpose. - return nil - case len(dataKeys) > 1: - return fmt.Errorf("%s: too many data key (%d) for %q found: %w", op, len(dataKeys), purpose, ErrInternal) - } - opts := getOpts(opt...) - dekKeyBytes, err := generateKey(ctx, opts.withRandomReader) - if err != nil { - return fmt.Errorf("%s: unable to generate %s data key version: %w", op, purpose, err) - } - dv := dataKeyVersion{ - tableNamePrefix: tableNamePrefix, - } - id, err := newDataKeyVersionId() - if err != nil { - return fmt.Errorf("%s: %w", op, err) - } - dv.PrivateId = id - dv.DataKeyId = dataKeys[0].PrivateId - dv.RootKeyVersionId = rootKeyVersionId - dv.Key = dekKeyBytes - if err := dv.Encrypt(ctx, rkvWrapper); err != nil { - return fmt.Errorf("%s: unable to encrypt new data key version: %w", op, err) - } - if err := create(ctx, writer, &dv, dbw.WithTable(dv.TableName())); err != nil { - return fmt.Errorf("%s: unable to create data key version: %w", op, err) - } - return nil -} diff --git a/extras/kms/repository_data_key_version_test.go b/extras/kms/repository_data_key_version_test.go deleted file mode 100644 index c326ca53..00000000 --- a/extras/kms/repository_data_key_version_test.go +++ /dev/null @@ -1,1234 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "errors" - "sort" - "testing" - "time" - - "github.com/DATA-DOG/go-sqlmock" - "github.com/hashicorp/go-dbw" - "github.com/openbao/go-kms-wrapping/extras/kms/v2/migrations" - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/aead" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestRepository_CreateDataKeyVersion(t *testing.T) { - const ( - testScopeId = "o_1234567890" - testPurpose = "database" - ) - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - rk := testRootKey(t, db, testScopeId) - rkv, rkvWrapper := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - dk := testDataKey(t, db, rk.PrivateId, testPurpose) - - tests := []struct { - name string - repo *repository - key []byte - dataKeyId string - keyWrapper wrapping.Wrapper - opt []Option - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "nil-wrapper", - repo: testRepo, - key: []byte(testDefaultWrapperSecret), - keyWrapper: nil, - dataKeyId: dk.PrivateId, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key version wrapper", - }, - { - name: "missing-data-key-id", - repo: testRepo, - key: []byte(testDefaultWrapperSecret), - keyWrapper: rkvWrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing data key id", - }, - { - name: "missing-key", - repo: testRepo, - keyWrapper: rkvWrapper, - dataKeyId: dk.PrivateId, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key", - }, - { - name: "empty-key", - repo: testRepo, - keyWrapper: rkvWrapper, - dataKeyId: dk.PrivateId, - key: []byte(""), - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key", - }, - { - name: "wrapper-key-id-error", - repo: testRepo, - keyWrapper: &mockTestWrapper{err: errors.New("KeyId error")}, - dataKeyId: dk.PrivateId, - key: []byte(testDefaultWrapperSecret), - wantErr: true, - wantErrContains: "KeyId error", - }, - { - name: "missing-root-key-version-id", - repo: testRepo, - keyWrapper: aead.NewWrapper(), - dataKeyId: dk.PrivateId, - key: []byte(testDefaultWrapperSecret), - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key version id", - }, - { - name: "invalid-root-key-version-id", - repo: testRepo, - keyWrapper: func() wrapping.Wrapper { - w := aead.NewWrapper() - w.SetConfig(testCtx, wrapping.WithKeyId("invalid-root-key-version-id")) - return w - }(), dataKeyId: dk.PrivateId, - key: []byte(testDefaultWrapperSecret), - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "doesn't start with prefix", - }, - { - name: "encrypt-error", - repo: testRepo, - keyWrapper: func() wrapping.Wrapper { - w := aead.NewWrapper() - w.SetConfig(testCtx, wrapping.WithKeyId(rkv.PrivateId)) - return w - }(), dataKeyId: dk.PrivateId, - key: []byte(testDefaultWrapperSecret), - wantErr: true, - wantErrContains: "error wrapping value", - }, - { - name: "create-dkv-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectBegin() - mock.ExpectQuery(`INSERT INTO "kms_data_key_version"`).WillReturnError(errors.New("create-dkv-error")) - mock.ExpectRollback() - return testRepo - }(), - key: []byte(testDefaultWrapperSecret), - keyWrapper: rkvWrapper, - dataKeyId: dk.PrivateId, - wantErr: true, - wantErrContains: "create-dkv-error", - }, - { - name: "valid", - repo: testRepo, - key: []byte(testDefaultWrapperSecret), - keyWrapper: rkvWrapper, - dataKeyId: dk.PrivateId, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - prevVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - - k, err := tc.repo.CreateDataKeyVersion(context.Background(), tc.keyWrapper, tc.dataKeyId, tc.key, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.NotNil(k.CreateTime) - foundKey, err := tc.repo.LookupDataKeyVersion(context.Background(), tc.keyWrapper, k.PrivateId) - assert.NoError(err) - assert.Equal(k, foundKey) - - currVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - assert.Greater(currVersion, prevVersion) - }) - } -} - -func TestRepository_DeleteDataKeyVersion(t *testing.T) { - const ( - testScopeId = "o_1234567890" - testPurpose = "database" - ) - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - rk := testRootKey(t, db, testScopeId) - _, rkvWrapper := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - dk := testDataKey(t, db, rk.PrivateId, testPurpose) - - tests := []struct { - name string - repo *repository - key *dataKeyVersion - opt []Option - wantRowsDeleted int - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "no-private-id", - repo: testRepo, - key: func() *dataKeyVersion { - return &dataKeyVersion{} - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing private id", - }, - { - name: "not-found", - repo: testRepo, - key: func() *dataKeyVersion { - id, err := dbw.NewId(dataKeyPrefix) - require.NoError(t, err) - k := dataKeyVersion{} - k.PrivateId = id - require.NoError(t, err) - return &k - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrIs: ErrRecordNotFound, - wantErrContains: "record not found", - }, - { - name: "lookup-by-error", - key: testDataKeyVersion(t, db, rkvWrapper, dk.PrivateId, []byte(testDefaultWrapperSecret)), - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("lookup-by-error")) - mock.ExpectRollback() - return r - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrContains: "lookup-by-error", - }, - { - name: "delete-error", - key: func() *dataKeyVersion { - id, err := dbw.NewId(rootKeyPrefix) - require.NoError(t, err) - k := dataKeyVersion{} - k.PrivateId = id - require.NoError(t, err) - return &k - }(), - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"scope_id", "create_time"}).AddRow(testScopeId, time.Now())) - mock.ExpectBegin() - mock.ExpectExec(`DELETE`).WillReturnError(errors.New("delete-error")) - mock.ExpectRollback() - return r - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrContains: "delete-error", - }, - { - name: "delete-too-many-error", - key: func() *dataKeyVersion { - id, err := dbw.NewId(rootKeyPrefix) - require.NoError(t, err) - k := dataKeyVersion{} - k.PrivateId = id - require.NoError(t, err) - return &k - }(), - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"scope_id", "create_time"}).AddRow(testScopeId, time.Now())) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(`DELETE`).WillReturnResult(sqlmock.NewResult(0, 2)) - mock.ExpectRollback() - return r - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrIs: ErrMultipleRecords, - wantErrContains: "multiple records", - }, - { - name: "success", - repo: testRepo, - key: testDataKeyVersion(t, db, rkvWrapper, dk.PrivateId, []byte(testDefaultWrapperSecret)), - wantRowsDeleted: 1, - wantErr: false, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - prevVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - - deletedRows, err := tc.repo.DeleteDataKeyVersion(context.Background(), tc.key.PrivateId, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.wantRowsDeleted, deletedRows) - foundKey, err := tc.repo.LookupDataKeyVersion(context.Background(), wrapper, tc.key.PrivateId) - assert.Error(err) - assert.Nil(foundKey) - assert.ErrorIs(err, ErrRecordNotFound) - - currVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - assert.Greater(currVersion, prevVersion) - }) - } -} - -func TestRepository_LatestDataKeyVersion(t *testing.T) { - const ( - testScopeId = "o_1234567890" - testPurpose = "database" - ) - t.Parallel() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - rk := testRootKey(t, db, testScopeId) - _, rkvWrapper := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - dk := testDataKey(t, db, rk.PrivateId, testPurpose) - - tests := []struct { - name string - repo *repository - createCnt int - dataKeyId string - keyWrapper wrapping.Wrapper - wantVersion uint32 - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "5", - repo: testRepo, - dataKeyId: dk.PrivateId, - createCnt: 5, - keyWrapper: rkvWrapper, - wantVersion: 5, - }, - { - name: "1", - repo: testRepo, - dataKeyId: dk.PrivateId, - createCnt: 1, - keyWrapper: rkvWrapper, - wantVersion: 1, - }, - { - name: "0", - repo: testRepo, - dataKeyId: dk.PrivateId, - createCnt: 0, - keyWrapper: rkvWrapper, - wantErr: true, - wantErrIs: ErrRecordNotFound, - wantErrContains: "not found", - }, - { - name: "missing-data-key-id", - repo: testRepo, - createCnt: 5, - keyWrapper: wrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing data key id", - }, - { - name: "nil-wrapper", - repo: testRepo, - dataKeyId: dk.PrivateId, - createCnt: 5, - keyWrapper: nil, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key version wrapper", - }, - { - name: "search-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("search-error")) - return r - }(), - dataKeyId: dk.PrivateId, - createCnt: 5, - keyWrapper: wrapper, - wantErr: true, - wantErrContains: "search-error", - }, - { - name: "bad-wrapper", - repo: testRepo, - dataKeyId: dk.PrivateId, - createCnt: 5, - keyWrapper: aead.NewWrapper(), - wantErr: true, - wantErrContains: "error unwrapping value", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - testDeleteWhere(t, db, func() interface{} { i := dataKeyVersion{tableNamePrefix: DefaultTableNamePrefix}; return &i }(), "1=1") - testKeys := []*dataKeyVersion{} - for i := 0; i < tc.createCnt; i++ { - k := testDataKeyVersion(t, db, rkvWrapper, dk.PrivateId, []byte("test key")) - testKeys = append(testKeys, k) - } - assert.Equal(tc.createCnt, len(testKeys)) - got, err := tc.repo.LatestDataKeyVersion(context.Background(), tc.keyWrapper, tc.dataKeyId) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - require.NotNil(got) - assert.Equal(tc.wantVersion, got.Version) - }) - } -} - -func TestRepository_ListDataKeyVersions(t *testing.T) { - const ( - testLimit = 10 - testPurpose = "database" - testScopeId = "o_1234567890" - ) - t.Parallel() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw, withLimit(testLimit)) - require.NoError(t, err) - rk := testRootKey(t, db, testScopeId) - _, rkvWrapper := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - dk := testDataKey(t, db, rk.PrivateId, testPurpose) - - tests := []struct { - name string - repo *repository - dataKeyId string - keyWrapper wrapping.Wrapper - opt []Option - createCnt int - wantCnt int - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "no-limit", - repo: testRepo, - createCnt: testLimit * 2, - dataKeyId: dk.PrivateId, - keyWrapper: rkvWrapper, - opt: []Option{withLimit(-1)}, - wantCnt: testLimit * 2, - }, - { - name: "default-limit", - repo: testRepo, - createCnt: testLimit + 1, - keyWrapper: rkvWrapper, - dataKeyId: dk.PrivateId, - wantCnt: testLimit, - }, - { - name: "custom-limit", - repo: testRepo, - createCnt: testLimit + 1, - keyWrapper: rkvWrapper, - dataKeyId: dk.PrivateId, - opt: []Option{withLimit(3)}, - wantCnt: 3, - }, - { - name: "bad-wrapper", - repo: testRepo, - createCnt: 1, - keyWrapper: aead.NewWrapper(), - dataKeyId: dk.PrivateId, - wantErr: true, - wantErrContains: "error decrypting", - }, - { - name: "missing-data-key-id", - repo: testRepo, - createCnt: 1, - keyWrapper: wrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing data key id", - }, - { - name: "missing-wrapper", - repo: testRepo, - createCnt: 1, - keyWrapper: nil, - dataKeyId: dk.PrivateId, - wantCnt: 0, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key version wrapper", - }, - { - name: "list-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("list-error")) - return r - }(), - keyWrapper: wrapper, - dataKeyId: dk.PrivateId, - createCnt: testLimit, - wantErr: true, - wantErrContains: "list-error", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - testDeleteWhere(t, db, func() interface{} { i := dataKeyVersion{tableNamePrefix: DefaultTableNamePrefix}; return &i }(), "1=1") - keyVersions := []*dataKeyVersion{} - for i := 0; i < tc.createCnt; i++ { - k := testDataKeyVersion(t, db, rkvWrapper, dk.PrivateId, []byte("data key")) - keyVersions = append(keyVersions, k) - } - assert.Equal(tc.createCnt, len(keyVersions)) - got, err := tc.repo.ListDataKeyVersions(context.Background(), tc.keyWrapper, tc.dataKeyId, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.wantCnt, len(got)) - }) - } - t.Run("order-by", func(t *testing.T) { - const createCnt = 10 - assert, require := assert.New(t), require.New(t) - testDeleteWhere(t, db, func() interface{} { i := dataKeyVersion{tableNamePrefix: DefaultTableNamePrefix}; return &i }(), "1=1") - keyVersions := []*dataKeyVersion{} - for i := 0; i < createCnt; i++ { - k := testDataKeyVersion(t, db, rkvWrapper, dk.PrivateId, []byte("data key")) - keyVersions = append(keyVersions, k) - } - assert.Equal(createCnt, len(keyVersions)) - got, err := testRepo.ListDataKeyVersions(context.Background(), wrapper, dk.PrivateId, withOrderByVersion(descendingOrderBy)) - require.NoError(err) - assert.NotNil(got) - lastVersion := -1 - for _, dkv := range got { - if lastVersion != -1 { - currentVersion := dkv.Version - assert.Greater(lastVersion, lastVersion) - lastVersion = int(currentVersion) - } - } - - got, err = testRepo.ListDataKeyVersions(context.Background(), wrapper, dk.PrivateId, withOrderByVersion(ascendingOrderBy)) - require.NoError(err) - assert.NotNil(got) - lastVersion = -1 - for _, dkv := range got { - if lastVersion != -1 { - currentVersion := dkv.Version - assert.Less(lastVersion, lastVersion) - lastVersion = int(currentVersion) - } - } - }) -} - -func TestRepository_LookupDataKeyVersion(t *testing.T) { - const ( - testPurpose = "database" - testScopeId = "o_1234567890" - ) - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - rk := testRootKey(t, db, testScopeId) - _, rkvWrapper := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - dk := testDataKey(t, db, rk.PrivateId, testPurpose) - tests := []struct { - name string - repo *repository - wrapper wrapping.Wrapper - privateKeyId string - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-private-id", - repo: testRepo, - wrapper: wrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing private id", - }, - { - name: "missing-wrapper", - repo: testRepo, - privateKeyId: func() string { - id, err := dbw.NewId("o") - require.NoError(t, err) - k := testRootKey(t, db, id) - return k.PrivateId - }(), - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key wrapper", - }, - { - name: "lookup-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("lookup-error")) - return r - }(), - wrapper: wrapper, - privateKeyId: func() string { - id, err := dbw.NewId("o") - require.NoError(t, err) - k := testRootKey(t, db, id) - return k.PrivateId - }(), - wantErr: true, - wantErrContains: "lookup-error", - }, - { - name: "bad-wrapper", - repo: testRepo, - wrapper: aead.NewWrapper(), - privateKeyId: func() string { - k := testDataKeyVersion(t, db, rkvWrapper, dk.PrivateId, []byte("data key")) - return k.PrivateId - }(), - wantErr: true, - wantErrContains: "error unwrapping value", - }, - { - name: "success", - repo: testRepo, - wrapper: wrapper, - privateKeyId: func() string { - k := testDataKeyVersion(t, db, rkvWrapper, dk.PrivateId, []byte("data key")) - return k.PrivateId - }(), - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - got, err := tc.repo.LookupDataKeyVersion(testCtx, tc.wrapper, tc.privateKeyId) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.privateKeyId, got.PrivateId) - }) - } -} - -func Test_rotateDataKeyVersionTx(t *testing.T) { - t.Parallel() - const ( - testScopeId = "global" - testPlainText = "simple plain-text" - ) - - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - rootWrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - testKms, err := New(rw, rw, []KeyPurpose{"database"}) - require.NoError(t, err) - testKms.AddExternalWrapper(testCtx, KeyPurposeRootKey, rootWrapper) - require.NoError(t, testKms.CreateKeys(testCtx, testScopeId, []KeyPurpose{"database"})) - - rkvWrapper, rootKeyId, err := testKms.loadRoot(testCtx, testScopeId) - require.NoError(t, err) - rkvId, err := rkvWrapper.KeyId(testCtx) - require.NoError(t, err) - - tests := []struct { - name string - reader dbw.Reader - writer dbw.Writer - repo *repository - rootKeyVersionId string - rkvWrapper wrapping.Wrapper - rootKeyId string - purpose KeyPurpose - opt []Option - expectNoRotation bool - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-reader", - repo: testRepo, - writer: rw, - rootKeyId: rootKeyId, - rkvWrapper: rkvWrapper, - rootKeyVersionId: rkvId, - purpose: "database", - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing reader", - }, - { - name: "missing-writer", - repo: testRepo, - reader: rw, - rootKeyId: rootKeyId, - rkvWrapper: rkvWrapper, - rootKeyVersionId: rkvId, - purpose: "database", - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing writer", - }, - { - name: "missing-root-key-version-id", - repo: testRepo, - reader: rw, - writer: rw, - rootKeyId: rootKeyId, - rkvWrapper: rkvWrapper, - purpose: "database", - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key version id", - }, - { - name: "missing-root-key-version-wrapper", - repo: testRepo, - reader: rw, - writer: rw, - rootKeyId: rootKeyId, - rootKeyVersionId: rkvId, - purpose: "database", - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key version wrapper", - }, - { - name: "missing-root-key-id", - repo: testRepo, - reader: rw, - writer: rw, - rootKeyVersionId: rkvId, - rkvWrapper: rkvWrapper, - purpose: "database", - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key id", - }, - { - name: "missing-purpose", - repo: testRepo, - reader: rw, - writer: rw, - rootKeyId: rootKeyId, - rootKeyVersionId: rkvId, - rkvWrapper: rkvWrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key purpose", - }, - { - name: "newRepository-error", - repo: testRepo, - reader: &dbw.RW{}, - writer: rw, - rootKeyId: rootKeyId, - rootKeyVersionId: rkvId, - rkvWrapper: rkvWrapper, - purpose: "database", - wantErr: true, - wantErrContains: "unable to create repo", - }, - { - name: "ListDataKeys-error", - repo: testRepo, - reader: func() dbw.Reader { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("ListDataKeys-error")) - return rw - }(), - writer: rw, - rootKeyId: rootKeyId, - rootKeyVersionId: rkvId, - rkvWrapper: rkvWrapper, - purpose: "database", - wantErr: true, - wantErrContains: "unable to lookup data key", - }, - { - name: "success-ListDataKeys-no-rows", - repo: testRepo, - reader: func() dbw.Reader { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"})) - return rw - }(), - writer: rw, - rootKeyId: rootKeyId, - rootKeyVersionId: rkvId, - rkvWrapper: rkvWrapper, - purpose: "database", - expectNoRotation: true, - }, - { - name: "ListDataKeys-too-many-rows", - repo: testRepo, - reader: func() dbw.Reader { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"private_id", "root_key_id", "purpose", "create_time"}).AddRow("1", "1", "database", time.Now()).AddRow("2", "2", "database", time.Now())) - return rw - }(), - writer: rw, - rootKeyId: rootKeyId, - rootKeyVersionId: rkvId, - rkvWrapper: rkvWrapper, - purpose: "database", - wantErr: true, - }, - { - name: "randReader-error", - repo: testRepo, - reader: rw, - writer: rw, - rootKeyId: rootKeyId, - rootKeyVersionId: rkvId, - rkvWrapper: rkvWrapper, - purpose: "database", - opt: []Option{WithRandomReader(newMockRandReader(1, "bad-reader"))}, - wantErr: true, - wantErrContains: "bad-reader", - }, - { - name: "Encrypt-error", - repo: testRepo, - reader: rw, - writer: rw, - rootKeyId: rootKeyId, - rootKeyVersionId: rkvId, - rkvWrapper: &mockTestWrapper{err: errors.New("Encrypt-error"), encryptError: true}, - purpose: "database", - wantErr: true, - wantErrContains: "unable to encrypt new data key version", - }, - { - name: "create-error", - repo: testRepo, - reader: rw, - writer: &dbw.RW{}, - rootKeyId: rootKeyId, - rootKeyVersionId: rkvId, - rkvWrapper: rkvWrapper, - purpose: "database", - wantErr: true, - wantErrContains: "unable to create data key version", - }, - { - name: "success", - repo: testRepo, - reader: rw, - writer: rw, - rootKeyId: rootKeyId, - rootKeyVersionId: rkvId, - rkvWrapper: rkvWrapper, - purpose: "database", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - var currentDataKeyVersions []*dataKeyVersion - var encryptedBlob *wrapping.BlobInfo - var currentWrapper wrapping.Wrapper - if !tc.wantErr { - currDataKeys, err := tc.repo.ListDataKeys(testCtx, withRootKeyId(tc.rootKeyId)) - require.NoError(err) - for _, dk := range currDataKeys { - var versions []*dataKeyVersion - tc.repo.list(testCtx, &versions, "data_key_id = ?", []interface{}{dk.PrivateId}, withOrderByVersion(ascendingOrderBy)) - require.NoError(err) - currentDataKeyVersions = append(currentDataKeyVersions, versions...) - } - sort.Slice(currentDataKeyVersions, func(i, j int) bool { - return currentDataKeyVersions[i].PrivateId < currentDataKeyVersions[j].PrivateId - }) - - currentWrapper, err = testKms.GetWrapper(testCtx, testScopeId, tc.purpose) - require.NoError(err) - encryptedBlob, err = currentWrapper.Encrypt(testCtx, []byte(testPlainText)) - require.NoError(err) - - // rotateDataKeyVersionTx doesn't increment the collection - // version, so we have to do it here - err = updateKeyCollectionVersion(testCtx, tc.writer, DefaultTableNamePrefix) - require.NoError(err) - } - - err = rotateDataKeyVersionTx(testCtx, tc.reader, tc.writer, DefaultTableNamePrefix, tc.rootKeyVersionId, tc.rkvWrapper, tc.rootKeyId, tc.purpose, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - - // ensure we can decrypt something using the rotated wrapper (does - // the previous key still work) - rotatedWrapper, err := testKms.GetWrapper(testCtx, testScopeId, tc.purpose) - require.NoError(err) - pt, err := rotatedWrapper.Decrypt(testCtx, encryptedBlob) - require.NoError(err) - assert.Equal(testPlainText, string(pt)) - - newDataKeys, err := tc.repo.ListDataKeys(testCtx, withRootKeyId(tc.rootKeyId)) - require.NoError(err) - var newDataKeyVersions []*dataKeyVersion - for _, dk := range newDataKeys { - var versions []*dataKeyVersion - tc.repo.list(testCtx, &versions, "data_key_id = ?", []interface{}{dk.PrivateId}, withOrderByVersion(ascendingOrderBy)) - require.NoError(err) - newDataKeyVersions = append(newDataKeyVersions, versions...) - } - sort.Slice(newDataKeyVersions, func(i, j int) bool { - return newDataKeyVersions[i].PrivateId < newDataKeyVersions[j].PrivateId - }) - switch { - case tc.expectNoRotation: - assert.Equal(len(newDataKeyVersions), len(currentDataKeyVersions)) - default: - assert.Equal(len(newDataKeyVersions), len(currentDataKeyVersions)*2) - // encrypt pt with new version and make sure none of the old - // versions can decrypt it - encryptedBlob, err = rotatedWrapper.Encrypt(testCtx, []byte(testPlainText)) - require.NoError(err) - _, err = currentWrapper.Decrypt(testCtx, encryptedBlob) - assert.Error(err) - } - }) - } -} - -func Test_rewrapDataKeyVersionsTx(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - rootWrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testKms, err := New(rw, rw, []KeyPurpose{"database"}) - require.NoError(t, err) - testKms.AddExternalWrapper(testCtx, KeyPurposeRootKey, rootWrapper) - require.NoError(t, testKms.CreateKeys(testCtx, "global", []KeyPurpose{"database"})) - - rkvWrapper, rootKeyId, err := testKms.loadRoot(testCtx, "global") - require.NoError(t, err) - - tests := []struct { - name string - reader dbw.Reader - writer dbw.Writer - rkvWrapper wrapping.Wrapper - rootKeyId string - opt []Option - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-reader", - writer: rw, - rkvWrapper: rkvWrapper, - rootKeyId: rootKeyId, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing reader", - }, - { - name: "missing-writer", - reader: rw, - rkvWrapper: rkvWrapper, - rootKeyId: rootKeyId, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing writer", - }, - { - name: "missing-rkvWrapper", - reader: rw, - writer: rw, - rootKeyId: rootKeyId, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key version wrapper", - }, - { - name: "missing-rootKeyId", - reader: rw, - writer: rw, - rkvWrapper: rkvWrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key id", - }, - { - name: "newRepository-error", - reader: &dbw.RW{}, - writer: rw, - rootKeyId: rootKeyId, - rkvWrapper: rkvWrapper, - wantErr: true, - wantErrContains: "unable to create repo", - }, - { - name: "ListDataKeys-error", - reader: func() dbw.Reader { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("ListDataKeys-error")) - return rw - }(), - writer: rw, - rootKeyId: rootKeyId, - rkvWrapper: rkvWrapper, - wantErr: true, - wantErrContains: "unable to list the current data keys", - }, - { - name: "list-error", - reader: func() dbw.Reader { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"private_id", "root_key_id", "purpose", "create_time"}).AddRow("1", "1", "database", time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("list-error")) - return rw - }(), - writer: rw, - rootKeyId: rootKeyId, - rkvWrapper: rkvWrapper, - wantErr: true, - wantErrContains: "unable to list the current data key versions", - }, - { - name: "Decrypt-error", - reader: rw, - writer: rw, - rootKeyId: rootKeyId, - rkvWrapper: &mockTestWrapper{err: errors.New("Decrypt-error"), decryptError: true}, - wantErr: true, - wantErrContains: "failed to decrypt data key version", - }, - { - name: "Encrypt-error", - reader: rw, - writer: rw, - rootKeyId: rootKeyId, - rkvWrapper: &mockTestWrapper{err: errors.New("Encrypt-error"), encryptError: true}, - wantErr: true, - wantErrContains: "failed to rewrap data key version", - }, - { - name: "success", - reader: rw, - writer: rw, - rkvWrapper: rkvWrapper, - rootKeyId: rootKeyId, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - err := rewrapDataKeyVersionsTx(testCtx, tc.reader, tc.writer, DefaultTableNamePrefix, tc.rkvWrapper, tc.rootKeyId, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - }) - } -} - -func TestRepository_ListDataKeyVersionReferencers(t *testing.T) { - t.Parallel() - db, _ := TestDb(t) - rw := dbw.New(db) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - - t.Run("No options", func(t *testing.T) { - tableNames, err := testRepo.ListDataKeyVersionReferencers(context.Background()) - require.NoError(t, err) - require.ElementsMatch(t, []string{"kms_test_encrypted_data"}, tableNames) - }) - t.Run("WithTx", func(t *testing.T) { - tx, err := rw.Begin(context.Background()) - require.NoError(t, err) - t.Cleanup(func() { - _ = tx.Rollback(context.Background()) - }) - tableNames, err := testRepo.ListDataKeyVersionReferencers(context.Background(), WithTx(tx)) - require.NoError(t, err) - require.ElementsMatch(t, []string{"kms_test_encrypted_data"}, tableNames) - err = tx.Commit(context.Background()) - require.NoError(t, err) - }) - - t.Run("WithReaderWriter", func(t *testing.T) { - tx, err := rw.Begin(context.Background()) - require.NoError(t, err) - t.Cleanup(func() { - _ = tx.Rollback(context.Background()) - }) - tableNames, err := testRepo.ListDataKeyVersionReferencers(context.Background(), WithReaderWriter(tx, tx)) - require.NoError(t, err) - require.ElementsMatch(t, []string{"kms_test_encrypted_data"}, tableNames) - err = tx.Commit(context.Background()) - require.NoError(t, err) - }) -} diff --git a/extras/kms/repository_root_key.go b/extras/kms/repository_root_key.go deleted file mode 100644 index 95ac90e7..00000000 --- a/extras/kms/repository_root_key.go +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "errors" - "fmt" - - "github.com/hashicorp/go-dbw" - wrapping "github.com/openbao/go-kms-wrapping/v2" -) - -// CreateRootKey inserts into the repository and returns the new root key and -// root key version. Supported options: WithRetryCnt, WithRetryErrorsMatching -func (r *repository) CreateRootKey(ctx context.Context, keyWrapper wrapping.Wrapper, scopeId string, key []byte, opt ...Option) (*rootKey, *rootKeyVersion, error) { - const op = "kms.(repository).CreateRootKey" - opts := getOpts(opt...) - var returnedRk *rootKey - var returnedKv *rootKeyVersion - _, err := r.writer.DoTx( - ctx, - opts.withErrorsMatching, - opts.withRetryCnt, - dbw.ExpBackoff{}, - func(_ dbw.Reader, w dbw.Writer) error { - if err := updateKeyCollectionVersion(ctx, w, r.tableNamePrefix); err != nil { - return err - } - var err error - if returnedRk, returnedKv, err = createRootKeyTx(ctx, w, keyWrapper, scopeId, key, r.tableNamePrefix); err != nil { - return fmt.Errorf("%s: %w", op, err) - } - return nil - }, - ) - if err != nil { - return nil, nil, fmt.Errorf("%s: failed for %q: %w", op, scopeId, err) - } - return returnedRk, returnedKv, nil -} - -// createRootKeyTx inserts into the db (via dbw.Writer) and returns the new root key -// and root key version. This function encapsulates all the work required within -// a dbw.TxHandler -func createRootKeyTx(ctx context.Context, w dbw.Writer, keyWrapper wrapping.Wrapper, scopeId string, key []byte, tableNamePrefix string) (*rootKey, *rootKeyVersion, error) { - const op = "kms.createRootKeyTx" - if scopeId == "" { - return nil, nil, fmt.Errorf("%s: missing scope id: %w", op, ErrInvalidParameter) - } - if keyWrapper == nil { - return nil, nil, fmt.Errorf("%s: missing key wrapper: %w", op, ErrInvalidParameter) - } - if len(key) == 0 { - return nil, nil, fmt.Errorf("%s: missing key: %w", op, ErrInvalidParameter) - } - if tableNamePrefix == "" { - return nil, nil, fmt.Errorf("%s: missing table name prefix: %w", op, ErrInvalidParameter) - } - rk := rootKey{tableNamePrefix: tableNamePrefix} - kv := rootKeyVersion{tableNamePrefix: tableNamePrefix} - id, err := newRootKeyId() - if err != nil { - return nil, nil, fmt.Errorf("%s: %w", op, err) - } - rk.PrivateId = id - rk.ScopeId = scopeId - - id, err = newRootKeyVersionId() - if err != nil { - return nil, nil, fmt.Errorf("%s: %w", op, err) - } - kv.PrivateId = id - kv.RootKeyId = rk.PrivateId - kv.Key = key - if err := kv.Encrypt(ctx, keyWrapper); err != nil { - return nil, nil, fmt.Errorf("%s: %w", op, err) - } - - if err := create(ctx, w, &rk, dbw.WithTable(rk.TableName())); err != nil { - return nil, nil, fmt.Errorf("%s: root keys: %w", op, err) - } - if err := create(ctx, w, &kv, dbw.WithTable(kv.TableName())); err != nil { - return nil, nil, fmt.Errorf("%s: key versions: %w", op, err) - } - - return &rk, &kv, nil -} - -// LookupRootKey will look up a root key in the repository. If the key is not -// found then an ErrRecordNotFound will be returned. -func (r *repository) LookupRootKey(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*rootKey, error) { - const op = "kms.(Repository).LookupRootKey" - if privateId == "" { - return nil, fmt.Errorf("%s: missing private id: %w", op, ErrInvalidParameter) - } - if keyWrapper == nil { - return nil, fmt.Errorf("%s: missing key wrapper: %w", op, ErrInvalidParameter) - } - k := rootKey{ - tableNamePrefix: r.tableNamePrefix, - } - k.PrivateId = privateId - if err := r.reader.LookupBy(ctx, &k, dbw.WithTable(k.TableName())); err != nil { - if errors.Is(err, dbw.ErrRecordNotFound) { - return nil, fmt.Errorf("%s: failed for %q: %w", op, privateId, ErrRecordNotFound) - } - return nil, fmt.Errorf("%s: failed for %q: %w", op, privateId, err) - } - return &k, nil -} - -// DeleteRootKey deletes the root key for the provided id from the -// repository returning a count of the number of records deleted. Supported -// options: WithRetryCnt, WithRetryErrorsMatching -func (r *repository) DeleteRootKey(ctx context.Context, privateId string, opt ...Option) (int, error) { - const op = "kms.(repository).DeleteRootKey" - if privateId == "" { - return noRowsAffected, fmt.Errorf("%s: missing private id: %w", op, ErrInvalidParameter) - } - k := rootKey{ - tableNamePrefix: r.tableNamePrefix, - } - k.PrivateId = privateId - if err := r.reader.LookupBy(ctx, &k, dbw.WithTable(k.TableName())); err != nil { - if errors.Is(err, dbw.ErrRecordNotFound) { - return noRowsAffected, fmt.Errorf("%s: failed for %q: %w", op, privateId, ErrRecordNotFound) - } - return noRowsAffected, fmt.Errorf("%s: failed for %q: %w", op, privateId, err) - } - - opts := getOpts(opt...) - - var rowsDeleted int - _, err := r.writer.DoTx( - ctx, - opts.withErrorsMatching, - opts.withRetryCnt, - dbw.ExpBackoff{}, - func(_ dbw.Reader, w dbw.Writer) (err error) { - if err := updateKeyCollectionVersion(ctx, w, r.tableNamePrefix); err != nil { - return err - } - dk := k.Clone() - // no oplog entries for root keys - rowsDeleted, err = w.Delete(ctx, dk, dbw.WithTable(dk.TableName())) - if err != nil { - return fmt.Errorf("%s: %w", op, err) - } - if rowsDeleted > 1 { - return fmt.Errorf("%s: more than 1 resource would have been deleted: %w", op, ErrMultipleRecords) - } - return nil - }, - ) - if err != nil { - return noRowsAffected, fmt.Errorf("%s: failed for %q: %w", op, privateId, err) - } - return rowsDeleted, nil -} - -// ListRootKeys will list the root keys. Supported options: WithLimit, -// WithOrderByVersion, WithReader -func (r *repository) ListRootKeys(ctx context.Context, opt ...Option) ([]*rootKey, error) { - const op = "kms.(repository).ListRootKeys" - { - rk := rootKey{ - tableNamePrefix: r.tableNamePrefix, - } - opt = append(opt, withTableName(rk.TableName())) - } - var keys []*rootKey - err := r.list(ctx, &keys, "1=1", nil, opt...) - if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - return keys, nil -} - -// LookupRootKeyByScope will lookup the rootKey for a given scope id. Supported -// options: WithReader -func (r *repository) LookupRootKeyByScope(ctx context.Context, scopeId string, opt ...Option) (*rootKey, error) { - const op = "kms.(repository).ScopeRootKey" - opts := getOpts(opt...) - if opts.withReader == nil { - opts.withReader = r.reader - } - k := rootKey{ - tableNamePrefix: r.tableNamePrefix, - } - err := opts.withReader.LookupWhere(ctx, &k, "scope_id=?", []interface{}{scopeId}, dbw.WithTable(k.TableName())) - if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - return &k, nil -} diff --git a/extras/kms/repository_root_key_test.go b/extras/kms/repository_root_key_test.go deleted file mode 100644 index 2cde50e4..00000000 --- a/extras/kms/repository_root_key_test.go +++ /dev/null @@ -1,612 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "errors" - "testing" - "time" - - "github.com/DATA-DOG/go-sqlmock" - "github.com/hashicorp/go-dbw" - "github.com/openbao/go-kms-wrapping/extras/kms/v2/migrations" - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/aead" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestRepository_CreateRootKey(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - testScopeId := "o_1234567890" - - tests := []struct { - name string - repo *repository - scopeId string - key []byte - keyWrapper wrapping.Wrapper - opt []Option - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-scope", - repo: testRepo, - key: []byte("empty-scope"), - keyWrapper: wrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing scope", - }, - { - name: "nil-wrapper", - repo: testRepo, - scopeId: testScopeId, - key: []byte("test key"), - keyWrapper: nil, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key wrapper", - }, - { - name: "missing-key", - repo: testRepo, - scopeId: testScopeId, - keyWrapper: wrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key", - }, - { - name: "bad-wrapper", - repo: testRepo, - scopeId: testScopeId, - key: []byte("test key"), - keyWrapper: aead.NewWrapper(), - wantErr: true, - wantErrContains: "error wrapping value", - }, - { - name: "create-rk-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectQuery(`INSERT INTO "kms_root_key"`).WillReturnError(errors.New("create-rk-error")) - mock.ExpectRollback() - return testRepo - }(), - scopeId: testScopeId, - key: []byte("test key"), - keyWrapper: wrapper, - wantErr: true, - wantErrContains: "create-rk-error", - }, - { - name: "create-rkv-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectQuery(`INSERT INTO "kms_root_key"`).WillReturnRows(sqlmock.NewRows([]string{"scope_id", "create_time"}).AddRow(testScopeId, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"scope_id", "create_time"}).AddRow(testScopeId, time.Now())) - mock.ExpectQuery(`INSERT INTO "kms_root_key_version"`).WillReturnError(errors.New("create-rkv-error")) - mock.ExpectRollback() - return testRepo - }(), - scopeId: testScopeId, - key: []byte("test key"), - keyWrapper: wrapper, - wantErr: true, - wantErrContains: "create-rkv-error", - }, - { - name: "success", - repo: testRepo, - scopeId: testScopeId, - key: []byte("test key"), - keyWrapper: wrapper, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - prevVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - - rk, kv, err := tc.repo.CreateRootKey(context.Background(), tc.keyWrapper, tc.scopeId, tc.key, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.NotNil(rk.CreateTime) - foundKey, err := tc.repo.LookupRootKey(context.Background(), tc.keyWrapper, rk.PrivateId) - assert.NoError(err) - assert.Equal(rk, foundKey) - - assert.NotNil(kv.CreateTime) - foundKeyVersion, err := tc.repo.LookupRootKeyVersion(context.Background(), tc.keyWrapper, kv.PrivateId) - assert.NoError(err) - assert.Equal(kv, foundKeyVersion) - - currVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - assert.Greater(currVersion, prevVersion) - }) - } -} - -func TestRepository_DeleteRootKey(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - testScopeId := "o_1234567890" - - tests := []struct { - name string - repo *repository - key *rootKey - opt []Option - wantRowsDeleted int - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "no-private-id", - repo: testRepo, - key: func() *rootKey { - return &rootKey{} - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing private id", - }, - { - name: "not-found", - repo: testRepo, - key: func() *rootKey { - id, err := dbw.NewId(rootKeyPrefix) - require.NoError(t, err) - k := rootKey{} - k.PrivateId = id - return &k - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrIs: ErrRecordNotFound, - wantErrContains: "record not found", - }, - { - name: "lookup-by-error", - key: func() *rootKey { - id, err := dbw.NewId(rootKeyPrefix) - require.NoError(t, err) - k := rootKey{} - k.PrivateId = id - require.NoError(t, err) - return &k - }(), - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("lookup-by-error")) - mock.ExpectRollback() - return r - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrContains: "lookup-by-error", - }, - { - name: "delete-error", - key: func() *rootKey { - id, err := dbw.NewId(rootKeyPrefix) - require.NoError(t, err) - k := rootKey{} - k.PrivateId = id - require.NoError(t, err) - return &k - }(), - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"scope_id", "create_time"}).AddRow(testScopeId, time.Now())) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(`DELETE`).WillReturnError(errors.New("delete-error")) - mock.ExpectRollback() - return r - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrContains: "delete-error", - }, - { - name: "delete-too-many-error", - key: func() *rootKey { - id, err := dbw.NewId(rootKeyPrefix) - require.NoError(t, err) - k := rootKey{} - k.PrivateId = id - require.NoError(t, err) - return &k - }(), - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"scope_id", "create_time"}).AddRow(testScopeId, time.Now())) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(`DELETE`).WillReturnResult(sqlmock.NewResult(0, 2)) - mock.ExpectRollback() - return r - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrIs: ErrMultipleRecords, - wantErrContains: "multiple records", - }, - { - name: "valid", - repo: testRepo, - key: testRootKey(t, db, testScopeId), - wantRowsDeleted: 1, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - prevVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - - deletedRows, err := tc.repo.DeleteRootKey(context.Background(), tc.key.PrivateId, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.wantRowsDeleted, deletedRows) - foundKey, err := tc.repo.LookupRootKey(context.Background(), wrapper, tc.key.PrivateId) - assert.Error(err) - assert.Nil(foundKey) - assert.ErrorIs(err, ErrRecordNotFound) - - currVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - assert.Greater(currVersion, prevVersion) - }) - } -} - -func TestRepository_ListRootKeys(t *testing.T) { - const testLimit = 10 - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw, withLimit(testLimit)) - require.NoError(t, err) - - tests := []struct { - name string - repo *repository - createCnt int - opt []Option - wantCnt int - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "no-limit", - repo: testRepo, - createCnt: testLimit * 2, - opt: []Option{withLimit(-1)}, - wantCnt: testLimit * 2, - }, - { - name: "default-limit", - repo: testRepo, - createCnt: testLimit + 5, - wantCnt: testLimit, - }, - { - name: "custom-limit", - repo: testRepo, - createCnt: testLimit + 1, - opt: []Option{withLimit(3)}, - wantCnt: 3, - wantErr: false, - }, - { - name: "ignored-option-WithOrderByVersion", - repo: testRepo, - createCnt: testLimit * 5, - opt: []Option{withOrderByVersion(ascendingOrderBy)}, - wantCnt: testLimit, - }, - { - name: "list-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("list-error")) - return r - }(), - createCnt: testLimit, - wantErr: true, - wantErrContains: "list-error", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - testDeleteWhere(t, db, func() interface{} { i := rootKey{tableNamePrefix: DefaultTableNamePrefix}; return &i }(), "1=1") - for i := 0; i < tc.createCnt; i++ { - id, err := dbw.NewId(rootKeyPrefix) - require.NoError(err) - _, _, err = testRepo.CreateRootKey(testCtx, wrapper, id, []byte(testDefaultWrapperSecret)) - require.NoError(err) - } - got, err := tc.repo.ListRootKeys(context.Background(), tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.wantCnt, len(got)) - }) - } -} - -func TestRepository_LookupRootKey(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - tests := []struct { - name string - repo *repository - wrapper wrapping.Wrapper - privateKeyId string - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-private-id", - repo: testRepo, - wrapper: wrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing private id", - }, - { - name: "missing-wrapper", - repo: testRepo, - privateKeyId: func() string { - id, err := dbw.NewId("o") - require.NoError(t, err) - k := testRootKey(t, db, id) - return k.PrivateId - }(), - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key wrapper", - }, - { - name: "lookup-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("lookup-error")) - return r - }(), - wrapper: wrapper, - privateKeyId: func() string { - id, err := dbw.NewId("o") - require.NoError(t, err) - k := testRootKey(t, db, id) - return k.PrivateId - }(), - wantErr: true, - wantErrContains: "lookup-error", - }, - { - name: "success", - repo: testRepo, - wrapper: wrapper, - privateKeyId: func() string { - id, err := dbw.NewId("o") - require.NoError(t, err) - k := testRootKey(t, db, id) - return k.PrivateId - }(), - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - got, err := tc.repo.LookupRootKey(testCtx, tc.wrapper, tc.privateKeyId) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.privateKeyId, got.PrivateId) - }) - } -} - -func TestRepository_LookupRootKeyVersion(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - testScopeId := "o_1234567890" - rk := testRootKey(t, db, testScopeId) - - tests := []struct { - name string - repo *repository - wrapper wrapping.Wrapper - privateKeyId string - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-private-id", - repo: testRepo, - wrapper: wrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing private id", - }, - { - name: "missing-wrapper", - repo: testRepo, - privateKeyId: func() string { - id, err := dbw.NewId("o") - require.NoError(t, err) - k := testRootKey(t, db, id) - return k.PrivateId - }(), - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key wrapper", - }, - { - name: "lookup-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("lookup-error")) - return r - }(), - wrapper: wrapper, - privateKeyId: func() string { - id, err := dbw.NewId("o") - require.NoError(t, err) - k := testRootKey(t, db, id) - return k.PrivateId - }(), - wantErr: true, - wantErrContains: "lookup-error", - }, - { - name: "bad-wrapper", - repo: testRepo, - wrapper: aead.NewWrapper(), - privateKeyId: func() string { - k, _ := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - return k.PrivateId - }(), - wantErr: true, - wantErrContains: "unable to decrypt", - }, - { - name: "success", - repo: testRepo, - wrapper: wrapper, - privateKeyId: func() string { - k, _ := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - return k.PrivateId - }(), - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - got, err := tc.repo.LookupRootKeyVersion(testCtx, tc.wrapper, tc.privateKeyId) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.privateKeyId, got.PrivateId) - }) - } -} diff --git a/extras/kms/repository_root_key_version.go b/extras/kms/repository_root_key_version.go deleted file mode 100644 index 39b53b71..00000000 --- a/extras/kms/repository_root_key_version.go +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "errors" - "fmt" - - "github.com/hashicorp/go-dbw" - wrapping "github.com/openbao/go-kms-wrapping/v2" -) - -// LookupRootKeyVersion will look up a root key version in the repository. If -// the key version is not found then an ErrRecordNotFound will be returned. -func (r *repository) LookupRootKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, rootKeyVersionId string, _ ...Option) (*rootKeyVersion, error) { - const op = "kms.(repository).LookupRootKeyVersion" - if rootKeyVersionId == "" { - return nil, fmt.Errorf("%s: missing private id: %w", op, ErrInvalidParameter) - } - if keyWrapper == nil { - return nil, fmt.Errorf("%s: missing key wrapper: %w", op, ErrInvalidParameter) - } - k := rootKeyVersion{ - tableNamePrefix: r.tableNamePrefix, - } - k.PrivateId = rootKeyVersionId - if err := r.reader.LookupBy(ctx, &k, dbw.WithTable(k.TableName())); err != nil { - if errors.Is(err, dbw.ErrRecordNotFound) { - return nil, fmt.Errorf("%s: failed for %q: %w", op, rootKeyVersionId, ErrRecordNotFound) - } - return nil, fmt.Errorf("%s: failed for %q: %w", op, rootKeyVersionId, err) - } - if err := k.Decrypt(ctx, keyWrapper); err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - return &k, nil -} - -// CreateRootKeyVersion inserts into the repository and returns the new root key -// version with its PrivateId. Supported options: WithRetryCnt, -// WithRetryErrorsMatching -func (r *repository) CreateRootKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, rootKeyId string, key []byte, opt ...Option) (*rootKeyVersion, error) { - const op = "kms.(repository).CreateRootKeyVersion" - if rootKeyId == "" { - return nil, fmt.Errorf("%s: missing root key id: %w", op, ErrInvalidParameter) - } - if keyWrapper == nil { - return nil, fmt.Errorf("%s: missing key wrapper: %w", op, ErrInvalidParameter) - } - if len(key) == 0 { - return nil, fmt.Errorf("%s: missing key: %w", op, ErrInvalidParameter) - } - kv := rootKeyVersion{ - tableNamePrefix: r.tableNamePrefix, - } - id, err := newRootKeyVersionId() - if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - kv.PrivateId = id - kv.RootKeyId = rootKeyId - kv.Key = key - if err := kv.Encrypt(ctx, keyWrapper); err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - - opts := getOpts(opt...) - - var returnedKey interface{} - _, err = r.writer.DoTx( - ctx, - opts.withErrorsMatching, - opts.withRetryCnt, - dbw.ExpBackoff{}, - func(_ dbw.Reader, w dbw.Writer) error { - if err := updateKeyCollectionVersion(ctx, w, r.tableNamePrefix); err != nil { - return err - } - returnedKey = kv.Clone() - if err := create(ctx, w, returnedKey, dbw.WithTable(kv.TableName())); err != nil { - return fmt.Errorf("%s: %w", op, err) - } - return nil - }, - ) - if err != nil { - return nil, fmt.Errorf("%s: failed for %q root key id: %w", op, kv.RootKeyId, err) - } - k, ok := returnedKey.(*rootKeyVersion) - if !ok { - return nil, fmt.Errorf("%s: not a RootKeyVersion: %w", op, ErrInternal) - } - return k, nil -} - -// DeleteRootKeyVersion deletes the root key version for the provided id from the -// repository returning a count of the number of records deleted. Supported -// options: WithRetryCnt, WithRetryErrorsMatching -func (r *repository) DeleteRootKeyVersion(ctx context.Context, rootKeyVersionId string, opt ...Option) (int, error) { - const op = "kms.(repository).DeleteRootKeyVersion" - if rootKeyVersionId == "" { - return noRowsAffected, fmt.Errorf("%s: missing private id: %w", op, ErrInvalidParameter) - } - k := rootKeyVersion{ - tableNamePrefix: r.tableNamePrefix, - } - k.PrivateId = rootKeyVersionId - if err := r.reader.LookupBy(ctx, &k, dbw.WithTable(k.TableName())); err != nil { - if errors.Is(err, dbw.ErrRecordNotFound) { - return noRowsAffected, fmt.Errorf("%s: failed for %q: %w", op, rootKeyVersionId, ErrRecordNotFound) - } - return noRowsAffected, fmt.Errorf("%s: failed for %q: %w", op, rootKeyVersionId, err) - } - - opts := getOpts(opt...) - - var rowsDeleted int - _, err := r.writer.DoTx( - ctx, - opts.withErrorsMatching, - opts.withRetryCnt, - dbw.ExpBackoff{}, - func(_ dbw.Reader, w dbw.Writer) (err error) { - if err := updateKeyCollectionVersion(ctx, w, r.tableNamePrefix); err != nil { - return err - } - dk := k.Clone() - // no oplog entries for root key version - rowsDeleted, err = w.Delete(ctx, dk, dbw.WithTable(dk.TableName())) - if err != nil { - return fmt.Errorf("%s: %w", op, err) - } - if rowsDeleted > 1 { - return fmt.Errorf("%s: more than 1 resource would have been deleted: %w", op, ErrMultipleRecords) - } - return nil - }, - ) - if err != nil { - return noRowsAffected, fmt.Errorf("%s: failed for %q: %w", op, rootKeyVersionId, err) - } - return rowsDeleted, nil -} - -// LatestRootKeyVersion searches for the root key version with the highest -// version number. When no results are found, it returns nil with an -// ErrRecordNotFound error. -func (r *repository) LatestRootKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, rootKeyId string, _ ...Option) (*rootKeyVersion, error) { - const op = "kms.(repository).LatestRootKeyVersion" - if rootKeyId == "" { - return nil, fmt.Errorf("%s: missing root key id: %w", op, ErrInvalidParameter) - } - if keyWrapper == nil { - return nil, fmt.Errorf("%s: missing key wrapper: %w", op, ErrInvalidParameter) - } - rkv := rootKeyVersion{ - tableNamePrefix: r.tableNamePrefix, - } - var foundKeys []*rootKeyVersion - if err := r.reader.SearchWhere(ctx, &foundKeys, "root_key_id = ?", []interface{}{rootKeyId}, dbw.WithLimit(1), dbw.WithOrder("version desc"), dbw.WithTable(rkv.TableName())); err != nil { - return nil, fmt.Errorf("%s: failed for %q: %w", op, rootKeyId, err) - } - if len(foundKeys) == 0 { - return nil, fmt.Errorf("%s: %w", op, ErrRecordNotFound) - } - if err := foundKeys[0].Decrypt(ctx, keyWrapper); err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - return foundKeys[0], nil -} - -// ListRootKeyVersions in versions of a root key. Supported options: WithLimit, -// WithOrderByVersion, WithReader -func (r *repository) ListRootKeyVersions(ctx context.Context, keyWrapper wrapping.Wrapper, rootKeyId string, opt ...Option) ([]*rootKeyVersion, error) { - const op = "kms.(repository).ListRootKeyVersions" - if rootKeyId == "" { - return nil, fmt.Errorf("%s: missing root key id: %w", op, ErrInvalidParameter) - } - if keyWrapper == nil { - return nil, fmt.Errorf("%s: missing key wrapper: %w", op, ErrInvalidParameter) - } - { - rkv := rootKeyVersion{ - tableNamePrefix: r.tableNamePrefix, - } - opt = append(opt, withTableName(rkv.TableName())) - } - var versions []*rootKeyVersion - err := r.list(ctx, &versions, "root_key_id = ?", []interface{}{rootKeyId}, opt...) - if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - for i, k := range versions { - if err := k.Decrypt(ctx, keyWrapper); err != nil { - return nil, fmt.Errorf("%s: error decrypting key num %d: %w", op, i, err) - } - } - return versions, nil -} - -// rewrapRootKeyVersionsTx will rewrap (re-encrypt) the root key versions for a -// given rootKeyId with the latest wrapper. -// This function encapsulates all the work required within a dbw.TxHandler and -// allows this capability to be shared with other repositories or just called -// within a transaction. To be clear, this repository function doesn't include -// its own transaction and is intended to be used within a transaction provided -// by the caller. Supported options: WithTableNamePrefix -func rewrapRootKeyVersionsTx(ctx context.Context, reader dbw.Reader, writer dbw.Writer, rootWrapper wrapping.Wrapper, rootKeyId string, opt ...Option) error { - const ( - op = "kms.rewrapRootKeyVersionsTx" - keyFieldName = "CtKey" - ) - if isNil(reader) { - return fmt.Errorf("%s: missing reader: %w", op, ErrInvalidParameter) - } - if isNil(writer) { - return fmt.Errorf("%s: missing writer: %w", op, ErrInvalidParameter) - } - if isNil(rootWrapper) { - return fmt.Errorf("%s: missing root wrapper: %w", op, ErrInvalidParameter) - } - if rootKeyId == "" { - return fmt.Errorf("%s: missing root key id: %w", op, ErrInvalidParameter) - } - r, err := newRepository(reader, writer, opt...) - if err != nil { - return fmt.Errorf("%s: unable to create repo: %w", op, err) - } - // rewrap the rootKey versions using the scope's root key to find them - rkvs, err := r.ListRootKeyVersions(ctx, rootWrapper, rootKeyId, WithReader(reader)) - if err != nil { - return fmt.Errorf("%s: unable to list root key versions: %w", op, err) - } - opts := getOpts(opt...) - for _, kv := range rkvs { - if err := kv.Encrypt(ctx, rootWrapper); err != nil { - return fmt.Errorf("%s: failed to rewrap root key version: %w", op, err) - } - kv.tableNamePrefix = opts.withTableNamePrefix - rowsAffected, err := writer.Update(ctx, kv, []string{keyFieldName}, nil, dbw.WithVersion(&kv.Version), dbw.WithTable(kv.TableName())) - if err != nil { - return fmt.Errorf("%s: failed to update root key version: %w", op, err) - } - if rowsAffected != 1 { - return fmt.Errorf("%s: expected to update 1 root key version and updated %d", op, rowsAffected) - } - } - return nil -} - -// rotateRootKeyVersionTx will rotate the key version for the given rootKeyId. -// This function encapsulates all the work required within a dbw.TxHandler and -// allows this capability to be shared with other repositories or just called -// within a transaction. To be clear, this repository function doesn't include -// its own transaction and is intended to be used within a transaction provided -// by the caller. -// Supported options: withRandomReader -func rotateRootKeyVersionTx(ctx context.Context, writer dbw.Writer, rootWrapper wrapping.Wrapper, rootKeyId string, opt ...Option) (*rootKeyVersion, error) { - const op = "kms.rotateRootKeyVersionTx" - if isNil(rootWrapper) { - return nil, fmt.Errorf("%s: missing root wrapper: %w", op, ErrInvalidParameter) - } - if rootKeyId == "" { - return nil, fmt.Errorf("%s: missing root key id: %w", op, ErrInvalidParameter) - } - - if isNil(writer) { - return nil, fmt.Errorf("%s: missing writer: %w", op, ErrInvalidParameter) - } - opts := getOpts(opt...) - rootKeyBytes, err := generateKey(ctx, opts.withRandomReader) - if err != nil { - return nil, fmt.Errorf("%s: unable to generate key: %w", op, err) - } - rkv := rootKeyVersion{ - tableNamePrefix: opts.withTableNamePrefix, - } - id, err := newRootKeyVersionId() - if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } - rkv.PrivateId = id - rkv.RootKeyId = rootKeyId - rkv.Key = rootKeyBytes - if err := rkv.Encrypt(ctx, rootWrapper); err != nil { - return nil, fmt.Errorf("%s: unable to encrypt new root key version: %w", op, err) - } - if err := create(ctx, writer, &rkv, dbw.WithTable(rkv.TableName())); err != nil { - return nil, fmt.Errorf("%s: unable to create root key version: %w", op, err) - } - return &rkv, nil -} diff --git a/extras/kms/repository_root_key_version_test.go b/extras/kms/repository_root_key_version_test.go deleted file mode 100644 index 9058936f..00000000 --- a/extras/kms/repository_root_key_version_test.go +++ /dev/null @@ -1,881 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "crypto/rand" - "errors" - "sort" - "testing" - "time" - - "github.com/DATA-DOG/go-sqlmock" - "github.com/hashicorp/go-dbw" - "github.com/openbao/go-kms-wrapping/extras/kms/v2/migrations" - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/aead" - "github.com/openbao/go-kms-wrapping/v2/extras/multi" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestRepository_CreateRootKeyVersion(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - testScopeId := "o_1234567890" - rk := testRootKey(t, db, testScopeId) - - tests := []struct { - name string - repo *repository - rootKeyId string - key []byte - keyWrapper wrapping.Wrapper - opt []Option - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-root-key-id", - repo: testRepo, - keyWrapper: wrapper, - key: []byte("test key"), - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key id", - }, - { - name: "missing-wrapper", - repo: testRepo, - rootKeyId: rk.PrivateId, - key: []byte("test key"), - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key wrapper", - }, - { - name: "missing-key", - repo: testRepo, - rootKeyId: rk.PrivateId, - keyWrapper: wrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key", - }, - { - name: "bad-wrapper-fails-encrypt", - repo: testRepo, - rootKeyId: rk.PrivateId, - keyWrapper: aead.NewWrapper(), - key: []byte("test key"), - wantErr: true, - wantErrContains: "unable to encrypt", - }, - { - name: "create-rkv-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectBegin() - mock.ExpectQuery(`INSERT INTO "kms_root_key_version"`).WillReturnError(errors.New("create-rkv-error")) - mock.ExpectRollback() - return testRepo - }(), - rootKeyId: rk.PrivateId, - keyWrapper: wrapper, - key: []byte("test key"), - wantErr: true, - wantErrContains: "create-rkv-error", - }, - { - name: "valid", - repo: testRepo, - rootKeyId: rk.PrivateId, - keyWrapper: wrapper, - key: []byte("test key"), - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - prevVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - - k, err := tc.repo.CreateRootKeyVersion(context.Background(), tc.keyWrapper, tc.rootKeyId, tc.key, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.NotNil(k.CreateTime) - assert.Equal(uint32(1), k.Version) - foundKey, err := tc.repo.LookupRootKeyVersion(context.Background(), tc.keyWrapper, k.PrivateId) - assert.NoError(err) - assert.Equal(k, foundKey) - - currVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - assert.Greater(currVersion, prevVersion) - }) - } -} - -func TestRepository_DeleteRootKeyVersion(t *testing.T) { - t.Parallel() - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - testScopeId := "o_1234567890" - rk := testRootKey(t, db, testScopeId) - - tests := []struct { - name string - repo *repository - key *rootKeyVersion - opt []Option - wantRowsDeleted int - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-private-id", - repo: testRepo, - key: func() *rootKeyVersion { - return &rootKeyVersion{} - }(), - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing private id", - }, - { - name: "not-found", - repo: testRepo, - key: func() *rootKeyVersion { - id, err := dbw.NewId(rootKeyPrefix) - require.NoError(t, err) - k := rootKeyVersion{} - k.PrivateId = id - return &k - }(), - wantErr: true, - wantErrIs: ErrRecordNotFound, - wantErrContains: "record not found", - }, - { - name: "lookup-by-error", - key: func() *rootKeyVersion { - id, err := dbw.NewId(rootKeyPrefix) - require.NoError(t, err) - k := rootKeyVersion{} - k.PrivateId = id - require.NoError(t, err) - return &k - }(), - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("lookup-by-error")) - mock.ExpectRollback() - return r - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrContains: "lookup-by-error", - }, - { - name: "delete-error", - key: func() *rootKeyVersion { - id, err := dbw.NewId(rootKeyPrefix) - require.NoError(t, err) - k := rootKeyVersion{} - k.PrivateId = id - require.NoError(t, err) - return &k - }(), - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"scope_id", "create_time"}).AddRow(testScopeId, time.Now())) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(`DELETE`).WillReturnError(errors.New("delete-error")) - mock.ExpectRollback() - return r - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrContains: "delete-error", - }, - { - name: "delete-too-many-error", - key: func() *rootKeyVersion { - id, err := dbw.NewId(rootKeyPrefix) - require.NoError(t, err) - k := rootKeyVersion{} - k.PrivateId = id - require.NoError(t, err) - return &k - }(), - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"scope_id", "create_time"}).AddRow(testScopeId, time.Now())) - mock.ExpectBegin() - mock.ExpectExec(`update kms_collection_version`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(`DELETE`).WillReturnResult(sqlmock.NewResult(0, 2)) - mock.ExpectRollback() - return r - }(), - wantRowsDeleted: 0, - wantErr: true, - wantErrIs: ErrMultipleRecords, - wantErrContains: "multiple records", - }, - { - name: "valid", - repo: testRepo, - key: func() *rootKeyVersion { - k, _ := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - return k - }(), - wantRowsDeleted: 1, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - prevVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - - deletedRows, err := tc.repo.DeleteRootKeyVersion(context.Background(), tc.key.PrivateId, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.wantRowsDeleted, deletedRows) - foundKey, err := tc.repo.LookupRootKeyVersion(context.Background(), wrapper, tc.key.PrivateId) - assert.Error(err) - assert.Nil(foundKey) - assert.ErrorIs(err, ErrRecordNotFound) - - currVersion, err := currentCollectionVersion(testCtx, rw, DefaultTableNamePrefix) - require.NoError(err) - assert.Greater(currVersion, prevVersion) - }) - } -} - -func TestRepository_LatestRootKeyVersion(t *testing.T) { - t.Parallel() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - testScopeId := "o_1234567890" - rk := testRootKey(t, db, testScopeId) - - tests := []struct { - name string - repo *repository - createCnt int - rootKeyId string - keyWrapper wrapping.Wrapper - wantVersion uint32 - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "5", - repo: testRepo, - createCnt: 5, - rootKeyId: rk.PrivateId, - keyWrapper: wrapper, - wantVersion: 5, - }, - { - name: "1", - repo: testRepo, - createCnt: 1, - rootKeyId: rk.PrivateId, - keyWrapper: wrapper, - wantVersion: 1, - }, - { - name: "0", - repo: testRepo, - createCnt: 0, - rootKeyId: rk.PrivateId, - keyWrapper: wrapper, - wantErr: true, - wantErrIs: ErrRecordNotFound, - wantErrContains: "record not found", - }, - { - name: "missing-root-key-id", - repo: testRepo, - createCnt: 5, - keyWrapper: wrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key id", - }, - { - name: "nil-wrapper", - repo: testRepo, - createCnt: 5, - rootKeyId: rk.PrivateId, - keyWrapper: nil, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key wrapper", - }, - { - name: "search-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("search-error")) - return r - }(), - createCnt: 5, - rootKeyId: rk.PrivateId, - keyWrapper: wrapper, - wantErr: true, - wantErrContains: "search-error", - }, - { - name: "bad-wrapper", - repo: testRepo, - createCnt: 5, - rootKeyId: rk.PrivateId, - keyWrapper: aead.NewWrapper(), - wantErr: true, - wantErrContains: "unable to decrypt", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - testDeleteWhere(t, db, func() interface{} { i := rootKeyVersion{tableNamePrefix: DefaultTableNamePrefix}; return &i }(), "1=1") - testKeys := []*rootKeyVersion{} - for i := 0; i < tc.createCnt; i++ { - k, _ := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - testKeys = append(testKeys, k) - } - assert.Equal(tc.createCnt, len(testKeys)) - got, err := tc.repo.LatestRootKeyVersion(context.Background(), tc.keyWrapper, tc.rootKeyId) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - require.NotNil(got) - assert.Equal(tc.wantVersion, got.Version) - }) - } -} - -func TestRepository_ListRootKeyVersions(t *testing.T) { - const testLimit = 10 - t.Parallel() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw, withLimit(testLimit)) - require.NoError(t, err) - testScopeId := "o_1234567890" - rk := testRootKey(t, db, testScopeId) - - tests := []struct { - name string - repo *repository - createCnt int - rootKeyId string - keyWrapper wrapping.Wrapper - opt []Option - wantCnt int - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "no-limit", - repo: testRepo, - createCnt: testLimit * 2, - rootKeyId: rk.PrivateId, - keyWrapper: wrapper, - opt: []Option{withLimit(-1)}, - wantCnt: testLimit * 2, - }, - { - name: "default-limit", - repo: testRepo, - createCnt: testLimit + 1, - keyWrapper: wrapper, - rootKeyId: rk.PrivateId, - wantCnt: testLimit, - wantErr: false, - }, - { - name: "custom-limit", - repo: testRepo, - createCnt: testLimit + 1, - keyWrapper: wrapper, - rootKeyId: rk.PrivateId, - opt: []Option{withLimit(3)}, - wantCnt: 3, - wantErr: false, - }, - { - name: "bad-wrapper", - repo: testRepo, - createCnt: 1, - keyWrapper: aead.NewWrapper(), - rootKeyId: rk.PrivateId, - wantErr: true, - wantErrContains: "unable to decrypt", - }, - { - name: "missing-root-key-id", - repo: testRepo, - createCnt: 1, - keyWrapper: wrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key id", - }, - { - name: "missing-wrapper", - repo: testRepo, - createCnt: 1, - keyWrapper: nil, - rootKeyId: rk.PrivateId, - wantCnt: 0, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key wrapper", - }, - { - name: "list-error", - repo: func() *repository { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("list-error")) - return r - }(), - keyWrapper: wrapper, - rootKeyId: rk.PrivateId, - createCnt: testLimit, - wantErr: true, - wantErrContains: "list-error", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - testDeleteWhere(t, db, func() interface{} { i := rootKeyVersion{tableNamePrefix: DefaultTableNamePrefix}; return &i }(), "1=1") - testRootKeyVersions := []*rootKeyVersion{} - for i := 0; i < tc.createCnt; i++ { - k, _ := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - testRootKeyVersions = append(testRootKeyVersions, k) - } - assert.Equal(tc.createCnt, len(testRootKeyVersions)) - got, err := tc.repo.ListRootKeyVersions(context.Background(), tc.keyWrapper, tc.rootKeyId, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.wantCnt, len(got)) - }) - } -} - -func Test_rewrapRootKeyVersionsTx(t *testing.T) { - t.Parallel() - const ( - globalScope = "global" - testPlainText = "simple plain-text" - ) - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - - newWrapper := func(id string) wrapping.Wrapper { - tmpWrapper := aead.NewWrapper() - _, err := tmpWrapper.SetConfig(testCtx, wrapping.WithKeyId(id)) - require.NoError(t, err) - key, err := generateKey(testCtx, rand.Reader) - require.NoError(t, err) - err = tmpWrapper.SetAesGcmKeyBytes(key) - require.NoError(t, err) - return tmpWrapper - } - rootWrapper, err := multi.NewPooledWrapper(testCtx, newWrapper("1")) - require.NoError(t, err) - - testKms, err := New(rw, rw, []KeyPurpose{"database"}) - require.NoError(t, err) - testKms.AddExternalWrapper(testCtx, KeyPurposeRootKey, rootWrapper) - require.NoError(t, testKms.CreateKeys(testCtx, globalScope, []KeyPurpose{"database"})) - - _, rootKeyId, err := testKms.loadRoot(testCtx, globalScope) - require.NoError(t, err) - - tests := []struct { - name string - reader dbw.Reader - writer dbw.Writer - rootWrapper wrapping.Wrapper - rootKeyId string - setup func() - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-reader", - writer: rw, - rootWrapper: rootWrapper, - rootKeyId: rootKeyId, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing reader", - }, - { - name: "missing-writer", - reader: rw, - rootWrapper: rootWrapper, - rootKeyId: rootKeyId, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing writer", - }, - { - name: "missing-root-wrapper", - reader: rw, - writer: rw, - rootKeyId: rootKeyId, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root wrapper", - }, - { - name: "missing-root-key-id", - reader: rw, - writer: rw, - rootWrapper: rootWrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key id", - }, - { - name: "ListRootKeyVersions-error", - reader: func() dbw.Reader { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("ListRootKeyVersions-error")) - return rw - }(), - writer: rw, - rootWrapper: rootWrapper, - rootKeyId: rootKeyId, - wantErr: true, - wantErrContains: "unable to list root key versions", - }, - { - name: "Encrypt-error", - reader: rw, - writer: rw, - rootWrapper: &mockTestWrapper{err: errors.New("Encrypt-error"), encryptError: true}, - rootKeyId: rootKeyId, - wantErr: true, - wantErrContains: "failed to rewrap root key version", - }, - { - name: "update-error", - reader: rw, - writer: &dbw.RW{}, - rootKeyId: rootKeyId, - rootWrapper: rootWrapper, - wantErr: true, - wantErrContains: "failed to update root key version", - }, - { - name: "success", - reader: rw, - writer: rw, - rootWrapper: rootWrapper, - rootKeyId: rootKeyId, - setup: func() { - _, err := rootWrapper.SetEncryptingWrapper(testCtx, newWrapper("2")) - require.NoError(t, err) - }, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - if tc.setup != nil { - tc.setup() - } - - var currentRootKeyVersions []*rootKeyVersion - var encryptedBlob *wrapping.BlobInfo - if !tc.wantErr { - currentRootKeyVersions, err = testKms.repo.ListRootKeyVersions(testCtx, rootWrapper, globalScope, withOrderByVersion(ascendingOrderBy)) - require.NoError(err) - - currentWrapper, _, err := testKms.loadRoot(testCtx, globalScope) - require.NoError(err) - encryptedBlob, err = currentWrapper.Encrypt(testCtx, []byte(testPlainText)) - require.NoError(err) - } - - err := rewrapRootKeyVersionsTx(testCtx, tc.reader, tc.writer, tc.rootWrapper, tc.rootKeyId) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - - // ensure we can decrypt something using the rotated wrapper (does - // the previous key still work) - rotatedWrapper, _, err := testKms.loadRoot(testCtx, globalScope) - require.NoError(err) - pt, err := rotatedWrapper.Decrypt(testCtx, encryptedBlob) - require.NoError(err) - assert.Equal(testPlainText, string(pt)) - - newRootKeyVersions, err := testKms.repo.ListRootKeyVersions(testCtx, rootWrapper, globalScope, withOrderByVersion(ascendingOrderBy)) - require.NoError(err) - for i := range currentRootKeyVersions { - assert.NotEqual(currentRootKeyVersions[i].CtKey, newRootKeyVersions[i].CtKey) - } - }) - } -} - -func Test_rotateRootKeyVersionTx(t *testing.T) { - t.Parallel() - const ( - testScopeId = "global" - testPlainText = "simple plain-text" - ) - - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - rootWrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testRepo, err := newRepository(rw, rw) - require.NoError(t, err) - testKms, err := New(rw, rw, []KeyPurpose{"database"}) - require.NoError(t, err) - testKms.AddExternalWrapper(testCtx, KeyPurposeRootKey, rootWrapper) - require.NoError(t, testKms.CreateKeys(testCtx, testScopeId, []KeyPurpose{"database"})) - - _, rootKeyId, err := testKms.loadRoot(testCtx, testScopeId) - require.NoError(t, err) - - tests := []struct { - name string - writer dbw.Writer - repo *repository - rootWrapper wrapping.Wrapper - rootKeyId string - opt []Option - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-root-wrapper", - writer: rw, - repo: testRepo, - rootKeyId: rootKeyId, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root wrapper", - }, - { - name: "missing-root-key-id", - writer: rw, - repo: testRepo, - rootWrapper: rootWrapper, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key id", - }, - { - name: "success-missing-writer", - repo: testRepo, - rootWrapper: rootWrapper, - rootKeyId: rootKeyId, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing writer", - }, - { - name: "randReader-error", - writer: rw, - repo: testRepo, - rootWrapper: rootWrapper, - rootKeyId: rootKeyId, - opt: []Option{WithRandomReader(newMockRandReader(1, "bad-reader"))}, - wantErr: true, - wantErrContains: "bad-reader", - }, - { - name: "Encrypt-error", - writer: rw, - repo: testRepo, - rootWrapper: &mockTestWrapper{err: errors.New("Encrypt-error"), encryptError: true}, - rootKeyId: rootKeyId, - wantErr: true, - wantErrContains: "unable to encrypt new root key version", - }, - { - name: "create-error", - writer: &dbw.RW{}, - repo: testRepo, - rootWrapper: rootWrapper, - rootKeyId: rootKeyId, - wantErr: true, - wantErrContains: "unable to create root key version", - }, - { - name: "success", - writer: rw, - repo: testRepo, - rootWrapper: rootWrapper, - rootKeyId: rootKeyId, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - var currentRootKeyVersions []*rootKeyVersion - var currentWrapper *multi.PooledWrapper - var encryptedBlob *wrapping.BlobInfo - if !tc.wantErr { - currentRootKeyVersions, err = tc.repo.ListRootKeyVersions(testCtx, tc.rootWrapper, tc.rootKeyId) - require.NoError(err) - sort.Slice(currentRootKeyVersions, func(i, j int) bool { - return currentRootKeyVersions[i].PrivateId < currentRootKeyVersions[j].PrivateId - }) - currentWrapper, _, err = testKms.loadRoot(testCtx, testScopeId) - require.NoError(err) - encryptedBlob, err = currentWrapper.Encrypt(testCtx, []byte(testPlainText)) - require.NoError(err) - } - - got, err := rotateRootKeyVersionTx(testCtx, tc.writer, tc.rootWrapper, tc.rootKeyId, tc.opt...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.NotNil(got) - - // ensure we can decrypt something using the rotated wrapper (does - // the previous key still work) - rotatedWrapper, _, err := testKms.loadRoot(testCtx, testScopeId) - require.NoError(err) - pt, err := rotatedWrapper.Decrypt(testCtx, encryptedBlob) - require.NoError(err) - assert.Equal(testPlainText, string(pt)) - - // make sure the rotated rkv wasn't in the orig set - for _, currRkv := range currentRootKeyVersions { - assert.NotEqual(got, currRkv) - } - - newRootKeyVersions, err := tc.repo.ListRootKeyVersions(testCtx, tc.rootWrapper, tc.rootKeyId) - require.NoError(err) - sort.Slice(newRootKeyVersions, func(i, j int) bool { - return newRootKeyVersions[i].PrivateId < newRootKeyVersions[j].PrivateId - }) - assert.Equal(len(newRootKeyVersions), len(currentRootKeyVersions)*2) - - // encrypt pt with new version and make sure none of the old - // versions can decrypt it - encryptedBlob, err = rotatedWrapper.Encrypt(testCtx, []byte(testPlainText)) - require.NoError(err) - _, err = currentWrapper.Decrypt(testCtx, encryptedBlob) - assert.Error(err) - }) - } -} diff --git a/extras/kms/repository_test.go b/extras/kms/repository_test.go deleted file mode 100644 index 4f45c1b8..00000000 --- a/extras/kms/repository_test.go +++ /dev/null @@ -1,358 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "crypto/rand" - "errors" - "io" - "testing" - "time" - - "github.com/DATA-DOG/go-sqlmock" - "github.com/hashicorp/go-dbw" - "github.com/openbao/go-kms-wrapping/extras/kms/v2/migrations" - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewRepository(t *testing.T) { - t.Parallel() - db, _ := TestDb(t) - rw := dbw.New(db) - type args struct { - r dbw.Reader - w dbw.Writer - } - tests := []struct { - name string - args args - want *repository - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "valid", - args: args{ - r: rw, - w: rw, - }, - want: testRepo(t, db), - wantErr: false, - }, - { - name: "nil-writer", - args: args{ - r: rw, - w: nil, - }, - want: nil, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "nil writer", - }, - { - name: "nil-reader", - args: args{ - r: nil, - w: rw, - }, - want: nil, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "nil reader", - }, - { - name: "valid", - args: args{ - r: func() dbw.Reader { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow("invalid-version", time.Now())) - return rw - }(), - w: rw, - }, - want: testRepo(t, db), - wantErr: true, - wantErrIs: ErrInvalidVersion, - wantErrContains: "invalid schema version", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - got, err := newRepository(tc.args.r, tc.args.w) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.want, got) - }) - } -} - -func TestRepository_ValidateVersion(t *testing.T) { - testCtx := context.Background() - db, _ := TestDb(t) - tests := []struct { - name string - repo *repository - wantVersion string - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "valid", - repo: testRepo(t, db), - wantVersion: migrations.Version, - }, - { - name: "invalid-version", - repo: func() *repository { - mDb, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(mDb) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version"}).AddRow(100)) - return r - }(), - wantErr: true, - wantErrContains: "invalid version", - }, - { - name: "failed-lookup", - repo: func() *repository { - mDb, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(mDb) - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) - r, err := newRepository(rw, rw) - require.NoError(t, err) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("failed-lookup")) - return r - }(), - wantErr: true, - wantErrContains: "failed-lookup", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - version, err := tc.repo.ValidateSchema(testCtx) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.wantVersion, version) - }) - } -} - -type mockRandReader struct { - readCnt uint - errMsg string - errOnRead uint -} - -func newMockRandReader(errOnRead uint, errMsg string) *mockRandReader { - return &mockRandReader{ - readCnt: 0, - errMsg: errMsg, - errOnRead: errOnRead, - } -} - -func (m *mockRandReader) Read(p []byte) (n int, err error) { - m.readCnt++ - if m.readCnt < m.errOnRead { - return rand.Read(p) - } - return 0, errors.New(m.errMsg) -} - -func TestRepository_createKeysTx(t *testing.T) { - const testScopeId = "o_1234567890" - t.Parallel() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - - tests := []struct { - name string - rw *dbw.RW - rootWrapper wrapping.Wrapper - rand io.Reader - scopeId string - purpose []KeyPurpose - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-wrapper", - rw: rw, - rand: rand.Reader, - scopeId: testScopeId, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root wrapper", - }, - { - name: "missing-random-reader", - rw: rw, - rootWrapper: wrapper, - scopeId: testScopeId, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing random reader", - }, - { - name: "missing-scope-id", - rw: rw, - rootWrapper: wrapper, - rand: rand.Reader, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing scope id", - }, - { - name: "reserved-purpose", - rw: rw, - rootWrapper: wrapper, - rand: rand.Reader, - scopeId: testScopeId, - purpose: []KeyPurpose{"rootKey", "database"}, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "reserved key purpose", - }, - { - name: "dup-purpose", - rw: rw, - rootWrapper: wrapper, - rand: rand.Reader, - scopeId: testScopeId, - purpose: []KeyPurpose{"database", "database"}, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "duplicate key purpose", - }, - { - name: "gen-root-key-error", - rw: rw, - rootWrapper: wrapper, - rand: newMockRandReader(1, "gen-root-key-error"), - scopeId: testScopeId, - purpose: []KeyPurpose{"database", "session"}, - wantErr: true, - wantErrContains: "gen-root-key-error", - }, - { - name: "gen-purpose-key-error", - rw: rw, - rootWrapper: wrapper, - rand: newMockRandReader(2, "gen-purpose-key-error"), - scopeId: testScopeId, - purpose: []KeyPurpose{"database", "session"}, - wantErr: true, - wantErrContains: "gen-purpose-key-error", - }, - { - name: "createRootKeyTx-error", - rw: func() *dbw.RW { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectBegin() - mock.ExpectQuery(`INSERT INTO "kms_root_key"`).WillReturnError(errors.New("createRootKeyTx-error")) - mock.ExpectRollback() - rw, err := rw.Begin(context.Background()) - require.NoError(t, err) - return rw - }(), - rootWrapper: wrapper, - rand: rand.Reader, - scopeId: testScopeId, - purpose: []KeyPurpose{"database", "session"}, - wantErr: true, - wantErrContains: "createRootKeyTx-error", - }, - { - name: "createDataKeyTx-error", - rw: func() *dbw.RW { - db, mock := dbw.TestSetupWithMock(t) - rw := dbw.New(db) - mock.ExpectBegin() - mock.ExpectQuery(`INSERT INTO`).WillReturnRows(sqlmock.NewRows([]string{"scope_id", "create_time"}).AddRow(testScopeId, time.Now())) // rk - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"scope_id", "create_time"}).AddRow(testScopeId, time.Now())) - mock.ExpectQuery(`INSERT INTO`).WillReturnRows(sqlmock.NewRows([]string{"scope_id", "create_time"}).AddRow(testScopeId, time.Now())) // rkv - mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"scope_id", "create_time"}).AddRow(testScopeId, time.Now())) - mock.ExpectQuery(`SELECT`).WillReturnError(errors.New("createDataKeyTx-error")) - rw, err := rw.Begin(context.Background()) - require.NoError(t, err) - return rw - }(), - rootWrapper: wrapper, - rand: rand.Reader, - scopeId: testScopeId, - purpose: []KeyPurpose{"database", "session"}, - wantErr: true, - wantErrContains: "createDataKeyTx-error", - }, - { - name: "success", - rw: rw, - rootWrapper: wrapper, - rand: rand.Reader, - scopeId: testScopeId, - purpose: []KeyPurpose{"database", "session"}, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - testDeleteWhere(t, db, func() interface{} { i := rootKey{tableNamePrefix: DefaultTableNamePrefix}; return &i }(), "1=1") - keys, err := createKeysTx(context.Background(), tc.rw, tc.rw, tc.rootWrapper, tc.rand, DefaultTableNamePrefix, tc.scopeId, tc.purpose...) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.NotNil(keys) - }) - } -} - -func TestRepository_DefaultLimit(t *testing.T) { - t.Parallel() - db, _ := TestDb(t) - rw := dbw.New(db) - testRepo, err := newRepository(rw, rw, withLimit(3)) - require.NoError(t, err) - assert.Equal(t, 3, testRepo.DefaultLimit()) -} diff --git a/extras/kms/root_key.go b/extras/kms/root_key.go deleted file mode 100644 index 43dd909c..00000000 --- a/extras/kms/root_key.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "fmt" - "time" -) - -// rootKey represents the KEKs (keys to encrypt keys) of the system. -type rootKey struct { - // PrivateId is used to access the root key - PrivateId string `json:"private_id,omitempty" gorm:"primary_key"` - // ScopeId for the root key - ScopeId string `json:"scope_id,omitempty" gorm:"default:null"` - // CreateTime from the db - CreateTime time.Time `json:"create_time,omitempty" gorm:"default:current_timestamp"` - - // tableNamePrefix defines the prefix to use before the table name and - // allows us to support custom prefixes as well as multi KMSs within a - // single schema. - tableNamePrefix string `gorm:"-"` -} - -// newRootKey creates a new in memory root key. No optionsare currently -// supported. -func newRootKey(scopeId string, _ ...Option) (*rootKey, error) { - const op = "kms.NewRootKey" - if scopeId == "" { - return nil, fmt.Errorf("%s: missing scope id: %w", op, ErrInvalidParameter) - } - c := &rootKey{ - ScopeId: scopeId, - } - return c, nil -} - -// TableName returns the table name -func (k *rootKey) TableName() string { - const tableName = "root_key" - return fmt.Sprintf("%s_%s", k.tableNamePrefix, tableName) -} - -// Clone creates a clone of the RootKeyVersion -func (k *rootKey) Clone() *rootKey { - return &rootKey{ - PrivateId: k.PrivateId, - ScopeId: k.ScopeId, - CreateTime: k.CreateTime, - tableNamePrefix: k.tableNamePrefix, - } -} - -// GetPrivateId returns the key's private id -func (k *rootKey) GetPrivateId() string { return k.PrivateId } diff --git a/extras/kms/root_key_test.go b/extras/kms/root_key_test.go deleted file mode 100644 index d1df5526..00000000 --- a/extras/kms/root_key_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_NewRootKey(t *testing.T) { - t.Parallel() - tests := []struct { - name string - scopeId string - want *rootKey - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-scope-id", - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing scope id", - }, - { - name: "success", - scopeId: "scope-id", - want: &rootKey{ - ScopeId: "scope-id", - }, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - got, err := newRootKey(tc.scopeId) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.want, got) - }) - } -} diff --git a/extras/kms/root_key_version.go b/extras/kms/root_key_version.go deleted file mode 100644 index 9a8f40a4..00000000 --- a/extras/kms/root_key_version.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "fmt" - "time" - - "github.com/hashicorp/go-dbw" - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/extras/structwrapping" -) - -// rootKeyVersion represents a version of a RootKey -type rootKeyVersion struct { - // PrivateId is used to access the root key - PrivateId string `gorm:"primary_key"` - // RootKeyId is the root_key_id for this version - RootKeyId string `gorm:"default:null"` - // plain-text of the key data. we are NOT storing this plain-text key - // in the db. - Key []byte `json:"key,omitempty" gorm:"-" wrapping:"pt,key_data"` - // ciphertext key data stored in the database - // @inject_tag: `gorm:"column:key;not_null" wrapping:"ct,key_data"` - CtKey []byte `json:"ct_key,omitempty" gorm:"column:key;not_null" wrapping:"ct,key_data"` - // version of the key data. This is not used for optimistic locking, since - // key versions are immutable. It's just the version of the key. - // @inject_tag: `gorm:"default:null"` - Version uint32 `json:"version,omitempty" gorm:"default:null"` - // CreateTime from the db - CreateTime time.Time `json:"create_time,omitempty" gorm:"default:current_timestamp"` - - // tableNamePrefix defines the prefix to use before the table name and - // allows us to support custom prefixes as well as multi KMSs within a - // single schema. - tableNamePrefix string `gorm:"-"` -} - -// newRootKeyVersion creates a new in memory root key. No options are currently -// supported. -func newRootKeyVersion(rootKeyId string, key []byte, _ ...Option) (*rootKeyVersion, error) { - const op = "kms.newRootKeyVersion" - if rootKeyId == "" { - return nil, fmt.Errorf("%s: missing root key id: %w", op, ErrInvalidParameter) - } - if len(key) == 0 { - return nil, fmt.Errorf("%s: missing key id: %w", op, ErrInvalidParameter) - } - - k := &rootKeyVersion{ - RootKeyId: rootKeyId, - Key: key, - } - return k, nil -} - -// TableName returns the table name -func (k *rootKeyVersion) TableName() string { - const tableName = "root_key_version" - return fmt.Sprintf("%s_%s", k.tableNamePrefix, tableName) -} - -// Clone creates a clone of the RootKeyVersion -func (k *rootKeyVersion) Clone() *rootKeyVersion { - clone := &rootKeyVersion{ - PrivateId: k.PrivateId, - RootKeyId: k.RootKeyId, - CreateTime: k.CreateTime, - tableNamePrefix: k.tableNamePrefix, - } - clone.Key = make([]byte, len(k.Key)) - copy(clone.Key, k.Key) - - clone.CtKey = make([]byte, len(k.CtKey)) - copy(clone.CtKey, k.CtKey) - return clone -} - -// vetForWrite validates the root key version before it's written. -func (k *rootKeyVersion) vetForWrite(ctx context.Context, opType dbw.OpType) error { - const op = "kms.(rootKeyVersion).vetForWrite" - if k.PrivateId == "" { - return fmt.Errorf("%s: missing private id: %w", op, ErrInvalidParameter) - } - switch opType { - case dbw.CreateOp: - if k.CtKey == nil { - return fmt.Errorf("%s: missing key: %w", op, ErrInvalidParameter) - } - if k.RootKeyId == "" { - return fmt.Errorf("%s: missing root key id: %w", op, ErrInvalidParameter) - } - case dbw.UpdateOp: - return fmt.Errorf("%s: key is immutable: %w", op, ErrInvalidParameter) - } - return nil -} - -// Encrypt will encrypt the root key version's key -func (k *rootKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error { - const op = "kms.(rootKeyVersion).Encrypt" - if cipher == nil { - return fmt.Errorf("%s: missing cipher: %w", op, ErrInvalidParameter) - } - if err := structwrapping.WrapStruct(ctx, cipher, k, nil); err != nil { - return fmt.Errorf("%s: unable to encrypt: %w", op, err) - } - return nil -} - -// Decrypt will decrypt the root key version's key -func (k *rootKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error { - const op = "kms.(rootKeyVersion).Decrypt" - if cipher == nil { - return fmt.Errorf("%s: missing cipher: %w", op, ErrInvalidParameter) - } - if err := structwrapping.UnwrapStruct(ctx, cipher, k, nil); err != nil { - return fmt.Errorf("%s: unable to decrypt: %w", op, err) - } - return nil -} - -// GetPrivateId returns the key's private id -func (k *rootKeyVersion) GetPrivateId() string { return k.PrivateId } diff --git a/extras/kms/root_key_version_test.go b/extras/kms/root_key_version_test.go deleted file mode 100644 index 6b3841d5..00000000 --- a/extras/kms/root_key_version_test.go +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "testing" - - "github.com/hashicorp/go-dbw" - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/aead" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_NewRootKeyVersion(t *testing.T) { - t.Parallel() - tests := []struct { - name string - rootKeyId string - key []byte - want *rootKeyVersion - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "missing-root-key-id", - key: []byte("key"), - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key id", - }, - { - name: "missing-key", - rootKeyId: "root-key-id", - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key", - }, - { - name: "valid", - rootKeyId: "root-key-id", - key: []byte("key"), - want: &rootKeyVersion{ - RootKeyId: "root-key-id", - Key: []byte("key"), - }, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - got, err := newRootKeyVersion(tc.rootKeyId, tc.key) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(tc.want, got) - }) - } -} - -func TestRootKeyVersion_vetForWrite(t *testing.T) { - t.Parallel() - testCtx := context.Background() - tests := []struct { - name string - key *rootKeyVersion - opType dbw.OpType - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "create-missing-private-id", - key: &rootKeyVersion{ - RootKeyId: "root-key-id", - CtKey: []byte("key"), - }, - opType: dbw.CreateOp, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing private id", - }, - { - name: "create-missing-ct-key", - key: &rootKeyVersion{ - PrivateId: "private-id", - RootKeyId: "root-key-id", - }, - opType: dbw.CreateOp, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing key", - }, - { - name: "create-missing-root-key-id", - key: &rootKeyVersion{ - PrivateId: "private-id", - CtKey: []byte("key"), - }, - opType: dbw.CreateOp, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "missing root key id", - }, - { - name: "update-immutable", - key: &rootKeyVersion{ - PrivateId: "private-id", - }, - opType: dbw.UpdateOp, - wantErr: true, - wantErrIs: ErrInvalidParameter, - wantErrContains: "key is immutable", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - err := tc.key.vetForWrite(testCtx, tc.opType) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - }) - } -} - -func TestRootKeyVersion_Encrypt(t *testing.T) { - t.Parallel() - const ( - testKey = "test-key" - ) - testCtx := context.Background() - testWrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - tests := []struct { - name string - key *rootKeyVersion - wrapper wrapping.Wrapper - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "bad-cipher", - key: &rootKeyVersion{ - Key: []byte(testKey), - }, - wrapper: aead.NewWrapper(), - wantErr: true, - wantErrContains: "error wrapping value", - }, - { - name: "missing-cipher", - key: &rootKeyVersion{ - Key: []byte(testKey), - }, - wantErr: true, - wantErrContains: "missing cipher", - }, - { - name: "success", - key: &rootKeyVersion{ - Key: []byte(testKey), - }, - wrapper: testWrapper, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - require.Empty(tc.key.CtKey) - require.NotEmpty(tc.key.Key) - err := tc.key.Encrypt(testCtx, tc.wrapper) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.NotEmpty(tc.key.CtKey) - tc.key.Key = nil - err = tc.key.Decrypt(testCtx, tc.wrapper) - require.NoError(err) - assert.Equal(testKey, string(tc.key.Key)) - }) - } -} - -func TestRootKeyVersion_Decrypt(t *testing.T) { - t.Parallel() - const ( - testKey = "test-key" - ) - testCtx := context.Background() - testWrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - testDataKey := &rootKeyVersion{ - Key: []byte(testKey), - } - err := testDataKey.Encrypt(testCtx, testWrapper) - require.NoError(t, err) - tests := []struct { - name string - key *rootKeyVersion - wrapper wrapping.Wrapper - wantErr bool - wantErrIs error - wantErrContains string - }{ - { - name: "bad-cipher", - key: testDataKey, - wrapper: aead.NewWrapper(), - wantErr: true, - wantErrContains: "error unwrapping value", - }, - { - name: "missing-cipher", - key: testDataKey, - wantErr: true, - wantErrContains: "missing cipher", - }, - { - name: "success", - key: testDataKey, - wrapper: testWrapper, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - require.NotEmpty(tc.key.CtKey) - require.NotEmpty(tc.key.Key) - err := tc.key.Decrypt(testCtx, tc.wrapper) - if tc.wantErr { - require.Error(err) - if tc.wantErrIs != nil { - assert.ErrorIs(err, tc.wantErrIs) - } - if tc.wantErrContains != "" { - assert.Contains(err.Error(), tc.wantErrContains) - } - return - } - require.NoError(err) - assert.Equal(testKey, string(tc.key.Key)) - }) - } -} diff --git a/extras/kms/schema.go b/extras/kms/schema.go deleted file mode 100644 index c5e85f65..00000000 --- a/extras/kms/schema.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "fmt" - "time" -) - -// schema represents the current schema in the database -type schema struct { - // Version of the schema - Version string - // UpdateTime is the last update of the version - UpdateTime time.Time - // CreateTime is the create time of the initial version - CreateTime time.Time - - // tableNamePrefix defines the prefix to use before the table name and - // allows us to support custom prefixes as well as multi KMSs within a - // single schema. - tableNamePrefix string `gorm:"-"` -} - -// TableName returns the table name -func (k *schema) TableName() string { - const tableName = "schema_version" - return fmt.Sprintf("%s_%s", k.tableNamePrefix, tableName) -} diff --git a/extras/kms/schema_test.go b/extras/kms/schema_test.go deleted file mode 100644 index 6db1a2cb..00000000 --- a/extras/kms/schema_test.go +++ /dev/null @@ -1,399 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "strings" - "testing" - "time" - - "github.com/hashicorp/go-dbw" - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestRootKey_ScopeId(t *testing.T) { - t.Parallel() - assert, require := assert.New(t), require.New(t) - db, _ := TestDb(t) - rw := dbw.New(db) - testScopeId := "o_1234567890" - _ = testRootKey(t, db, testScopeId) - - k, err := newRootKey(testScopeId) - require.NoError(err) - id, err := dbw.NewId(rootKeyPrefix) - require.NoError(err) - k.PrivateId = id - k.tableNamePrefix = DefaultTableNamePrefix - err = rw.Create(context.Background(), k, dbw.WithTable(k.TableName())) - assert.Error(err) - assert.Contains(strings.ToLower(err.Error()), "unique") -} - -func TestRootKeyVersion_ImmutableFields(t *testing.T) { - t.Parallel() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - - testScopeId := "o_1234567890" - rk := testRootKey(t, db, testScopeId) - new, _ := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - - tests := []struct { - name string - update *rootKeyVersion - fieldMask []string - }{ - { - name: "private_id", - update: new.Clone(), - fieldMask: []string{"PrivateId"}, - }, - { - name: "create_time", - update: func() *rootKeyVersion { - k := new.Clone() - k.CreateTime = time.Now() - return k - }(), - fieldMask: []string{"CreateTime"}, - }, - { - name: "root_key_id", - update: func() *rootKeyVersion { - k := new.Clone() - k.RootKeyId = "o_thisIsNotAValidId" - return k - }(), - fieldMask: []string{"RootKeyId"}, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - orig := new.Clone() - orig.tableNamePrefix = DefaultTableNamePrefix - err := rw.LookupBy(context.Background(), orig, dbw.WithTable(orig.TableName())) - require.NoError(err) - - err = tc.update.Encrypt(context.Background(), wrapper) - require.NoError(err) - rowsUpdated, err := rw.Update(context.Background(), tc.update, tc.fieldMask, nil) - require.Error(err) - assert.Equal(0, rowsUpdated) - - after := new.Clone() - after.tableNamePrefix = DefaultTableNamePrefix - err = rw.LookupBy(context.Background(), after, dbw.WithTable(after.TableName())) - require.NoError(err) - - assert.Equal(orig, after) - }) - } -} - -func TestRootKey_ImmutableFields(t *testing.T) { - t.Parallel() - db, _ := TestDb(t) - rw := dbw.New(db) - - testScopeId := "o_1234567890" - new := testRootKey(t, db, testScopeId) - - tests := []struct { - name string - update *rootKey - fieldMask []string - }{ - { - name: "private_id", - update: func() *rootKey { - k := new.Clone() - k.PrivateId = "o_thisIsNotAValidId" - return k - }(), - fieldMask: []string{"PrivateId"}, - }, - { - name: "create_time", - update: func() *rootKey { - k := new.Clone() - k.CreateTime = time.Now() - return k - }(), - fieldMask: []string{"CreateTime"}, - }, - { - name: "scope_id", - update: func() *rootKey { - k := new.Clone() - k.ScopeId = "o_thisIsNotAValidId" - return k - }(), - fieldMask: []string{"ScopeId"}, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - orig := new.Clone() - orig.tableNamePrefix = DefaultTableNamePrefix - err := rw.LookupBy(context.Background(), orig, dbw.WithTable(orig.TableName())) - require.NoError(err) - - rowsUpdated, err := rw.Update(context.Background(), tc.update, tc.fieldMask, nil) - require.Error(err) - assert.Equal(0, rowsUpdated) - - after := new.Clone() - after.tableNamePrefix = DefaultTableNamePrefix - err = rw.LookupBy(context.Background(), after, dbw.WithTable(after.TableName())) - require.NoError(err) - - assert.Equal(orig, after) - }) - } -} - -func TestDataKey_ImmutableFields(t *testing.T) { - t.Parallel() - db, _ := TestDb(t) - rw := dbw.New(db) - - testScopeId := "o_1234567890" - rk := testRootKey(t, db, testScopeId) - new := testDataKey(t, db, rk.PrivateId, "test") - - tests := []struct { - name string - update *dataKey - fieldMask []string - }{ - { - name: "private_id", - update: func() *dataKey { - k := new.Clone() - k.PrivateId = "o_thisIsNotAValidId" - return k - }(), - fieldMask: []string{"PrivateId"}, - }, - { - name: "create_time", - update: func() *dataKey { - k := new.Clone() - k.CreateTime = time.Now() - return k - }(), - fieldMask: []string{"CreateTime"}, - }, - { - name: "root_key_id", - update: func() *dataKey { - k := new.Clone() - k.RootKeyId = "o_thisIsNotAValidId" - return k - }(), - fieldMask: []string{"RootKeyId"}, - }, - { - name: "purpose", - update: func() *dataKey { - k := new.Clone() - k.Purpose = "changed" - return k - }(), - fieldMask: []string{"Purpose"}, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - new.tableNamePrefix = DefaultTableNamePrefix - orig := new.Clone() - err := rw.LookupBy(context.Background(), orig, dbw.WithTable(orig.TableName())) - require.NoError(err) - - rowsUpdated, err := rw.Update(context.Background(), tc.update, tc.fieldMask, nil) - require.Error(err) - assert.Equal(0, rowsUpdated) - - after := new.Clone() - err = rw.LookupBy(context.Background(), after, dbw.WithTable(after.TableName())) - require.NoError(err) - - assert.Equal(orig, after) - }) - } -} - -func TestDataKeyVersion_ImmutableFields(t *testing.T) { - t.Parallel() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - - testScopeId := "o_1234567890" - rk := testRootKey(t, db, testScopeId) - _, rkvWrapper := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - - dk := testDataKey(t, db, rk.PrivateId, "test") - new := testDataKeyVersion(t, db, rkvWrapper, dk.PrivateId, []byte("data-key")) - - tests := []struct { - name string - update *dataKeyVersion - fieldMask []string - }{ - { - name: "private_id", - update: func() *dataKeyVersion { - k := new.Clone() - k.PrivateId = "o_thisIsNotAValidId" - return k - }(), - fieldMask: []string{"PrivateId"}, - }, - { - name: "create_time", - update: func() *dataKeyVersion { - k := new.Clone() - k.CreateTime = time.Now() - return k - }(), - fieldMask: []string{"CreateTime"}, - }, - { - name: "data_key_id", - update: func() *dataKeyVersion { - k := new.Clone() - k.DataKeyId = "o_thisIsNotAValidId" - return k - }(), - fieldMask: []string{"RootKeyId"}, - }, - { - name: "root_key_version_id", - update: func() *dataKeyVersion { - k := new.Clone() - k.RootKeyVersionId = "o_thisIsNotAValidId" - return k - }(), - fieldMask: []string{"RootKeyId"}, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - new.tableNamePrefix = DefaultTableNamePrefix - orig := new.Clone() - err := rw.LookupBy(context.Background(), orig, dbw.WithTable(orig.TableName())) - require.NoError(err) - - err = tc.update.Encrypt(context.Background(), wrapper) - require.NoError(err) - rowsUpdated, err := rw.Update(context.Background(), tc.update, tc.fieldMask, nil) - require.Error(err) - assert.Equal(0, rowsUpdated) - - after := new.Clone() - err = rw.LookupBy(context.Background(), after, dbw.WithTable(after.TableName())) - require.NoError(err) - - assert.Equal(orig, after) - }) - } -} - -func TestRootKey_Version(t *testing.T) { - assert, require := assert.New(t), require.New(t) - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - - testScopeId := "o_1234567890" - rk := testRootKey(t, db, testScopeId) - rkv1, _ := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - assert.Equal(uint32(1), rkv1.Version) - - found := &rootKeyVersion{ - PrivateId: rkv1.PrivateId, - tableNamePrefix: DefaultTableNamePrefix, - } - require.NoError(rw.LookupBy(testCtx, found, dbw.WithTable(found.TableName()))) - found.Decrypt(testCtx, wrapper) - assert.Equal(rkv1, found) - - rkv2, _ := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - assert.Equal(uint32(2), rkv2.Version) -} - -func TestDataKey_Version(t *testing.T) { - t.Run("test-version-trigger", func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - testCtx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - wrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - - testScopeId := "o_1234567890" - rk := testRootKey(t, db, testScopeId) - _, rkvWrapper := testRootKeyVersion(t, db, wrapper, rk.PrivateId) - - dk := testDataKey(t, db, rk.PrivateId, "test") - - dkv1 := testDataKeyVersion(t, db, rkvWrapper, dk.PrivateId, []byte("data-key-1")) - assert.Equal(uint32(1), dkv1.Version) - - found := &dataKeyVersion{ - PrivateId: dkv1.PrivateId, - tableNamePrefix: DefaultTableNamePrefix, - } - require.NoError(rw.LookupBy(testCtx, found, dbw.WithTable(found.TableName()))) - found.Decrypt(testCtx, wrapper) - assert.Equal(dkv1, found) - - dkv2 := testDataKeyVersion(t, db, rkvWrapper, dk.PrivateId, []byte("data-key-2")) - assert.Equal(uint32(2), dkv2.Version) - - dk2 := testDataKey(t, db, rk.PrivateId, "test-2") - dkv3 := testDataKeyVersion(t, db, rkvWrapper, dk2.PrivateId, []byte("data-key-1")) - assert.Equal(uint32(1), dkv1.Version) - - found = &dataKeyVersion{ - PrivateId: dkv3.PrivateId, - tableNamePrefix: DefaultTableNamePrefix, - } - require.NoError(rw.LookupBy(testCtx, found, dbw.WithTable(found.TableName()))) - found.Decrypt(testCtx, wrapper) - assert.Equal(dkv3, found) - }) - t.Run("test-dup-purpose", func(t *testing.T) { - const testPurpose = "test" - require := require.New(t) - db, _ := TestDb(t) - rw := dbw.New(db) - testScopeId := "o_1234567890" - rk := testRootKey(t, db, testScopeId) - - // first data key with testPurpose - _ = testDataKey(t, db, rk.PrivateId, testPurpose) - - // we can't use the std test fixture of TestDataKey(...) because - // it's guaranteed to succeed even with duplicates - k, err := newDataKey(rk.PrivateId, testPurpose) - require.NoError(err) - id, err := dbw.NewId(dataKeyPrefix) - require.NoError(err) - k.PrivateId = id - k.RootKeyId = rk.PrivateId - err = rw.Create(context.Background(), k) - require.Error(err) - }) -} diff --git a/extras/kms/testing.go b/extras/kms/testing.go deleted file mode 100644 index 5ef48aa5..00000000 --- a/extras/kms/testing.go +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "database/sql" - "fmt" - "io/fs" - "net/http" - "os" - "strings" - "testing" - - "github.com/golang-migrate/migrate/v4" - "github.com/golang-migrate/migrate/v4/database" - "github.com/golang-migrate/migrate/v4/database/postgres" - source "github.com/golang-migrate/migrate/v4/source" - "github.com/golang-migrate/migrate/v4/source/httpfs" - "github.com/hashicorp/go-dbw" - "github.com/openbao/go-kms-wrapping/extras/kms/v2/migrations" - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/stretchr/testify/require" -) - -// testRootKey returns a new test RootKey -func testRootKey(t *testing.T, conn *dbw.DB, scopeId string) *rootKey { - t.Helper() - require := require.New(t) - rw := dbw.New(conn) - testDeleteWhere(t, conn, &rootKey{tableNamePrefix: DefaultTableNamePrefix}, "scope_id = ?", scopeId) - k, err := newRootKey(scopeId) - require.NoError(err) - id, err := newRootKeyId() - require.NoError(err) - k.PrivateId = id - k.tableNamePrefix = DefaultTableNamePrefix - err = create(context.Background(), rw, k, dbw.WithTable(k.TableName())) - require.NoError(err) - return k -} - -// testRootKeyVersion returns a new test RootKeyVersion with its associated wrapper -func testRootKeyVersion(t *testing.T, conn *dbw.DB, wrapper wrapping.Wrapper, rootId string) (kv *rootKeyVersion, kvWrapper wrapping.Wrapper) { - t.Helper() - require := require.New(t) - testCtx := context.Background() - rw := dbw.New(conn) - rootKeyVersionWrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - key, err := rootKeyVersionWrapper.KeyBytes(testCtx) - require.NoError(err) - k, err := newRootKeyVersion(rootId, key) - require.NoError(err) - k.tableNamePrefix = DefaultTableNamePrefix - id, err := newRootKeyVersionId() - require.NoError(err) - k.PrivateId = id - err = k.Encrypt(context.Background(), wrapper) - require.NoError(err) - err = create(context.Background(), rw, k, dbw.WithTable(k.TableName())) - require.NoError(err) - err = rw.LookupBy(context.Background(), k, dbw.WithTable(k.TableName())) - require.NoError(err) - rootKeyVersionWrapper.SetConfig(testCtx, wrapping.WithKeyId(k.PrivateId)) - require.NoError(err) - return k, rootKeyVersionWrapper -} - -// TestData returns a new test DataKey -func testDataKey(t *testing.T, conn *dbw.DB, rootKeyId string, purpose KeyPurpose) *dataKey { - t.Helper() - require := require.New(t) - testDeleteWhere(t, conn, &dataKey{tableNamePrefix: DefaultTableNamePrefix}, "root_key_id = ?", rootKeyId) - rw := dbw.New(conn) - k, err := newDataKey(rootKeyId, purpose) - require.NoError(err) - id, err := newDataKeyId() - require.NoError(err) - k.PrivateId = id - k.RootKeyId = rootKeyId - k.tableNamePrefix = DefaultTableNamePrefix - err = create(context.Background(), rw, k, dbw.WithTable(k.TableName())) - require.NoError(err) - return k -} - -// testDataKeyVersion returns a new test DataKeyVersion with its associated wrapper -func testDataKeyVersion(t *testing.T, conn *dbw.DB, rootKeyVersionWrapper wrapping.Wrapper, dataKeyId string, key []byte) *dataKeyVersion { - t.Helper() - require := require.New(t) - rw := dbw.New(conn) - rootKeyVersionId, err := rootKeyVersionWrapper.KeyId(context.Background()) - require.NoError(err) - require.NotEmpty(rootKeyVersionId) - k, err := newDataKeyVersion(dataKeyId, key, rootKeyVersionId) - require.NoError(err) - k.tableNamePrefix = DefaultTableNamePrefix - id, err := newDataKeyVersionId() - require.NoError(err) - k.PrivateId = id - err = k.Encrypt(context.Background(), rootKeyVersionWrapper) - require.NoError(err) - err = create(context.Background(), rw, k, dbw.WithTable(k.TableName())) - require.NoError(err) - err = rw.LookupBy(context.Background(), k, dbw.WithTable(k.TableName())) - require.NoError(err) - return k -} - -// testRepo returns are test repo -func testRepo(t *testing.T, db *dbw.DB, opt ...Option) *repository { - t.Helper() - require := require.New(t) - rw := dbw.New(db) - r, err := newRepository(rw, rw, opt...) - require.NoError(err) - return r -} - -// TestDb will return a test db and a url for that db -func TestDb(t *testing.T) (*dbw.DB, string) { - return dbw.TestSetup(t, dbw.WithTestMigrationUsingDB(testMigrationFn(t))) -} - -// TestDeleteKeysForPurpose allows you to delete all the keys for a KeyPurpose for testing. -func TestDeleteKeysForPurpose(t *testing.T, conn *dbw.DB, purpose KeyPurpose) { - testDeleteWhere(t, conn, func() interface{} { i := dataKey{tableNamePrefix: DefaultTableNamePrefix}; return &i }(), fmt.Sprintf("purpose='%s'", purpose)) -} - -// TestKmsDeleteAllKeys allows you to delete ALL the keys for testing. -func TestKmsDeleteAllKeys(t *testing.T, conn *dbw.DB) { - testDeleteWhere(t, conn, func() interface{} { i := rootKey{tableNamePrefix: DefaultTableNamePrefix}; return &i }(), "1=1") -} - -func testMigrationFn(t *testing.T) func(ctx context.Context, db *sql.DB) error { - return func(ctx context.Context, db *sql.DB) error { - t.Helper() - require := require.New(t) - var err error - var dialect string - var driver database.Driver - var source source.Driver - switch strings.ToLower(os.Getenv("DB_DIALECT")) { - case "postgres": - dialect = "postgres" - driver, err = postgres.WithInstance(db, &postgres.Config{}) - require.NoError(err) - source, err = httpfs.New(http.FS(migrations.PostgresFS), dialect) - require.NoError(err) - - default: - // we're intentionally choosing to NOT use go-migrate for these - // sqlite migrations since we don't want to introduce a CGO - // dependency outside of the test packages and this code is NOT in a - // test package. - dialect = "sqlite" - cliMigrations, _ := fs.ReadDir(migrations.SqliteFS, dialect) - for _, m := range cliMigrations { - sql, err := fs.ReadFile(migrations.SqliteFS, fmt.Sprintf("%s/%s", dialect, m.Name())) - require.NoError(err) - _, err = db.Exec(string(sql)) - require.NoError(err) - } - _, err = db.Exec(testSqliteSchemaAdditions(t)) - require.NoError(err) - return nil - } - m, err := migrate.NewWithInstance( - dialect, - source, - dialect, - driver) - require.NoError(err) - - err = m.Up() - require.NoError(err) - - _, err = db.Exec(testSqliteSchemaAdditions(t)) - require.NoError(err) - return nil - } -} - -func testPostgresSchemaAdditions(t *testing.T) string { - const sql = ` - begin; - create table kms_test_encrypted_data ( - private_id text primary key, - key_version_id text not null - references kms_data_key_version(private_id) - on delete restrict - on update cascade, - cipher_text text not null); - commit;` - return sql -} - -func testSqliteSchemaAdditions(t *testing.T) string { - const sql = ` - create table kms_test_encrypted_data ( - private_id text primary key, - key_version_id text not null - references kms_data_key_version(private_id) - on delete restrict - on update cascade, - cipher_text text not null);` - return sql -} - -// testDeleteWhere allows you to easily delete resources for testing purposes -// including all the current resources. The collection version is updated when -// the resource is a key. -func testDeleteWhere(t *testing.T, conn *dbw.DB, i interface{}, whereClause string, args ...interface{}) { - t.Helper() - require := require.New(t) - ctx := context.Background() - tabler, ok := i.(interface { - TableName() string - }) - require.True(ok) - _, err := dbw.New(conn).Exec(ctx, fmt.Sprintf(`delete from "%s" where %s`, tabler.TableName(), whereClause), args) - require.NoError(err) - - switch i.(type) { - case *rootKey, *rootKeyVersion, *dataKey, *dataKeyVersion: - require.NoError(err, updateKeyCollectionVersion(ctx, dbw.New(conn), DefaultTableNamePrefix)) - } -} diff --git a/extras/kms/testing_test.go b/extras/kms/testing_test.go deleted file mode 100644 index 38dd8610..00000000 --- a/extras/kms/testing_test.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package kms - -import ( - "context" - "testing" - - "github.com/hashicorp/go-dbw" - wrapping "github.com/openbao/go-kms-wrapping/v2" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_stub(t *testing.T) { - db, _ := TestDb(t) - rw := dbw.New(db) - rows, err := rw.Query(context.Background(), "select * from kms_root_key", nil) - t.Log("rows: ", rows) - require.NoError(t, err) -} - -func Test_TestDeleteKeysForPurpose_TestDeleteAllKeys(t *testing.T) { - // common setup for both tests. - const ( - globalScope = "global" - orgScope = "o_1234567890" - ) - t.Parallel() - assert, require := assert.New(t), require.New(t) - ctx := context.Background() - db, _ := TestDb(t) - rw := dbw.New(db) - extWrapper := wrapping.NewTestWrapper([]byte(testDefaultWrapperSecret)) - - databaseKeyPurpose := KeyPurpose("database") - authKeyPurpose := KeyPurpose("auth") - dekPurposes := []KeyPurpose{databaseKeyPurpose, authKeyPurpose} - - // init kms with a cache - kmsCache, err := New(rw, rw, dekPurposes) - require.NoError(err) - require.NoError(kmsCache.AddExternalWrapper(ctx, KeyPurposeRootKey, extWrapper)) - // Make the global scope base keys - err = kmsCache.CreateKeys(ctx, globalScope, dekPurposes) - require.NoError(err) - - for _, p := range dekPurposes { - _, err = kmsCache.GetWrapper(ctx, globalScope, p) - require.NoError(err) - } - - // first we'll test TestDeleteKeysForPurpose. - TestDeleteKeysForPurpose(t, db, databaseKeyPurpose) - - _, err = kmsCache.GetWrapper(ctx, globalScope, databaseKeyPurpose) - require.Error(err) - assert.ErrorIs(err, ErrKeyNotFound) - - _, err = kmsCache.GetWrapper(ctx, globalScope, authKeyPurpose) - require.NoError(err) - - err = kmsCache.ReconcileKeys(ctx, []string{globalScope}, []KeyPurpose{databaseKeyPurpose}) - require.NoError(err) - - _, err = kmsCache.GetWrapper(ctx, globalScope, databaseKeyPurpose) - require.NoError(err) - - err = kmsCache.CreateKeys(ctx, orgScope, []KeyPurpose{databaseKeyPurpose}) - require.NoError(err) - - // Now we'll test TestDeleteAllKeys - TestKmsDeleteAllKeys(t, db) - for _, p := range dekPurposes { - _, err = kmsCache.GetWrapper(ctx, globalScope, p) - require.Error(err) - } - _, err = kmsCache.GetWrapper(ctx, globalScope, KeyPurposeRootKey) - require.Error(err) - assert.ErrorIs(err, ErrKeyNotFound) -} diff --git a/extras/kms/tools/tools.go b/extras/kms/tools/tools.go deleted file mode 100644 index 5446130d..00000000 --- a/extras/kms/tools/tools.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build tools -// +build tools - -// This file ensures tool dependencies are kept in sync. This is the -// recommended way of doing this according to -// https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module -// To install the following tools at the version used by this repo run: -// $ make tools -// or -// $ go generate -tags tools tools/tools.go - -package tools - -// NOTE: This must not be indented, so to stop goimports from trying to be -// helpful, it's separated out from the import block below. Please try to keep -// them in the same order. -//go:generate go install mvdan.cc/gofumpt - -import ( - _ "mvdan.cc/gofumpt" -) diff --git a/extras/structwrapping/README.md b/extras/structwrapping/README.md deleted file mode 100644 index 3bbfff32..00000000 --- a/extras/structwrapping/README.md +++ /dev/null @@ -1,78 +0,0 @@ -# Structwrapping - -[![Godoc](https://godoc.org/github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping?status.svg)](https://godoc.org/github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping) - -`structwrapping` provides a convenience package for dealing with encrypted -values in structs in an automated way. This can be used for e.g. performing -encryption and decryption before sending to and when retrieving values from a -database, via a hook/middleware. - -Caveats: - -* The input must be a pointer to a struct -* Tags must be balanced (there must be a matching ct tag entry with the same - identifier as a pt tag entry, and vice versa) -* This does not currently recurse -* Currently there is no checking for overwriting of values, although this may - change - -## Usage - -Add struct tags that specify fields for wrapping. Wrapping tags must contain: - -* A field type of `pt` (plaintext) or `ct` (ciphertext) -* A unique name that ties together a pair of `pt` and `ct` fields - -This unique name does _not_ need to match the name of a struct field; this is -on purpose, so that struct fields can be renamed without breaking this -functionality. - -The plaintext struct fields can be either a `[]byte` or `string`. The -ciphertext fields can be `[]byte`, `string`, or `*wrapping.EncryptedBlobInfo`. -The library will automatically convert types (including marshaling/unmarshaling -`*wrapping.EncryptedBlobInfo`) as necessary. - -The best way to see how to use the package is via one of the tests for this -package, reproduced below: -```go - var err error - type sutStruct struct { - PT1 []byte `wrapping:"pt,foo"` - PT2 string `wrapping:"pt,bar"` - PT3 []byte `wrapping:"pt,zip"` - CT1 *wrapping.EncryptedBlobInfo `wrapping:"ct,foo"` - CT2 []byte `wrapping:"ct,bar"` - CT3 string `wrapping:"ct,zip"` - } - sut := &sutStruct{PT1: []byte("foo"), PT2: "bar", PT3: []byte("zip")} - err = WrapStruct(nil, wrapper, sut, nil) - assert.Nil(t, err) - assert.NotNil(t, sut.CT1) - assert.NotNil(t, sut.CT2) - assert.NotNil(t, sut.CT3) - - fooVal, err := wrapper.Decrypt(nil, sut.CT1, nil) - assert.Nil(t, err) - assert.Equal(t, fooVal, []byte("foo")) - - ebi := new(wrapping.EncryptedBlobInfo) - err = proto.Unmarshal(sut.CT2, ebi) - assert.Nil(t, err) - barVal, err := wrapper.Decrypt(nil, ebi, nil) - assert.Nil(t, err) - assert.Equal(t, barVal, []byte("bar")) - - ebi = new(wrapping.EncryptedBlobInfo) - err = proto.Unmarshal([]byte(sut.CT3), ebi) - assert.Nil(t, err) - zipVal, err := wrapper.Decrypt(nil, ebi, nil) - assert.Nil(t, err) - assert.Equal(t, zipVal, []byte("zip")) - - sut2 := &sutStruct{CT1: sut.CT1, CT2: sut.CT2, CT3: sut.CT3} - err = UnwrapStruct(nil, wrapper, sut2, nil) - assert.Nil(t, err) - assert.Equal(t, sut2.PT1, []byte("foo")) - assert.Equal(t, sut2.PT2, "bar") - assert.Equal(t, sut2.PT3, []byte("zip")) -``` diff --git a/extras/structwrapping/structwrapping.go b/extras/structwrapping/structwrapping.go deleted file mode 100644 index 00a7db5a..00000000 --- a/extras/structwrapping/structwrapping.go +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package structwrapping - -import ( - "context" - "errors" - "fmt" - "reflect" - "strings" - - wrapping "github.com/openbao/go-kms-wrapping/v2" - "google.golang.org/protobuf/proto" -) - -type entry struct { - index int -} - -type encDecMap map[string][2]*entry - -func buildEncDecMap(ctx context.Context, in interface{}) (encDecMap, error) { - val := reflect.ValueOf(in) - switch { - case !val.IsValid(): - return nil, errors.New("input not valid") - case val.IsZero(): - return nil, errors.New("input was not initialized") - case val.Kind() != reflect.Ptr: - return nil, errors.New("input not a pointer") - } - - val = reflect.Indirect(val) - if val.Kind() != reflect.Struct { - return nil, errors.New("input not a struct") - } - - typ := val.Type() - // plaintext,ciphertext - edMap := make(encDecMap, typ.NumField()/2) - for i := 0; i < typ.NumField(); i++ { - field := typ.Field(i) - tag, ok := field.Tag.Lookup("wrapping") - if !ok { - continue - } - tagParts := strings.Split(tag, ",") - if len(tagParts) != 2 { - return nil, errors.New("error in wrapping tag specification") - } - - fieldKind := field.Type.Kind() - switch tagParts[0] { - case "pt": - if !field.Type.ConvertibleTo(reflect.TypeOf([]byte(nil))) { - return nil, errors.New("plaintext value can not be used as a byte slice") - } - curr := edMap[tagParts[1]] - if curr[0] != nil { - return nil, errors.New("detected two pt wrapping tags with the same identifier") - } - curr[0] = &entry{index: i} - edMap[tagParts[1]] = curr - - case "ct": - switch fieldKind { - case reflect.Ptr: - if !field.Type.ConvertibleTo(reflect.TypeOf((*wrapping.BlobInfo)(nil))) { - return nil, errors.New("ciphertext pointer value is not the expected type") - } - case reflect.String, reflect.Slice: - if !field.Type.ConvertibleTo(reflect.TypeOf([]byte(nil))) { - return nil, errors.New("ciphertext string/byte value cannot be used as a byte slice") - } - default: - return nil, errors.New("unsupported ciphertext value type") - } - curr := edMap[tagParts[1]] - if curr[1] != nil { - return nil, errors.New("detected two ct wrapping tags with the same identifier") - } - curr[1] = &entry{index: i} - edMap[tagParts[1]] = curr - - default: - return nil, errors.New("unknown tag type for wrapping tag") - } - } - - for k, v := range edMap { - if v[0] == nil { - return nil, fmt.Errorf("no pt wrapping tag found for identifier %q", k) - } - if v[1] == nil { - return nil, fmt.Errorf("no ct wrapping tag found for identifier %q", k) - } - } - - return edMap, nil -} - -// WrapStruct wraps values in the struct. Options are passed through to the -// wrapper Encrypt function. -func WrapStruct(ctx context.Context, wrapper wrapping.Wrapper, in interface{}, opt ...wrapping.Option) error { - if wrapper == nil { - return errors.New("nil wrapper passed in") - } - - edMap, err := buildEncDecMap(ctx, in) - if err != nil { - return err - } - - val := reflect.Indirect(reflect.ValueOf(in)) - for _, v := range edMap { - encRaw := val.Field(v[0].index).Interface() - var enc []byte - switch t := encRaw.(type) { - case []byte: - enc = t - case string: - enc = []byte(t) - default: - return errors.New("could not convert value for encryption to []byte") - } - if enc == nil { - return errors.New("plaintext byte slice is nil") - } - blobInfo, err := wrapper.Encrypt(ctx, enc, opt...) - if err != nil { - return fmt.Errorf("error wrapping value: %w", err) - } - - field := val.Field(v[1].index) - switch field.Interface().(type) { - case *wrapping.BlobInfo: - field.Set(reflect.ValueOf(blobInfo)) - case []byte: - protoBytes, err := proto.Marshal(blobInfo) - if err != nil { - return fmt.Errorf("error marshaling proto in byte field: %w", err) - } - field.Set(reflect.ValueOf(protoBytes)) - case string: - protoBytes, err := proto.Marshal(blobInfo) - if err != nil { - return fmt.Errorf("error marshaling proto in string field: %w", err) - } - field.Set(reflect.ValueOf(string(protoBytes))) - default: - return errors.New("could not set value on ciphertext field, incorrect type") - } - } - - return nil -} - -// UnwrapStruct unwraps values in the struct. Options are passed through to the -// wrapper Dencrypt function. -func UnwrapStruct(ctx context.Context, wrapper wrapping.Wrapper, in interface{}, opt ...wrapping.Option) error { - if wrapper == nil { - return errors.New("nil wrapper passed in") - } - - edMap, err := buildEncDecMap(ctx, in) - if err != nil { - return err - } - - val := reflect.Indirect(reflect.ValueOf(in)) - for _, v := range edMap { - decRaw := val.Field(v[1].index).Interface() - var dec *wrapping.BlobInfo - var decBytes []byte - switch typedDec := decRaw.(type) { - case *wrapping.BlobInfo: - dec = typedDec - case string: - decBytes = []byte(typedDec) - case []byte: - decBytes = typedDec - default: - return errors.New("could not convert value for decryption to a known type") - } - if dec == nil { - if decBytes != nil { - dec = new(wrapping.BlobInfo) - if err := proto.Unmarshal(decBytes, dec); err != nil { - return fmt.Errorf("error unmarshaling encrypted blob info: %w", err) - } - } else { - return errors.New("ciphertext pointer is nil") - } - } - bs, err := wrapper.Decrypt(ctx, dec, opt...) - if err != nil { - return fmt.Errorf("error unwrapping value: %w", err) - } - field := val.Field(v[0].index) - switch field.Interface().(type) { - case []byte: - field.Set(reflect.ValueOf(bs)) - case string: - field.Set(reflect.ValueOf(string(bs))) - default: - return errors.New("could not set value on plaintext field, incorrect type") - } - } - - return nil -} diff --git a/extras/structwrapping/structwrapping_test.go b/extras/structwrapping/structwrapping_test.go deleted file mode 100644 index d91f4cfe..00000000 --- a/extras/structwrapping/structwrapping_test.go +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package structwrapping_test - -import ( - "context" - "testing" - - wrapping "github.com/openbao/go-kms-wrapping/v2" - "github.com/openbao/go-kms-wrapping/v2/aead" - "github.com/openbao/go-kms-wrapping/v2/extras/structwrapping" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" -) - -func TestStructWrapping(t *testing.T) { - wrapper := aead.NewWrapper() - - _, err := wrapper.SetConfig(context.Background(), wrapping.WithConfigMap(map[string]string{ - "aead_type": wrapping.AeadTypeAesGcm.String(), - "key": "QmjueU/LMsZAO+rvSUMcpkBziCD5ON7BgxVqcZ6+TCI=", - })) - require.Nil(t, err) - ctx := context.Background() - - t.Run("shared encryption/decryption tests", func(t *testing.T) { - t.Parallel() - t.Run("bad basic values", func(t *testing.T) { - t.Parallel() - assert := assert.New(t) - var err error - // Zero - err = structwrapping.WrapStruct(ctx, nil, nil) - assert.Error(err, "nil wrapper passed in") - err = structwrapping.WrapStruct(ctx, wrapper, nil) - assert.Error(err, "input not valid") - err = structwrapping.WrapStruct(ctx, wrapper, interface{}(nil)) - assert.Error(err, "input not valid") - err = structwrapping.WrapStruct(ctx, wrapper, "foobar") - assert.Error(err, "input not a pointer") - err = structwrapping.WrapStruct(ctx, wrapper, new(int32)) - assert.Error(err, "input not a struct") - - type badTagStruct struct { - field string `wrapping:"foobar"` - } - err = structwrapping.WrapStruct(ctx, wrapper, new(badTagStruct)) - assert.Error(err, "error in wrapping tag specification") - - type badTagPrefixStruct struct { - field string `wrapping:"dr,foobar"` - } - err = structwrapping.WrapStruct(ctx, wrapper, new(badTagPrefixStruct)) - assert.Error(err, "unknown tag type for wrapping tag") - }) - - t.Run("doubled values", func(t *testing.T) { - t.Parallel() - assert := assert.New(t) - var err error - type doubledPTIdentifierStruct struct { - PT1 []byte `wrapping:"pt,foobar"` - PT2 []byte `wrapping:"pt,foobar"` - } - err = structwrapping.WrapStruct(ctx, wrapper, &doubledPTIdentifierStruct{PT1: []byte("foo"), PT2: []byte("bar")}, nil) - assert.Error(err, "detected two pt wrapping tags with the same identifier") - - type doubledCTIdentifierStruct struct { - CT1 *wrapping.BlobInfo `wrapping:"ct,foobar"` - CT2 *wrapping.BlobInfo `wrapping:"ct,foobar"` - } - err = structwrapping.WrapStruct(ctx, wrapper, &doubledCTIdentifierStruct{}) - assert.Error(err, "detected two ct wrapping tags with the same identifier") - }) - - t.Run("mismatched values", func(t *testing.T) { - t.Parallel() - assert := assert.New(t) - var err error - type mismatchedPTStruct struct { - PT1 []byte `wrapping:"pt,foo"` - CT1 *wrapping.BlobInfo `wrapping:"ct,foo"` - PT2 []byte `wrapping:"pt,bar"` - } - err = structwrapping.WrapStruct(ctx, wrapper, &mismatchedPTStruct{PT1: []byte("foo"), PT2: []byte("bar")}, nil) - assert.Error(err, "no ct wrapping tag found for identifier \"bar\"") - - type mismatchedCTStruct struct { - PT1 []byte `wrapping:"pt,bar"` - CT1 *wrapping.BlobInfo `wrapping:"ct,bar"` - CT2 *wrapping.BlobInfo `wrapping:"ct,foo"` - } - err = structwrapping.WrapStruct(ctx, wrapper, &mismatchedPTStruct{PT1: []byte("foo")}) - assert.Error(err, "no pt wrapping tag found for identifier \"foo\"") - }) - }) - - t.Run("bad encryption tests", func(t *testing.T) { - t.Parallel() - t.Run("bad plaintext values", func(t *testing.T) { - t.Parallel() - assert := assert.New(t) - var err error - type badPTTypeStruct struct { - field string `wrapping:"pt,foobar"` - } - err = structwrapping.WrapStruct(ctx, wrapper, new(badPTTypeStruct)) - assert.Error(err, "plaintext value is not a slice") - - type badPTSliceTypeStruct struct { - field []int `wrapping:"pt,foobar"` - } - err = structwrapping.WrapStruct(ctx, wrapper, new(badPTSliceTypeStruct)) - assert.Error(err, "plaintext value is not a byte slice") - - type nilPTSliceStruct struct { - Field []byte `wrapping:"pt,foobar"` - CTField *wrapping.BlobInfo `wrapping:"ct,foobar"` - } - err = structwrapping.WrapStruct(ctx, wrapper, new(nilPTSliceStruct)) - assert.Error(err, "plaintext byte slice is nil") - }) - }) - - t.Run("bad decryption tests", func(t *testing.T) { - t.Parallel() - t.Run("bad ciphertext values", func(t *testing.T) { - t.Parallel() - assert := assert.New(t) - var err error - type badCTTypeStruct struct { - field string `wrapping:"ct,foobar"` - } - err = structwrapping.UnwrapStruct(ctx, wrapper, new(badCTTypeStruct)) - assert.Error(err, "ciphertext value is not a pointer") - - type badCTSliceTypeStruct struct { - field *int `wrapping:"ct,foobar"` - } - err = structwrapping.UnwrapStruct(ctx, wrapper, new(badCTSliceTypeStruct)) - assert.Error(err, "ciphertext value is not the expected type") - - type nilCTStruct struct { - Field []byte `wrapping:"pt,foobar"` - CTField *wrapping.BlobInfo `wrapping:"ct,foobar"` - } - err = structwrapping.UnwrapStruct(ctx, wrapper, new(nilCTStruct)) - assert.Error(err, "ciphertext pointer is nil") - }) - }) - - t.Run("good values", func(t *testing.T) { - t.Parallel() - assert := assert.New(t) - var err error - type sutStruct struct { - PT1 []byte `wrapping:"pt,foo"` - PT2 string `wrapping:"pt,bar"` - PT3 []byte `wrapping:"pt,zip"` - CT1 *wrapping.BlobInfo `wrapping:"ct,foo"` - CT2 []byte `wrapping:"ct,bar"` - CT3 string `wrapping:"ct,zip"` - } - sut := &sutStruct{PT1: []byte("foo"), PT2: "bar", PT3: []byte("zip")} - err = structwrapping.WrapStruct(ctx, wrapper, sut) - assert.Nil(err) - assert.NotNil(sut.CT1) - assert.NotNil(sut.CT2) - assert.NotNil(sut.CT3) - - fooVal, err := wrapper.Decrypt(ctx, sut.CT1) - assert.Nil(err) - assert.Equal(fooVal, []byte("foo")) - - ebi := new(wrapping.BlobInfo) - err = proto.Unmarshal(sut.CT2, ebi) - assert.Nil(err) - barVal, err := wrapper.Decrypt(ctx, ebi) - assert.Nil(err) - assert.Equal(barVal, []byte("bar")) - - ebi = new(wrapping.BlobInfo) - err = proto.Unmarshal([]byte(sut.CT3), ebi) - assert.Nil(err) - zipVal, err := wrapper.Decrypt(ctx, ebi) - assert.Nil(err) - assert.Equal(zipVal, []byte("zip")) - - sut2 := &sutStruct{CT1: sut.CT1, CT2: sut.CT2, CT3: sut.CT3} - err = structwrapping.UnwrapStruct(ctx, wrapper, sut2) - assert.Nil(err) - assert.Equal(sut2.PT1, []byte("foo")) - assert.Equal(sut2.PT2, "bar") - assert.Equal(sut2.PT3, []byte("zip")) - }) -} diff --git a/go.mod b/go.mod index fa11d32d..41fdd35b 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.22.1 require ( github.com/hashicorp/go-uuid v1.0.3 - github.com/mr-tron/base58 v1.2.0 github.com/stretchr/testify v1.8.4 golang.org/x/crypto v0.17.0 golang.org/x/exp v0.0.0-20231006140011-7918f672742d @@ -15,7 +14,6 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/pretty v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.15.0 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1af6b422..caa30b6d 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= -github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 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/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= @@ -26,8 +24,6 @@ golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=