From 162a857a29e4e9bee193f01394472854a33e7be8 Mon Sep 17 00:00:00 2001 From: Damian Debkowski Date: Tue, 16 Jul 2024 14:43:44 -0700 Subject: [PATCH] feat(sbc): Add domain changes for SBC --- .../defaults.json | 4 +- .../override.json | 2 +- internal/storage/plugin/rewrapping.go | 15 +- internal/storage/plugin/rewrapping_test.go | 78 +----- internal/storage/plugin/storage_bucket.go | 174 ++----------- .../environmental/query.go | 14 ++ .../environmental/register.go | 37 +++ .../storage_bucket_credential.go | 152 ++++++++++++ .../managedsecret/query.go | 18 ++ .../managedsecret/register.go | 37 +++ .../storage_bucket_credential.go | 229 ++++++++++++++++++ .../storagebucketcredential/options.go | 45 ++++ .../storagebucketcredential/options_test.go | 42 ++++ .../storagebucketcredential/registry.go | 126 ++++++++++ .../storage_bucket_credential.go | 128 ++++++++++ .../storage_bucket_credential_state.go | 33 +++ 16 files changed, 892 insertions(+), 242 deletions(-) create mode 100644 internal/storage/storagebucketcredential/environmental/query.go create mode 100644 internal/storage/storagebucketcredential/environmental/register.go create mode 100644 internal/storage/storagebucketcredential/environmental/storage_bucket_credential.go create mode 100644 internal/storage/storagebucketcredential/managedsecret/query.go create mode 100644 internal/storage/storagebucketcredential/managedsecret/register.go create mode 100644 internal/storage/storagebucketcredential/managedsecret/storage_bucket_credential.go create mode 100644 internal/storage/storagebucketcredential/options.go create mode 100644 internal/storage/storagebucketcredential/options_test.go create mode 100644 internal/storage/storagebucketcredential/registry.go create mode 100644 internal/storage/storagebucketcredential/storage_bucket_credential.go create mode 100644 internal/storage/storagebucketcredential/storage_bucket_credential_state.go diff --git a/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/defaults.json b/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/defaults.json index dac2c5e1e..99f58cb63 100644 --- a/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/defaults.json +++ b/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/defaults.json @@ -6,7 +6,7 @@ "data": { "data": { "limits": { - "alias": { + "alias": { "create": [ { "action": "create", @@ -4266,4 +4266,4 @@ "version": "v0.1" }, "datacontentype": "application/cloudevents" -} +} \ No newline at end of file diff --git a/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/override.json b/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/override.json index 5a2b452c8..3aed24de3 100644 --- a/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/override.json +++ b/internal/daemon/controller/testdata/Test_rateLimiterConfig_writeSysEvent/override.json @@ -4108,4 +4108,4 @@ "version": "v0.1" }, "datacontentype": "application/cloudevents" -} +} \ No newline at end of file diff --git a/internal/storage/plugin/rewrapping.go b/internal/storage/plugin/rewrapping.go index e5c11edaa..b75bc8049 100644 --- a/internal/storage/plugin/rewrapping.go +++ b/internal/storage/plugin/rewrapping.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/storage/storagebucketcredential/managedsecret" "github.com/hashicorp/boundary/internal/util" ) @@ -17,12 +18,12 @@ const ( ) func init() { - kms.RegisterTableRewrapFn(storageBucketCredentialManagedSecretTablename, storageBucketCredentialManagedSecretRewrapFn) + kms.RegisterTableRewrapFn(storageBucketCredentialManagedSecretTablename, storageBucketCredentialRewrapFn) } -// storageBucketRewrapFn provides a kms.Rewrapfn for the StorageBucketCredentialManagedSecret type -func storageBucketCredentialManagedSecretRewrapFn(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kmsRepo kms.GetWrapperer) error { - const op = "storage.storageBucketRewrapFn" +// storageBucketCredentialRewrapFn provides a kms.Rewrapfn for the StorageBucketCredentialManagedSecret type +func storageBucketCredentialRewrapFn(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kmsRepo kms.GetWrapperer) error { + const op = "storage.storageBucketCredentialRewrapFn" switch { case dataKeyVersionId == "": return errors.New(ctx, errors.InvalidParameter, op, "missing data key version id") @@ -35,7 +36,7 @@ func storageBucketCredentialManagedSecretRewrapFn(ctx context.Context, dataKeyVe case kmsRepo == nil: return errors.New(ctx, errors.InvalidParameter, op, "missing kms repository") } - var sbcms []*StorageBucketCredentialManagedSecret + var sbcms []*managedsecret.StorageBucketCredential // The only index on this table is on the primary key (storage bucket id) and we can't find it that way. // This is the fastest query we can use without creating a new index on key_id. if err := reader.SearchWhere(ctx, &sbcms, "key_id=?", []any{dataKeyVersionId}, db.WithLimit(-1)); err != nil { @@ -46,10 +47,10 @@ func storageBucketCredentialManagedSecretRewrapFn(ctx context.Context, dataKeyVe return errors.Wrap(ctx, err, op, errors.WithMsg("failed to fetch kms wrapper for rewrapping")) } for _, secret := range sbcms { - if err := secret.decrypt(ctx, wrapper); err != nil { + if err := secret.Decrypt(ctx, wrapper); err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("failed to decrypt storage bucket secret")) } - if err := secret.encrypt(ctx, wrapper); err != nil { + if err := secret.Encrypt(ctx, wrapper); err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("failed to re-encrypt storage bucket secret")) } if _, err := writer.Update(ctx, secret, []string{"CtSecrets", "KeyId"}, nil); err != nil { diff --git a/internal/storage/plugin/rewrapping_test.go b/internal/storage/plugin/rewrapping_test.go index 5f392f779..ae49224d6 100644 --- a/internal/storage/plugin/rewrapping_test.go +++ b/internal/storage/plugin/rewrapping_test.go @@ -11,15 +11,9 @@ import ( "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/boundary/internal/db" - "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" - "github.com/hashicorp/boundary/internal/libs/patchstruct" - "github.com/hashicorp/boundary/internal/plugin" - "github.com/hashicorp/boundary/internal/storage/plugin/store" "github.com/hashicorp/go-kms-wrapping/extras/kms/v2/migrations" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "google.golang.org/protobuf/types/known/structpb" ) func TestRewrap_storageBucketCredentialManagedSecretRewrapFn(t *testing.T) { @@ -38,77 +32,7 @@ func TestRewrap_storageBucketCredentialManagedSecretRewrapFn(t *testing.T) { mock.ExpectQuery( `SELECT \* FROM "storage_plugin_storage_bucket_secret" WHERE key_id=\$1`, ).WillReturnError(errors.New("Query error")) - err := storageBucketCredentialManagedSecretRewrapFn(ctx, "some_id", "some_scope", rw, rw, kmsCache) + err := storageBucketCredentialRewrapFn(ctx, "some_id", "some_scope", rw, rw, kmsCache) require.Error(t, err) }) - t.Run("success", func(t *testing.T) { - require, assert := require.New(t), assert.New(t) - conn, _ := db.TestSetup(t, "postgres") - - rw := db.New(conn) - wrapper := db.TestWrapper(t) - - org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) - plg := plugin.TestPlugin(t, conn, "test", plugin.WithStorageFlag(true)) - - kmsCache := kms.TestKms(t, conn, wrapper) - sb := &StorageBucket{ - StorageBucket: &store.StorageBucket{ - ScopeId: org.PublicId, - PluginId: plg.PublicId, - BucketName: "test-bucket-name", - Attributes: []byte{}, - SecretsHmac: []byte("test"), - WorkerFilter: "test worker filter", - }, - Secrets: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "AWS_ACCESS_KEY_ID": structpb.NewStringValue("access_key_id"), - "AWS_SECRET_ACCESS_KEY": structpb.NewStringValue("secret_access_key"), - }, - }, - } - - // first store bucket - var err error - sb.PublicId, err = newStorageBucketId(ctx) - require.NoError(err) - sb.Attributes, err = patchstruct.PatchBytes([]byte{}, sb.Attributes) - require.NoError(err) - require.NoError(rw.Create(ctx, sb)) - - // now store secret - cred, err := newStorageBucketCredentialManagedSecret(ctx, sb.PublicId, sb.Secrets) - require.NoError(err) - kmsWrapper1, err := kmsCache.GetWrapper(context.Background(), org.GetPublicId(), 1) - require.NoError(err) - require.NoError(cred.encrypt(ctx, kmsWrapper1)) - require.NoError(rw.Create(ctx, cred)) - assert.NoError(cred.decrypt(ctx, kmsWrapper1)) - - // now things are stored in the db, we can rotate and rewrap - require.NoError(kmsCache.RotateKeys(ctx, org.Scope.GetPublicId())) - require.NoError(storageBucketCredentialManagedSecretRewrapFn(ctx, cred.KeyId, org.Scope.GetPublicId(), rw, rw, kmsCache)) - - // now we pull the config back from the db, decrypt it with the new key, and ensure things match - got := &StorageBucketCredentialManagedSecret{ - StorageBucketCredentialManagedSecret: &store.StorageBucketCredentialManagedSecret{ - StorageBucketId: sb.PublicId, - }, - } - assert.NoError(rw.LookupById(ctx, got)) - - // fetch the new key version - kmsWrapper2, err := kmsCache.GetWrapper(ctx, org.Scope.GetPublicId(), kms.KeyPurposeDatabase, kms.WithKeyId(got.GetKeyId())) - assert.NoError(err) - newKeyVersion, err := kmsWrapper2.KeyId(ctx) - assert.NoError(err) - - // decrypt with the new key version and check to make sure things match - assert.NoError(got.decrypt(ctx, kmsWrapper2)) - assert.NotEmpty(got.GetKeyId()) - assert.NotEqual(cred.GetKeyId(), got.GetKeyId()) - assert.Equal(newKeyVersion, got.GetKeyId()) - assert.Equal(cred.Secrets, got.GetSecrets()) - }) } diff --git a/internal/storage/plugin/storage_bucket.go b/internal/storage/plugin/storage_bucket.go index e69f97e6a..656cc5222 100644 --- a/internal/storage/plugin/storage_bucket.go +++ b/internal/storage/plugin/storage_bucket.go @@ -4,18 +4,10 @@ package plugin import ( - "context" - "github.com/hashicorp/boundary/internal/db/timestamp" - "github.com/hashicorp/boundary/internal/errors" - "github.com/hashicorp/boundary/internal/libs/crypto" "github.com/hashicorp/boundary/internal/oplog" "github.com/hashicorp/boundary/internal/storage/plugin/store" "github.com/hashicorp/boundary/internal/types/resource" - "github.com/hashicorp/boundary/internal/util" - "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" - wrapping "github.com/hashicorp/go-kms-wrapping/v2" - "github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" ) @@ -83,145 +75,23 @@ func (s *StorageBucket) oplog(op oplog.OpType) oplog.Metadata { return metadata } -type StorageBucketCredentialManagedSecret struct { - *store.StorageBucketCredentialManagedSecret - tableName string `gorm:"-"` -} - -func allocStorageBucketCredentialManagedSecret() *StorageBucketCredentialManagedSecret { - return &StorageBucketCredentialManagedSecret{ - StorageBucketCredentialManagedSecret: &store.StorageBucketCredentialManagedSecret{}, - } -} - -// newStorageBucketCredentialManagedSecret creates an in memory storage bucket secret from a json -// formatted generic struct. -func newStorageBucketCredentialManagedSecret(ctx context.Context, storageBucketId string, secret *structpb.Struct) (*StorageBucketCredentialManagedSecret, error) { - const op = "plugin.newStorageBucketCredentialManagedSecret" - s := &StorageBucketCredentialManagedSecret{ - StorageBucketCredentialManagedSecret: &store.StorageBucketCredentialManagedSecret{ - StorageBucketId: storageBucketId, - }, - } - - if secret != nil { - sec, err := proto.Marshal(secret) - if err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter)) - } - s.Secrets = sec - } - return s, nil -} - -func (sbs *StorageBucketCredentialManagedSecret) clone() *StorageBucketCredentialManagedSecret { - cp := proto.Clone(sbs.StorageBucketCredentialManagedSecret) - return &StorageBucketCredentialManagedSecret{ - StorageBucketCredentialManagedSecret: cp.(*store.StorageBucketCredentialManagedSecret), - } -} - -// TableName returns the table name for the storage bucket secrets. -func (sbs *StorageBucketCredentialManagedSecret) TableName() string { - if sbs.tableName != "" { - return sbs.tableName - } - return "storage_bucket_credential_managed_secret" -} - -// SetTableName sets the table name. -func (sbs *StorageBucketCredentialManagedSecret) SetTableName(n string) { - sbs.tableName = n -} - -// hmacField simply hmac's a field in a consistent manner for this pkg -func hmacField(ctx context.Context, cipher wrapping.Wrapper, field []byte, publicId string) ([]byte, error) { - const op = "plugin.hmacField" - hm, err := crypto.HmacSha256(ctx, field, cipher, []byte(publicId), nil) - if err != nil { - return nil, errors.Wrap(ctx, err, op) - } - return []byte(hm), nil -} - -// only hmac's the secret's value. does not modify the underlying secret -// returns nil on failure -func (sbs *StorageBucketCredentialManagedSecret) hmacSecrets(ctx context.Context, cipher wrapping.Wrapper) ([]byte, error) { - const op = "plugin.(StorageBucketCredentialManagedSecret).hmacSecrets" - if cipher == nil { - return nil, errors.New(ctx, errors.InvalidParameter, op, "missing cipher") - } - var err error - if _, err = cipher.KeyId(ctx); err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("failed to read cipher key id")) - } - var hmac []byte - if hmac, err = hmacField(ctx, cipher, sbs.Secrets, sbs.StorageBucketId); err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("failed to hmac secrets")) - } - - return hmac, nil -} - -// encrypt the bind credential before writing it to the database -func (sbs *StorageBucketCredentialManagedSecret) encrypt(ctx context.Context, cipher wrapping.Wrapper) error { - const op = "plugin.(StorageBucketCredentialManagedSecret).encrypt" - if util.IsNil(cipher) { - return errors.New(ctx, errors.InvalidParameter, op, "missing cipher") - } - if err := structwrapping.WrapStruct(ctx, cipher, sbs.StorageBucketCredentialManagedSecret); err != nil { - return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt)) - } - var err error - if sbs.KeyId, err = cipher.KeyId(ctx); err != nil { - return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("failed to read cipher key id")) - } - - return nil -} - -func (sbs *StorageBucketCredentialManagedSecret) decrypt(ctx context.Context, cipher wrapping.Wrapper) error { - const op = "plugin.(StorageBucketCredentialManagedSecret).decrypt" - if util.IsNil(cipher) { - return errors.New(ctx, errors.InvalidParameter, op, "missing cipher") - } - if err := structwrapping.UnwrapStruct(ctx, cipher, sbs.StorageBucketCredentialManagedSecret, nil); err != nil { - return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt)) - } - sbs.CtSecrets = nil - return nil -} - -func (sbs *StorageBucketCredentialManagedSecret) toPersisted(ctx context.Context) (*storagebuckets.StorageBucketPersisted, error) { - const op = "plugin.(StorageBucketCredentialManagedSecret).toPersisted" - if sbs.Secrets == nil { - return nil, errors.New(ctx, errors.InvalidParameter, op, "secret data not populated") - } - sec := &storagebuckets.StorageBucketPersisted{ - Data: &structpb.Struct{}, - } - if err := proto.Unmarshal(sbs.Secrets, sec.Data); err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter)) - } - return sec, nil -} - type storageBucketAgg struct { - PublicId string `gorm:"primary_key"` - ScopeId string - Name string - Description string - CreateTime *timestamp.Timestamp - UpdateTime *timestamp.Timestamp - Version uint32 - PluginId string - BucketName string - BucketPrefix string - WorkerFilter string - Attributes []byte - SecretsEncrypted []byte - SecretsHmac []byte - KeyId string + PublicId string `gorm:"primary_key"` + ScopeId string + Name string + Description string + CreateTime *timestamp.Timestamp + UpdateTime *timestamp.Timestamp + Version uint32 + PluginId string + BucketName string + BucketPrefix string + WorkerFilter string + Attributes []byte + SecretsEncrypted []byte + SecretsHmac []byte + KeyId string + StorageBucketCredentialId string } // TableName returns the table name for gorm @@ -233,7 +103,7 @@ func (sba *storageBucketAgg) GetPublicId() string { return sba.PublicId } -func (sba *storageBucketAgg) toStorageBucketAndSecret() (*StorageBucket, *StorageBucketCredentialManagedSecret) { +func (sba *storageBucketAgg) toStorageBucketAndSBC() (*StorageBucket, error) { sb := allocStorageBucket() sb.PublicId = sba.PublicId sb.ScopeId = sba.ScopeId @@ -248,15 +118,9 @@ func (sba *storageBucketAgg) toStorageBucketAndSecret() (*StorageBucket, *Storag sb.WorkerFilter = sba.WorkerFilter sb.Attributes = sba.Attributes sb.SecretsHmac = sba.SecretsHmac + sb.StorageBucketCredentialId = sba.StorageBucketCredentialId - var sbs *StorageBucketCredentialManagedSecret - if len(sba.SecretsEncrypted) > 0 { - sbs = allocStorageBucketCredentialManagedSecret() - sbs.StorageBucketId = sba.PublicId - sbs.CtSecrets = sba.SecretsEncrypted - sbs.KeyId = sba.KeyId - } - return sb, sbs + return sb, nil } type deletedStorageBucket struct { diff --git a/internal/storage/storagebucketcredential/environmental/query.go b/internal/storage/storagebucketcredential/environmental/query.go new file mode 100644 index 000000000..40e81662d --- /dev/null +++ b/internal/storage/storagebucketcredential/environmental/query.go @@ -0,0 +1,14 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package environmental + +// CreateStorageBucketCredentialEnvironmentalQuery is the query to create a storage bucket credential environmental +const CreateStorageBucketCredentialEnvironmentalQuery = ` + insert into storage_bucket_credential_environmental ( + storage_bucket_id + ) values ( + @storage_bucket_id -- storage_bucket_id + ) + returning *; +` diff --git a/internal/storage/storagebucketcredential/environmental/register.go b/internal/storage/storagebucketcredential/environmental/register.go new file mode 100644 index 000000000..9be80266d --- /dev/null +++ b/internal/storage/storagebucketcredential/environmental/register.go @@ -0,0 +1,37 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package environmental + +import ( + "context" + + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/storage/storagebucketcredential" +) + +type sbcHooks struct{} + +func init() { + storagebucketcredential.Register(storagebucketcredential.EnvironmentalSubtype, sbcHooks{}) +} + +// Vet validates that the given storagebucketcredential.StorageBucketCredential is a environmental.StorageBucketCredential +// and that it has a StorageBucketCredential store. +func (h sbcHooks) Vet(ctx context.Context, sbc storagebucketcredential.StorageBucketCredential) error { + const op = "environmental.vet" + + sbce, ok := sbc.(*StorageBucketCredential) + if !ok { + return errors.New(ctx, errors.InvalidParameter, op, "StorageBucketCredential is not a sbce.StorageBucketCredential") + } + + if sbce == nil { + return errors.New(ctx, errors.InvalidParameter, op, "missing storage bucket credential") + } + + if sbce.StorageBucketCredentialEnvironmental == nil { + return errors.New(ctx, errors.InvalidParameter, op, "missing storage bucket credential store") + } + return nil +} diff --git a/internal/storage/storagebucketcredential/environmental/storage_bucket_credential.go b/internal/storage/storagebucketcredential/environmental/storage_bucket_credential.go new file mode 100644 index 000000000..5cc579add --- /dev/null +++ b/internal/storage/storagebucketcredential/environmental/storage_bucket_credential.go @@ -0,0 +1,152 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +// Package environmental provides a SBC subtype for a Environmental SBC. +// Importing this package will register it with the SBC package and +// allow the sbc.Repository to support environmental.StorageBucketCredential. +package environmental + +import ( + "context" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/storage/plugin/store" + "github.com/hashicorp/boundary/internal/storage/storagebucketcredential" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" + wrapping "github.com/hashicorp/go-kms-wrapping/v2" + "google.golang.org/protobuf/proto" +) + +const ( + defaultTableName = "storage_bucket_credential_environmental" +) + +// StorageBucketCredential is a resources that represents an Environmental SBC +// It is a subtype of storagebucketcredential.StorageBucketCredential. +type StorageBucketCredential struct { + *store.StorageBucketCredentialEnvironmental + tableName string `gorm:"-"` +} + +// Ensure StorageBucketCredential implements interfaces +var ( + _ storagebucketcredential.StorageBucketCredential = (*StorageBucketCredential)(nil) + _ db.VetForWriter = (*StorageBucketCredential)(nil) +) + +// NewStorageBucketCredential creates a new in memory environmental SBC. +func (h sbcHooks) NewStorageBucketCredential( + ctx context.Context, + storageBucketId string, + _ ...storagebucketcredential.Option, +) (storagebucketcredential.StorageBucketCredential, error) { + const op = "environmental.NewStorageBucketCredential" + if storageBucketId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing storage bucket id") + } + sbc := &StorageBucketCredential{ + StorageBucketCredentialEnvironmental: &store.StorageBucketCredentialEnvironmental{ + StorageBucketId: storageBucketId, + }, + } + return sbc, nil +} + +// AllocStorageBucketCredential will allocate a new StorageBucketCredential +func (h sbcHooks) AllocStorageBucketCredential() storagebucketcredential.StorageBucketCredential { + return &StorageBucketCredential{ + StorageBucketCredentialEnvironmental: &store.StorageBucketCredentialEnvironmental{}, + } +} + +// Clone creates a clone of the StorageBucketCredential +func (sbc *StorageBucketCredential) Clone() storagebucketcredential.StorageBucketCredential { + cp := proto.Clone(sbc.StorageBucketCredentialEnvironmental) + return &StorageBucketCredential{ + StorageBucketCredentialEnvironmental: cp.(*store.StorageBucketCredentialEnvironmental), + } +} + +// VetForWrite implements db.VetForWrite() interface and validates the environmental SBC +// before it's written. +func (sbc *StorageBucketCredential) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error { + const op = "environmental.(StorageBucketCredential).VetForWrite" + if sbc.StorageBucketId == "" { + return errors.New(ctx, errors.InvalidParameter, op, "missing storage bucket id") + } + return nil +} + +// TableName returns the tablename to override the default gorm table name +func (sbc *StorageBucketCredential) TableName() string { + if sbc.tableName != "" { + return sbc.tableName + } + return defaultTableName +} + +// SetTableName sets the tablename and satisfies the ReplayableMessage +// interface. If the caller attempts to set the name to "" the name will be +// reset to the default name. +func (sbc *StorageBucketCredential) SetTableName(n string) { + sbc.tableName = n +} + +func (sbc *StorageBucketCredential) SetPrivateId(privateId string) { + sbc.PrivateId = privateId +} + +func (sbc *StorageBucketCredential) SetStorageBucketId(storageBucketId string) { + sbc.StorageBucketId = storageBucketId +} + +func (sbc *StorageBucketCredential) SetCtSecrets(_ []byte) {} + +func (sbc *StorageBucketCredential) SetSecrets(_ []byte) {} + +func (sbc *StorageBucketCredential) SetKeyId(_ string) {} + +func (sbc *StorageBucketCredential) GetPrivateId() string { + return sbc.PrivateId +} + +func (sbc *StorageBucketCredential) GetType() storagebucketcredential.Subtype { + return storagebucketcredential.EnvironmentalSubtype +} + +func (sbc *StorageBucketCredential) GetCtSecrets() []byte { + return nil +} + +func (sbc *StorageBucketCredential) GetSecrets() []byte { + return nil +} + +func (sbc *StorageBucketCredential) GetKeyId() string { + return "" +} + +func (sbc *StorageBucketCredential) CreateDBQuery() string { + return CreateStorageBucketCredentialEnvironmentalQuery +} + +func (sbc *StorageBucketCredential) HmacSecrets(ctx context.Context, _ wrapping.Wrapper) ([]byte, error) { + const op = "environmental.(StorageBucketCredential).HmacSecrets" + return nil, errors.New(ctx, errors.Unknown, op, "HmacSecrets not implemented") +} + +func (sbc *StorageBucketCredential) Encrypt(ctx context.Context, _ wrapping.Wrapper) error { + const op = "environmental.(StorageBucketCredential).Encrypt" + return errors.New(ctx, errors.Unknown, op, "Encrypt not implemented") +} + +func (sbc *StorageBucketCredential) Decrypt(ctx context.Context, _ wrapping.Wrapper) error { + const op = "environmental.(StorageBucketCredential).Decrypt" + return errors.New(ctx, errors.Unknown, op, "Decrypt not implemented") +} + +func (sbc *StorageBucketCredential) ToPersisted(ctx context.Context) (*storagebuckets.StorageBucketPersisted, error) { + const op = "environmental.(StorageBucketCredential).ToPersisted" + return nil, errors.New(ctx, errors.Unknown, op, "ToPersisted not implemented") +} diff --git a/internal/storage/storagebucketcredential/managedsecret/query.go b/internal/storage/storagebucketcredential/managedsecret/query.go new file mode 100644 index 000000000..8470e1ec5 --- /dev/null +++ b/internal/storage/storagebucketcredential/managedsecret/query.go @@ -0,0 +1,18 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package managedsecret + +// CreateStorageBucketCredentialManagedSecretQuery is the query to create a storage bucket credential managed secret +const CreateStorageBucketCredentialManagedSecretQuery = ` + insert into storage_bucket_credential_managed_secret ( + storage_bucket_id, + secrets_encrypted, + key_id + ) values ( + @storage_bucket_id, -- storage_bucket_id + @secrets_encrypted, -- secrets_encrypted + @key_id -- key_id + ) + returning *; +` diff --git a/internal/storage/storagebucketcredential/managedsecret/register.go b/internal/storage/storagebucketcredential/managedsecret/register.go new file mode 100644 index 000000000..827c692ca --- /dev/null +++ b/internal/storage/storagebucketcredential/managedsecret/register.go @@ -0,0 +1,37 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package managedsecret + +import ( + "context" + + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/storage/storagebucketcredential" +) + +type sbcHooks struct{} + +func init() { + storagebucketcredential.Register(storagebucketcredential.ManagedSecretSubtype, sbcHooks{}) +} + +// Vet validates that the given storagebucketcredential.StorageBucketCredential is a managedsecret.StorageBucketCredential +// and that it has a StorageBucketCredential store. +func (h sbcHooks) Vet(ctx context.Context, sbc storagebucketcredential.StorageBucketCredential) error { + const op = "managedsecret.vet" + + sbcms, ok := sbc.(*StorageBucketCredential) + if !ok { + return errors.New(ctx, errors.InvalidParameter, op, "StorageBucketCredential is not a sbcms.StorageBucketCredential") + } + + if sbcms == nil { + return errors.New(ctx, errors.InvalidParameter, op, "missing storage bucket credential") + } + + if sbcms.StorageBucketCredentialManagedSecret == nil { + return errors.New(ctx, errors.InvalidParameter, op, "missing storage bucket credential store") + } + return nil +} diff --git a/internal/storage/storagebucketcredential/managedsecret/storage_bucket_credential.go b/internal/storage/storagebucketcredential/managedsecret/storage_bucket_credential.go new file mode 100644 index 000000000..f0878bb36 --- /dev/null +++ b/internal/storage/storagebucketcredential/managedsecret/storage_bucket_credential.go @@ -0,0 +1,229 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +// Package managedsecret provides a SBC subtype for a Managed Secret SBC. +// Importing this package will register it with the SBC package and +// allow the sbc.Repository to support managedsecret.StorageBucketCredential. +package managedsecret + +import ( + "context" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/libs/crypto" + "github.com/hashicorp/boundary/internal/storage/plugin/store" + "github.com/hashicorp/boundary/internal/storage/storagebucketcredential" + "github.com/hashicorp/boundary/internal/util" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" + wrapping "github.com/hashicorp/go-kms-wrapping/v2" + "github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" +) + +const ( + defaultTableName = "storage_bucket_credential_managed_secret" +) + +// StorageBucketCredential is a resources that represents an Managed Secret SBC +// It is a subtype of storagebucketcredential.StorageBucketCredential. +type StorageBucketCredential struct { + *store.StorageBucketCredentialManagedSecret + tableName string `gorm:"-"` +} + +// Ensure StorageBucketCredential implements interfaces +var ( + _ storagebucketcredential.StorageBucketCredential = (*StorageBucketCredential)(nil) + _ db.VetForWriter = (*StorageBucketCredential)(nil) +) + +// NewStorageBucketCredential creates a new in memory managed secret SBC. +func (h sbcHooks) NewStorageBucketCredential( + ctx context.Context, + storageBucketId string, + opt ...storagebucketcredential.Option, +) (storagebucketcredential.StorageBucketCredential, error) { + const op = "managedsecret.NewStorageBucketCredential" + opts := storagebucketcredential.GetOpts(opt...) + if storageBucketId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing storage bucket id") + } + if opts.WithSecret == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing secret") + } + var secrets []byte + if opts.WithSecret != nil { + var err error + secrets, err = proto.Marshal(opts.WithSecret) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("failed to marshal secrets")) + } + } + + sbc := &StorageBucketCredential{ + StorageBucketCredentialManagedSecret: &store.StorageBucketCredentialManagedSecret{ + StorageBucketId: storageBucketId, + Secrets: secrets, + KeyId: opts.WithKeyId, + }, + } + return sbc, nil +} + +// AllocStorageBucketCredential will allocate a new StorageBucketCredential +func (h sbcHooks) AllocStorageBucketCredential() storagebucketcredential.StorageBucketCredential { + return &StorageBucketCredential{ + StorageBucketCredentialManagedSecret: &store.StorageBucketCredentialManagedSecret{}, + } +} + +// Clone creates a clone of the StorageBucketCredential +func (sbc *StorageBucketCredential) Clone() storagebucketcredential.StorageBucketCredential { + cp := proto.Clone(sbc.StorageBucketCredentialManagedSecret) + return &StorageBucketCredential{ + StorageBucketCredentialManagedSecret: cp.(*store.StorageBucketCredentialManagedSecret), + } +} + +func (sbc *StorageBucketCredential) CreateDBQuery() string { + return CreateStorageBucketCredentialManagedSecretQuery +} + +// VetForWrite implements db.VetForWrite() interface and validates the managed secret SBC +// before it's written. +func (sbc *StorageBucketCredential) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error { + const op = "managedsecret.(StorageBucketCredential).VetForWrite" + if sbc.StorageBucketId == "" { + return errors.New(ctx, errors.InvalidParameter, op, "missing storage bucket id") + } + return nil +} + +// hmacField simply hmac's a field in a consistent manner for this pkg +func hmacField(ctx context.Context, cipher wrapping.Wrapper, field []byte, publicId string) ([]byte, error) { + const op = "plugin.hmacField" + hm, err := crypto.HmacSha256(ctx, field, cipher, []byte(publicId), nil) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + return []byte(hm), nil +} + +// only hmac's the secret's value. does not modify the underlying secret +// returns nil on failure +func (sbc *StorageBucketCredential) HmacSecrets(ctx context.Context, cipher wrapping.Wrapper) ([]byte, error) { + const op = "managedsecret.(StorageBucketCredential).hmacSecrets" + if cipher == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing cipher") + } + var err error + if _, err = cipher.KeyId(ctx); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("failed to read cipher key id")) + } + var hmac []byte + if hmac, err = hmacField(ctx, cipher, sbc.Secrets, sbc.StorageBucketId); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("failed to hmac secrets")) + } + + return hmac, nil +} + +// encrypt the bind credential before writing it to the database +func (sbc *StorageBucketCredential) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "managedsecret.(StorageBucketCredential)).encrypt" + if util.IsNil(cipher) { + return errors.New(ctx, errors.InvalidParameter, op, "missing cipher") + } + if err := structwrapping.WrapStruct(ctx, cipher, sbc.StorageBucketCredentialManagedSecret); err != nil { + return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt)) + } + var err error + if sbc.KeyId, err = cipher.KeyId(ctx); err != nil { + return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("failed to read cipher key id")) + } + + return nil +} + +func (sbc *StorageBucketCredential) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "managedsecret.(StorageBucketCredential).decrypt" + if util.IsNil(cipher) { + return errors.New(ctx, errors.InvalidParameter, op, "missing cipher") + } + if err := structwrapping.UnwrapStruct(ctx, cipher, sbc.StorageBucketCredentialManagedSecret, nil); err != nil { + return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt)) + } + sbc.CtSecrets = nil + return nil +} + +func (sbc *StorageBucketCredential) ToPersisted(ctx context.Context) (*storagebuckets.StorageBucketPersisted, error) { + const op = "plugin.(StorageBucketCredentialManagedSecret).toPersisted" + if sbc.Secrets == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "secret data not populated") + } + sec := &storagebuckets.StorageBucketPersisted{ + Data: &structpb.Struct{}, + } + if err := proto.Unmarshal(sbc.Secrets, sec.Data); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter)) + } + return sec, nil +} + +// TableName returns the tablename to override the default gorm table name +func (sbc *StorageBucketCredential) TableName() string { + if sbc.tableName != "" { + return sbc.tableName + } + return defaultTableName +} + +// SetTableName sets the tablename and satisfies the ReplayableMessage +// interface. If the caller attempts to set the name to "" the name will be +// reset to the default name. +func (sbc *StorageBucketCredential) SetTableName(n string) { + sbc.tableName = n +} + +func (sbc *StorageBucketCredential) SetPrivateId(privateId string) { + sbc.PrivateId = privateId +} + +func (sbc *StorageBucketCredential) SetStorageBucketId(storageBucketId string) { + sbc.StorageBucketId = storageBucketId +} + +func (sbc *StorageBucketCredential) SetCtSecrets(s []byte) { + sbc.CtSecrets = s +} + +func (sbc *StorageBucketCredential) SetSecrets(s []byte) { + sbc.Secrets = s +} + +func (sbc *StorageBucketCredential) SetKeyId(s string) { + sbc.KeyId = s +} + +func (sbc *StorageBucketCredential) GetPrivateId() string { + return sbc.PrivateId +} + +func (sbc *StorageBucketCredential) GetType() storagebucketcredential.Subtype { + return storagebucketcredential.ManagedSecretSubtype +} + +func (sbc *StorageBucketCredential) GetCtSecrets() []byte { + return sbc.CtSecrets +} + +func (sbc *StorageBucketCredential) GetSecrets() []byte { + return sbc.Secrets +} + +func (sbc *StorageBucketCredential) GetKeyId() string { + return sbc.KeyId +} diff --git a/internal/storage/storagebucketcredential/options.go b/internal/storage/storagebucketcredential/options.go new file mode 100644 index 000000000..c52a7e3d9 --- /dev/null +++ b/internal/storage/storagebucketcredential/options.go @@ -0,0 +1,45 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package storagebucketcredential + +import "google.golang.org/protobuf/types/known/structpb" + +// 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 { + WithSecret *structpb.Struct + WithKeyId string +} + +func getDefaultOptions() options { + return options{ + WithSecret: nil, + WithKeyId: "", + } +} + +// WithSecret provides an optional secret +func WithSecret(s *structpb.Struct) Option { + return func(o *options) { + o.WithSecret = s + } +} + +// WithKeyId provides an key id +func WithKeyId(s string) Option { + return func(o *options) { + o.WithKeyId = s + } +} diff --git a/internal/storage/storagebucketcredential/options_test.go b/internal/storage/storagebucketcredential/options_test.go new file mode 100644 index 000000000..eb45c73ea --- /dev/null +++ b/internal/storage/storagebucketcredential/options_test.go @@ -0,0 +1,42 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package storagebucketcredential + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/structpb" +) + +// Test_GetOpts provides unit tests for GetOpts and all the options +func Test_GetOpts(t *testing.T) { + t.Parallel() + t.Run("WithSecret", func(t *testing.T) { + assert := assert.New(t) + opts := GetOpts(WithSecret( + &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "ACCESS_KEY_ID": structpb.NewStringValue("access_key_id"), + "SECRET_ACCESS_KEY": structpb.NewStringValue("secret_access_key"), + }, + }, + )) + testOpts := getDefaultOptions() + testOpts.WithSecret = &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "ACCESS_KEY_ID": structpb.NewStringValue("access_key_id"), + "SECRET_ACCESS_KEY": structpb.NewStringValue("secret_access_key"), + }, + } + assert.Equal(opts, testOpts) + }) + t.Run("WithKeyId", func(t *testing.T) { + assert := assert.New(t) + opts := GetOpts(WithKeyId("test-key-id")) + testOpts := getDefaultOptions() + testOpts.WithKeyId = "test-key-id" + assert.Equal(opts, testOpts) + }) +} diff --git a/internal/storage/storagebucketcredential/registry.go b/internal/storage/storagebucketcredential/registry.go new file mode 100644 index 000000000..6fd9049a7 --- /dev/null +++ b/internal/storage/storagebucketcredential/registry.go @@ -0,0 +1,126 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package storagebucketcredential + +import ( + "context" + "fmt" + "sync" + + "github.com/hashicorp/boundary/internal/errors" +) + +const Domain = "storage-bucket-credential" + +// NewFunc is a function that creates a storage bucket credential with the provided storage bucket Id, +// and options. +type NewFunc func(ctx context.Context, storageBucketId string, opt ...Option) (StorageBucketCredential, error) + +// AllocFunc is a function that creates an in-memory storage bucket credential. +type AllocFunc func() StorageBucketCredential + +// VetFunc is a function that checks the given storage bucket credential to ensure it can +// be used by the Repository. +type VetFunc func(context.Context, StorageBucketCredential) error + +// VetForUpdateFunc is a function that checks the given StorageBucketCredential and field mask +// paths are valid and be used to update a storage bucket credential in the Repository. +type VetForUpdateFunc func(context.Context, StorageBucketCredential, []string) error + +// sbcHooks defines the interface containing all the hooks needed for +// managing storage bucket credential suptypes. +type sbcHooks interface { + // NewStorageBucketCredential creates a new in memory storage bucket credential. + NewStorageBucketCredential(ctx context.Context, storageBucketId string, opt ...Option) (StorageBucketCredential, error) + // AllocStorageBucketCredential will allocate an empty storage bucket credential. + AllocStorageBucketCredential() StorageBucketCredential + // Vet validates that the given StorageBucketCredential has the proper fields and values + // for creation in the database for this type of storage bucket credential. + Vet(ctx context.Context, t StorageBucketCredential) error +} + +type registryEntry struct { + sbcHooks sbcHooks +} + +type Registry struct { + m map[Subtype]*registryEntry + sync.RWMutex +} + +func (r *Registry) set(s Subtype, entry *registryEntry) { + r.Lock() + defer r.Unlock() + + _, previouslySet := r.m[s] + if previouslySet { + panic(fmt.Sprintf("storage bucket credential subtype %s already registered", s)) + } + + r.m[s] = entry +} + +func (r *Registry) get(s Subtype) (*registryEntry, bool) { + r.RLock() + defer r.RUnlock() + + entry, ok := r.m[s] + if ok { + return entry, ok + } + return nil, ok +} + +func (r *Registry) NewFunc(s Subtype) (NewFunc, bool) { + entry, ok := r.get(s) + if !ok { + return nil, ok + } + return entry.sbcHooks.NewStorageBucketCredential, ok +} + +func (r *Registry) AllocFunc(s Subtype) (AllocFunc, bool) { + entry, ok := r.get(s) + if !ok { + return nil, ok + } + return entry.sbcHooks.AllocStorageBucketCredential, ok +} + +func (r *Registry) VetFunc(s Subtype) (VetFunc, bool) { + entry, ok := r.get(s) + if !ok { + return nil, ok + } + return entry.sbcHooks.Vet, ok +} + +var SubtypeRegistry = Registry{ + m: make(map[Subtype]*registryEntry), +} + +// SubtypeFromType returns the Subtype from the provided string or if +// no Subtype was registered with that string Unknown is returned. +func SubtypeFromType(t string) Subtype { + return Subtype(t) +} + +// New creates a StorageBucketCredential of the given subtype and storageBucketId. +func New(ctx context.Context, subtype Subtype, storageBucketId string, opt ...Option) (StorageBucketCredential, error) { + const op = "storagebucketcredential.New" + nf, ok := SubtypeRegistry.NewFunc(subtype) + if !ok { + return nil, errors.New(ctx, errors.InvalidParameter, op, "unsupported subtype") + } + return nf(ctx, storageBucketId, opt...) +} + +// Register registers repository hooks for a provided Subtype. Register +// panics if the subtype has already been registered or if any of the +// prefixes are associated with another subtype. +func Register(s Subtype, sh sbcHooks) { + SubtypeRegistry.set(s, ®istryEntry{ + sbcHooks: sh, + }) +} diff --git a/internal/storage/storagebucketcredential/storage_bucket_credential.go b/internal/storage/storagebucketcredential/storage_bucket_credential.go new file mode 100644 index 000000000..2fb30d8da --- /dev/null +++ b/internal/storage/storagebucketcredential/storage_bucket_credential.go @@ -0,0 +1,128 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package storagebucketcredential + +import ( + "context" + goerrs "errors" + "fmt" + + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" + wrapping "github.com/hashicorp/go-kms-wrapping/v2" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" +) + +// StorageBucketCredential is a commmon interface for all storage bucket credential subtypes +type StorageBucketCredential interface { + HmacSecrets(context.Context, wrapping.Wrapper) ([]byte, error) + Encrypt(context.Context, wrapping.Wrapper) error + Decrypt(context.Context, wrapping.Wrapper) error + ToPersisted(context.Context) (*storagebuckets.StorageBucketPersisted, error) + Clone() StorageBucketCredential + CreateDBQuery() string + GetPrivateId() string + GetStorageBucketId() string + GetType() Subtype + GetSecrets() []byte + GetCtSecrets() []byte + GetKeyId() string + SetPrivateId(string) + SetStorageBucketId(string) + SetSecrets([]byte) + SetCtSecrets([]byte) + SetKeyId(string) +} + +const ( + sbcDefaultTable = "storage_bucket_credential_all_subtypes" +) + +type Subtype string + +const ( + UnknownSubtype Subtype = "unknown" + ManagedSecretSubtype Subtype = "managed_secret" + EnvironmentalSubtype Subtype = "environmental" +) + +var errSBCSubtypeNotFound = goerrs.New("storage bucket credential subtype not found") + +// storageBucketCredential provides a common way to return storage bucket credentials +// regardless of their underlying type. +type storageBucketCredential struct { + // tableName is the name of the table in the database + tableName string `gorm:"-"` + // PrivateId is a surrogate key suitable for use via API. + PrivateId string `gorm:"primary_key"` + // StorageBucketId is the public id of the storage bucket. + StorageBucketId string `gorm:"not_null"` + // Secrets is the plain-text of the secret data. We are not storing this plain-text + // value in the database. + Secrets []byte `gorm:"-" wrapping:"pt,secrets_data"` + // CtSecrets is the ciphertext of the secret data stored in the db. + CtSecrets []byte `gorm:"column:secrets_encrypted;not_null" wrapping:"ct,secrets_data"` + // The KeyId of the kms database key used for encrypting this entry. + // It must be set. + KeyId string `gorm:"not_null"` + // Type represents the type of the Storage Bucket Credential + Type string `gorm:"default:null"` +} + +// AllocStorageBucketCredential will allocate a storage bucket credential +func AllocStorageBucketCredential() *storageBucketCredential { + return &storageBucketCredential{} +} + +// TableName provides an overridden gorm table name for storage bucket credentials. +func (sbc *storageBucketCredential) TableName() string { + if sbc.tableName != "" { + return sbc.tableName + } + return sbcDefaultTable +} + +// SetTableName sets the table name for the resource. If the caller attempts to +// set the name to "" the name will be reset to the default name. +func (sbc *storageBucketCredential) SetTableName(n string) { + switch n { + case "": + sbc.tableName = sbcDefaultTable + default: + sbc.tableName = n + } +} + +func (sbc *storageBucketCredential) Subtype() Subtype { + return Subtype(sbc.Type) +} + +// SBCSubtype converts the storage bucket credential to the concrete subtype +func (sbc *storageBucketCredential) SBCSubtype(ctx context.Context) (StorageBucketCredential, error) { + const op = "storagebucketcredential.storageBucketCredential.sbcSubtype" + + alloc, ok := SubtypeRegistry.AllocFunc(sbc.Subtype()) + if !ok { + return nil, errors.Wrap(ctx, + errSBCSubtypeNotFound, + op, + errors.WithCode(errors.InvalidParameter), + errors.WithMsg(fmt.Sprintf("%s is an unknown sbc subtype of %s", sbc.PrivateId, sbc.Type)), + ) + } + + aSBC := alloc() + aSBC.SetPrivateId(sbc.PrivateId) + aSBC.SetStorageBucketId(sbc.StorageBucketId) + if sbc.Subtype() == ManagedSecretSubtype { + secrets := &structpb.Struct{} + if err := proto.Unmarshal(sbc.Secrets, secrets); err != nil { + return nil, err + } + aSBC.SetSecrets(sbc.Secrets) + aSBC.SetCtSecrets(sbc.CtSecrets) + } + return aSBC, nil +} diff --git a/internal/storage/storagebucketcredential/storage_bucket_credential_state.go b/internal/storage/storagebucketcredential/storage_bucket_credential_state.go new file mode 100644 index 000000000..7c2270fec --- /dev/null +++ b/internal/storage/storagebucketcredential/storage_bucket_credential_state.go @@ -0,0 +1,33 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package storagebucketcredential + +import ( + "github.com/hashicorp/boundary/internal/server/store" +) + +// NewWorkerStorageBucketCredentialState returns a new WorkerStorageBucketCredentialState. +func NewWorkerStorageBucketCredentialState() *WorkerStorageBucketCredentialState { + return &WorkerStorageBucketCredentialState{ + WorkerStorageBucketCredentialState: &store.WorkerStorageBucketCredentialState{}, + } +} + +type WorkerStorageBucketCredentialState struct { + *store.WorkerStorageBucketCredentialState + tableName string `gorm:"-"` +} + +// TableName returns the table name. +func (sbc *WorkerStorageBucketCredentialState) TableName() string { + if sbc.tableName != "" { + return sbc.tableName + } + return "worker_storage_bucket_credential_state" +} + +// SetTableName sets the table name. +func (sbc *WorkerStorageBucketCredentialState) SetTableName(n string) { + sbc.tableName = n +}