feat(sbc): Add domain changes for SBC
This commit is contained in:
1 parent
7a3c484147
commit
162a857a29
16 files changed
+892
-242
No files matched your search
+2
-2
@@ -6,7 +6,7 @@
|
||||
"data": {
|
||||
"data": {
|
||||
"limits": {
|
||||
"alias": {
|
||||
"alias": {
|
||||
"create": [
|
||||
{
|
||||
"action": "create",
|
||||
@@ -4266,4 +4266,4 @@
|
||||
"version": "v0.1"
|
||||
},
|
||||
"datacontentype": "application/cloudevents"
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4108,4 +4108,4 @@
|
||||
"version": "v0.1"
|
||||
},
|
||||
"datacontentype": "application/cloudevents"
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 *;
|
||||
`
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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 *;
|
||||
`
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user