refactor (kms): adopt the go-kms-wrapping/extras/kms package (#2027)

* feature (db): add Db.UnderlyingDB()

This is needed to allow for integration with bits that need a *dbw.DB

* feature (kms): add new go-kms-wrapping/extras/kms dependency

* refactor (kms): schema changes required to adopt extras/kms pkg

* refactor (kms/iam): rename to DeprecateCreateKeysTx(...)

* refactor (kms): remove deprecated test

* refactor (kms): refactor Kms type

This refactor includes renaming the existing type to DeprecatedKms
and then refactoring the Kms type to become a wrapper of
go-kms-wrapping/extras/kms

* refactor (kms): delete all the unneeded bits

All this stuff is not needed now that we're using
go-kms-wrapping/extras/kms

* refactor (db): fixup tests after kms refactor

* refactor (kms/tests): use testing.TB for test helpers

Our test helper packages were not possible to use
with benchmarks. This refactor switches to testing.TB
to allow both tests and benchmarks to use the helpers.

* tests (session benchmarks): refactor to use new Kms type

* refactor (schema): rm test which relies on deprecated Kms code

* refactor (kms): post rebase on main changes required

* chore (kms refactor): re-add unit test from main

* refactor (kms): uses migrations that don't include KMS schema changes
This commit is contained in:
Jim authored and GitHub committed 2022-05-13 13:53:22 -04:00
1 parent 8cef1017e9
commit 785241237e
97 files changed
+2580 -15154

No files matched your search

-82
View File
@@ -1,82 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
"google.golang.org/protobuf/proto"
)
const (
DefaultAuditKeyTableName = "kms_audit_key"
)
type AuditKey struct {
*store.AuditKey
tableName string `gorm:"-"`
}
// NewAuditKey creates a new in memory audit key. This key is used for crypto
// operations on audit entries. No options are currently supported.
func NewAuditKey(ctx context.Context, rootKeyId string, _ ...Option) (*AuditKey, error) {
const op = "kms.NewAuditKey"
if rootKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
k := &AuditKey{
AuditKey: &store.AuditKey{
RootKeyId: rootKeyId,
},
}
return k, nil
}
// AllocAuditKey will allocate a AuditKey
func AllocAuditKey() AuditKey {
return AuditKey{
AuditKey: &store.AuditKey{},
}
}
// Clone creates a clone of the AuditKey
func (k *AuditKey) Clone() interface{} {
cp := proto.Clone(k.AuditKey)
return &AuditKey{
AuditKey: cp.(*store.AuditKey),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// before it's written.
func (k *AuditKey) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "kms.(AuditKey).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
switch opType {
case db.CreateOp:
if k.RootKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
case db.UpdateOp:
return errors.New(ctx, errors.InvalidParameter, op, "key is immutable")
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *AuditKey) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultAuditKeyTableName
}
// 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 (k *AuditKey) SetTableName(n string) {
k.tableName = n
}
-209
View File
@@ -1,209 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the AuditKey attributes are
// immutable and those tests are covered by TestAuditKey_ImmutableFields
func TestAuditKey_Create(t *testing.T) {
t.Parallel()
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootKeyId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.AuditKey
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-rootKeyId",
args: args{},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
rootKeyId: rk.PrivateId,
},
want: func() *kms.AuditKey {
k := kms.AllocAuditKey()
k.RootKeyId = rk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocAuditKey(); return &i }(), "1=1")
got, err := kms.NewAuditKey(testCtx, tt.args.rootKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.AuditKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestAuditKey_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocAuditKey(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.AuditKey
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestAuditKey(t, conn, rk.PrivateId),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.AuditKey {
k := kms.AllocAuditKey()
id, err := db.NewPublicId(kms.AuditKeyPrefix)
require.NoError(t, err)
k.PrivateId = id
k.RootKeyId = rk.PrivateId
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocAuditKey()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocAuditKey()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestAuditKey_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k := kms.TestAuditKey(t, conn, rk.PrivateId)
cp := k.Clone()
assert.True(proto.Equal(cp.(*kms.AuditKey).AuditKey, k.AuditKey))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
k := kms.TestAuditKey(t, conn, rk.PrivateId)
k2 := kms.TestAuditKey(t, conn, rk2.PrivateId)
cp := k.Clone()
assert.True(!proto.Equal(cp.(*kms.AuditKey).AuditKey, k2.AuditKey))
})
}
func TestAuditKey_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultAuditKeyTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocAuditKey()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocAuditKey()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-122
View File
@@ -1,122 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
const (
DefaultAuditKeyVersionTableName = "kms_audit_key_version"
)
type AuditKeyVersion struct {
*store.AuditKeyVersion
tableName string `gorm:"-"`
}
// NewAuditKeyVersion creates a new in memory audit key version. This key is
// used for crypto operations on audit entries. No options are currently
// supported.
func NewAuditKeyVersion(ctx context.Context, auditKeyId string, key []byte, rootKeyVersionId string, _ ...Option) (*AuditKeyVersion, error) {
const op = "kms.NewAuditKeyVersion"
if auditKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing audit key id")
}
if len(key) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
if rootKeyVersionId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
k := &AuditKeyVersion{
AuditKeyVersion: &store.AuditKeyVersion{
AuditKeyId: auditKeyId,
RootKeyVersionId: rootKeyVersionId,
Key: key,
},
}
return k, nil
}
// AllocAuditKeyVersion allocates a AuditKeyVersion
func AllocAuditKeyVersion() AuditKeyVersion {
return AuditKeyVersion{
AuditKeyVersion: &store.AuditKeyVersion{},
}
}
// Clone creates a clone of the AuditKeyVersion
func (k *AuditKeyVersion) Clone() interface{} {
cp := proto.Clone(k.AuditKeyVersion)
return &AuditKeyVersion{
AuditKeyVersion: cp.(*store.AuditKeyVersion),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the audit key
// version before it's written.
func (k *AuditKeyVersion) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "kms.(AuditKeyVersion).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
switch opType {
case db.CreateOp:
if k.CtKey == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
if k.AuditKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing audit key id")
}
if k.RootKeyVersionId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
case db.UpdateOp:
return errors.New(ctx, errors.InvalidParameter, op, "key is immutable")
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *AuditKeyVersion) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultAuditKeyVersionTableName
}
// 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 (k *AuditKeyVersion) SetTableName(n string) {
k.tableName = n
}
// Encrypt will encrypt the audit key version's key
func (k *AuditKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(AuditKeyVersion).Encrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.AuditKeyVersion directly
if err := structwrapping.WrapStruct(ctx, cipher, k.AuditKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
}
return nil
}
// Decrypt will decrypt the audit key version's key
func (k *AuditKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(AuditKeyVersion).Decrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.AuditKeyVersion directly
if err := structwrapping.UnwrapStruct(ctx, cipher, k.AuditKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
-250
View File
@@ -1,250 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the AuditKeyVersion attributes are
// immutable and those tests are covered by TestAuditKey_ImmutableFields
func TestAuditKeyVersion_Create(t *testing.T) {
t.Parallel()
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rkv, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestAuditKey(t, conn, rk.PrivateId)
type args struct {
AuditKeyId string
key []byte
rootKeyVersionId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.AuditKeyVersion
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-audit-id",
args: args{
key: []byte("audit key"),
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
AuditKeyId: dk.PrivateId,
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-root-key-version-id",
args: args{
AuditKeyId: dk.PrivateId,
key: []byte("audit key"),
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
AuditKeyId: dk.PrivateId,
key: []byte("audit key"),
rootKeyVersionId: rkv.PrivateId,
},
want: func() *kms.AuditKeyVersion {
k := kms.AllocAuditKeyVersion()
k.RootKeyVersionId = rkv.PrivateId
k.Key = []byte("audit key")
k.AuditKeyId = dk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocAuditKeyVersion(); return &i }(), "1=1")
got, err := kms.NewAuditKeyVersion(testCtx, tt.args.AuditKeyId, tt.args.key, tt.args.rootKeyVersionId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.AuditKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = got.Encrypt(context.Background(), rkvWrapper)
require.NoError(err)
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestAuditKeyVersion_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestAuditKey(t, conn, rk.PrivateId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocAuditKeyVersion(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.AuditKeyVersion
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestAuditKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("audit key")),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.AuditKeyVersion {
k := kms.AllocAuditKeyVersion()
id, err := db.NewPublicId(kms.AuditKeyVersionPrefix)
require.NoError(t, err)
k.PrivateId = id
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocAuditKeyVersion()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocAuditKeyVersion()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestAuditKeyVersion_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
k := kms.TestAuditKey(t, conn, rk.PrivateId)
kv := kms.TestAuditKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test db key"))
cp := kv.Clone()
assert.True(proto.Equal(cp.(*kms.AuditKeyVersion).AuditKeyVersion, kv.AuditKeyVersion))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
_, rkvWrapper2 := kms.TestRootKeyVersion(t, conn, wrapper, rk2.PrivateId)
k := kms.TestAuditKey(t, conn, rk.PrivateId)
k2 := kms.TestAuditKey(t, conn, rk2.PrivateId)
kv := kms.TestAuditKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test db key"))
kv2 := kms.TestAuditKeyVersion(t, conn, rkvWrapper2, k2.PrivateId, []byte("test db key 2"))
cp := kv.Clone()
assert.True(!proto.Equal(cp.(*kms.AuditKeyVersion).AuditKeyVersion, kv2.AuditKeyVersion))
})
}
func TestAuditKeyVersion_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultAuditKeyVersionTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocAuditKeyVersion()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocAuditKeyVersion()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
+21 -59
View File
@@ -24,6 +24,9 @@ const (
// KeyPurposeRecovery is used for recovery access
KeyPurposeRecovery
// KeyPurposeWorkerAuth is used for worker auth
KeyPurposeWorkerAuth
// KeyPurposeTokens is used for token encryption
KeyPurposeTokens
@@ -36,6 +39,9 @@ const (
// KeyPurposeAudit is used for audit operations
KeyPurposeAudit
// KeyPurposeRootKey is used as the root key
KeyPurposeRootKey
)
// String returns the key purpose cast as a string, just so it can be called as
@@ -48,6 +54,8 @@ func (k KeyPurpose) String() string {
return "oplog"
case KeyPurposeRecovery:
return "recovery"
case KeyPurposeWorkerAuth:
return "workerauth"
case KeyPurposeTokens:
return "tokens"
case KeyPurposeSessions:
@@ -56,67 +64,21 @@ func (k KeyPurpose) String() string {
return "oidc"
case KeyPurposeAudit:
return "audit"
default:
return "unknown"
}
}
// KeyType allows the kms repo to return a map[KeyType]Key which can be easily
// used without type casting.
type KeyType uint
const (
KeyTypeUnknown KeyType = iota
KeyTypeRootKey
KeyTypeRootKeyVersion
KeyTypeDatabaseKey
KeyTypeDatabaseKeyVersion
KeyTypeOplogKey
KeyTypeOplogKeyVersion
KeyTypeTokenKey
KeyTypeTokenKeyVersion
KeyTypeSessionKey
KeyTypeSessionKeyVersion
KeyTypeOidcKey
KeyTypeOidcKeyVersion
KeyTypeAuditKey
KeyTypeAuditKeyVersion
)
// String returns the key type cast as a string, just so it can be called as
// a function instead of direct casting elsewhere, yw
func (k KeyType) String() string {
switch k {
case KeyTypeRootKey:
case KeyPurposeRootKey:
return "rootKey"
case KeyTypeRootKeyVersion:
return "rootKeyVersion"
case KeyTypeDatabaseKey:
return "databaseKey"
case KeyTypeDatabaseKeyVersion:
return "databaseKeyVersion"
case KeyTypeOplogKey:
return "oplogKey"
case KeyTypeOplogKeyVersion:
return "oplogKeyVersion"
case KeyTypeTokenKey:
return "tokenKey"
case KeyTypeTokenKeyVersion:
return "tokenKeyVersion"
case KeyTypeSessionKey:
return "sessionKey"
case KeyTypeSessionKeyVersion:
return "sessionKeyVersion"
case KeyTypeOidcKey:
return "oidcKey"
case KeyTypeOidcKeyVersion:
return "oidcKeyVersion"
case KeyTypeAuditKey:
return "auditKey"
case KeyTypeAuditKeyVersion:
return "auditKeyVersion"
default:
return "unknown"
}
}
// ValidDekPurposes returns the current list of valid DEK key purposes
func ValidDekPurposes() []KeyPurpose {
return []KeyPurpose{
KeyPurposeDatabase,
KeyPurposeOplog,
KeyPurposeTokens,
KeyPurposeSessions,
KeyPurposeOidc,
KeyPurposeAudit,
}
}
-82
View File
@@ -1,82 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
"google.golang.org/protobuf/proto"
)
const (
DefaultDatabaseKeyTableName = "kms_database_key"
)
type DatabaseKey struct {
*store.DatabaseKey
tableName string `gorm:"-"`
}
// NewDatabaseKey creates a new in memory database key. No options
// are currently supported.
func NewDatabaseKey(rootKeyId string, _ ...Option) (*DatabaseKey, error) {
const op = "kms.NewDatabaseKey"
if rootKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key id")
}
c := &DatabaseKey{
DatabaseKey: &store.DatabaseKey{
RootKeyId: rootKeyId,
},
}
return c, nil
}
// AllocDatabaseKey will allocate a DatabaseKey
func AllocDatabaseKey() DatabaseKey {
return DatabaseKey{
DatabaseKey: &store.DatabaseKey{},
}
}
// Clone creates a clone of the DatabaseKey
func (k *DatabaseKey) Clone() interface{} {
cp := proto.Clone(k.DatabaseKey)
return &DatabaseKey{
DatabaseKey: cp.(*store.DatabaseKey),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// before it's written.
func (k *DatabaseKey) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "kms.(DatabaseKey).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
switch opType {
case db.CreateOp:
if k.RootKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
case db.UpdateOp:
return errors.New(ctx, errors.InvalidParameter, op, "key is immutable")
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *DatabaseKey) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultDatabaseKeyTableName
}
// 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 (k *DatabaseKey) SetTableName(n string) {
k.tableName = n
}
-206
View File
@@ -1,206 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the DatabaseKey attributes are
// immutable and those tests are covered by TestDatabaseKey_ImmutableFields
func TestDatabaseKey_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootKeyId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.DatabaseKey
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-rootKeyId",
args: args{},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
rootKeyId: rk.PrivateId,
},
want: func() *kms.DatabaseKey {
k := kms.AllocDatabaseKey()
k.RootKeyId = rk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocDatabaseKey(); return &i }(), "1=1")
got, err := kms.NewDatabaseKey(tt.args.rootKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.DatabaseKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestDatabaseKey_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocDatabaseKey(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.DatabaseKey
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestDatabaseKey(t, conn, rk.PrivateId),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.DatabaseKey {
k := kms.AllocDatabaseKey()
id, err := db.NewPublicId(kms.DatabaseKeyPrefix)
require.NoError(t, err)
k.PrivateId = id
k.RootKeyId = rk.PrivateId
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocDatabaseKey()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocDatabaseKey()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestDatabaseKey_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k := kms.TestDatabaseKey(t, conn, rk.PrivateId)
cp := k.Clone()
assert.True(proto.Equal(cp.(*kms.DatabaseKey).DatabaseKey, k.DatabaseKey))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
k := kms.TestDatabaseKey(t, conn, rk.PrivateId)
k2 := kms.TestDatabaseKey(t, conn, rk2.PrivateId)
cp := k.Clone()
assert.True(!proto.Equal(cp.(*kms.DatabaseKey).DatabaseKey, k2.DatabaseKey))
})
}
func TestDatabaseKey_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultDatabaseKeyTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocDatabaseKey()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocDatabaseKey()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-121
View File
@@ -1,121 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
const (
DefaultDatabaseKeyVersionTableName = "kms_database_key_version"
)
type DatabaseKeyVersion struct {
*store.DatabaseKeyVersion
tableName string `gorm:"-"`
}
// NewDatabaseKeyVersion creates a new in memory database key version. No options are
// currently supported.
func NewDatabaseKeyVersion(databaseKeyId string, key []byte, rootKeyVersionId string, _ ...Option) (*DatabaseKeyVersion, error) {
const op = "kms.NewDatabaseKeyVersion"
if databaseKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing database key id")
}
if len(key) == 0 {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing key")
}
if rootKeyVersionId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key version id")
}
k := &DatabaseKeyVersion{
DatabaseKeyVersion: &store.DatabaseKeyVersion{
DatabaseKeyId: databaseKeyId,
RootKeyVersionId: rootKeyVersionId,
Key: key,
},
}
return k, nil
}
// AllocDatabaseKeyVersion allocates a DatabaseKeyVersion
func AllocDatabaseKeyVersion() DatabaseKeyVersion {
return DatabaseKeyVersion{
DatabaseKeyVersion: &store.DatabaseKeyVersion{},
}
}
// Clone creates a clone of the DatabaseKeyVersion
func (k *DatabaseKeyVersion) Clone() interface{} {
cp := proto.Clone(k.DatabaseKeyVersion)
return &DatabaseKeyVersion{
DatabaseKeyVersion: cp.(*store.DatabaseKeyVersion),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the database key
// version before it's written.
func (k *DatabaseKeyVersion) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "kms.(DatabaseKeyVersion).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
switch opType {
case db.CreateOp:
if k.CtKey == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
if k.DatabaseKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing database key id")
}
if k.RootKeyVersionId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
case db.UpdateOp:
return errors.New(ctx, errors.InvalidParameter, op, "key is immutable")
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *DatabaseKeyVersion) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultDatabaseKeyVersionTableName
}
// 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 (k *DatabaseKeyVersion) SetTableName(n string) {
k.tableName = n
}
// Encrypt will encrypt the database key version's key
func (k *DatabaseKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(DatabaseKeyVersion).Encrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.DatabaseKeyVersion directly
if err := structwrapping.WrapStruct(ctx, cipher, k.DatabaseKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
}
return nil
}
// Decrypt will decrypt the database key version's key
func (k *DatabaseKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(DatabaseKeyVersion).Decrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.DatabaseKeyVersion directly
if err := structwrapping.UnwrapStruct(ctx, cipher, k.DatabaseKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
-249
View File
@@ -1,249 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the DatabaseKeyVersion attributes are
// immutable and those tests are covered by TestDatabaseKey_ImmutableFields
func TestDatabaseKeyVersion_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rkv, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestDatabaseKey(t, conn, rk.PrivateId)
type args struct {
databaseKeyId string
key []byte
rootKeyVersionId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.DatabaseKeyVersion
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-database-id",
args: args{
key: []byte("database key"),
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
databaseKeyId: dk.PrivateId,
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-root-key-version-id",
args: args{
databaseKeyId: dk.PrivateId,
key: []byte("database key"),
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
databaseKeyId: dk.PrivateId,
key: []byte("database key"),
rootKeyVersionId: rkv.PrivateId,
},
want: func() *kms.DatabaseKeyVersion {
k := kms.AllocDatabaseKeyVersion()
k.RootKeyVersionId = rkv.PrivateId
k.Key = []byte("database key")
k.DatabaseKeyId = dk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocDatabaseKeyVersion(); return &i }(), "1=1")
got, err := kms.NewDatabaseKeyVersion(tt.args.databaseKeyId, tt.args.key, tt.args.rootKeyVersionId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.DatabaseKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = got.Encrypt(context.Background(), rkvWrapper)
require.NoError(err)
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestDatabaseKeyVersion_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestDatabaseKey(t, conn, rk.PrivateId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocDatabaseKeyVersion(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.DatabaseKeyVersion
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestDatabaseKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("database key")),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.DatabaseKeyVersion {
k := kms.AllocDatabaseKeyVersion()
id, err := db.NewPublicId(kms.DatabaseKeyVersionPrefix)
require.NoError(t, err)
k.PrivateId = id
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocDatabaseKeyVersion()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocDatabaseKeyVersion()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestDatabaseKeyVersion_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
k := kms.TestDatabaseKey(t, conn, rk.PrivateId)
kv := kms.TestDatabaseKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test db key"))
cp := kv.Clone()
assert.True(proto.Equal(cp.(*kms.DatabaseKeyVersion).DatabaseKeyVersion, kv.DatabaseKeyVersion))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
_, rkvWrapper2 := kms.TestRootKeyVersion(t, conn, wrapper, rk2.PrivateId)
k := kms.TestDatabaseKey(t, conn, rk.PrivateId)
k2 := kms.TestDatabaseKey(t, conn, rk2.PrivateId)
kv := kms.TestDatabaseKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test db key"))
kv2 := kms.TestDatabaseKeyVersion(t, conn, rkvWrapper2, k2.PrivateId, []byte("test db key 2"))
cp := kv.Clone()
assert.True(!proto.Equal(cp.(*kms.DatabaseKeyVersion).DatabaseKeyVersion, kv2.DatabaseKeyVersion))
})
}
func TestDatabaseKeyVersion_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultDatabaseKeyVersionTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocDatabaseKeyVersion()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocDatabaseKeyVersion()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
+28
View File
@@ -0,0 +1,28 @@
package kms
import (
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// ExternalWrappers holds wrappers defined outside of Boundary, e.g. in its
// configuration file.
type ExternalWrappers struct {
root wrapping.Wrapper
workerAuth wrapping.Wrapper
recovery wrapping.Wrapper
}
// Root returns the wrapper for root keys
func (e *ExternalWrappers) Root() wrapping.Wrapper {
return e.root
}
// WorkerAuth returns the wrapper for worker authentication
func (e *ExternalWrappers) WorkerAuth() wrapping.Wrapper {
return e.workerAuth
}
// Recovery returns the wrapper for recovery operations
func (e *ExternalWrappers) Recovery() wrapping.Wrapper {
return e.recovery
}
+46
View File
@@ -0,0 +1,46 @@
package kms
import (
"context"
"fmt"
"testing"
"github.com/hashicorp/boundary/internal/db"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestExternalWrappers(t *testing.T) {
t.Parallel()
assert, require := assert.New(t), require.New(t)
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rootWrapper := db.TestWrapper(t)
recoveryWrapper := db.TestWrapper(t)
workerAuthWrapper := db.TestWrapper(t)
k := TestKms(t, conn, rootWrapper)
err := k.AddExternalWrappers(testCtx, WithRecoveryWrapper(recoveryWrapper), WithWorkerAuthWrapper(workerAuthWrapper))
require.NoError(err)
assert.Equal(rootWrapper, k.GetExternalWrappers(testCtx).Root())
assert.Equal(recoveryWrapper, k.GetExternalWrappers(testCtx).Recovery())
assert.Equal(workerAuthWrapper, k.GetExternalWrappers(testCtx).WorkerAuth())
err = k.AddExternalWrappers(testCtx, WithRootWrapper(&invalidWrapper{}))
assert.Error(err)
err = k.AddExternalWrappers(testCtx, WithRecoveryWrapper(&invalidWrapper{}))
assert.Error(err)
err = k.AddExternalWrappers(testCtx, WithWorkerAuthWrapper(&invalidWrapper{}))
assert.Error(err)
}
type invalidWrapper struct {
wrapping.Wrapper
}
func (w *invalidWrapper) KeyId(context.Context) (string, error) {
return "", fmt.Errorf("invalid-error")
}
-151
View File
@@ -1,151 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
)
const (
RootKeyPrefix = "krk"
RootKeyVersionPrefix = "krkv"
DatabaseKeyPrefix = "kdk"
DatabaseKeyVersionPrefix = "kdkv"
OplogKeyPrefix = "kopk"
OplogKeyVersionPrefix = "kopkv"
TokenKeyPrefix = "ktk"
TokenKeyVersionPrefix = "ktv"
SessionKeyPrefix = "ksk"
SessionKeyVersionPrefix = "kskv"
OidcKeyPrefix = "koidck"
OidcKeyVersionPrefix = "koidckv"
AuditKeyPrefix = "kak"
AuditKeyVersionPrefix = "kakv"
)
func newRootKeyId() (string, error) {
const op = "kms.newRootKeyId"
id, err := db.NewPublicId(RootKeyPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newRootKeyVersionId() (string, error) {
const op = "kms.newRootKeyVersionId"
id, err := db.NewPublicId(RootKeyVersionPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newDatabaseKeyId() (string, error) {
const op = "kms.newDatabaseKeyId"
id, err := db.NewPublicId(DatabaseKeyPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newDatabaseKeyVersionId() (string, error) {
const op = "kms.newDatabaseKeyVersionId"
id, err := db.NewPublicId(DatabaseKeyVersionPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newOplogKeyId() (string, error) {
const op = "kms.newOplogKeyId"
id, err := db.NewPublicId(OplogKeyPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newOplogKeyVersionId() (string, error) {
const op = "kms.newOplogKeyVersionId"
id, err := db.NewPublicId(OplogKeyVersionPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newTokenKeyId() (string, error) {
const op = "kms.newTokenKeyId"
id, err := db.NewPublicId(TokenKeyPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newTokenKeyVersionId() (string, error) {
const op = "kms.newTokenKeyVersionId"
id, err := db.NewPublicId(TokenKeyVersionPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newSessionKeyId() (string, error) {
const op = "kms.newSessionKeyId"
id, err := db.NewPublicId(SessionKeyPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newSessionKeyVersionId() (string, error) {
const op = "kms.newSessionKeyVersionId"
id, err := db.NewPublicId(SessionKeyVersionPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newOidcKeyId() (string, error) {
const op = "kms.newOidcKeyId"
id, err := db.NewPublicId(OidcKeyPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newOidcKeyVersionId() (string, error) {
const op = "kms.newOidcKeyVersionId"
id, err := db.NewPublicId(OidcKeyVersionPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newAuditKeyId(ctx context.Context) (string, error) {
const op = "kms.newAuditKeyId"
id, err := db.NewPublicId(AuditKeyPrefix)
if err != nil {
return "", errors.Wrap(ctx, err, op)
}
return id, nil
}
func newAuditKeyVersionId(ctx context.Context) (string, error) {
const op = "kms.newAuditKeyVersionId"
id, err := db.NewPublicId(AuditKeyVersionPrefix)
if err != nil {
return "", errors.Wrap(ctx, err, op)
}
return id, nil
}
-84
View File
@@ -1,84 +0,0 @@
package kms
import (
"context"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_Ids(t *testing.T) {
t.Parallel()
t.Run("krk", func(t *testing.T) {
id, err := newRootKeyId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, RootKeyPrefix+"_"))
})
t.Run("krkv", func(t *testing.T) {
id, err := newRootKeyVersionId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, RootKeyVersionPrefix+"_"))
})
t.Run("kdk", func(t *testing.T) {
id, err := newDatabaseKeyId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, DatabaseKeyPrefix+"_"))
})
t.Run("kdkv", func(t *testing.T) {
id, err := newDatabaseKeyVersionId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, DatabaseKeyVersionPrefix+"_"))
})
t.Run("kopk", func(t *testing.T) {
id, err := newOplogKeyId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, OplogKeyPrefix+"_"))
})
t.Run("kopkv", func(t *testing.T) {
id, err := newOplogKeyVersionId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, OplogKeyVersionPrefix+"_"))
})
t.Run("ktk", func(t *testing.T) {
id, err := newTokenKeyId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, TokenKeyPrefix+"_"))
})
t.Run("ktkv", func(t *testing.T) {
id, err := newTokenKeyVersionId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, TokenKeyVersionPrefix+"_"))
})
t.Run("ksk", func(t *testing.T) {
id, err := newSessionKeyId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, SessionKeyPrefix+"_"))
})
t.Run("kskv", func(t *testing.T) {
id, err := newSessionKeyVersionId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, SessionKeyVersionPrefix+"_"))
})
t.Run("koidck", func(t *testing.T) {
id, err := newOidcKeyId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, OidcKeyPrefix+"_"))
})
t.Run("koidckv", func(t *testing.T) {
id, err := newOidcKeyVersionId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, OidcKeyVersionPrefix+"_"))
})
t.Run("kak", func(t *testing.T) {
id, err := newAuditKeyId(context.Background())
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, AuditKeyPrefix+"_"))
})
t.Run("kakv", func(t *testing.T) {
id, err := newAuditKeyVersionId(context.Background())
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, AuditKeyVersionPrefix+"_"))
})
}
File diff suppressed because it is too large. Load diff
+179 -378
View File
@@ -6,285 +6,150 @@ import (
"io"
"reflect"
"sync"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/go-dbw"
wrappingKms "github.com/hashicorp/go-kms-wrapping/extras/kms/v2"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/hashicorp/go-kms-wrapping/v2/extras/multi"
)
// ExternalWrappers holds wrappers defined outside of Boundary, e.g. in its
// configuration file.
type ExternalWrappers struct {
m sync.RWMutex
root wrapping.Wrapper
workerAuth wrapping.Wrapper
recovery wrapping.Wrapper
}
// Root returns the wrapper for root keys
func (e *ExternalWrappers) Root() wrapping.Wrapper {
e.m.RLock()
defer e.m.RUnlock()
return e.root
}
// WorkerAuth returns the wrapper for worker authentication
func (e *ExternalWrappers) WorkerAuth() wrapping.Wrapper {
e.m.RLock()
defer e.m.RUnlock()
return e.workerAuth
}
// Recovery returns the wrapper for recovery operations
func (e *ExternalWrappers) Recovery() wrapping.Wrapper {
e.m.RLock()
defer e.m.RUnlock()
return e.recovery
}
// Kms is a way to access wrappers for a given scope and purpose. Since keys can
// never change, only be added or (eventually) removed, it opportunistically
// caches, going to the database as needed.
type Kms struct {
// scopePurposeCache holds a per-scope-purpose multiwrapper containing the
// current encrypting key and all previous key versions, for decryption
scopePurposeCache sync.Map
externalScopeCache map[string]*ExternalWrappers
externalScopeCacheMutex sync.RWMutex
underlying *wrappingKms.Kms
reader db.Reader
derivedPurposeCache sync.Map
repo *Repository
}
// NewKms takes in a repo and returns a Kms.
func NewKms(repo *Repository, opt ...Option) (*Kms, error) {
const op = "kms.NewKms"
if repo == nil {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing underlying repo")
// New creates a Kms using the provided reader and writer. No options are
// currently supported.
func New(ctx context.Context, reader *db.Db, writer *db.Db, _ ...Option) (*Kms, error) {
const op = "kms.(Kms).New"
if isNil(reader) {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing reader")
}
if isNil(writer) {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing writer")
}
purposes := stdNewKmsPurposes()
r := dbw.New(reader.UnderlyingDB())
w := dbw.New(writer.UnderlyingDB())
k, err := wrappingKms.New(r, w, purposes, wrappingKms.WithCache(true))
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error creating new in-memory kms"))
}
return &Kms{
externalScopeCache: make(map[string]*ExternalWrappers),
repo: repo,
underlying: k,
reader: reader,
}, nil
}
// GetScopePurposeCache is used in test functions for validation. Since the
// tests need to be in a different package to avoid circular dependencies, this
// is exported.
func (k *Kms) GetScopePurposeCache() *sync.Map {
return &k.scopePurposeCache
// NewUsingReaderWriter creates a Kms using the provided reader and writer. No
// options are currently supported.
func NewUsingReaderWriter(ctx context.Context, reader db.Reader, writer db.Writer, _ ...Option) (*Kms, error) {
const op = "kms.(Kms).New"
if isNil(reader) {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing reader")
}
if isNil(writer) {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing writer")
}
purposes := stdNewKmsPurposes()
r, err := convertToRW(ctx, reader)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
w, err := convertToRW(ctx, writer)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
k, err := wrappingKms.New(r, w, purposes, wrappingKms.WithCache(true))
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error creating new in-memory kms"))
}
return &Kms{
underlying: k,
reader: reader,
}, nil
}
func (k *Kms) GetDerivedPurposeCache() *sync.Map {
return &k.derivedPurposeCache
func convertToRW(ctx context.Context, r interface{}) (*dbw.RW, error) {
const op = "kms.convertToDb"
d, ok := r.(*db.Db)
if !ok {
return nil, errors.New(ctx, errors.InvalidParameter, op, "unable to convert to db.DB")
}
return dbw.New(d.UnderlyingDB()), nil
}
// AddExternalWrappers allows setting the external keys.
//
// TODO: If we support more than one, e.g. for encrypting against many in case
// of a key loss, there will need to be some refactoring here to have the values
// being stored in the struct be a multiwrapper, but that's for a later project.
func (k *Kms) AddExternalWrappers(ctx context.Context, opt ...Option) error {
const op = "kms.AddExternalWrappers"
k.externalScopeCacheMutex.Lock()
defer k.externalScopeCacheMutex.Unlock()
ext := k.externalScopeCache[scope.Global.String()]
if ext == nil {
ext = &ExternalWrappers{}
}
ext.m.Lock()
defer ext.m.Unlock()
const op = "kms.(Kms).AddExternalWrappers"
opts := getOpts(opt...)
if opts.withRootWrapper != nil {
ext.root = opts.withRootWrapper
keyId, err := ext.root.KeyId(ctx)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("error reading root wrapper key ID"))
}
if keyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "root wrapper has no key ID")
if err := k.underlying.AddExternalWrapper(ctx, wrappingKms.KeyPurpose(KeyPurposeRootKey.String()), opts.withRootWrapper); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add root wrapper"))
}
}
if opts.withWorkerAuthWrapper != nil {
ext.workerAuth = opts.withWorkerAuthWrapper
keyId, err := ext.workerAuth.KeyId(ctx)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("error reading worker auth wrapper key ID"))
}
if keyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "worker auth wrapper has no key ID")
if err := k.underlying.AddExternalWrapper(ctx, wrappingKms.KeyPurpose(KeyPurposeWorkerAuth.String()), opts.withWorkerAuthWrapper); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add worker auth wrapper"))
}
}
if opts.withRecoveryWrapper != nil {
ext.recovery = opts.withRecoveryWrapper
keyId, err := ext.root.KeyId(ctx)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("error reading recovery wrapper key ID"))
}
if keyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "recovery wrapper has no key ID")
if err := k.underlying.AddExternalWrapper(ctx, wrappingKms.KeyPurpose(KeyPurposeRecovery.String()), opts.withRecoveryWrapper); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add recovery wrapper"))
}
}
k.externalScopeCache[scope.Global.String()] = ext
return nil
}
func (k *Kms) GetExternalWrappers() *ExternalWrappers {
k.externalScopeCacheMutex.RLock()
defer k.externalScopeCacheMutex.RUnlock()
ext := k.externalScopeCache[scope.Global.String()]
ext.m.RLock()
defer ext.m.RUnlock()
ret := &ExternalWrappers{
root: ext.root,
workerAuth: ext.workerAuth,
recovery: ext.recovery,
}
return ret
}
// GetWrapper returns a wrapper for the given scope and purpose. When a keyId is
// passed, it will ensure that the returning wrapper has that key ID in the
// multiwrapper. This is not necesary for encryption but should be supplied for
// multiwrapper. This is not necessary for encryption but should be supplied for
// decryption.
func (k *Kms) GetWrapper(ctx context.Context, scopeId string, purpose KeyPurpose, opt ...Option) (wrapping.Wrapper, error) {
const op = "kms.GetWrapper"
const op = "kms.(Kms).GetWrapper"
if scopeId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
}
switch purpose {
case KeyPurposeOplog, KeyPurposeDatabase, KeyPurposeTokens, KeyPurposeSessions, KeyPurposeOidc, KeyPurposeAudit:
case KeyPurposeUnknown:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key purpose")
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported purpose %q", purpose))
if purpose == KeyPurposeUnknown {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing purpose")
}
opts := getOpts(opt...)
// Fast-path: we have a valid key at the scope/purpose. Verify the key with
// that ID is in the multiwrapper; if not, fall through to reload from the
// DB.
val, ok := k.scopePurposeCache.Load(scopeId + purpose.String())
if ok {
wrapper := val.(*multi.PooledWrapper)
if opts.withKeyId == "" {
return wrapper, nil
}
if keyIdWrapper := wrapper.WrapperForKeyId(opts.withKeyId); keyIdWrapper != nil {
return keyIdWrapper, nil
}
// Fall through to refresh our multiwrapper for this scope/purpose from the DB
}
// We don't have it cached, so we'll need to read from the database. Get the
// root for the scope as we'll need it to decrypt the value coming from the
// DB. We don't cache the roots as we expect that after a few calls the
// scope-purpose cache will catch everything in steady-state.
rootWrapper, rootKeyId, err := k.loadRoot(ctx, scopeId, opt...)
w, err := k.underlying.GetWrapper(ctx, scopeId, wrappingKms.KeyPurpose(purpose.String()), wrappingKms.WithKeyId(opts.withKeyId))
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error loading root key for scope %s", scopeId)))
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get wrapper"))
}
if rootWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("got nil root wrapper for scope %s", scopeId))
}
wrapper, err := k.loadDek(ctx, scopeId, purpose, rootWrapper, rootKeyId, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error loading %s for scope %s", purpose.String(), scopeId)))
}
k.scopePurposeCache.Store(scopeId+purpose.String(), wrapper)
if opts.withKeyId != "" {
if keyIdWrapper := wrapper.WrapperForKeyId(opts.withKeyId); keyIdWrapper != nil {
return keyIdWrapper, nil
}
return nil, errors.New(ctx, errors.KeyNotFound, op, "unable to find specified key ID")
}
return wrapper, nil
return w, err
}
func (k *Kms) loadRoot(ctx context.Context, scopeId string, opt ...Option) (*multi.PooledWrapper, string, error) {
const op = "kms.loadRoot"
opts := getOpts(opt...)
repo := opts.withRepository
if repo == nil {
repo = k.repo
// GetExternalWrappers returns the Kms' ExternalWrappers
func (k *Kms) GetExternalWrappers(ctx context.Context) *ExternalWrappers {
const op = "kms.(Kms).GetExternalWrappers"
ret := &ExternalWrappers{}
if root, err := k.underlying.GetExternalRootWrapper(); err == nil {
ret.root = root
}
rootKeys, err := repo.ListRootKeys(ctx)
if err != nil {
return nil, "", errors.Wrap(ctx, err, op)
if workerAuth, err := k.underlying.GetExternalWrapper(ctx, wrappingKms.KeyPurpose(KeyPurposeWorkerAuth.String())); err == nil {
ret.workerAuth = workerAuth
}
var rootKeyId string
for _, k := range rootKeys {
if k.GetScopeId() == scopeId {
rootKeyId = k.GetPrivateId()
break
}
}
if rootKeyId == "" {
return nil, "", errors.New(ctx, errors.KeyNotFound, op, fmt.Sprintf("missing root key for scope %s", scopeId))
if recovery, err := k.underlying.GetExternalWrapper(ctx, wrappingKms.KeyPurpose(KeyPurposeRecovery.String())); err == nil {
ret.recovery = recovery
}
return ret
}
// Now: find the external KMS that can be used to decrypt the root values
// from the DB.
k.externalScopeCacheMutex.Lock()
externalWrappers := k.externalScopeCache[scope.Global.String()]
k.externalScopeCacheMutex.Unlock()
if externalWrappers == nil {
return nil, "", errors.New(ctx, errors.KeyNotFound, op, "could not find kms information at either the needed scope or global fallback")
}
externalWrappers.m.RLock()
defer externalWrappers.m.RUnlock()
if externalWrappers.root == nil {
return nil, "", errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key wrapper for scope %s is nil", scopeId))
}
rootKeyVersions, err := repo.ListRootKeyVersions(ctx, externalWrappers.root, rootKeyId, WithOrderByVersion(db.DescendingOrderBy))
if err != nil {
return nil, "", errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error looking up root key versions for scope %s", scopeId)))
}
if len(rootKeyVersions) == 0 {
return nil, "", errors.New(ctx, errors.KeyNotFound, op, fmt.Sprintf("no root key versions found for scope %s", scopeId))
}
var pooled *multi.PooledWrapper
for i, key := range rootKeyVersions {
var err error
wrapper := aead.NewWrapper()
if _, err = wrapper.SetConfig(ctx, wrapping.WithKeyId(key.GetPrivateId())); err != nil {
return nil, "", errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error setting config on aead root wrapper in scope %s", scopeId)))
}
if err = wrapper.SetAesGcmKeyBytes(key.GetKey()); err != nil {
return nil, "", errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error setting key bytes on aead root wrapper in scope %s", scopeId)))
}
if i == 0 {
pooled, err = multi.NewPooledWrapper(ctx, wrapper)
if err != nil {
return nil, "", errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error getting root pooled wrapper for key version 0 in scope %s", scopeId)))
}
} else {
_, err = pooled.AddWrapper(ctx, wrapper)
if err != nil {
return nil, "", errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error adding pooled wrapper for key version %d in scope %s", i, scopeId)))
}
}
}
return pooled, rootKeyId, nil
// GetDerivedPurposeCache returns the raw derived purpose cache
func (k *Kms) GetDerivedPurposeCache() *sync.Map {
return &k.derivedPurposeCache
}
// ReconcileKeys will reconcile the keys in the kms against known possible
@@ -295,71 +160,108 @@ func (k *Kms) ReconcileKeys(ctx context.Context, randomReader io.Reader, opt ...
if isNil(randomReader) {
return errors.New(ctx, errors.InvalidParameter, op, "missing rand reader")
}
// it's possible that the global audit key was created after this instance's
// database was initialized... so check if the audit wrapper is available
// for the global scope and if not, then add one to the global scope
if _, err := k.GetWrapper(ctx, scope.Global.String(), KeyPurposeAudit); err != nil {
switch {
case errors.Match(errors.T(errors.KeyNotFound), err):
globalRootWrapper, _, err := k.loadRoot(ctx, scope.Global.String())
if err != nil {
return errors.Wrap(ctx, err, op)
}
key, err := generateKey(ctx, randomReader)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for database key in scope %s", scope.Global.String())))
}
if _, _, err := k.repo.CreateAuditKey(ctx, globalRootWrapper, key); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error creating audit key in scope %s", scope.Global.String())))
}
default:
errors.Wrap(ctx, err, op)
if err := k.underlying.ReconcileKeys(
ctx,
[]string{scope.Global.String()},
[]wrappingKms.KeyPurpose{wrappingKms.KeyPurpose(KeyPurposeAudit.String())},
wrappingKms.WithRandomReader(randomReader),
); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error creating audit key in scope %s", scope.Global.String())))
}
}
opts := getOpts(opt...)
for _, id := range opts.withScopeIds {
var scopeRootWrapper *multi.PooledWrapper
for _, purpose := range []KeyPurpose{
if len(opts.withScopeIds) > 0 {
if err := k.underlying.ReconcileKeys(
ctx,
opts.withScopeIds,
// just add additional purposes as needed going forward to reconcile
// new keys as they are added.
//
// NOTE: don't add an audit key here since it can only be created in
// the global scope.
KeyPurposeOidc,
} {
if _, err := k.GetWrapper(ctx, id, purpose); err != nil {
switch {
case errors.Match(errors.T(errors.KeyNotFound), err):
if scopeRootWrapper == nil {
if scopeRootWrapper, _, err = k.loadRoot(ctx, id); err != nil {
return errors.Wrap(ctx, err, op)
}
}
key, err := generateKey(ctx, randomReader)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for %q key in scope %q", purpose.String(), id)))
}
switch purpose {
case KeyPurposeOidc:
if _, _, err := k.repo.CreateOidcKey(ctx, scopeRootWrapper, key); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error creating %q key in scope %s", purpose.String(), id)))
}
default:
return errors.New(ctx, errors.Internal, op, fmt.Sprintf("error creating %q key in scope %s since it's not a supported KeyPurpose", purpose.String(), id))
}
default:
errors.Wrap(ctx, err, op)
}
}
[]wrappingKms.KeyPurpose{wrappingKms.KeyPurpose(KeyPurposeOidc.String())},
wrappingKms.WithRandomReader(randomReader),
); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error creating keys in scopes %s", opts.withScopeIds)))
}
}
return nil
}
// CreateKeys creates the root key and DEKs returns a map of the new keys.
// Supports the WithRandomReader(...) and WithReaderWriter(...) options.
// When WithReaderWriter(...) is used the caller is responsible for managing the
// transaction which allows this capability to be shared with the iam repo when
// it's creating Scopes.
func (k *Kms) CreateKeys(ctx context.Context, scopeId string, opt ...Option) error {
const op = "kms.(Kms).CreateKeysTx"
if scopeId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
}
opts := getOpts(opt...)
kmsOpts := []wrappingKms.Option{wrappingKms.WithRandomReader(opts.withRandomReader)}
switch {
case !isNil(opts.withReader) && isNil(opts.withWriter):
return errors.New(ctx, errors.InvalidParameter, op, "missing writer")
case isNil(opts.withReader) && !isNil(opts.withWriter):
return errors.New(ctx, errors.InvalidParameter, op, "missing reader")
case !isNil(opts.withReader) && !isNil(opts.withWriter):
r, err := convertToRW(ctx, opts.withReader)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to convert reader"))
}
w, err := convertToRW(ctx, opts.withWriter)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to convert writer"))
}
kmsOpts = append(kmsOpts, wrappingKms.WithReaderWriter(r, w))
}
purposes := make([]wrappingKms.KeyPurpose, 0, len(ValidDekPurposes()))
for _, p := range ValidDekPurposes() {
purposes = append(purposes, wrappingKms.KeyPurpose(p.String()))
}
if err := k.underlying.CreateKeys(ctx, scopeId, purposes, kmsOpts...); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
}
// ClearCache will clear the kms's cache which is useful after a scope has been
// deleted.
func (k *Kms) ClearCache(ctx context.Context) error {
const op = "kms.(Kms).ClearCache"
if err := k.underlying.ClearCache(ctx); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
}
// VerifyGlobalRoot will verify that the global root wrapper is reasonable.
func (k *Kms) VerifyGlobalRoot(ctx context.Context) error {
const op = "kms.(Kms).VerifyGlobalRoot"
var keys []*rootKey
if err := k.reader.SearchWhere(ctx, &keys, "1=1", nil, db.WithLimit(1), db.WithOrder("create_time asc")); err != nil {
return errors.Wrap(ctx, err, op)
}
for _, rk := range keys {
if rk.ScopeId == scope.Global.String() {
return nil
}
}
return errors.New(ctx, errors.MigrationIntegrity, op, "can't find global scoped root key")
}
func stdNewKmsPurposes() []wrappingKms.KeyPurpose {
purposes := make([]wrappingKms.KeyPurpose, 0, len(ValidDekPurposes()))
for _, p := range ValidDekPurposes() {
purposes = append(purposes, wrappingKms.KeyPurpose(p.String()))
}
purposes = append(purposes, wrappingKms.KeyPurpose(KeyPurposeWorkerAuth.String()), wrappingKms.KeyPurpose(KeyPurposeRecovery.String()))
return purposes
}
func isNil(i interface{}) bool {
if i == nil {
return true
@@ -371,111 +273,10 @@ func isNil(i interface{}) bool {
return false
}
// Dek is an interface wrapping dek types to allow a lot less switching in loadDek
type Dek interface {
GetRootKeyId() string
GetPrivateId() string
type rootKey struct {
PrivateId string `gorm:"primary_key"`
ScopeId string `gorm:"default:null"`
CreateTime time.Time `gorm:"default:current_timestamp"`
}
// DekVersion is an interface wrapping versioned dek types to allow a lot less switching in loadDek
type DekVersion interface {
GetPrivateId() string
GetKey() []byte
}
func (k *Kms) loadDek(ctx context.Context, scopeId string, purpose KeyPurpose, rootWrapper wrapping.Wrapper, rootKeyId string, opt ...Option) (*multi.PooledWrapper, error) {
const op = "kms.loadDek"
if rootWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("nil root wrapper for scope %s", scopeId))
}
if rootKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("missing root key ID for scope %s", scopeId))
}
opts := getOpts(opt...)
repo := opts.withRepository
if repo == nil {
repo = k.repo
}
var keys []Dek
var err error
switch purpose {
case KeyPurposeDatabase:
keys, err = repo.ListDatabaseKeys(ctx)
case KeyPurposeOplog:
keys, err = repo.ListOplogKeys(ctx)
case KeyPurposeTokens:
keys, err = repo.ListTokenKeys(ctx)
case KeyPurposeSessions:
keys, err = repo.ListSessionKeys(ctx)
case KeyPurposeOidc:
keys, err = repo.ListOidcKeys(ctx)
case KeyPurposeAudit:
keys, err = repo.ListAuditKeys(ctx)
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, "unknown or invalid DEK purpose specified")
}
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error listing root keys"))
}
var keyId string
for _, k := range keys {
if k.GetRootKeyId() == rootKeyId {
keyId = k.GetPrivateId()
break
}
}
if keyId == "" {
return nil, errors.New(ctx, errors.KeyNotFound, op, fmt.Sprintf("error finding %s key for scope %s", purpose.String(), scopeId))
}
var keyVersions []DekVersion
switch purpose {
case KeyPurposeDatabase:
keyVersions, err = repo.ListDatabaseKeyVersions(ctx, rootWrapper, keyId, WithOrderByVersion(db.DescendingOrderBy))
case KeyPurposeOplog:
keyVersions, err = repo.ListOplogKeyVersions(ctx, rootWrapper, keyId, WithOrderByVersion(db.DescendingOrderBy))
case KeyPurposeTokens:
keyVersions, err = repo.ListTokenKeyVersions(ctx, rootWrapper, keyId, WithOrderByVersion(db.DescendingOrderBy))
case KeyPurposeSessions:
keyVersions, err = repo.ListSessionKeyVersions(ctx, rootWrapper, keyId, WithOrderByVersion(db.DescendingOrderBy))
case KeyPurposeOidc:
keyVersions, err = repo.ListOidcKeyVersions(ctx, rootWrapper, keyId, WithOrderByVersion(db.DescendingOrderBy))
case KeyPurposeAudit:
keyVersions, err = repo.ListAuditKeyVersions(ctx, rootWrapper, keyId, WithOrderByVersion(db.DescendingOrderBy))
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, "unknown or invalid DEK purpose specified")
}
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error looking up %s key versions for scope %s", purpose.String(), scopeId)))
}
if len(keyVersions) == 0 {
return nil, errors.New(ctx, errors.KeyNotFound, op, fmt.Sprintf("no %s key versions found for scope %s", purpose.String(), scopeId))
}
var pooled *multi.PooledWrapper
for i, keyVersion := range keyVersions {
var err error
wrapper := aead.NewWrapper()
if _, err = wrapper.SetConfig(ctx, wrapping.WithKeyId(keyVersion.GetPrivateId())); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error setting config on aead %s wrapper in scope %s", purpose.String(), scopeId)))
}
if err = wrapper.SetAesGcmKeyBytes(keyVersion.GetKey()); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error setting key bytes on aead %s wrapper in scope %s", purpose.String(), scopeId)))
}
if i == 0 {
pooled, err = multi.NewPooledWrapper(ctx, wrapper)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error getting %s pooled wrapper for key version 0 in scope %s", purpose.String(), scopeId)))
}
} else {
_, err = pooled.AddWrapper(ctx, wrapper)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error getting %s pooled wrapper for key version %d in scope %s", purpose.String(), i, scopeId)))
}
}
}
return pooled, nil
}
func (*rootKey) TableName() string { return "kms_root_key" }
+180 -65
View File
@@ -26,55 +26,15 @@ func TestKms(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
extWrapper := db.TestWrapper(t)
badExtWrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
kmsCache := kms.TestKms(t, conn, extWrapper)
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, extWrapper))
// Verify that the cache is empty, so we can show that by the end of the
// test sequence we did actually look up keys and store them in the cache
t.Run("verify cache empty", func(t *testing.T) {
var count int
kmsCache.GetScopePurposeCache().Range(func(key interface{}, value interface{}) bool {
count++
return true
})
assert.Equal(t, 0, count)
})
// Verify that the root keys are all in the database and can be decrypted
// with the correct wrapper from the KMS object
t.Run("verify root keys", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
rootKeys, err := repo.ListRootKeys(ctx)
require.NoError(err)
wrappers := kmsCache.GetExternalWrappers()
for _, key := range rootKeys {
kvs, err := repo.ListRootKeyVersions(ctx, wrappers.Root(), key.GetPrivateId())
require.NoError(err)
assert.Len(kvs, 1)
assert.Len(kvs[0].GetKey(), 32)
}
})
// Verify that the wrong wrapper causes decryption to fail
t.Run("bad external keys", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
rootKeys, err := repo.ListRootKeys(ctx)
require.NoError(err)
for _, key := range rootKeys {
_, err := repo.ListRootKeyVersions(ctx, badExtWrapper, key.GetPrivateId())
require.Error(err)
assert.True(strings.Contains(err.Error(), "message authentication failed"), err.Error())
}
})
// This next sequence is run twice to ensure that calling for the keys twice
// returns the same value each time and doesn't simply populate more keys
// into the KMS object
keyBytes := map[string]bool{}
keyIds := map[string]bool{}
scopePurposeMap := map[string]interface{}{}
for i := 1; i < 3; i++ {
// This iterates through wrappers for all three scopes and four purposes,
// ensuring that the key bytes and IDs are different for each of them,
@@ -116,24 +76,6 @@ func TestKms(t *testing.T) {
}
}
})
// Verify that the cache has been populated with unique values. The
// second time we validate that the items we find when going through the
// cache a second time are the same as the first. If they were recreated
// the pointers would be different.
t.Run(fmt.Sprintf("verify cache populated x %d", i), func(t *testing.T) {
var count int
kmsCache.GetScopePurposeCache().Range(func(key interface{}, value interface{}) bool {
count++
if i == 1 {
scopePurposeMap[key.(string)] = value
} else {
assert.Same(t, scopePurposeMap[key.(string)], value)
}
return true
})
// four purposes x 3 scopes
assert.Equal(t, 12, count)
})
}
}
@@ -141,7 +83,6 @@ func TestKms_ReconcileKeys(t *testing.T) {
t.Parallel()
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
@@ -183,9 +124,10 @@ func TestKms_ReconcileKeys(t *testing.T) {
kms: kms.TestKms(t, conn, wrapper),
reader: rand.Reader,
setup: func(k *kms.Kms) {
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocAuditKey(); return &i }(), "1=1")
kms.TestKmsDeleteKeyPurpose(t, conn, kms.KeyPurposeAudit)
// make sure the kms is in the proper state for the unit test
// before proceeding.
k.ClearCache(testCtx)
_, err := k.GetWrapper(testCtx, scope.Global.String(), kms.KeyPurposeAudit)
require.Error(t, err)
},
@@ -200,14 +142,15 @@ func TestKms_ReconcileKeys(t *testing.T) {
setup: func(k *kms.Kms) {
// create initial keys for the test scope ids...
for _, id := range []string{org.PublicId, org2.PublicId} {
_, err := kms.CreateKeysTx(context.Background(), rw, rw, wrapper, rand.Reader, id)
err := k.CreateKeys(testCtx, id)
require.NoError(t, err)
}
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOidcKey(); return &i }(), "1=1")
kms.TestKmsDeleteKeyPurpose(t, conn, kms.KeyPurposeOidc)
// make sure the kms is in the proper state for the unit test
// before proceeding.
for _, id := range []string{org.PublicId, org2.PublicId} {
_, err := k.GetWrapper(testCtx, id, kms.KeyPurposeAudit)
_, err := k.GetWrapper(testCtx, id, kms.KeyPurposeOidc)
require.Error(t, err)
}
},
@@ -218,10 +161,10 @@ func TestKms_ReconcileKeys(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
// start with no keys...
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
kms.TestKmsDeleteAllKeys(t, conn)
// create initial keys for the global scope...
_, err := kms.CreateKeysTx(context.Background(), rw, rw, wrapper, rand.Reader, scope.Global.String())
err := tt.kms.CreateKeys(context.Background(), scope.Global.String())
require.NoError(err)
if tt.setup != nil {
@@ -252,3 +195,175 @@ func TestKms_ReconcileKeys(t *testing.T) {
})
}
}
func TestKms_GetDerivedPurposeCache(t *testing.T) {
t.Parallel()
assert := assert.New(t)
conn, _ := db.TestSetup(t, "postgres")
rootWrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, rootWrapper)
derivedWrapper := db.TestWrapper(t)
kmsCache.GetDerivedPurposeCache().Store(1, derivedWrapper)
v, ok := kmsCache.GetDerivedPurposeCache().Load(1)
assert.True(ok)
assert.Equal(derivedWrapper, v)
}
func TestKms_VerifyGlobalRoot(t *testing.T) {
t.Parallel()
testCtx := context.Background()
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
rootWrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, rootWrapper)
assert.Error(kmsCache.VerifyGlobalRoot(testCtx))
require.NoError(kmsCache.CreateKeys(testCtx, "global"))
assert.NoError(kmsCache.VerifyGlobalRoot(testCtx))
}
func TestKms_GetWrapper(t *testing.T) {
t.Parallel()
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rootWrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, rootWrapper)
kmsCache.CreateKeys(testCtx, "global")
tests := []struct {
name string
kms *kms.Kms
purpose kms.KeyPurpose
scopeId string
opt []kms.Option
wantErr bool
wantErrMatch *errors.Template
wantErrContains string
}{
{
name: "missing-purpose",
kms: kmsCache,
scopeId: "global",
wantErr: true,
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing purpose",
},
{
name: "missing-scope-id",
kms: kmsCache,
purpose: kms.KeyPurposeDatabase,
wantErr: true,
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing scope id",
},
{
name: "success",
kms: kmsCache,
purpose: kms.KeyPurposeDatabase,
scopeId: "global",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := tc.kms.GetWrapper(testCtx, tc.scopeId, tc.purpose, tc.opt...)
if tc.wantErr {
require.Error(err)
if tc.wantErrMatch != nil {
assert.Truef(errors.Match(tc.wantErrMatch, err), "expected %q and got err: %+v", tc.wantErrMatch.Code, err)
}
if tc.wantErrContains != "" {
assert.True(strings.Contains(err.Error(), tc.wantErrContains))
}
return
}
require.NoError(err)
assert.NotNil(got)
})
}
}
func TestKms_CreateKeys(t *testing.T) {
t.Parallel()
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rootWrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, rootWrapper)
kmsCache.CreateKeys(testCtx, "global")
rw := db.New(conn)
tests := []struct {
name string
kms *kms.Kms
scopeId string
opt []kms.Option
wantErr bool
wantErrMatch *errors.Template
wantErrContains string
}{
{
name: "missing-scope-id",
kms: kmsCache,
wantErr: true,
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing scope id",
},
{
name: "invalid-scope",
kms: kmsCache,
scopeId: "o_1234567890",
wantErr: true,
wantErrContains: "violates foreign key constraint",
},
{
name: "missing-writer-opt",
kms: kmsCache,
scopeId: "global",
opt: []kms.Option{kms.WithReaderWriter(rw, nil)},
wantErr: true,
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing writer",
},
{
name: "missing-reader-opt",
kms: kmsCache,
scopeId: "global",
opt: []kms.Option{kms.WithReaderWriter(nil, rw)},
wantErr: true,
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing reader",
},
{
name: "success",
kms: kmsCache,
scopeId: "global",
},
{
name: "success-with-reader-writer",
kms: kmsCache,
opt: []kms.Option{kms.WithReaderWriter(rw, rw)},
scopeId: "global",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
kms.TestKmsDeleteAllKeys(t, conn)
err := tc.kms.CreateKeys(testCtx, tc.scopeId, tc.opt...)
if tc.wantErr {
require.Error(err)
if tc.wantErrMatch != nil {
assert.Truef(errors.Match(tc.wantErrMatch, err), "expected %q and got err: %+v", tc.wantErrMatch.Code, err)
}
if tc.wantErrContains != "" {
assert.True(strings.Contains(err.Error(), tc.wantErrContains))
}
return
}
require.NoError(err)
})
}
}
+159 -57
View File
@@ -2,73 +2,175 @@ package kms
import (
"context"
"crypto/rand"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/go-dbw"
wrappingKms "github.com/hashicorp/go-kms-wrapping/extras/kms/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestKms_KeyId(t *testing.T) {
func Test_New(t *testing.T) {
t.Parallel()
require := require.New(t)
ctx := context.Background()
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
extWrapper := db.TestWrapper(t)
repo, err := NewRepository(rw, rw)
require.NoError(err)
// Make the global scope base keys
_, err = CreateKeysTx(ctx, rw, rw, extWrapper, rand.Reader, scope.Global.String())
require.NoError(err)
tests := []struct {
name string
r *db.Db
w *db.Db
want *Kms
wantErr bool
wantErrMatch *errors.Template
wantErrContains string
}{
{
name: "nil-reader",
w: rw,
wantErr: true,
wantErrContains: "missing reader",
wantErrMatch: errors.T(errors.InvalidParameter),
},
{
name: "nil-writer",
r: rw,
wantErr: true,
wantErrContains: "missing writer",
wantErrMatch: errors.T(errors.InvalidParameter),
},
{
name: "success",
r: rw,
w: rw,
want: &Kms{
reader: rw,
underlying: func() *wrappingKms.Kms {
purposes := make([]wrappingKms.KeyPurpose, 0, len(ValidDekPurposes()))
for _, p := range ValidDekPurposes() {
purposes = append(purposes, wrappingKms.KeyPurpose(p.String()))
}
purposes = append(purposes, wrappingKms.KeyPurpose(KeyPurposeWorkerAuth.String()), wrappingKms.KeyPurpose(KeyPurposeRecovery.String()))
// Get the global scope's root wrapper
kmsCache, err := NewKms(repo)
require.NoError(err)
require.NoError(kmsCache.AddExternalWrappers(ctx, WithRootWrapper(extWrapper)))
globalRootWrapper, _, err := kmsCache.loadRoot(ctx, scope.Global.String())
require.NoError(err)
r := dbw.New(rw.UnderlyingDB())
w := dbw.New(rw.UnderlyingDB())
wrapped, err := wrappingKms.New(r, w, purposes, wrappingKms.WithCache(true))
require.NoError(t, err)
return wrapped
}(),
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
dks, err := repo.ListDatabaseKeys(ctx)
require.NoError(err)
require.Len(dks, 1)
// Create another key version
newKeyBytes, err := uuid.GenerateRandomBytes(32)
require.NoError(err)
_, err = repo.CreateDatabaseKeyVersion(ctx, globalRootWrapper, dks[0].GetPrivateId(), newKeyBytes)
require.NoError(err)
dkvs, err := repo.ListDatabaseKeyVersions(ctx, globalRootWrapper, dks[0].GetPrivateId())
require.NoError(err)
require.Len(dkvs, 2)
keyId1 := dkvs[0].GetPrivateId()
keyId2 := dkvs[1].GetPrivateId()
// First test: just getting the key should return the latest
wrapper, err := kmsCache.GetWrapper(ctx, scope.Global.String(), KeyPurposeDatabase)
require.NoError(err)
tKeyId, err := wrapper.KeyId(context.Background())
require.NoError(err)
require.Equal(keyId2, tKeyId)
// Second: ask for each in turn
wrapper, err = kmsCache.GetWrapper(ctx, scope.Global.String(), KeyPurposeDatabase, WithKeyId(keyId1))
require.NoError(err)
tKeyId, err = wrapper.KeyId(context.Background())
require.NoError(err)
require.Equal(keyId1, tKeyId)
wrapper, err = kmsCache.GetWrapper(ctx, scope.Global.String(), KeyPurposeDatabase, WithKeyId(keyId2))
require.NoError(err)
tKeyId, err = wrapper.KeyId(context.Background())
require.NoError(err)
require.Equal(keyId2, tKeyId)
// Last: verify something bogus finds nothing
_, err = kmsCache.GetWrapper(ctx, scope.Global.String(), KeyPurposeDatabase, WithKeyId("foo"))
require.Error(err)
got, err := New(testCtx, tc.r, tc.w)
if tc.wantErr {
require.Error(err)
if tc.wantErrMatch != nil {
assert.Truef(errors.Match(tc.wantErrMatch, err), "expected %q and got err: %+v", tc.wantErrMatch.Code, err)
}
if tc.wantErrContains != "" {
assert.Contains(err.Error(), tc.wantErrContains)
}
return
}
require.NoError(err)
assert.Equal(tc.want, got)
})
}
}
func Test_NewUsingReaderWriter(t *testing.T) {
t.Parallel()
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
tests := []struct {
name string
r db.Reader
w db.Writer
want *Kms
wantErr bool
wantErrMatch *errors.Template
wantErrContains string
}{
{
name: "nil-reader",
w: rw,
wantErr: true,
wantErrContains: "missing reader",
wantErrMatch: errors.T(errors.InvalidParameter),
},
{
name: "nil-writer",
r: rw,
wantErr: true,
wantErrContains: "missing writer",
wantErrMatch: errors.T(errors.InvalidParameter),
},
{
name: "invalid-reader",
r: &invalidReader{},
w: rw,
wantErr: true,
wantErrContains: "unable to convert to db.DB",
wantErrMatch: errors.T(errors.InvalidParameter),
},
{
name: "invalid-writer",
r: rw,
w: &invalidWriter{},
wantErr: true,
wantErrContains: "unable to convert to db.DB",
wantErrMatch: errors.T(errors.InvalidParameter),
},
{
name: "success",
r: rw,
w: rw,
want: &Kms{
reader: rw,
underlying: func() *wrappingKms.Kms {
purposes := stdNewKmsPurposes()
r := dbw.New(rw.UnderlyingDB())
w := dbw.New(rw.UnderlyingDB())
wrapped, err := wrappingKms.New(r, w, purposes, wrappingKms.WithCache(true))
require.NoError(t, err)
return wrapped
}(),
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := NewUsingReaderWriter(testCtx, tc.r, tc.w)
if tc.wantErr {
require.Error(err)
if tc.wantErrMatch != nil {
assert.Truef(errors.Match(tc.wantErrMatch, err), "expected %q and got err: %+v", tc.wantErrMatch.Code, err)
}
if tc.wantErrContains != "" {
assert.Contains(err.Error(), tc.wantErrContains)
}
return
}
require.NoError(err)
assert.Equal(tc.want, got)
})
}
}
type invalidReader struct {
db.Reader
}
type invalidWriter struct {
db.Writer
}
-83
View File
@@ -1,83 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
"google.golang.org/protobuf/proto"
)
const (
DefaultOidcKeyTableName = "kms_oidc_key"
)
type OidcKey struct {
*store.OidcKey
tableName string `gorm:"-"`
}
// NewOidcKey creates a new in memory oidc key. This key is used to encrypt
// oidc state before it's included in the oidc auth url. No options are
// currently supported.
func NewOidcKey(rootKeyId string, _ ...Option) (*OidcKey, error) {
const op = "kms.NewOidcKey"
if rootKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key id")
}
c := &OidcKey{
OidcKey: &store.OidcKey{
RootKeyId: rootKeyId,
},
}
return c, nil
}
// AllocOidcKey will allocate a OidcKey
func AllocOidcKey() OidcKey {
return OidcKey{
OidcKey: &store.OidcKey{},
}
}
// Clone creates a clone of the OidcKey
func (k *OidcKey) Clone() interface{} {
cp := proto.Clone(k.OidcKey)
return &OidcKey{
OidcKey: cp.(*store.OidcKey),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// before it's written.
func (k *OidcKey) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "kms.(OidcKey).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
switch opType {
case db.CreateOp:
if k.RootKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
case db.UpdateOp:
return errors.New(ctx, errors.InvalidParameter, op, "key is immutable")
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *OidcKey) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultOidcKeyTableName
}
// 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 (k *OidcKey) SetTableName(n string) {
k.tableName = n
}
-206
View File
@@ -1,206 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the OidcKey attributes are
// immutable and those tests are covered by TestOidcKey_ImmutableFields
func TestOidcKey_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootKeyId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.OidcKey
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-rootKeyId",
args: args{},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
rootKeyId: rk.PrivateId,
},
want: func() *kms.OidcKey {
k := kms.AllocOidcKey()
k.RootKeyId = rk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOidcKey(); return &i }(), "1=1")
got, err := kms.NewOidcKey(tt.args.rootKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.OidcKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestOidcKey_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOidcKey(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.OidcKey
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestOidcKey(t, conn, rk.PrivateId),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.OidcKey {
k := kms.AllocOidcKey()
id, err := db.NewPublicId(kms.OidcKeyPrefix)
require.NoError(t, err)
k.PrivateId = id
k.RootKeyId = rk.PrivateId
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocOidcKey()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocOidcKey()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestOidcKey_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k := kms.TestOidcKey(t, conn, rk.PrivateId)
cp := k.Clone()
assert.True(proto.Equal(cp.(*kms.OidcKey).OidcKey, k.OidcKey))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
k := kms.TestOidcKey(t, conn, rk.PrivateId)
k2 := kms.TestOidcKey(t, conn, rk2.PrivateId)
cp := k.Clone()
assert.True(!proto.Equal(cp.(*kms.OidcKey).OidcKey, k2.OidcKey))
})
}
func TestOidcKey_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultOidcKeyTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocOidcKey()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocOidcKey()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-122
View File
@@ -1,122 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
const (
DefaultOidcKeyVersionTableName = "kms_oidc_key_version"
)
type OidcKeyVersion struct {
*store.OidcKeyVersion
tableName string `gorm:"-"`
}
// NewOidcKeyVersion creates a new in memory oidc key version. This key is used
// to encrypt oidc state before it's included in the oidc auth url. No options
// are currently supported.
func NewOidcKeyVersion(oidcKeyId string, key []byte, rootKeyVersionId string, _ ...Option) (*OidcKeyVersion, error) {
const op = "kms.NewOidcKeyVersion"
if oidcKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing oidc key id")
}
if len(key) == 0 {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing key")
}
if rootKeyVersionId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key version id")
}
k := &OidcKeyVersion{
OidcKeyVersion: &store.OidcKeyVersion{
OidcKeyId: oidcKeyId,
RootKeyVersionId: rootKeyVersionId,
Key: key,
},
}
return k, nil
}
// AllocOidcKeyVersion allocates a OidcKeyVersion
func AllocOidcKeyVersion() OidcKeyVersion {
return OidcKeyVersion{
OidcKeyVersion: &store.OidcKeyVersion{},
}
}
// Clone creates a clone of the OidcKeyVersion
func (k *OidcKeyVersion) Clone() interface{} {
cp := proto.Clone(k.OidcKeyVersion)
return &OidcKeyVersion{
OidcKeyVersion: cp.(*store.OidcKeyVersion),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the oidc key
// version before it's written.
func (k *OidcKeyVersion) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "kms.(OidcKeyVersion).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
switch opType {
case db.CreateOp:
if k.CtKey == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
if k.OidcKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing oidc key id")
}
if k.RootKeyVersionId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
case db.UpdateOp:
return errors.New(ctx, errors.InvalidParameter, op, "key is immutable")
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *OidcKeyVersion) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultOidcKeyVersionTableName
}
// 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 (k *OidcKeyVersion) SetTableName(n string) {
k.tableName = n
}
// Encrypt will encrypt the oidc key version's key
func (k *OidcKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(OidcKeyVersion).Encrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.OidcKeyVersion directly
if err := structwrapping.WrapStruct(ctx, cipher, k.OidcKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
}
return nil
}
// Decrypt will decrypt the oidc key version's key
func (k *OidcKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(OidcKeyVersion).Decrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.OidcKeyVersion directly
if err := structwrapping.UnwrapStruct(ctx, cipher, k.OidcKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
-249
View File
@@ -1,249 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the OidcKeyVersion attributes are
// immutable and those tests are covered by TestOidcKey_ImmutableFields
func TestOidcKeyVersion_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rkv, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOidcKey(t, conn, rk.PrivateId)
type args struct {
OidcKeyId string
key []byte
rootKeyVersionId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.OidcKeyVersion
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-oidc-id",
args: args{
key: []byte("oidc key"),
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
OidcKeyId: dk.PrivateId,
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-root-key-version-id",
args: args{
OidcKeyId: dk.PrivateId,
key: []byte("oidc key"),
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
OidcKeyId: dk.PrivateId,
key: []byte("oidc key"),
rootKeyVersionId: rkv.PrivateId,
},
want: func() *kms.OidcKeyVersion {
k := kms.AllocOidcKeyVersion()
k.RootKeyVersionId = rkv.PrivateId
k.Key = []byte("oidc key")
k.OidcKeyId = dk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOidcKeyVersion(); return &i }(), "1=1")
got, err := kms.NewOidcKeyVersion(tt.args.OidcKeyId, tt.args.key, tt.args.rootKeyVersionId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.OidcKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = got.Encrypt(context.Background(), rkvWrapper)
require.NoError(err)
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestOidcKeyVersion_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOidcKey(t, conn, rk.PrivateId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOidcKeyVersion(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.OidcKeyVersion
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestOidcKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("oidc key")),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.OidcKeyVersion {
k := kms.AllocOidcKeyVersion()
id, err := db.NewPublicId(kms.OidcKeyVersionPrefix)
require.NoError(t, err)
k.PrivateId = id
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocOidcKeyVersion()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocOidcKeyVersion()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestOidcKeyVersion_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
k := kms.TestOidcKey(t, conn, rk.PrivateId)
kv := kms.TestOidcKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test db key"))
cp := kv.Clone()
assert.True(proto.Equal(cp.(*kms.OidcKeyVersion).OidcKeyVersion, kv.OidcKeyVersion))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
_, rkvWrapper2 := kms.TestRootKeyVersion(t, conn, wrapper, rk2.PrivateId)
k := kms.TestOidcKey(t, conn, rk.PrivateId)
k2 := kms.TestOidcKey(t, conn, rk2.PrivateId)
kv := kms.TestOidcKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test db key"))
kv2 := kms.TestOidcKeyVersion(t, conn, rkvWrapper2, k2.PrivateId, []byte("test db key 2"))
cp := kv.Clone()
assert.True(!proto.Equal(cp.(*kms.OidcKeyVersion).OidcKeyVersion, kv2.OidcKeyVersion))
})
}
func TestOidcKeyVersion_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultOidcKeyVersionTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocOidcKeyVersion()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocOidcKeyVersion()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-79
View File
@@ -1,79 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
"google.golang.org/protobuf/proto"
)
const (
DefaultOplogKeyTableName = "kms_oplog_key"
)
type OplogKey struct {
*store.OplogKey
tableName string `gorm:"-"`
}
// NewOplogKey creates a new in memory key. No options
// are currently supported.
func NewOplogKey(rootKeyId string, _ ...Option) (*OplogKey, error) {
const op = "kms.NewOplogKey"
if rootKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key id")
}
c := &OplogKey{
OplogKey: &store.OplogKey{
RootKeyId: rootKeyId,
},
}
return c, nil
}
// AllocOplogKey will allocate a key
func AllocOplogKey() OplogKey {
return OplogKey{
OplogKey: &store.OplogKey{},
}
}
// Clone creates a clone of the key
func (k *OplogKey) Clone() interface{} {
cp := proto.Clone(k.OplogKey)
return &OplogKey{
OplogKey: cp.(*store.OplogKey),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// before it's written.
func (k *OplogKey) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "kms.(OplogKey).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if opType == db.CreateOp {
if k.RootKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *OplogKey) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultOplogKeyTableName
}
// 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 (k *OplogKey) SetTableName(n string) {
k.tableName = n
}
-206
View File
@@ -1,206 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the OplogKey attributes are
// immutable and those tests are covered by TestOplogKey_ImmutableFields
func TestOplogKey_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootKeyId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.OplogKey
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-rootKeyId",
args: args{},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
rootKeyId: rk.PrivateId,
},
want: func() *kms.OplogKey {
k := kms.AllocOplogKey()
k.RootKeyId = rk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOplogKey(); return &i }(), "1=1")
got, err := kms.NewOplogKey(tt.args.rootKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.OplogKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestOplogKey_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOplogKey(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.OplogKey
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestOplogKey(t, conn, rk.PrivateId),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.OplogKey {
k := kms.AllocOplogKey()
id, err := db.NewPublicId(kms.OplogKeyPrefix)
require.NoError(t, err)
k.PrivateId = id
k.RootKeyId = rk.PrivateId
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocOplogKey()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocOplogKey()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestOplogKey_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k := kms.TestOplogKey(t, conn, rk.PrivateId)
cp := k.Clone()
assert.True(proto.Equal(cp.(*kms.OplogKey).OplogKey, k.OplogKey))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
k := kms.TestOplogKey(t, conn, rk.PrivateId)
k2 := kms.TestOplogKey(t, conn, rk2.PrivateId)
cp := k.Clone()
assert.True(!proto.Equal(cp.(*kms.OplogKey).OplogKey, k2.OplogKey))
})
}
func TestOplogKey_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultOplogKeyTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocOplogKey()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocOplogKey()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-118
View File
@@ -1,118 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
const (
DefaultOplogKeyVersionTableName = "kms_oplog_key_version"
)
type OplogKeyVersion struct {
*store.OplogKeyVersion
tableName string `gorm:"-"`
}
// OplogKeyVersion creates a new in memory key version. No options are
// currently supported.
func NewOplogKeyVersion(oplogKeyId string, key []byte, rootKeyVersionId string, _ ...Option) (*OplogKeyVersion, error) {
const op = "kms.NewOplogKeyVersion"
if oplogKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing oplog key id")
}
if len(key) == 0 {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing key")
}
if rootKeyVersionId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key version id")
}
k := &OplogKeyVersion{
OplogKeyVersion: &store.OplogKeyVersion{
OplogKeyId: oplogKeyId,
RootKeyVersionId: rootKeyVersionId,
Key: key,
},
}
return k, nil
}
// AllocOplogKeyVersion allocates a key version
func AllocOplogKeyVersion() OplogKeyVersion {
return OplogKeyVersion{
OplogKeyVersion: &store.OplogKeyVersion{},
}
}
// Clone creates a clone of the key version
func (k *OplogKeyVersion) Clone() interface{} {
cp := proto.Clone(k.OplogKeyVersion)
return &OplogKeyVersion{
OplogKeyVersion: cp.(*store.OplogKeyVersion),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// version before it's written.
func (k *OplogKeyVersion) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "kms.(OplogKeyVersion).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if opType == db.CreateOp {
if k.CtKey == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
if k.OplogKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing oplog key id")
}
if k.RootKeyVersionId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *OplogKeyVersion) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultOplogKeyVersionTableName
}
// 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 (k *OplogKeyVersion) SetTableName(n string) {
k.tableName = n
}
// Encrypt will encrypt the key version's key
func (k *OplogKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(OplogKeyVersion).Encrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.OplogKeyVersion directly
if err := structwrapping.WrapStruct(ctx, cipher, k.OplogKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
}
return nil
}
// Decrypt will decrypt the key version's key
func (k *OplogKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(OplogKeyVersion).Decrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.OplogKeyVersion directly
if err := structwrapping.UnwrapStruct(ctx, cipher, k.OplogKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
-249
View File
@@ -1,249 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the OplogKeyVersion attributes are
// immutable and those tests are covered by TestOplogKey_ImmutableFields
func TestOplogKeyVersion_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rkv, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOplogKey(t, conn, rk.PrivateId)
type args struct {
oplogKeyId string
key []byte
rootKeyVersionId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.OplogKeyVersion
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-id",
args: args{
key: []byte("test key"),
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
oplogKeyId: dk.PrivateId,
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-root-key-version-id",
args: args{
oplogKeyId: dk.PrivateId,
key: []byte("test key"),
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
oplogKeyId: dk.PrivateId,
key: []byte("test key"),
rootKeyVersionId: rkv.PrivateId,
},
want: func() *kms.OplogKeyVersion {
k := kms.AllocOplogKeyVersion()
k.RootKeyVersionId = rkv.PrivateId
k.Key = []byte("test key")
k.OplogKeyId = dk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOplogKeyVersion(); return &i }(), "1=1")
got, err := kms.NewOplogKeyVersion(tt.args.oplogKeyId, tt.args.key, tt.args.rootKeyVersionId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.OplogKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = got.Encrypt(context.Background(), rkvWrapper)
require.NoError(err)
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestOplogKeyVersion_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
opk := kms.TestOplogKey(t, conn, rk.PrivateId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOplogKeyVersion(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.OplogKeyVersion
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestOplogKeyVersion(t, conn, rkvWrapper, opk.PrivateId, []byte("test key")),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.OplogKeyVersion {
k := kms.AllocOplogKeyVersion()
id, err := db.NewPublicId(kms.OplogKeyVersionPrefix)
require.NoError(t, err)
k.PrivateId = id
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocOplogKeyVersion()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocOplogKeyVersion()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestOplogKeyVersion_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
k := kms.TestOplogKey(t, conn, rk.PrivateId)
kv := kms.TestOplogKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test key"))
cp := kv.Clone()
assert.True(proto.Equal(cp.(*kms.OplogKeyVersion).OplogKeyVersion, kv.OplogKeyVersion))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
_, rkvWrapper2 := kms.TestRootKeyVersion(t, conn, wrapper, rk2.PrivateId)
k := kms.TestOplogKey(t, conn, rk.PrivateId)
k2 := kms.TestOplogKey(t, conn, rk2.PrivateId)
kv := kms.TestOplogKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test key"))
kv2 := kms.TestOplogKeyVersion(t, conn, rkvWrapper2, k2.PrivateId, []byte("test key 2"))
cp := kv.Clone()
assert.True(!proto.Equal(cp.(*kms.OplogKeyVersion).OplogKeyVersion, kv2.OplogKeyVersion))
})
}
func TestOplogKeyVersion_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultOplogKeyVersionTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocOplogKeyVersion()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocOplogKeyVersion()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
+7
View File
@@ -35,4 +35,11 @@ func Test_GetOpts(t *testing.T) {
testOpts.withOrderByVersion = db.DescendingOrderBy
assert.Equal(opts, testOpts)
})
t.Run("WithKeyId", func(t *testing.T) {
assert := assert.New(t)
opts := getOpts(WithKeyId("100"))
testOpts := getDefaultOptions()
testOpts.withKeyId = "100"
assert.Equal(opts, testOpts)
})
}
+25 -9
View File
@@ -1,6 +1,8 @@
package kms
import (
"io"
"github.com/hashicorp/boundary/internal/db"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
@@ -23,10 +25,12 @@ type options struct {
withRootWrapper wrapping.Wrapper
withWorkerAuthWrapper wrapping.Wrapper
withRecoveryWrapper wrapping.Wrapper
withRepository *Repository
withOrderByVersion db.OrderBy
withKeyId string
withScopeIds []string
withRandomReader io.Reader
withReader db.Reader
withWriter db.Writer
}
func getDefaultOptions() options {
@@ -64,14 +68,6 @@ func WithRecoveryWrapper(w wrapping.Wrapper) Option {
}
}
// WithRepository sets a repository for a given wrapper lookup, useful if in the
// middle of a transaction where the reader/writer need to be specified
func WithRepository(repo *Repository) Option {
return func(o *options) {
o.withRepository = repo
}
}
// WithOrderByVersion provides an option to specify ordering by the
// CreateTime field.
func WithOrderByVersion(orderBy db.OrderBy) Option {
@@ -95,3 +91,23 @@ func WithScopeIds(scopeId ...string) Option {
o.withScopeIds = scopeId
}
}
// WithRandomReader(...) option allows an optional random reader to be
// provided. By default the reader from crypto/rand will be used.
func WithRandomReader(randomReader io.Reader) Option {
return func(o *options) {
o.withRandomReader = randomReader
}
}
// WithReaderWriter allows the caller to pass an inflight transaction to be used
// for all database operations. If WithReaderWriter(...) is used, then the
// caller is responsible for managing the transaction. The purpose of the
// WithReaderWriter(...) option is to allow the caller to create the scope and
// all of its keys in the same transaction.
func WithReaderWriter(r db.Reader, w db.Writer) Option {
return func(o *options) {
o.withReader = r
o.withWriter = w
}
}
-200
View File
@@ -1,200 +0,0 @@
package kms
import (
"context"
"fmt"
"io"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/types/scope"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/hashicorp/go-uuid"
)
// Repository is the iam database repository
type Repository struct {
reader db.Reader
writer db.Writer
// defaultLimit provides a default for limiting the number of results returned from the repo
defaultLimit int
}
// NewRepository creates a new kms Repository. Supports the options: WithLimit
// which sets a default limit on results returned by repo operations.
func NewRepository(r db.Reader, w db.Writer, opt ...Option) (*Repository, error) {
const op = "kms.NewRepository"
if r == nil {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "nil reader")
}
if w == nil {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "nil writer")
}
opts := getOpts(opt...)
if opts.withLimit == 0 {
// zero signals the boundary defaults should be used.
opts.withLimit = db.DefaultLimit
}
return &Repository{
reader: r,
writer: w,
defaultLimit: opts.withLimit,
}, nil
}
// list will return a listing of resources and honor the WithLimit option or the
// repo defaultLimit
func (r *Repository) list(ctx context.Context, resources interface{}, where string, args []interface{}, opt ...Option) error {
opts := getOpts(opt...)
limit := r.defaultLimit
var dbOpts []db.Option
if opts.withLimit != 0 {
// non-zero signals an override of the default limit for the repo.
limit = opts.withLimit
}
dbOpts = append(dbOpts, db.WithLimit(limit))
switch opts.withOrderByVersion {
case db.AscendingOrderBy:
dbOpts = append(dbOpts, db.WithOrder("version asc"))
case db.DescendingOrderBy:
dbOpts = append(dbOpts, db.WithOrder("version desc"))
}
return r.reader.SearchWhere(ctx, resources, where, args, dbOpts...)
}
// DefaultLimit returns the default limit for listing as set on the repo
func (r *Repository) DefaultLimit() int {
return r.defaultLimit
}
// KeyIder defines a common interface for all keys returned from CreateKeysTx in
// a Keys map
type KeyIder interface {
GetPrivateId() string
}
// Keys defines a return type for CreateKeysTx so the returned keys can be
// easily accessed via their KeyType
type Keys map[KeyType]KeyIder
// CreateKeysTx creates the root key and DEKs returns a map of the new keys.
// This function encapsulates all the work required within a db.TxHandler and
// allows this capability to be shared with the iam repo.
func CreateKeysTx(ctx context.Context, dbReader db.Reader, dbWriter db.Writer, rootWrapper wrapping.Wrapper, randomReader io.Reader, scopeId string) (Keys, error) {
const op = "kms.CreateKeysTx"
if dbReader == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing db reader")
}
if dbWriter == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing db writer")
}
if rootWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root wrapper")
}
if randomReader == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing random reader")
}
if scopeId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
}
k, err := generateKey(ctx, randomReader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for root key in scope %s", scopeId)))
}
rootKey, rootKeyVersion, err := createRootKeyTx(ctx, dbWriter, rootWrapper, scopeId, k)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to create root key in scope %s", scopeId)))
}
rkvWrapper := aead.NewWrapper()
if _, err := rkvWrapper.SetConfig(ctx, wrapping.WithKeyId(rootKeyVersion.GetPrivateId())); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error setting config on aead root wrapper in scope %s", scopeId)))
}
if err := rkvWrapper.SetAesGcmKeyBytes(rootKeyVersion.GetKey()); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error setting key bytes on aead root wrapper in scope %s", scopeId)))
}
k, err = generateKey(ctx, randomReader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for database key in scope %s", scopeId)))
}
dbKey, dbKeyVersion, err := createDatabaseKeyTx(ctx, dbReader, dbWriter, rkvWrapper, k)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to create database key in scope %s", scopeId)))
}
k, err = generateKey(ctx, randomReader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for oplog key in scope %s", scopeId)))
}
oplogKey, oplogKeyVersion, err := createOplogKeyTx(ctx, dbReader, dbWriter, rkvWrapper, k)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to create oplog key in scope %s", scopeId)))
}
k, err = generateKey(ctx, randomReader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for session key in scope %s", scopeId)))
}
sessionKey, sessionKeyVersion, err := createSessionKeyTx(ctx, dbReader, dbWriter, rkvWrapper, k)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to create session key in scope %s", scopeId)))
}
k, err = generateKey(ctx, randomReader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for token key in scope %s", scopeId)))
}
tokenKey, tokenKeyVersion, err := createTokenKeyTx(ctx, dbReader, dbWriter, rkvWrapper, k)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to create token key in scope %s", scopeId)))
}
k, err = generateKey(ctx, randomReader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for oidc key in scope %s", scopeId)))
}
oidcKey, oidcKeyVersion, err := createOidcKeyTx(ctx, dbReader, dbWriter, rkvWrapper, k)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to create oidc key in scope %s", scopeId)))
}
keys := Keys{
KeyTypeRootKey: rootKey,
KeyTypeRootKeyVersion: rootKeyVersion,
KeyTypeDatabaseKey: dbKey,
KeyTypeDatabaseKeyVersion: dbKeyVersion,
KeyTypeOplogKey: oplogKey,
KeyTypeOplogKeyVersion: oplogKeyVersion,
KeyTypeSessionKey: sessionKey,
KeyTypeSessionKeyVersion: sessionKeyVersion,
KeyTypeTokenKey: tokenKey,
KeyTypeTokenKeyVersion: tokenKeyVersion,
KeyTypeOidcKey: oidcKey,
KeyTypeOidcKeyVersion: oidcKeyVersion,
}
if scopeId == scope.Global.String() {
k, err = generateKey(ctx, randomReader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for oidc key in scope %s", scopeId)))
}
auditKey, auditKeyVersion, err := createAuditKeyTx(ctx, dbReader, dbWriter, rkvWrapper, k)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to create audit key in scope %s", scopeId)))
}
keys[KeyTypeAuditKey] = auditKey
keys[KeyTypeAuditKeyVersion] = auditKeyVersion
}
return keys, nil
}
func generateKey(ctx context.Context, randomReader io.Reader) ([]byte, error) {
const op = "kms.generateKey"
k, err := uuid.GenerateRandomBytesWithReader(32, randomReader)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return k, nil
}
-163
View File
@@ -1,163 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateAuditKey inserts into the repository and returns the new audit key and
// audit key version. There are no valid options at this time.
func (r *Repository) CreateAuditKey(ctx context.Context, rkvWrapper wrapping.Wrapper, key []byte, _ ...Option) (*AuditKey, *AuditKeyVersion, error) {
const op = "kms.(Repository).CreateAuditKey"
var returnedDk, returnedDv interface{}
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
var err error
if returnedDk, returnedDv, err = createAuditKeyTx(ctx, reader, w, rkvWrapper, key); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
return returnedDk.(*AuditKey), returnedDv.(*AuditKeyVersion), nil
}
// createAuditKeyTx inserts into the db (via db.Writer) and returns the new audit key
// and audit key version. This function encapsulates all the work required within
// a db.TxHandler and allows this capability to be shared with the iam repo.
func createAuditKeyTx(ctx context.Context, r db.Reader, w db.Writer, rkvWrapper wrapping.Wrapper, key []byte) (*AuditKey, *AuditKeyVersion, error) {
const op = "kms.createAuditKeyTx"
if rkvWrapper == nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
if len(key) == 0 {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup root key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
rv := AllocRootKeyVersion()
rv.PrivateId = rootKeyVersionId
err = r.LookupById(ctx, &rv)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to lookup root key version %s", rootKeyVersionId)))
}
dk := AllocAuditKey()
dv := AllocAuditKeyVersion()
id, err := newAuditKeyId(ctx)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
dk.PrivateId = id
dk.RootKeyId = rv.RootKeyId
id, err = newAuditKeyVersionId(ctx)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
dv.PrivateId = id
dv.AuditKeyId = dk.PrivateId
dv.RootKeyVersionId = rootKeyVersionId
dv.Key = key
if err := dv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
// no oplog entries for keys
if err := w.Create(ctx, &dk); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("keys create"))
}
// no oplog entries for key versions
if err := w.Create(ctx, &dv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("key versions create"))
}
return &dk, &dv, nil
}
// LookupAuditKey will look up a key in the repository. If the key is not
// found, it will return nil, nil.
func (r *Repository) LookupAuditKey(ctx context.Context, privateId string, _ ...Option) (*AuditKey, error) {
const op = "kms.(Repository).LookupAuditKey"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocAuditKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return &k, nil
}
// DeleteAuditKey deletes the key for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteAuditKey(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteAuditKey"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocAuditKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for root keys
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// ListAuditKeys will list the keys. Supports the WithLimit option.
func (r *Repository) ListAuditKeys(ctx context.Context, opt ...Option) ([]Dek, error) {
const op = "kms.(Repository).ListAuditKeys"
var keys []*AuditKey
err := r.list(ctx, &keys, "1=1", nil, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
deks := make([]Dek, 0, len(keys))
for _, key := range keys {
deks = append(deks, key)
}
return deks, nil
}
-298
View File
@@ -1,298 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateAuditKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
type args struct {
scopeId string
key []byte
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid-org",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: rkvWrapper,
},
wantErr: false,
},
{
name: "nil-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
key: []byte(""),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-rkv-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: wrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "wrapper-missing-id",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: func() wrapping.Wrapper {
w := db.TestWrapper(t)
_, err = w.(*aead.Wrapper).SetConfig(
context.Background(),
wrapping.WithKeyId(""),
)
require.NoError(t, err)
return w
}(),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
dk, dv, err := repo.CreateAuditKey(context.Background(), tt.args.keyWrapper, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(dk)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(dk.CreateTime)
foundKey, err := repo.LookupAuditKey(context.Background(), dk.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, dk))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, dk.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
assert.NotNil(dv.CreateTime)
foundKeyVersion, err := repo.LookupAuditKeyVersion(context.Background(), tt.args.keyWrapper, dv.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKeyVersion, dv))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, dv.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteAuditKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
key *kms.AuditKey
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestAuditKey(t, conn, rk.PrivateId),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.AuditKey {
k := kms.AllocAuditKey()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.AuditKey {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k := kms.AllocAuditKey()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteAuditKey(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupAuditKey(context.Background(), tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_ListAuditKeys(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
wrapper := db.TestWrapper(t)
type args struct {
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1, // org and project both have keys, plus global scope
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
}
for _, tt := range tests {
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
for i := 0; i < tt.createCnt; i++ {
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "scope_id in(?)", "global", org.PublicId, proj.PublicId)
rk := kms.TestRootKey(t, conn, proj.PublicId)
kms.TestAuditKey(t, conn, rk.PrivateId)
require.NoError(err)
}
got, err := repo.ListAuditKeys(context.Background(), tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
@@ -1,176 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateAuditKeyVersion inserts into the repository and returns the new key
// version with its PrivateId. There are no valid options at this time.
func (r *Repository) CreateAuditKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, auditKeyId string, key []byte, _ ...Option) (*AuditKeyVersion, error) {
const op = "kms.(Repository).CreateAuditKeyVersion"
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup root key version ID"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
if auditKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing audit key id")
}
if len(key) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
kv := AllocAuditKeyVersion()
id, err := newAuditKeyVersionId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
kv.PrivateId = id
kv.RootKeyVersionId = rootKeyVersionId
kv.Key = key
kv.AuditKeyId = auditKeyId
if err := kv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var returnedKey interface{}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedKey = kv.Clone()
// no oplog entries for root key version
if err := w.Create(ctx, returnedKey); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s audit key id", kv.AuditKeyId)))
}
return returnedKey.(*AuditKeyVersion), nil
}
// LookupAuditKeyVersion will look up a key version in the repository. If
// the key version is not found, it will return nil, nil.
func (r *Repository) LookupAuditKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*AuditKeyVersion, error) {
const op = "kms.(Repository).LookupAuditKeyVersion"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
k := AllocAuditKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
if err := k.Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &k, nil
}
// DeleteAuditKeyVersion deletes the key version for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteAuditKeyVersion(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteAuditKeyVersion"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocAuditKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for the key version
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// LatestAuditKeyVersion searches for the key version with the highest
// version number. When no results are found, it returns nil with an
// errors.RecordNotFound error.
func (r *Repository) LatestAuditKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, AuditKeyId string, _ ...Option) (*AuditKeyVersion, error) {
const op = "kms.(Repository).LatestAuditKeyVersion"
if AuditKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing audit key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var foundKeys []*AuditKeyVersion
if err := r.reader.SearchWhere(ctx, &foundKeys, "audit_key_id = ?", []interface{}{AuditKeyId}, db.WithLimit(1), db.WithOrder("version desc")); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", AuditKeyId)))
}
if len(foundKeys) == 0 {
return nil, errors.E(ctx, errors.WithCode(errors.RecordNotFound), errors.WithOp(op))
}
if err := foundKeys[0].Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return foundKeys[0], nil
}
// ListAuditKeyVersions will lists versions of a key. Supports the WithLimit option.
func (r *Repository) ListAuditKeyVersions(ctx context.Context, rkvWrapper wrapping.Wrapper, AuditKeyId string, opt ...Option) ([]DekVersion, error) {
const op = "kms.(Repository).ListAuditKeyVersions"
if AuditKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing audit key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var versions []*AuditKeyVersion
err := r.list(ctx, &versions, "audit_key_id = ?", []interface{}{AuditKeyId}, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
for i, k := range versions {
if err := k.Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error decrypting key num %d", i)))
}
}
dekVersions := make([]DekVersion, 0, len(versions))
for _, version := range versions {
dekVersions = append(dekVersions, version)
}
return dekVersions, nil
}
@@ -1,367 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateAuditKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestAuditKey(t, conn, rk.PrivateId)
type args struct {
key []byte
AuditKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: []byte("test key"),
keyWrapper: rkvWrapper,
AuditKeyId: dk.PrivateId,
},
wantErr: false,
},
{
name: "invalid-rkv-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: wrapper,
AuditKeyId: dk.PrivateId,
},
wantErr: true,
},
{
name: "empty-key",
args: args{
key: nil,
keyWrapper: wrapper,
AuditKeyId: dk.PrivateId,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
k, err := repo.CreateAuditKeyVersion(context.Background(), tt.args.keyWrapper, tt.args.AuditKeyId, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(k)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(k.CreateTime)
foundKey, err := repo.LookupAuditKeyVersion(context.Background(), tt.args.keyWrapper, k.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, k))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, k.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteAuditKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestAuditKey(t, conn, rk.PrivateId)
type args struct {
key *kms.AuditKeyVersion
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestAuditKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("audit key")),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.AuditKeyVersion {
k := kms.AllocAuditKeyVersion()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.AuditKeyVersion {
id, err := db.NewPublicId(kms.AuditKeyPrefix)
require.NoError(t, err)
k := kms.AllocAuditKeyVersion()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteAuditKeyVersion(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupAuditKeyVersion(context.Background(), wrapper, tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_LatestAuditKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestAuditKey(t, conn, rk.PrivateId)
tests := []struct {
name string
createCnt int
keyWrapper wrapping.Wrapper
wantVersion uint32
wantErr bool
wantIsError errors.Code
}{
{
name: "5",
createCnt: 5,
keyWrapper: rkvWrapper,
wantVersion: 5,
wantErr: false,
},
{
name: "1",
createCnt: 1,
keyWrapper: rkvWrapper,
wantVersion: 1,
wantErr: false,
},
{
name: "0",
createCnt: 0,
keyWrapper: rkvWrapper,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
{
name: "nil-wrapper",
createCnt: 5,
keyWrapper: nil,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocAuditKeyVersion(); return &i }(), "1=1")
testKeys := []*kms.AuditKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestAuditKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("test audit key"))
testKeys = append(testKeys, k)
}
assert.Equal(tt.createCnt, len(testKeys))
got, err := repo.LatestAuditKeyVersion(context.Background(), tt.keyWrapper, dk.PrivateId)
if tt.wantErr {
require.Error(err)
assert.Nil(got)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
require.NotNil(got)
assert.Equal(tt.wantVersion, got.Version)
})
}
}
func TestRepository_ListAuditKeyVersions(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestAuditKey(t, conn, rk.PrivateId)
type args struct {
AuditKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
AuditKeyId: dk.PrivateId,
keyWrapper: rkvWrapper,
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1,
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
AuditKeyId: dk.PrivateId,
},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
AuditKeyId: dk.PrivateId,
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "bad-org",
createCnt: 1,
args: args{
keyWrapper: rkvWrapper,
AuditKeyId: "bad-id",
},
wantCnt: 0,
wantErr: false,
},
{
name: "nil-wrapper",
createCnt: 1,
args: args{
keyWrapper: nil,
AuditKeyId: dk.PrivateId,
},
wantCnt: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocAuditKeyVersion(); return &i }(), "1=1")
keyVersions := []*kms.AuditKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestAuditKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("audit key"))
keyVersions = append(keyVersions, k)
}
assert.Equal(tt.createCnt, len(keyVersions))
got, err := repo.ListAuditKeyVersions(context.Background(), tt.args.keyWrapper, tt.args.AuditKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-163
View File
@@ -1,163 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateDatabaseKey inserts into the repository and returns the new database key and
// database key version. There are no valid options at this time.
func (r *Repository) CreateDatabaseKey(ctx context.Context, rkvWrapper wrapping.Wrapper, key []byte, _ ...Option) (*DatabaseKey, *DatabaseKeyVersion, error) {
const op = "kms.(Repository).CreateDatabaseKey"
var returnedDk, returnedDv interface{}
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
var err error
if returnedDk, returnedDv, err = createDatabaseKeyTx(ctx, reader, w, rkvWrapper, key); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
return returnedDk.(*DatabaseKey), returnedDv.(*DatabaseKeyVersion), nil
}
// createDatabaseKeyTx inserts into the db (via db.Writer) and returns the new database key
// and database key version. This function encapsulates all the work required within
// a db.TxHandler and allows this capability to be shared with the iam repo.
func createDatabaseKeyTx(ctx context.Context, r db.Reader, w db.Writer, rkvWrapper wrapping.Wrapper, key []byte) (*DatabaseKey, *DatabaseKeyVersion, error) {
const op = "kms.createDatabaseKeyTx"
if rkvWrapper == nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
if len(key) == 0 {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup root key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
rv := AllocRootKeyVersion()
rv.PrivateId = rootKeyVersionId
err = r.LookupById(ctx, &rv)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to lookup root key version %s", rootKeyVersionId)))
}
dk := AllocDatabaseKey()
dv := AllocDatabaseKeyVersion()
id, err := newDatabaseKeyId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
dk.PrivateId = id
dk.RootKeyId = rv.RootKeyId
id, err = newDatabaseKeyVersionId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
dv.PrivateId = id
dv.DatabaseKeyId = dk.PrivateId
dv.RootKeyVersionId = rootKeyVersionId
dv.Key = key
if err := dv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
// no oplog entries for keys
if err := w.Create(ctx, &dk); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("keys create"))
}
// no oplog entries for key versions
if err := w.Create(ctx, &dv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("key versions create"))
}
return &dk, &dv, nil
}
// LookupDatabaseKey will look up a key in the repository. If the key is not
// found, it will return nil, nil.
func (r *Repository) LookupDatabaseKey(ctx context.Context, privateId string, _ ...Option) (*DatabaseKey, error) {
const op = "kms.(Repository).LookupDatabaseKey"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocDatabaseKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return &k, nil
}
// DeleteDatabaseKey deletes the key for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteDatabaseKey(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteDatabaseKey"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocDatabaseKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for root keys
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// ListDatabaseKeys will list the keys. Supports the WithLimit option.
func (r *Repository) ListDatabaseKeys(ctx context.Context, opt ...Option) ([]Dek, error) {
const op = "kms.(Repository).ListDatabaseKeys"
var keys []*DatabaseKey
err := r.list(ctx, &keys, "1=1", nil, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
deks := make([]Dek, 0, len(keys))
for _, key := range keys {
deks = append(deks, key)
}
return deks, nil
}
@@ -1,295 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateDatabaseKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
type args struct {
scopeId string
key []byte
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid-org",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: rkvWrapper,
},
wantErr: false,
},
{
name: "nil-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
key: []byte(""),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-rkv-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: wrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "wrapper-missing-id",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: func() wrapping.Wrapper {
w := db.TestWrapper(t)
_, err = w.(*aead.Wrapper).SetConfig(context.Background())
require.NoError(t, err)
return w
}(),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
dk, dv, err := repo.CreateDatabaseKey(context.Background(), tt.args.keyWrapper, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(dk)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(dk.CreateTime)
foundKey, err := repo.LookupDatabaseKey(context.Background(), dk.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, dk))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, dk.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
assert.NotNil(dv.CreateTime)
foundKeyVersion, err := repo.LookupDatabaseKeyVersion(context.Background(), tt.args.keyWrapper, dv.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKeyVersion, dv))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, dv.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteDatabaseKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
key *kms.DatabaseKey
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestDatabaseKey(t, conn, rk.PrivateId),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.DatabaseKey {
k := kms.AllocDatabaseKey()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.DatabaseKey {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k := kms.AllocDatabaseKey()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteDatabaseKey(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupDatabaseKey(context.Background(), tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_ListDatabaseKeys(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
wrapper := db.TestWrapper(t)
type args struct {
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1, // org and project both have keys, plus global scope
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
}
for _, tt := range tests {
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
for i := 0; i < tt.createCnt; i++ {
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "scope_id in(?)", "global", org.PublicId, proj.PublicId)
rk := kms.TestRootKey(t, conn, proj.PublicId)
kms.TestDatabaseKey(t, conn, rk.PrivateId)
require.NoError(err)
}
got, err := repo.ListDatabaseKeys(context.Background(), tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
@@ -1,176 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateDatabaseKeyVersion inserts into the repository and returns the new key
// version with its PrivateId. There are no valid options at this time.
func (r *Repository) CreateDatabaseKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, databaseKeyId string, key []byte, _ ...Option) (*DatabaseKeyVersion, error) {
const op = "kms.(Repository).CreateDatabaseKeyVersion"
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
if databaseKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing database key id")
}
if len(key) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
kv := AllocDatabaseKeyVersion()
id, err := newDatabaseKeyVersionId()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
kv.PrivateId = id
kv.RootKeyVersionId = rootKeyVersionId
kv.Key = key
kv.DatabaseKeyId = databaseKeyId
if err := kv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var returnedKey interface{}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedKey = kv.Clone()
// no oplog entries for root key version
if err := w.Create(ctx, returnedKey); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s database key id", kv.DatabaseKeyId)))
}
return returnedKey.(*DatabaseKeyVersion), nil
}
// LookupDatabaseKeyVersion will look up a key version in the repository. If
// the key version is not found, it will return nil, nil.
func (r *Repository) LookupDatabaseKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*DatabaseKeyVersion, error) {
const op = "kms.(Repository).LookupDatabaseKeyVersion"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
k := AllocDatabaseKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
if err := k.Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &k, nil
}
// DeleteDatabaseKeyVersion deletes the key version for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteDatabaseKeyVersion(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteDatabaseKeyVersion"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocDatabaseKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for the key version
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// LatestDatabaseKeyVersion searches for the key version with the highest
// version number. When no results are found, it returns nil with an
// errors.RecordNotFound error.
func (r *Repository) LatestDatabaseKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, databaseKeyId string, _ ...Option) (*DatabaseKeyVersion, error) {
const op = "kms.(Repository).LatestDatabaseKeyVersion"
if databaseKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing database key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var foundKeys []*DatabaseKeyVersion
if err := r.reader.SearchWhere(ctx, &foundKeys, "database_key_id = ?", []interface{}{databaseKeyId}, db.WithLimit(1), db.WithOrder("version desc")); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", databaseKeyId)))
}
if len(foundKeys) == 0 {
return nil, errors.E(ctx, errors.WithCode(errors.RecordNotFound), errors.WithOp(op))
}
if err := foundKeys[0].Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return foundKeys[0], nil
}
// ListDatabaseKeyVersions will lists versions of a key. Supports the WithLimit option.
func (r *Repository) ListDatabaseKeyVersions(ctx context.Context, rkvWrapper wrapping.Wrapper, databaseKeyId string, opt ...Option) ([]DekVersion, error) {
const op = "kms.(Repository).ListDatabaseKeyVersions"
if databaseKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing database key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var versions []*DatabaseKeyVersion
err := r.list(ctx, &versions, "database_key_id = ?", []interface{}{databaseKeyId}, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
for i, k := range versions {
if err := k.Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error decrypting key num %d", i)))
}
}
dekVersions := make([]DekVersion, 0, len(versions))
for _, version := range versions {
dekVersions = append(dekVersions, version)
}
return dekVersions, nil
}
@@ -1,367 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateDatabaseKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestDatabaseKey(t, conn, rk.PrivateId)
type args struct {
key []byte
databaseKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: []byte("test key"),
keyWrapper: rkvWrapper,
databaseKeyId: dk.PrivateId,
},
wantErr: false,
},
{
name: "invalid-rkv-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: wrapper,
databaseKeyId: dk.PrivateId,
},
wantErr: true,
},
{
name: "empty-key",
args: args{
key: nil,
keyWrapper: wrapper,
databaseKeyId: dk.PrivateId,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
k, err := repo.CreateDatabaseKeyVersion(context.Background(), tt.args.keyWrapper, tt.args.databaseKeyId, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(k)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(k.CreateTime)
foundKey, err := repo.LookupDatabaseKeyVersion(context.Background(), tt.args.keyWrapper, k.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, k))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, k.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteDatabaseKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestDatabaseKey(t, conn, rk.PrivateId)
type args struct {
key *kms.DatabaseKeyVersion
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestDatabaseKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("database key")),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.DatabaseKeyVersion {
k := kms.AllocDatabaseKeyVersion()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.DatabaseKeyVersion {
id, err := db.NewPublicId(kms.DatabaseKeyPrefix)
require.NoError(t, err)
k := kms.AllocDatabaseKeyVersion()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteDatabaseKeyVersion(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupDatabaseKeyVersion(context.Background(), wrapper, tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_LatestDatabaseKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestDatabaseKey(t, conn, rk.PrivateId)
tests := []struct {
name string
createCnt int
keyWrapper wrapping.Wrapper
wantVersion uint32
wantErr bool
wantIsError errors.Code
}{
{
name: "5",
createCnt: 5,
keyWrapper: rkvWrapper,
wantVersion: 5,
wantErr: false,
},
{
name: "1",
createCnt: 1,
keyWrapper: rkvWrapper,
wantVersion: 1,
wantErr: false,
},
{
name: "0",
createCnt: 0,
keyWrapper: rkvWrapper,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
{
name: "nil-wrapper",
createCnt: 5,
keyWrapper: nil,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocDatabaseKeyVersion(); return &i }(), "1=1")
testKeys := []*kms.DatabaseKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestDatabaseKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("test database key"))
testKeys = append(testKeys, k)
}
assert.Equal(tt.createCnt, len(testKeys))
got, err := repo.LatestDatabaseKeyVersion(context.Background(), tt.keyWrapper, dk.PrivateId)
if tt.wantErr {
require.Error(err)
assert.Nil(got)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
require.NotNil(got)
assert.Equal(tt.wantVersion, got.Version)
})
}
}
func TestRepository_ListDatabaseKeyVersions(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestDatabaseKey(t, conn, rk.PrivateId)
type args struct {
databaseKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
databaseKeyId: dk.PrivateId,
keyWrapper: rkvWrapper,
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1,
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
databaseKeyId: dk.PrivateId,
},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
databaseKeyId: dk.PrivateId,
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "bad-org",
createCnt: 1,
args: args{
keyWrapper: rkvWrapper,
databaseKeyId: "bad-id",
},
wantCnt: 0,
wantErr: false,
},
{
name: "nil-wrapper",
createCnt: 1,
args: args{
keyWrapper: nil,
databaseKeyId: dk.PrivateId,
},
wantCnt: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocDatabaseKeyVersion(); return &i }(), "1=1")
keyVersions := []*kms.DatabaseKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestDatabaseKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("database key"))
keyVersions = append(keyVersions, k)
}
assert.Equal(tt.createCnt, len(keyVersions))
got, err := repo.ListDatabaseKeyVersions(context.Background(), tt.args.keyWrapper, tt.args.databaseKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-162
View File
@@ -1,162 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateOidcKey inserts into the repository and returns the new oidc key and
// oidc key version. There are no valid options at this time.
func (r *Repository) CreateOidcKey(ctx context.Context, rkvWrapper wrapping.Wrapper, key []byte, _ ...Option) (*OidcKey, *OidcKeyVersion, error) {
const op = "kms.(Repository).CreateOidcKey"
var returnedDk, returnedDv interface{}
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
var err error
if returnedDk, returnedDv, err = createOidcKeyTx(ctx, reader, w, rkvWrapper, key); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
return returnedDk.(*OidcKey), returnedDv.(*OidcKeyVersion), nil
}
// createOidcKeyTx inserts into the db (via db.Writer) and returns the new oidc key
// and oidc key version. This function encapsulates all the work required within
// a db.TxHandler and allows this capability to be shared with the iam repo.
func createOidcKeyTx(ctx context.Context, r db.Reader, w db.Writer, rkvWrapper wrapping.Wrapper, key []byte) (*OidcKey, *OidcKeyVersion, error) {
const op = "kms.createOidcKeyTx"
if rkvWrapper == nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
if len(key) == 0 {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup root key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
rv := AllocRootKeyVersion()
rv.PrivateId = rootKeyVersionId
if err := r.LookupById(ctx, &rv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to lookup root key version %s", rootKeyVersionId)))
}
dk := AllocOidcKey()
dv := AllocOidcKeyVersion()
id, err := newOidcKeyId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
dk.PrivateId = id
dk.RootKeyId = rv.RootKeyId
id, err = newOidcKeyVersionId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
dv.PrivateId = id
dv.OidcKeyId = dk.PrivateId
dv.RootKeyVersionId = rootKeyVersionId
dv.Key = key
if err := dv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
// no oplog entries for keys
if err := w.Create(ctx, &dk); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("keys create"))
}
// no oplog entries for key versions
if err := w.Create(ctx, &dv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("key versions create"))
}
return &dk, &dv, nil
}
// LookupOidcKey will look up a key in the repository. If the key is not
// found, it will return nil, nil.
func (r *Repository) LookupOidcKey(ctx context.Context, privateId string, _ ...Option) (*OidcKey, error) {
const op = "kms.(Repository).LookupOidcKey"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocOidcKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return &k, nil
}
// DeleteOidcKey deletes the key for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteOidcKey(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteOidcKey"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocOidcKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for root keys
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// ListOidcKeys will list the keys. Supports the WithLimit option.
func (r *Repository) ListOidcKeys(ctx context.Context, opt ...Option) ([]Dek, error) {
const op = "kms.(Repository).ListOidcKeys"
var keys []*OidcKey
err := r.list(ctx, &keys, "1=1", nil, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
deks := make([]Dek, 0, len(keys))
for _, key := range keys {
deks = append(deks, key)
}
return deks, nil
}
-295
View File
@@ -1,295 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateOidcKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
type args struct {
scopeId string
key []byte
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid-org",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: rkvWrapper,
},
wantErr: false,
},
{
name: "nil-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
key: []byte(""),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-rkv-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: wrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "wrapper-missing-id",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: func() wrapping.Wrapper {
w := db.TestWrapper(t)
_, err = w.(*aead.Wrapper).SetConfig(context.Background())
require.NoError(t, err)
return w
}(),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
dk, dv, err := repo.CreateOidcKey(context.Background(), tt.args.keyWrapper, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(dk)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(dk.CreateTime)
foundKey, err := repo.LookupOidcKey(context.Background(), dk.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, dk))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, dk.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
assert.NotNil(dv.CreateTime)
foundKeyVersion, err := repo.LookupOidcKeyVersion(context.Background(), tt.args.keyWrapper, dv.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKeyVersion, dv))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, dv.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteOidcKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
key *kms.OidcKey
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestOidcKey(t, conn, rk.PrivateId),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.OidcKey {
k := kms.AllocOidcKey()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.OidcKey {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k := kms.AllocOidcKey()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteOidcKey(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupOidcKey(context.Background(), tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_ListOidcKeys(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
wrapper := db.TestWrapper(t)
type args struct {
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1, // org and project both have keys, plus global scope
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
}
for _, tt := range tests {
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
for i := 0; i < tt.createCnt; i++ {
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "scope_id in(?)", "global", org.PublicId, proj.PublicId)
rk := kms.TestRootKey(t, conn, proj.PublicId)
kms.TestOidcKey(t, conn, rk.PrivateId)
require.NoError(err)
}
got, err := repo.ListOidcKeys(context.Background(), tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-176
View File
@@ -1,176 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateOidcKeyVersion inserts into the repository and returns the new key
// version with its PrivateId. There are no valid options at this time.
func (r *Repository) CreateOidcKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, oidcKeyId string, key []byte, _ ...Option) (*OidcKeyVersion, error) {
const op = "kms.(Repository).CreateOidcKeyVersion"
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to fetch key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
if oidcKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing oidc key id")
}
if len(key) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
kv := AllocOidcKeyVersion()
id, err := newOidcKeyVersionId()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
kv.PrivateId = id
kv.RootKeyVersionId = rootKeyVersionId
kv.Key = key
kv.OidcKeyId = oidcKeyId
if err := kv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var returnedKey interface{}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedKey = kv.Clone()
// no oplog entries for root key version
if err := w.Create(ctx, returnedKey); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s oidc key id", kv.OidcKeyId)))
}
return returnedKey.(*OidcKeyVersion), nil
}
// LookupOidcKeyVersion will look up a key version in the repository. If
// the key version is not found, it will return nil, nil.
func (r *Repository) LookupOidcKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*OidcKeyVersion, error) {
const op = "kms.(Repository).LookupOidcKeyVersion"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
k := AllocOidcKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
if err := k.Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &k, nil
}
// DeleteOidcKeyVersion deletes the key version for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteOidcKeyVersion(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteOidcKeyVersion"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocOidcKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for the key version
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// LatestOidcKeyVersion searches for the key version with the highest
// version number. When no results are found, it returns nil with an
// errors.RecordNotFound error.
func (r *Repository) LatestOidcKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, OidcKeyId string, _ ...Option) (*OidcKeyVersion, error) {
const op = "kms.(Repository).LatestOidcKeyVersion"
if OidcKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing oidc key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var foundKeys []*OidcKeyVersion
if err := r.reader.SearchWhere(ctx, &foundKeys, "oidc_key_id = ?", []interface{}{OidcKeyId}, db.WithLimit(1), db.WithOrder("version desc")); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", OidcKeyId)))
}
if len(foundKeys) == 0 {
return nil, errors.E(ctx, errors.WithCode(errors.RecordNotFound), errors.WithOp(op))
}
if err := foundKeys[0].Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return foundKeys[0], nil
}
// ListOidcKeyVersions will lists versions of a key. Supports the WithLimit option.
func (r *Repository) ListOidcKeyVersions(ctx context.Context, rkvWrapper wrapping.Wrapper, OidcKeyId string, opt ...Option) ([]DekVersion, error) {
const op = "kms.(Repository).ListOidcKeyVersions"
if OidcKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing oidc key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var versions []*OidcKeyVersion
err := r.list(ctx, &versions, "oidc_key_id = ?", []interface{}{OidcKeyId}, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
for i, k := range versions {
if err := k.Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error decrypting key num %d", i)))
}
}
dekVersions := make([]DekVersion, 0, len(versions))
for _, version := range versions {
dekVersions = append(dekVersions, version)
}
return dekVersions, nil
}
@@ -1,367 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateOidcKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOidcKey(t, conn, rk.PrivateId)
type args struct {
key []byte
OidcKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: []byte("test key"),
keyWrapper: rkvWrapper,
OidcKeyId: dk.PrivateId,
},
wantErr: false,
},
{
name: "invalid-rkv-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: wrapper,
OidcKeyId: dk.PrivateId,
},
wantErr: true,
},
{
name: "empty-key",
args: args{
key: nil,
keyWrapper: wrapper,
OidcKeyId: dk.PrivateId,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
k, err := repo.CreateOidcKeyVersion(context.Background(), tt.args.keyWrapper, tt.args.OidcKeyId, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(k)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(k.CreateTime)
foundKey, err := repo.LookupOidcKeyVersion(context.Background(), tt.args.keyWrapper, k.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, k))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, k.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteOidcKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOidcKey(t, conn, rk.PrivateId)
type args struct {
key *kms.OidcKeyVersion
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestOidcKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("oidc key")),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.OidcKeyVersion {
k := kms.AllocOidcKeyVersion()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.OidcKeyVersion {
id, err := db.NewPublicId(kms.OidcKeyPrefix)
require.NoError(t, err)
k := kms.AllocOidcKeyVersion()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteOidcKeyVersion(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupOidcKeyVersion(context.Background(), wrapper, tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_LatestOidcKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOidcKey(t, conn, rk.PrivateId)
tests := []struct {
name string
createCnt int
keyWrapper wrapping.Wrapper
wantVersion uint32
wantErr bool
wantIsError errors.Code
}{
{
name: "5",
createCnt: 5,
keyWrapper: rkvWrapper,
wantVersion: 5,
wantErr: false,
},
{
name: "1",
createCnt: 1,
keyWrapper: rkvWrapper,
wantVersion: 1,
wantErr: false,
},
{
name: "0",
createCnt: 0,
keyWrapper: rkvWrapper,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
{
name: "nil-wrapper",
createCnt: 5,
keyWrapper: nil,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOidcKeyVersion(); return &i }(), "1=1")
testKeys := []*kms.OidcKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestOidcKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("test oidc key"))
testKeys = append(testKeys, k)
}
assert.Equal(tt.createCnt, len(testKeys))
got, err := repo.LatestOidcKeyVersion(context.Background(), tt.keyWrapper, dk.PrivateId)
if tt.wantErr {
require.Error(err)
assert.Nil(got)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
require.NotNil(got)
assert.Equal(tt.wantVersion, got.Version)
})
}
}
func TestRepository_ListOidcKeyVersions(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOidcKey(t, conn, rk.PrivateId)
type args struct {
OidcKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
OidcKeyId: dk.PrivateId,
keyWrapper: rkvWrapper,
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1,
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
OidcKeyId: dk.PrivateId,
},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
OidcKeyId: dk.PrivateId,
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "bad-org",
createCnt: 1,
args: args{
keyWrapper: rkvWrapper,
OidcKeyId: "bad-id",
},
wantCnt: 0,
wantErr: false,
},
{
name: "nil-wrapper",
createCnt: 1,
args: args{
keyWrapper: nil,
OidcKeyId: dk.PrivateId,
},
wantCnt: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOidcKeyVersion(); return &i }(), "1=1")
keyVersions := []*kms.OidcKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestOidcKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("oidc key"))
keyVersions = append(keyVersions, k)
}
assert.Equal(tt.createCnt, len(keyVersions))
got, err := repo.ListOidcKeyVersions(context.Background(), tt.args.keyWrapper, tt.args.OidcKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-162
View File
@@ -1,162 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateOplogKey inserts into the repository and returns the new key and the
// key version. There are no valid options at this time.
func (r *Repository) CreateOplogKey(ctx context.Context, rkvWrapper wrapping.Wrapper, key []byte, _ ...Option) (*OplogKey, *OplogKeyVersion, error) {
const op = "kms.(Repository).CreateOplogKey"
var returnedDk, returnedDv interface{}
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
var err error
if returnedDk, returnedDv, err = createOplogKeyTx(ctx, reader, w, rkvWrapper, key); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
return returnedDk.(*OplogKey), returnedDv.(*OplogKeyVersion), nil
}
// createOplogKeyTx inserts into the db (via db.Writer) and returns the new key
// and the key version. This function encapsulates all the work required within
// a db.TxHandler and allows this capability to be shared with the iam repo.
func createOplogKeyTx(ctx context.Context, r db.Reader, w db.Writer, rkvWrapper wrapping.Wrapper, key []byte) (*OplogKey, *OplogKeyVersion, error) {
const op = "kms.createOplogKeyTx"
if rkvWrapper == nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
if len(key) == 0 {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup root key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
rv := AllocRootKeyVersion()
rv.PrivateId = rootKeyVersionId
if err := r.LookupById(ctx, &rv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to lookup root key version %s", rootKeyVersionId)))
}
opk := AllocOplogKey()
opv := AllocOplogKeyVersion()
id, err := newOplogKeyId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
opk.PrivateId = id
opk.RootKeyId = rv.RootKeyId
id, err = newOplogKeyVersionId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
opv.PrivateId = id
opv.OplogKeyId = opk.PrivateId
opv.RootKeyVersionId = rootKeyVersionId
opv.Key = key
if err := opv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
// no oplog entries for keys
if err := w.Create(ctx, &opk); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("key create"))
}
// no oplog entries for key versions
if err := w.Create(ctx, &opv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("key versions create"))
}
return &opk, &opv, nil
}
// LookupOplogKey will look up a key in the repository. If the key is not
// found, it will return nil, nil.
func (r *Repository) LookupOplogKey(ctx context.Context, privateId string, _ ...Option) (*OplogKey, error) {
const op = "kms.(Repository).LookupOplogKey"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocOplogKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return &k, nil
}
// DeleteOplogKey deletes the key for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteOplogKey(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteOplogKey"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocOplogKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for root keys
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// ListOplogKeys will list the keys. Supports the WithLimit option.
func (r *Repository) ListOplogKeys(ctx context.Context, opt ...Option) ([]Dek, error) {
const op = "kms.(Repository).ListOplogKeys"
var keys []*OplogKey
err := r.list(ctx, &keys, "1=1", nil, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
deks := make([]Dek, 0, len(keys))
for _, key := range keys {
deks = append(deks, key)
}
return deks, nil
}
-295
View File
@@ -1,295 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateOplogKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
type args struct {
scopeId string
key []byte
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid-org",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: rkvWrapper,
},
wantErr: false,
},
{
name: "nil-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
key: []byte(""),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-rkv-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: wrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "wrapper-missing-id",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: func() wrapping.Wrapper {
w := db.TestWrapper(t)
_, err = w.(*aead.Wrapper).SetConfig(context.Background())
require.NoError(t, err)
return w
}(),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
opk, opv, err := repo.CreateOplogKey(context.Background(), tt.args.keyWrapper, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(opk)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(opk.CreateTime)
foundKey, err := repo.LookupOplogKey(context.Background(), opk.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, opk))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, opk.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
assert.NotNil(opv.CreateTime)
foundKeyVersion, err := repo.LookupOplogKeyVersion(context.Background(), tt.args.keyWrapper, opv.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKeyVersion, opv))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, opv.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteOplogKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
key *kms.OplogKey
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestOplogKey(t, conn, rk.PrivateId),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.OplogKey {
k := kms.AllocOplogKey()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.OplogKey {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k := kms.AllocOplogKey()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteOplogKey(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupOplogKey(context.Background(), tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_ListOplogKeys(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
wrapper := db.TestWrapper(t)
type args struct {
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1, // org and project both have keys, plus global scope
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
}
for _, tt := range tests {
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
for i := 0; i < tt.createCnt; i++ {
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "scope_id in(?)", "global", org.PublicId, proj.PublicId)
rk := kms.TestRootKey(t, conn, proj.PublicId)
kms.TestOplogKey(t, conn, rk.PrivateId)
require.NoError(err)
}
got, err := repo.ListOplogKeys(context.Background(), tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
@@ -1,176 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateOplogKeyVersion inserts into the repository and returns the new key
// version with its PrivateId. There are no valid options at this time.
func (r *Repository) CreateOplogKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, oplogKeyId string, key []byte, _ ...Option) (*OplogKeyVersion, error) {
const op = "kms.(Repository).CreateOplogKeyVersion"
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to fetch key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
if oplogKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing oplog key id")
}
if len(key) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
kv := AllocOplogKeyVersion()
id, err := newOplogKeyVersionId()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
kv.PrivateId = id
kv.RootKeyVersionId = rootKeyVersionId
kv.Key = key
kv.OplogKeyId = oplogKeyId
if err := kv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var returnedKey interface{}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedKey = kv.Clone()
// no oplog entries for root key version
if err := w.Create(ctx, returnedKey); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s oplog key id", kv.OplogKeyId)))
}
return returnedKey.(*OplogKeyVersion), nil
}
// LookupOplogKeyVersion will look up a key version in the repository. If
// the key version is not found, it will return nil, nil.
func (r *Repository) LookupOplogKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*OplogKeyVersion, error) {
const op = "kms.(Repository).LookupOplogKeyVersion"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
k := AllocOplogKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
if err := k.Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &k, nil
}
// DeleteOplogKeyVersion deletes the key version for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteOplogKeyVersion(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteOplogKeyVersion"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocOplogKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for the key version
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// LatestOplogKeyVersion searches for the key version with the highest
// version number. When no results are found, it returns nil with an
// errors.RecordNotFound error.
func (r *Repository) LatestOplogKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, oplogKeyId string, _ ...Option) (*OplogKeyVersion, error) {
const op = "kms.(Repository).LatestOplogKeyVersion"
if oplogKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing oplog key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var foundKeys []*OplogKeyVersion
if err := r.reader.SearchWhere(ctx, &foundKeys, "oplog_key_id = ?", []interface{}{oplogKeyId}, db.WithLimit(1), db.WithOrder("version desc")); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", oplogKeyId)))
}
if len(foundKeys) == 0 {
return nil, errors.E(ctx, errors.WithCode(errors.RecordNotFound), errors.WithOp(op))
}
if err := foundKeys[0].Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return foundKeys[0], nil
}
// ListOplogKeyVersions will lists versions of a key. Supports the WithLimit option.
func (r *Repository) ListOplogKeyVersions(ctx context.Context, rkvWrapper wrapping.Wrapper, oplogKeyId string, opt ...Option) ([]DekVersion, error) {
const op = "kms.(Repository).ListOplogKeyVersions"
if oplogKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing oplog key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var versions []*OplogKeyVersion
err := r.list(ctx, &versions, "oplog_key_id = ?", []interface{}{oplogKeyId}, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
for i, k := range versions {
if err := k.Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error decrypting key num %d", i)))
}
}
dekVersions := make([]DekVersion, 0, len(versions))
for _, version := range versions {
dekVersions = append(dekVersions, version)
}
return dekVersions, nil
}
@@ -1,367 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateOplogKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOplogKey(t, conn, rk.PrivateId)
type args struct {
key []byte
oplogKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: []byte("test key"),
keyWrapper: rkvWrapper,
oplogKeyId: dk.PrivateId,
},
wantErr: false,
},
{
name: "invalid-rkv-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: wrapper,
oplogKeyId: dk.PrivateId,
},
wantErr: true,
},
{
name: "empty-key",
args: args{
key: nil,
keyWrapper: wrapper,
oplogKeyId: dk.PrivateId,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
k, err := repo.CreateOplogKeyVersion(context.Background(), tt.args.keyWrapper, tt.args.oplogKeyId, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(k)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(k.CreateTime)
foundKey, err := repo.LookupOplogKeyVersion(context.Background(), tt.args.keyWrapper, k.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, k))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, k.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteOplogKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOplogKey(t, conn, rk.PrivateId)
type args struct {
key *kms.OplogKeyVersion
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestOplogKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("oplog key")),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.OplogKeyVersion {
k := kms.AllocOplogKeyVersion()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.OplogKeyVersion {
id, err := db.NewPublicId(kms.OplogKeyPrefix)
require.NoError(t, err)
k := kms.AllocOplogKeyVersion()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteOplogKeyVersion(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupOplogKeyVersion(context.Background(), wrapper, tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_LatestOplogKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOplogKey(t, conn, rk.PrivateId)
tests := []struct {
name string
createCnt int
keyWrapper wrapping.Wrapper
wantVersion uint32
wantErr bool
wantIsError errors.Code
}{
{
name: "5",
createCnt: 5,
keyWrapper: rkvWrapper,
wantVersion: 5,
wantErr: false,
},
{
name: "1",
createCnt: 1,
keyWrapper: rkvWrapper,
wantVersion: 1,
wantErr: false,
},
{
name: "0",
createCnt: 0,
keyWrapper: rkvWrapper,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
{
name: "nil-wrapper",
createCnt: 5,
keyWrapper: nil,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOplogKeyVersion(); ; return &i }(), "1=1")
testKeys := []*kms.OplogKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestOplogKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("test oplog key"))
testKeys = append(testKeys, k)
}
assert.Equal(tt.createCnt, len(testKeys))
got, err := repo.LatestOplogKeyVersion(context.Background(), tt.keyWrapper, dk.PrivateId)
if tt.wantErr {
require.Error(err)
assert.Nil(got)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
require.NotNil(got)
assert.Equal(tt.wantVersion, got.Version)
})
}
}
func TestRepository_ListOplogKeyVersions(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOplogKey(t, conn, rk.PrivateId)
type args struct {
oplogKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
oplogKeyId: dk.PrivateId,
keyWrapper: rkvWrapper,
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1,
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
oplogKeyId: dk.PrivateId,
},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
oplogKeyId: dk.PrivateId,
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "bad-org",
createCnt: 1,
args: args{
keyWrapper: rkvWrapper,
oplogKeyId: "bad-id",
},
wantCnt: 0,
wantErr: false,
},
{
name: "nil-wrapper",
createCnt: 1,
args: args{
keyWrapper: nil,
oplogKeyId: dk.PrivateId,
},
wantCnt: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOplogKeyVersion(); return &i }(), "1=1")
keyVersions := []*kms.OplogKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestOplogKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("oplog key"))
keyVersions = append(keyVersions, k)
}
assert.Equal(tt.createCnt, len(keyVersions))
got, err := repo.ListOplogKeyVersions(context.Background(), tt.args.keyWrapper, tt.args.oplogKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-146
View File
@@ -1,146 +0,0 @@
package kms
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateRootKey inserts into the repository and returns the new root key and
// root key version. There are no valid options at this time.
func (r *Repository) CreateRootKey(ctx context.Context, keyWrapper wrapping.Wrapper, scopeId string, key []byte, _ ...Option) (*RootKey, *RootKeyVersion, error) {
const op = "kms.(Repository).CreateRootKey"
var returnedRk, returnedKv interface{}
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
var err error
if returnedRk, returnedKv, err = createRootKeyTx(ctx, w, keyWrapper, scopeId, key); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", scopeId)))
}
return returnedRk.(*RootKey), returnedKv.(*RootKeyVersion), nil
}
// createRootKeyTx inserts into the db (via db.Writer) and returns the new root key
// and root key version. This function encapsulates all the work required within
// a db.TxHandler and allows this capability to be shared with the iam repo.
func createRootKeyTx(ctx context.Context, w db.Writer, keyWrapper wrapping.Wrapper, scopeId string, key []byte) (*RootKey, *RootKeyVersion, error) {
const op = "kms.createRootKeyTx"
if scopeId == "" {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
}
if keyWrapper == nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
if len(key) == 0 {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
rk := AllocRootKey()
kv := AllocRootKeyVersion()
id, err := newRootKeyId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
rk.PrivateId = id
rk.ScopeId = scopeId
id, err = newRootKeyVersionId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
kv.PrivateId = id
kv.RootKeyId = rk.PrivateId
kv.Key = key
if err := kv.Encrypt(ctx, keyWrapper); err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
// no oplog entries for root keys
if err := w.Create(ctx, &rk); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("root keys"))
}
// no oplog entries for root key versions
if err := w.Create(ctx, &kv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("key versions"))
}
return &rk, &kv, nil
}
// LookupRootKey will look up a root key in the repository. If the key is not
// found, it will return nil, nil.
func (r *Repository) LookupRootKey(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*RootKey, error) {
const op = "kms.(Repository).LookupRootKey"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
k := AllocRootKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return &k, nil
}
// DeleteRootKey deletes the root key for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteRootKey(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteRootKey"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocRootKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for root keys
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// ListRootKeys will list the root keys. Supports the WithLimit option.
func (r *Repository) ListRootKeys(ctx context.Context, opt ...Option) ([]*RootKey, error) {
const op = "kms.(Repository).ListRootKeys"
var keys []*RootKey
err := r.list(ctx, &keys, "1=1", nil, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return keys, nil
}
-270
View File
@@ -1,270 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateRootKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
type args struct {
scopeId string
key []byte
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid-org",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: wrapper,
},
wantErr: false,
},
{
name: "valid-global",
args: args{
scopeId: "global",
key: []byte("valid-global"),
keyWrapper: wrapper,
},
wantErr: false,
},
{
name: "invalid-scope",
args: args{
scopeId: "o_notAValidScopeId",
key: []byte("invalid-scope"),
keyWrapper: wrapper,
},
wantErr: true,
},
{
name: "empty-scope",
args: args{
key: []byte("empty-scope"),
keyWrapper: wrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
rk, kv, err := repo.CreateRootKey(context.Background(), tt.args.keyWrapper, tt.args.scopeId, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(rk)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(rk.CreateTime)
foundKey, err := repo.LookupRootKey(context.Background(), tt.args.keyWrapper, rk.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, rk))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, rk.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
assert.NotNil(kv.CreateTime)
foundKeyVersion, err := repo.LookupRootKeyVersion(context.Background(), tt.args.keyWrapper, kv.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKeyVersion, kv))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, kv.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteRootKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
type args struct {
key *kms.RootKey
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestRootKey(t, conn, org.PublicId),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.RootKey {
k := kms.AllocRootKey()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.RootKey {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k := kms.AllocRootKey()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteRootKey(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupRootKey(context.Background(), wrapper, tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_ListRootKeys(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
wrapper := db.TestWrapper(t)
type args struct {
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: (repo.DefaultLimit()+1)*2 + 1, // org and project both have keys, plus global scope
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
}
for _, tt := range tests {
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
for i := 0; i < tt.createCnt; i++ {
_, _ = iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
}
got, err := repo.ListRootKeys(context.Background(), tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-160
View File
@@ -1,160 +0,0 @@
package kms
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateRootKeyVersion inserts into the repository and returns the new root key
// version with its PrivateId. There are no valid options at this time.
func (r *Repository) CreateRootKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, rootKeyId string, key []byte, _ ...Option) (*RootKeyVersion, error) {
const op = "kms.(Repository).CreateRootKeyVersion"
if rootKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
if len(key) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
kv := AllocRootKeyVersion()
id, err := newRootKeyVersionId()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
kv.PrivateId = id
kv.RootKeyId = rootKeyId
kv.Key = key
if err := kv.Encrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var returnedKey interface{}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedKey = kv.Clone()
// no oplog entries for root key version
if err := w.Create(ctx, returnedKey); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s root key id", kv.RootKeyId)))
}
return returnedKey.(*RootKeyVersion), nil
}
// LookupRootKeyVersion will look up a root key version in the repository. If
// the key version is not found, it will return nil, nil.
func (r *Repository) LookupRootKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*RootKeyVersion, error) {
const op = "kms.(Repository).LookupRootKeyVersion"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
k := AllocRootKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
if err := k.Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &k, nil
}
// DeleteRootKeyVersion deletes the root key version for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteRootKeyVersion(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteRootKeyVersion"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocRootKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for root key version
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// LatestRootKeyVersion searches for the root key version with the highest
// version number. When no results are found, it returns nil with an
// errors.RecordNotFound error.
func (r *Repository) LatestRootKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, rootKeyId string, _ ...Option) (*RootKeyVersion, error) {
const op = "kms.(Repository).LatestRootKeyVersion"
if rootKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
var foundKeys []RootKeyVersion
if err := r.reader.SearchWhere(ctx, &foundKeys, "root_key_id = ?", []interface{}{rootKeyId}, db.WithLimit(1), db.WithOrder("version desc")); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", rootKeyId)))
}
if len(foundKeys) == 0 {
return nil, errors.E(ctx, errors.WithCode(errors.RecordNotFound), errors.WithOp(op))
}
if err := foundKeys[0].Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &foundKeys[0], nil
}
// ListRootKeyVersions in versions of a root key. Supports the WithLimit option.
func (r *Repository) ListRootKeyVersions(ctx context.Context, keyWrapper wrapping.Wrapper, rootKeyId string, opt ...Option) ([]*RootKeyVersion, error) {
const op = "kms.(Repository).ListRootKeyVersions"
if rootKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
var versions []*RootKeyVersion
err := r.list(ctx, &versions, "root_key_id = ?", []interface{}{rootKeyId}, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
for i, k := range versions {
if err := k.Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error decrypting key num %d", i)))
}
}
return versions, nil
}
@@ -1,363 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateRootKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootKeyId string
key []byte
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: []byte("test key"),
rootKeyId: rk.PrivateId,
keyWrapper: wrapper,
},
wantErr: false,
},
{
name: "invalid-root-key",
args: args{
key: []byte("test key"),
rootKeyId: "krk_thisIsNotValid",
keyWrapper: wrapper,
},
wantErr: true,
},
{
name: "empty-key",
args: args{
key: nil,
rootKeyId: rk.PrivateId,
keyWrapper: wrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
key: []byte("test key"),
rootKeyId: rk.PrivateId,
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
k, err := repo.CreateRootKeyVersion(context.Background(), tt.args.keyWrapper, tt.args.rootKeyId, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(k)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(k.CreateTime)
foundKey, err := repo.LookupRootKeyVersion(context.Background(), tt.args.keyWrapper, k.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, k))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, k.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteRootKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
key *kms.RootKeyVersion
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: func() *kms.RootKeyVersion {
k, _ := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
return k
}(),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.RootKeyVersion {
k := kms.AllocRootKeyVersion()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.RootKeyVersion {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k := kms.AllocRootKeyVersion()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteRootKeyVersion(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupRootKeyVersion(context.Background(), wrapper, tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_LatestRootKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
tests := []struct {
name string
createCnt int
keyWrapper wrapping.Wrapper
wantVersion uint32
wantErr bool
wantIsError errors.Code
}{
{
name: "5",
createCnt: 5,
keyWrapper: wrapper,
wantVersion: 5,
wantErr: false,
},
{
name: "1",
createCnt: 1,
keyWrapper: wrapper,
wantVersion: 1,
wantErr: false,
},
{
name: "0",
createCnt: 0,
keyWrapper: wrapper,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
{
name: "nil-wrapper",
createCnt: 5,
keyWrapper: nil,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKeyVersion(); return &i }(), "1=1")
testKeys := []*kms.RootKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k, _ := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
testKeys = append(testKeys, k)
}
assert.Equal(tt.createCnt, len(testKeys))
got, err := repo.LatestRootKeyVersion(context.Background(), tt.keyWrapper, rk.PrivateId)
if tt.wantErr {
require.Error(err)
assert.Nil(got)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
require.NotNil(got)
assert.Equal(tt.wantVersion, got.Version)
})
}
}
func TestRepository_ListRootKeyVersions(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
rootKeyId: rk.PrivateId,
keyWrapper: wrapper,
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1,
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: wrapper,
rootKeyId: rk.PrivateId,
},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: wrapper,
rootKeyId: rk.PrivateId,
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "bad-org",
createCnt: 1,
args: args{
keyWrapper: wrapper,
rootKeyId: "bad-id",
},
wantCnt: 0,
wantErr: false,
},
{
name: "nil-wrapper",
createCnt: 1,
args: args{
keyWrapper: nil,
rootKeyId: rk.PrivateId,
},
wantCnt: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKeyVersion(); return &i }(), "1=1")
testRootKeyVersions := []*kms.RootKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k, _ := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
testRootKeyVersions = append(testRootKeyVersions, k)
}
assert.Equal(tt.createCnt, len(testRootKeyVersions))
got, err := repo.ListRootKeyVersions(context.Background(), tt.args.keyWrapper, tt.args.rootKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-162
View File
@@ -1,162 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateSessionKey inserts into the repository and returns the new key and the
// key version. There are no valid options at this time.
func (r *Repository) CreateSessionKey(ctx context.Context, rkvWrapper wrapping.Wrapper, key []byte, _ ...Option) (*SessionKey, *SessionKeyVersion, error) {
const op = "kms.(Repository).CreateSessionKey"
var returnedDk, returnedDv interface{}
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
var err error
if returnedDk, returnedDv, err = createSessionKeyTx(ctx, reader, w, rkvWrapper, key); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
return returnedDk.(*SessionKey), returnedDv.(*SessionKeyVersion), nil
}
// createSessionKeyTx inserts into the db (via db.Writer) and returns the new key
// and the key version. This function encapsulates all the work required within
// a db.TxHandler and allows this capability to be shared with the iam repo.
func createSessionKeyTx(ctx context.Context, r db.Reader, w db.Writer, rkvWrapper wrapping.Wrapper, key []byte) (*SessionKey, *SessionKeyVersion, error) {
const op = "kms.createSessionKeyTx"
if rkvWrapper == nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
if len(key) == 0 {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup root key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
rv := AllocRootKeyVersion()
rv.PrivateId = rootKeyVersionId
if err := r.LookupById(ctx, &rv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to lookup root key version %s", rootKeyVersionId)))
}
tk := AllocSessionKey()
tv := AllocSessionKeyVersion()
id, err := newSessionKeyId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
tk.PrivateId = id
tk.RootKeyId = rv.RootKeyId
id, err = newSessionKeyVersionId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
tv.PrivateId = id
tv.SessionKeyId = tk.PrivateId
tv.RootKeyVersionId = rootKeyVersionId
tv.Key = key
if err := tv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
// no session entries for keys
if err := w.Create(ctx, &tk); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("keys create"))
}
// no session entries for key versions
if err := w.Create(ctx, &tv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("key versions create"))
}
return &tk, &tv, nil
}
// LookupSessionKey will look up a key in the repository. If the key is not
// found, it will return nil, nil.
func (r *Repository) LookupSessionKey(ctx context.Context, privateId string, _ ...Option) (*SessionKey, error) {
const op = "kms.(Repository).LookupSessionKey"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocSessionKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return &k, nil
}
// DeleteSessionKey deletes the key for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteSessionKey(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteSessionKey"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocSessionKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no session entries for root keys
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// ListSessionKeys will list the keys. Supports the WithLimit option.
func (r *Repository) ListSessionKeys(ctx context.Context, opt ...Option) ([]Dek, error) {
const op = "kms.(Repository).ListSessionKeys"
var keys []*SessionKey
err := r.list(ctx, &keys, "1=1", nil, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
deks := make([]Dek, 0, len(keys))
for _, key := range keys {
deks = append(deks, key)
}
return deks, nil
}
-296
View File
@@ -1,296 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateSessionKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
type args struct {
scopeId string
key []byte
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid-org",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: rkvWrapper,
},
wantErr: false,
},
{
name: "nil-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
key: []byte(""),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-rkv-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: wrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "wrapper-missing-id",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: func() wrapping.Wrapper {
w := db.TestWrapper(t)
_, err = w.(*aead.Wrapper).SetConfig(context.Background())
require.NoError(t, err)
return w
}(),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tk, tv, err := repo.CreateSessionKey(context.Background(), tt.args.keyWrapper, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(tk)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(tk.CreateTime)
foundKey, err := repo.LookupSessionKey(context.Background(), tk.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, tk))
// make sure there was no session written
err = db.TestVerifyOplog(t, rw, tk.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
assert.NotNil(tv.CreateTime)
foundKeyVersion, err := repo.LookupSessionKeyVersion(context.Background(), tt.args.keyWrapper, tv.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKeyVersion, tv))
// make sure there was no session written
err = db.TestVerifyOplog(t, rw, tv.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteSessionKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
key *kms.SessionKey
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestSessionKey(t, conn, rk.PrivateId),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.SessionKey {
k := kms.AllocSessionKey()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.SessionKey {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k := kms.AllocSessionKey()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteSessionKey(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
// make sure there was no session written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupSessionKey(context.Background(), tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no session written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_ListSessionKeys(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
wrapper := db.TestWrapper(t)
type args struct {
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1, // org and project both have keys, plus global scope
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
}
for _, tt := range tests {
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
for i := 0; i < tt.createCnt; i++ {
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "scope_id in(?)", "global", org.PublicId, proj.PublicId)
rk := kms.TestRootKey(t, conn, proj.PublicId)
kms.TestSessionKey(t, conn, rk.PrivateId)
require.NoError(err)
}
got, err := repo.ListSessionKeys(context.Background(), tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
@@ -1,176 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateSessionKeyVersion inserts into the repository and returns the new key
// version with its PrivateId. There are no valid options at this time.
func (r *Repository) CreateSessionKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, sessionKeyId string, key []byte, _ ...Option) (*SessionKeyVersion, error) {
const op = "kms.(Repository).CreateSessionKeyVersion"
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to fetch key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
if sessionKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing session key id")
}
if len(key) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
kv := AllocSessionKeyVersion()
id, err := newSessionKeyVersionId()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
kv.PrivateId = id
kv.RootKeyVersionId = rootKeyVersionId
kv.Key = key
kv.SessionKeyId = sessionKeyId
if err := kv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var returnedKey interface{}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedKey = kv.Clone()
// no session entries for root key version
if err := w.Create(ctx, returnedKey); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s session key id", kv.SessionKeyId)))
}
return returnedKey.(*SessionKeyVersion), nil
}
// LookupSessionKeyVersion will look up a key version in the repository. If
// the key version is not found, it will return nil, nil.
func (r *Repository) LookupSessionKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*SessionKeyVersion, error) {
const op = "kms.(Repository).LookupSessionKeyVersion"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
k := AllocSessionKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
if err := k.Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &k, nil
}
// DeleteSessionKeyVersion deletes the key version for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteSessionKeyVersion(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteSessionKeyVersion"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocSessionKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no session entries for the key version
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// LatestSessionKeyVersion searches for the key version with the highest
// version number. When no results are found, it returns nil with an
// errors.RecordNotFound error.
func (r *Repository) LatestSessionKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, sessionKeyId string, _ ...Option) (*SessionKeyVersion, error) {
const op = "kms.(Repository).LatestSessionKeyVersion"
if sessionKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing session key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var foundKeys []*SessionKeyVersion
if err := r.reader.SearchWhere(ctx, &foundKeys, "session_key_id = ?", []interface{}{sessionKeyId}, db.WithLimit(1), db.WithOrder("version desc")); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", sessionKeyId)))
}
if len(foundKeys) == 0 {
return nil, errors.E(ctx, errors.WithCode(errors.RecordNotFound), errors.WithOp(op))
}
if err := foundKeys[0].Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return foundKeys[0], nil
}
// ListSessionKeyVersions will lists versions of a key. Supports the WithLimit option.
func (r *Repository) ListSessionKeyVersions(ctx context.Context, rkvWrapper wrapping.Wrapper, sessionKeyId string, opt ...Option) ([]DekVersion, error) {
const op = "kms.(Repository).ListSessionKeyVersions"
if sessionKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing session key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var versions []*SessionKeyVersion
err := r.list(ctx, &versions, "session_key_id = ?", []interface{}{sessionKeyId}, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
for i, k := range versions {
if err := k.Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error decrypting key num %d", i)))
}
}
dekVersions := make([]DekVersion, 0, len(versions))
for _, version := range versions {
dekVersions = append(dekVersions, version)
}
return dekVersions, nil
}
@@ -1,367 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateSessionKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
sk := kms.TestSessionKey(t, conn, rk.PrivateId)
type args struct {
key []byte
sessionKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: []byte("test key"),
keyWrapper: rkvWrapper,
sessionKeyId: sk.PrivateId,
},
wantErr: false,
},
{
name: "invalid-rkv-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: wrapper,
sessionKeyId: sk.PrivateId,
},
wantErr: true,
},
{
name: "empty-key",
args: args{
key: nil,
keyWrapper: wrapper,
sessionKeyId: sk.PrivateId,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
k, err := repo.CreateSessionKeyVersion(context.Background(), tt.args.keyWrapper, tt.args.sessionKeyId, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(k)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(k.CreateTime)
foundKey, err := repo.LookupSessionKeyVersion(context.Background(), tt.args.keyWrapper, k.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, k))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, k.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteSessionKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
sk := kms.TestSessionKey(t, conn, rk.PrivateId)
type args struct {
key *kms.SessionKeyVersion
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestSessionKeyVersion(t, conn, rkvWrapper, sk.PrivateId, []byte("session key")),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.SessionKeyVersion {
k := kms.AllocSessionKeyVersion()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.SessionKeyVersion {
id, err := db.NewPublicId(kms.SessionKeyPrefix)
require.NoError(t, err)
k := kms.AllocSessionKeyVersion()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteSessionKeyVersion(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupSessionKeyVersion(context.Background(), wrapper, tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_LatestSessionKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
sk := kms.TestSessionKey(t, conn, rk.PrivateId)
tests := []struct {
name string
createCnt int
keyWrapper wrapping.Wrapper
wantVersion uint32
wantErr bool
wantIsError errors.Code
}{
{
name: "5",
createCnt: 5,
keyWrapper: rkvWrapper,
wantVersion: 5,
wantErr: false,
},
{
name: "1",
createCnt: 1,
keyWrapper: rkvWrapper,
wantVersion: 1,
wantErr: false,
},
{
name: "0",
createCnt: 0,
keyWrapper: rkvWrapper,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
{
name: "nil-wrapper",
createCnt: 5,
keyWrapper: nil,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocSessionKeyVersion(); return &i }(), "1=1")
testKeys := []*kms.SessionKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestSessionKeyVersion(t, conn, rkvWrapper, sk.PrivateId, []byte("test key"))
testKeys = append(testKeys, k)
}
assert.Equal(tt.createCnt, len(testKeys))
got, err := repo.LatestSessionKeyVersion(context.Background(), tt.keyWrapper, sk.PrivateId)
if tt.wantErr {
require.Error(err)
assert.Nil(got)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
require.NotNil(got)
assert.Equal(tt.wantVersion, got.Version)
})
}
}
func TestRepository_ListSessionKeyVersions(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
sk := kms.TestSessionKey(t, conn, rk.PrivateId)
type args struct {
sessionKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
sessionKeyId: sk.PrivateId,
keyWrapper: rkvWrapper,
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1,
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
sessionKeyId: sk.PrivateId,
},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
sessionKeyId: sk.PrivateId,
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "bad-org",
createCnt: 1,
args: args{
keyWrapper: rkvWrapper,
sessionKeyId: "bad-id",
},
wantCnt: 0,
wantErr: false,
},
{
name: "nil-wrapper",
createCnt: 1,
args: args{
keyWrapper: nil,
sessionKeyId: sk.PrivateId,
},
wantCnt: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocSessionKeyVersion(); return &i }(), "1=1")
keyVersions := []*kms.SessionKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestSessionKeyVersion(t, conn, rkvWrapper, sk.PrivateId, []byte("session key"))
keyVersions = append(keyVersions, k)
}
assert.Equal(tt.createCnt, len(keyVersions))
got, err := repo.ListSessionKeyVersions(context.Background(), tt.args.keyWrapper, tt.args.sessionKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-288
View File
@@ -1,288 +0,0 @@
package kms_test
import (
"context"
"crypto/rand"
"io"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/types/scope"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewRepository(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
type args struct {
r db.Reader
w db.Writer
}
tests := []struct {
name string
args args
want *kms.Repository
wantErr bool
wantErrString string
}{
{
name: "valid",
args: args{
r: rw,
w: rw,
},
want: func() *kms.Repository {
ret, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
return ret
}(),
wantErr: false,
},
{
name: "nil-writer",
args: args{
r: rw,
w: nil,
},
want: nil,
wantErr: true,
wantErrString: "kms.NewRepository: nil writer: parameter violation: error #100",
},
{
name: "nil-reader",
args: args{
r: nil,
w: rw,
},
want: nil,
wantErr: true,
wantErrString: "kms.NewRepository: nil reader: parameter violation: error #100",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := kms.NewRepository(tt.args.r, tt.args.w)
if tt.wantErr {
require.Error(err)
assert.Equal(tt.wantErrString, err.Error())
return
}
require.NoError(err)
assert.Equal(tt.want, got)
})
}
}
func TestCreateKeysTx(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
type args struct {
ctx context.Context
dbReader db.Reader
dbWriter db.Writer
rootWrapper wrapping.Wrapper
randomReader io.Reader
scopeId string
}
tests := []struct {
name string
args args
wantErr bool
wantErrIs errors.Code
}{
{
name: "valid",
args: args{
dbReader: rw,
dbWriter: rw,
rootWrapper: wrapper,
randomReader: rand.Reader,
scopeId: org.PublicId,
},
},
{
name: "valid-at-global",
args: args{
dbReader: rw,
dbWriter: rw,
rootWrapper: wrapper,
randomReader: rand.Reader,
scopeId: scope.Global.String(),
},
},
{
name: "nil-reader",
args: args{
dbReader: nil,
dbWriter: rw,
rootWrapper: wrapper,
randomReader: rand.Reader,
scopeId: org.PublicId,
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "nil-writer",
args: args{
dbReader: rw,
dbWriter: nil,
rootWrapper: wrapper,
randomReader: rand.Reader,
scopeId: org.PublicId,
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
dbReader: rw,
dbWriter: rw,
rootWrapper: nil,
randomReader: rand.Reader,
scopeId: org.PublicId,
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "empty-scope",
args: args{
dbReader: rw,
dbWriter: rw,
rootWrapper: wrapper,
randomReader: rand.Reader,
scopeId: "",
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "bad-scope",
args: args{
dbReader: rw,
dbWriter: rw,
rootWrapper: wrapper,
randomReader: rand.Reader,
scopeId: "o_thisIsAnInvalidScopeId",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
keys, err := kms.CreateKeysTx(tt.args.ctx, tt.args.dbReader, tt.args.dbWriter, tt.args.rootWrapper, tt.args.randomReader, tt.args.scopeId)
if tt.wantErr {
require.Error(err)
assert.Truef(errors.Match(errors.T(tt.wantErrIs), err), "unexpected error: %s", err.Error())
return
}
require.NoError(err)
rk := kms.AllocRootKey()
rk.PrivateId = keys[kms.KeyTypeRootKey].GetPrivateId()
err = rw.LookupById(context.Background(), &rk)
require.NoError(err)
assert.Equal(rk.ScopeId, tt.args.scopeId)
rkv := kms.AllocRootKeyVersion()
rkv.PrivateId = keys[kms.KeyTypeRootKeyVersion].GetPrivateId()
err = rw.LookupById(context.Background(), &rkv)
require.NoError(err)
assert.Equal(rkv.RootKeyId, rk.PrivateId)
dk := kms.AllocDatabaseKey()
dk.PrivateId = keys[kms.KeyTypeDatabaseKey].GetPrivateId()
err = rw.LookupById(context.Background(), &dk)
require.NoError(err)
assert.Equal(rk.PrivateId, dk.RootKeyId)
dkv := kms.AllocDatabaseKeyVersion()
dkv.PrivateId = keys[kms.KeyTypeDatabaseKeyVersion].GetPrivateId()
err = rw.LookupById(context.Background(), &dkv)
require.NoError(err)
assert.Equal(dk.PrivateId, dkv.DatabaseKeyId)
assert.Equal(rkv.PrivateId, dkv.RootKeyVersionId)
opk := kms.AllocOplogKey()
opk.PrivateId = keys[kms.KeyTypeOplogKey].GetPrivateId()
err = rw.LookupById(context.Background(), &opk)
require.NoError(err)
assert.Equal(rk.PrivateId, opk.RootKeyId)
opkv := kms.AllocOplogKeyVersion()
opkv.PrivateId = keys[kms.KeyTypeOplogKeyVersion].GetPrivateId()
err = rw.LookupById(context.Background(), &opkv)
require.NoError(err)
assert.Equal(opk.PrivateId, opkv.OplogKeyId)
assert.Equal(rkv.PrivateId, opkv.RootKeyVersionId)
sk := kms.AllocSessionKey()
sk.PrivateId = keys[kms.KeyTypeSessionKey].GetPrivateId()
err = rw.LookupById(context.Background(), &sk)
require.NoError(err)
assert.Equal(rk.PrivateId, sk.RootKeyId)
skv := kms.AllocSessionKeyVersion()
skv.PrivateId = keys[kms.KeyTypeSessionKeyVersion].GetPrivateId()
err = rw.LookupById(context.Background(), &skv)
require.NoError(err)
assert.Equal(sk.PrivateId, skv.SessionKeyId)
assert.Equal(rkv.PrivateId, skv.RootKeyVersionId)
tk := kms.AllocTokenKey()
tk.PrivateId = keys[kms.KeyTypeTokenKey].GetPrivateId()
err = rw.LookupById(context.Background(), &tk)
require.NoError(err)
assert.Equal(rk.PrivateId, tk.RootKeyId)
tkv := kms.AllocTokenKeyVersion()
tkv.PrivateId = keys[kms.KeyTypeTokenKeyVersion].GetPrivateId()
err = rw.LookupById(context.Background(), &tkv)
require.NoError(err)
assert.Equal(tk.PrivateId, tkv.TokenKeyId)
assert.Equal(rkv.PrivateId, tkv.RootKeyVersionId)
oidcK := kms.AllocOidcKey()
oidcK.PrivateId = keys[kms.KeyTypeOidcKey].GetPrivateId()
err = rw.LookupById(context.Background(), &oidcK)
require.NoError(err)
assert.Equal(rk.PrivateId, oidcK.RootKeyId)
oidcKv := kms.AllocOidcKeyVersion()
oidcKv.PrivateId = keys[kms.KeyTypeOidcKeyVersion].GetPrivateId()
err = rw.LookupById(context.Background(), &oidcKv)
require.NoError(err)
assert.Equal(oidcK.PrivateId, oidcKv.OidcKeyId)
assert.Equal(rkv.PrivateId, oidcKv.RootKeyVersionId)
if tt.args.scopeId == scope.Global.String() {
auditK := kms.AllocAuditKey()
auditK.PrivateId = keys[kms.KeyTypeAuditKey].GetPrivateId()
err = rw.LookupById(context.Background(), &auditK)
require.NoError(err)
assert.Equal(rk.PrivateId, auditK.RootKeyId)
auditKv := kms.AllocAuditKeyVersion()
auditKv.PrivateId = keys[kms.KeyTypeAuditKeyVersion].GetPrivateId()
err = rw.LookupById(context.Background(), &auditKv)
require.NoError(err)
assert.Equal(auditK.PrivateId, auditKv.AuditKeyId)
assert.Equal(rkv.PrivateId, auditKv.RootKeyVersionId)
}
})
}
}
-162
View File
@@ -1,162 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateTokenKey inserts into the repository and returns the new key and the
// key version. There are no valid options at this time.
func (r *Repository) CreateTokenKey(ctx context.Context, rkvWrapper wrapping.Wrapper, key []byte, _ ...Option) (*TokenKey, *TokenKeyVersion, error) {
const op = "kms.(Repository).CreateTokenKey"
var returnedDk, returnedDv interface{}
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
var err error
if returnedDk, returnedDv, err = createTokenKeyTx(ctx, reader, w, rkvWrapper, key); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
return returnedDk.(*TokenKey), returnedDv.(*TokenKeyVersion), nil
}
// createTokenKeyTx inserts into the db (via db.Writer) and returns the new key
// and the key version. This function encapsulates all the work required within
// a db.TxHandler and allows this capability to be shared with the iam repo.
func createTokenKeyTx(ctx context.Context, r db.Reader, w db.Writer, rkvWrapper wrapping.Wrapper, key []byte) (*TokenKey, *TokenKeyVersion, error) {
const op = "kms.createTokenKeyTx"
if rkvWrapper == nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
if len(key) == 0 {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup root key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
rv := AllocRootKeyVersion()
rv.PrivateId = rootKeyVersionId
if err := r.LookupById(ctx, &rv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to lookup root key version %s", rootKeyVersionId)))
}
tk := AllocTokenKey()
tv := AllocTokenKeyVersion()
id, err := newTokenKeyId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
tk.PrivateId = id
tk.RootKeyId = rv.RootKeyId
id, err = newTokenKeyVersionId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
tv.PrivateId = id
tv.TokenKeyId = tk.PrivateId
tv.RootKeyVersionId = rootKeyVersionId
tv.Key = key
if err := tv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
// no token entries for keys
if err := w.Create(ctx, &tk); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("keys create"))
}
// no token entries for key versions
if err := w.Create(ctx, &tv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("key versions create"))
}
return &tk, &tv, nil
}
// LookupTokenKey will look up a key in the repository. If the key is not
// found, it will return nil, nil.
func (r *Repository) LookupTokenKey(ctx context.Context, privateId string, _ ...Option) (*TokenKey, error) {
const op = "kms.(Repository).LookupTokenKey"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocTokenKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return &k, nil
}
// DeleteTokenKey deletes the key for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteTokenKey(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteTokenKey"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocTokenKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no token entries for root keys
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// ListTokenKeys will list the keys. Supports the WithLimit option.
func (r *Repository) ListTokenKeys(ctx context.Context, opt ...Option) ([]Dek, error) {
const op = "kms.(Repository).ListTokenKeys"
var keys []*TokenKey
err := r.list(ctx, &keys, "1=1", nil, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
deks := make([]Dek, 0, len(keys))
for _, key := range keys {
deks = append(deks, key)
}
return deks, nil
}
-295
View File
@@ -1,295 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateTokenKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
type args struct {
scopeId string
key []byte
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid-org",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: rkvWrapper,
},
wantErr: false,
},
{
name: "nil-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
key: []byte(""),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-rkv-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: wrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "wrapper-missing-id",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: func() wrapping.Wrapper {
w := db.TestWrapper(t)
_, err = w.(*aead.Wrapper).SetConfig(context.Background())
require.NoError(t, err)
return w
}(),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tk, tv, err := repo.CreateTokenKey(context.Background(), tt.args.keyWrapper, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(tk)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(tk.CreateTime)
foundKey, err := repo.LookupTokenKey(context.Background(), tk.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, tk))
// make sure there was no token written
err = db.TestVerifyOplog(t, rw, tk.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
assert.NotNil(tv.CreateTime)
foundKeyVersion, err := repo.LookupTokenKeyVersion(context.Background(), tt.args.keyWrapper, tv.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKeyVersion, tv))
// make sure there was no token written
err = db.TestVerifyOplog(t, rw, tv.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteTokenKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
key *kms.TokenKey
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestTokenKey(t, conn, rk.PrivateId),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.TokenKey {
k := kms.AllocTokenKey()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.TokenKey {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k := kms.AllocTokenKey()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteTokenKey(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
// make sure there was no token written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupTokenKey(context.Background(), tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no token written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_ListTokenKeys(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
wrapper := db.TestWrapper(t)
type args struct {
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1, // org and project both have keys, plus global scope
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
}
for _, tt := range tests {
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
for i := 0; i < tt.createCnt; i++ {
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "scope_id in(?)", "global", org.PublicId, proj.PublicId)
rk := kms.TestRootKey(t, conn, proj.PublicId)
kms.TestTokenKey(t, conn, rk.PrivateId)
require.NoError(err)
}
got, err := repo.ListTokenKeys(context.Background(), tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
@@ -1,176 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateTokenKeyVersion inserts into the repository and returns the new key
// version with its PrivateId. There are no valid options at this time.
func (r *Repository) CreateTokenKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, tokenKeyId string, key []byte, _ ...Option) (*TokenKeyVersion, error) {
const op = "kms.(Repository).CreateTokenKeyVersion"
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to fetch key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
if tokenKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token key id")
}
if len(key) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
kv := AllocTokenKeyVersion()
id, err := newTokenKeyVersionId()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
kv.PrivateId = id
kv.RootKeyVersionId = rootKeyVersionId
kv.Key = key
kv.TokenKeyId = tokenKeyId
if err := kv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var returnedKey interface{}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedKey = kv.Clone()
// no token entries for root key version
if err := w.Create(ctx, returnedKey); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s token key id", kv.TokenKeyId)))
}
return returnedKey.(*TokenKeyVersion), nil
}
// LookupTokenKeyVersion will look up a key version in the repository. If
// the key version is not found, it will return nil, nil.
func (r *Repository) LookupTokenKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*TokenKeyVersion, error) {
const op = "kms.(Repository).LookupTokenKeyVersion"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
k := AllocTokenKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
if err := k.Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &k, nil
}
// DeleteTokenKeyVersion deletes the key version for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteTokenKeyVersion(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteTokenKeyVersion"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocTokenKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no token entries for the key version
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// LatestTokenKeyVersion searches for the key version with the highest
// version number. When no results are found, it returns nil with an
// errors.RecordNotFound error.
func (r *Repository) LatestTokenKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, tokenKeyId string, _ ...Option) (*TokenKeyVersion, error) {
const op = "kms.(Repository).LatestTokenKeyVersion"
if tokenKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var foundKeys []*TokenKeyVersion
if err := r.reader.SearchWhere(ctx, &foundKeys, "token_key_id = ?", []interface{}{tokenKeyId}, db.WithLimit(1), db.WithOrder("version desc")); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", tokenKeyId)))
}
if len(foundKeys) == 0 {
return nil, errors.E(ctx, errors.WithCode(errors.RecordNotFound), errors.WithOp(op))
}
if err := foundKeys[0].Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return foundKeys[0], nil
}
// ListTokenKeyVersions will lists versions of a key. Supports the WithLimit option.
func (r *Repository) ListTokenKeyVersions(ctx context.Context, rkvWrapper wrapping.Wrapper, tokenKeyId string, opt ...Option) ([]DekVersion, error) {
const op = "kms.(Repository).ListTokenKeyVersions"
if tokenKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var versions []*TokenKeyVersion
err := r.list(ctx, &versions, "token_key_id = ?", []interface{}{tokenKeyId}, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
for i, k := range versions {
if err := k.Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error decrypting key num %d", i)))
}
}
dekVersions := make([]DekVersion, 0, len(versions))
for _, version := range versions {
dekVersions = append(dekVersions, version)
}
return dekVersions, nil
}
@@ -1,367 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateTokenKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestTokenKey(t, conn, rk.PrivateId)
type args struct {
key []byte
tokenKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: []byte("test key"),
keyWrapper: rkvWrapper,
tokenKeyId: dk.PrivateId,
},
wantErr: false,
},
{
name: "invalid-rkv-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: wrapper,
tokenKeyId: dk.PrivateId,
},
wantErr: true,
},
{
name: "empty-key",
args: args{
key: nil,
keyWrapper: wrapper,
tokenKeyId: dk.PrivateId,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
k, err := repo.CreateTokenKeyVersion(context.Background(), tt.args.keyWrapper, tt.args.tokenKeyId, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(k)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(k.CreateTime)
foundKey, err := repo.LookupTokenKeyVersion(context.Background(), tt.args.keyWrapper, k.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, k))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, k.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteTokenKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestTokenKey(t, conn, rk.PrivateId)
type args struct {
key *kms.TokenKeyVersion
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestTokenKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("token key")),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.TokenKeyVersion {
k := kms.AllocTokenKeyVersion()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.TokenKeyVersion {
id, err := db.NewPublicId(kms.TokenKeyPrefix)
require.NoError(t, err)
k := kms.AllocTokenKeyVersion()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteTokenKeyVersion(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupTokenKeyVersion(context.Background(), wrapper, tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_LatestTokenKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestTokenKey(t, conn, rk.PrivateId)
tests := []struct {
name string
createCnt int
keyWrapper wrapping.Wrapper
wantVersion uint32
wantErr bool
wantIsError errors.Code
}{
{
name: "5",
createCnt: 5,
keyWrapper: rkvWrapper,
wantVersion: 5,
wantErr: false,
},
{
name: "1",
createCnt: 1,
keyWrapper: rkvWrapper,
wantVersion: 1,
wantErr: false,
},
{
name: "0",
createCnt: 0,
keyWrapper: rkvWrapper,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
{
name: "nil-wrapper",
createCnt: 5,
keyWrapper: nil,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocTokenKeyVersion(); return &i }(), "1=1")
testKeys := []*kms.TokenKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestTokenKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("test key"))
testKeys = append(testKeys, k)
}
assert.Equal(tt.createCnt, len(testKeys))
got, err := repo.LatestTokenKeyVersion(context.Background(), tt.keyWrapper, dk.PrivateId)
if tt.wantErr {
require.Error(err)
assert.Nil(got)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
require.NotNil(got)
assert.Equal(tt.wantVersion, got.Version)
})
}
}
func TestRepository_ListTokenKeyVersions(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestTokenKey(t, conn, rk.PrivateId)
type args struct {
tokenKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
tokenKeyId: dk.PrivateId,
keyWrapper: rkvWrapper,
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1,
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
tokenKeyId: dk.PrivateId,
},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
tokenKeyId: dk.PrivateId,
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "bad-org",
createCnt: 1,
args: args{
keyWrapper: rkvWrapper,
tokenKeyId: "bad-id",
},
wantCnt: 0,
wantErr: false,
},
{
name: "nil-wrapper",
createCnt: 1,
args: args{
keyWrapper: nil,
tokenKeyId: dk.PrivateId,
},
wantCnt: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocTokenKeyVersion(); return &i }(), "1=1")
keyVersions := []*kms.TokenKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestTokenKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("token key"))
keyVersions = append(keyVersions, k)
}
assert.Equal(tt.createCnt, len(keyVersions))
got, err := repo.ListTokenKeyVersions(context.Background(), tt.args.keyWrapper, tt.args.tokenKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-84
View File
@@ -1,84 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
"google.golang.org/protobuf/proto"
)
const (
DefaultRootKeyTableName = "kms_root_key"
)
type RootKey struct {
*store.RootKey
tableName string `gorm:"-"`
}
// NewRootKey creates a new in memory root key. ScopeId must be
// for a global or org scope, but the scope type validation will be deferred
// until the in memory root key is written to the database. No options
// are currently supported.
func NewRootKey(scopeId string, _ ...Option) (*RootKey, error) {
const op = "kms.NewRootKey"
if scopeId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing scope id")
}
c := &RootKey{
RootKey: &store.RootKey{
ScopeId: scopeId,
},
}
return c, nil
}
// AllocRootKey will allocate a root key
func AllocRootKey() RootKey {
return RootKey{
RootKey: &store.RootKey{},
}
}
// Clone creates a clone of the RootKey
func (k *RootKey) Clone() interface{} {
cp := proto.Clone(k.RootKey)
return &RootKey{
RootKey: cp.(*store.RootKey),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// before it's written.
func (k *RootKey) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "kms.(RootKey).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
switch opType {
case db.CreateOp:
if k.ScopeId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
}
case db.UpdateOp:
return errors.New(ctx, errors.InvalidParameter, op, "key is immutable")
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *RootKey) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultRootKeyTableName
}
// 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 (c *RootKey) SetTableName(n string) {
c.tableName = n
}
-210
View File
@@ -1,210 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the RootKey attributes are
// immutable and those tests are covered by TestRootKey_ImmutableFields
func TestRootKey_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
type args struct {
scopeId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.RootKey
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-scopeId",
args: args{},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid-org-config",
args: args{
scopeId: org.PublicId,
},
want: func() *kms.RootKey {
k := kms.AllocRootKey()
k.ScopeId = org.PublicId
return &k
}(),
create: true,
},
{
name: "valid-global-config",
args: args{
scopeId: "global",
},
want: func() *kms.RootKey {
k := kms.AllocRootKey()
k.ScopeId = "global"
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
got, err := kms.NewRootKey(tt.args.scopeId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestRootKey_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.RootKey
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestRootKey(t, conn, org.PublicId),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.RootKey {
k := kms.AllocRootKey()
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k.PrivateId = id
k.ScopeId = org.PublicId
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocRootKey()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocRootKey()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRootKey_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
k := kms.TestRootKey(t, conn, org.PublicId)
cp := k.Clone()
assert.True(proto.Equal(cp.(*kms.RootKey).RootKey, k.RootKey))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
k := kms.TestRootKey(t, conn, org.PublicId)
k2 := kms.TestRootKey(t, conn, org2.PublicId)
cp := k.Clone()
assert.True(!proto.Equal(cp.(*kms.RootKey).RootKey, k2.RootKey))
})
}
func TestRootKey_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultRootKeyTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocRootKey()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocRootKey()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-114
View File
@@ -1,114 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
const (
DefaultRootKeyVersionTableName = "kms_root_key_version"
)
type RootKeyVersion struct {
*store.RootKeyVersion
tableName string `gorm:"-"`
}
// NewRootKeyVersion creates a new in memory root key version. No options are
// currently supported.
func NewRootKeyVersion(rootKeyId string, key []byte, _ ...Option) (*RootKeyVersion, error) {
const op = "kms.NewRootKeyVersion"
if rootKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key id")
}
if len(key) == 0 {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing key")
}
k := &RootKeyVersion{
RootKeyVersion: &store.RootKeyVersion{
RootKeyId: rootKeyId,
Key: key,
},
}
return k, nil
}
// AllocRootKeyVersion allocates a RootKeyVersion
func AllocRootKeyVersion() RootKeyVersion {
return RootKeyVersion{
RootKeyVersion: &store.RootKeyVersion{},
}
}
// Clone creates a clone of the RootKeyVersion
func (k *RootKeyVersion) Clone() interface{} {
cp := proto.Clone(k.RootKeyVersion)
return &RootKeyVersion{
RootKeyVersion: cp.(*store.RootKeyVersion),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the root key
// version before it's written.
func (k *RootKeyVersion) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "kms.(RootKeyVersion).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
switch opType {
case db.CreateOp:
if k.CtKey == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
if k.RootKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
case db.UpdateOp:
return errors.New(ctx, errors.InvalidParameter, op, "key is immutable")
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *RootKeyVersion) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultRootKeyVersionTableName
}
// 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 (k *RootKeyVersion) SetTableName(n string) {
k.tableName = n
}
// Encrypt will encrypt the root key version's key
func (k *RootKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(RootKeyVersion).Encrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.RootKey directly
if err := structwrapping.WrapStruct(ctx, cipher, k.RootKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
}
return nil
}
// Decrypt will decrypt the root key version's key
func (k *RootKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(RootKeyVersion).Decrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.RootKeyVersion directly
if err := structwrapping.UnwrapStruct(ctx, cipher, k.RootKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
-219
View File
@@ -1,219 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/kms/store"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the RootKeyVersion attributes are
// immutable and those tests are covered by TestRootKeyVersion_ImmutableFields
func TestRootKeyVersion_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rootKey := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootId string
key []byte
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.RootKeyVersion
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-root-id",
args: args{
key: []byte("test key"),
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
rootId: rootKey.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
rootId: rootKey.PrivateId,
key: []byte("test key"),
},
want: func() *kms.RootKeyVersion {
k := kms.AllocRootKeyVersion()
k.RootKeyId = rootKey.PrivateId
k.Key = []byte("test key")
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKeyVersion(); return &i }(), "1=1")
got, err := kms.NewRootKeyVersion(tt.args.rootId, tt.args.key, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.RootKeyVersionPrefix)
require.NoError(err)
got.PrivateId = id
err = got.Encrypt(context.Background(), wrapper)
require.NoError(err)
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
assert.Equal(uint32(1), got.Version)
}
})
}
}
func TestRootKeyVersion_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
rw := db.New(conn)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
tests := []struct {
name string
key *kms.RootKeyVersion
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: func() *kms.RootKeyVersion { k, _ := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId); return k }(),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.RootKeyVersion {
k := kms.AllocRootKeyVersion()
id, err := db.NewPublicId(kms.RootKeyVersionPrefix)
require.NoError(t, err)
k.PrivateId = id
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocRootKeyVersion()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocRootKey()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRootKeyVersion_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k, _ := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
cp := k.Clone()
assert.True(proto.Equal(cp.(*kms.RootKeyVersion).RootKeyVersion, k.RootKeyVersion))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k, _ := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
k2, _ := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
cp := k.Clone()
assert.True(!proto.Equal(cp.(*kms.RootKeyVersion).RootKeyVersion, k2.RootKeyVersion))
})
}
func TestRootKeyVersion_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultRootKeyVersionTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocRootKeyVersion()
require.Equal(defaultTableName, def.TableName())
s := &kms.RootKeyVersion{
RootKeyVersion: &store.RootKeyVersion{},
}
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-79
View File
@@ -1,79 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
"google.golang.org/protobuf/proto"
)
const (
DefaultSessionKeyTableName = "kms_session_key"
)
type SessionKey struct {
*store.SessionKey
tableName string `gorm:"-"`
}
// NewSessionKey creates a new in memory key. No options
// are currently supported.
func NewSessionKey(rootKeyId string, _ ...Option) (*SessionKey, error) {
const op = "kms.NewSessionKey"
if rootKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key id")
}
c := &SessionKey{
SessionKey: &store.SessionKey{
RootKeyId: rootKeyId,
},
}
return c, nil
}
// AllocSessionKey will allocate a key
func AllocSessionKey() SessionKey {
return SessionKey{
SessionKey: &store.SessionKey{},
}
}
// Clone creates a clone of the key
func (k *SessionKey) Clone() interface{} {
cp := proto.Clone(k.SessionKey)
return &SessionKey{
SessionKey: cp.(*store.SessionKey),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// before it's written.
func (k *SessionKey) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "kms.(SessionKey).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if opType == db.CreateOp {
if k.RootKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *SessionKey) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultSessionKeyTableName
}
// 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 (k *SessionKey) SetTableName(n string) {
k.tableName = n
}
-206
View File
@@ -1,206 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the SessionKey attributes are
// immutable and those tests are covered by TestSessionKey_ImmutableFields
func TestSessionKey_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootKeyId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.SessionKey
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-rootKeyId",
args: args{},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
rootKeyId: rk.PrivateId,
},
want: func() *kms.SessionKey {
k := kms.AllocSessionKey()
k.RootKeyId = rk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocSessionKey(); return &i }(), "1=1")
got, err := kms.NewSessionKey(tt.args.rootKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.SessionKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestSessionKey_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocSessionKey(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.SessionKey
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestSessionKey(t, conn, rk.PrivateId),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.SessionKey {
k := kms.AllocSessionKey()
id, err := db.NewPublicId(kms.SessionKeyPrefix)
require.NoError(t, err)
k.PrivateId = id
k.RootKeyId = rk.PrivateId
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocSessionKey()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocSessionKey()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestSessionKey_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k := kms.TestSessionKey(t, conn, rk.PrivateId)
cp := k.Clone()
assert.True(proto.Equal(cp.(*kms.SessionKey).SessionKey, k.SessionKey))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
k := kms.TestSessionKey(t, conn, rk.PrivateId)
k2 := kms.TestSessionKey(t, conn, rk2.PrivateId)
cp := k.Clone()
assert.True(!proto.Equal(cp.(*kms.SessionKey).SessionKey, k2.SessionKey))
})
}
func TestSessionKey_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultSessionKeyTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocSessionKey()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocSessionKey()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-118
View File
@@ -1,118 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
const (
DefaultSessionKeyVersionTableName = "kms_session_key_version"
)
type SessionKeyVersion struct {
*store.SessionKeyVersion
tableName string `gorm:"-"`
}
// SessionKeyVersion creates a new in memory key version. No options are
// currently supported.
func NewSessionKeyVersion(sessionKeyId string, key []byte, rootKeyVersionId string, _ ...Option) (*SessionKeyVersion, error) {
const op = "kms.NewSessionKeyVersion"
if sessionKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing session key id")
}
if len(key) == 0 {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing key")
}
if rootKeyVersionId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key version id")
}
k := &SessionKeyVersion{
SessionKeyVersion: &store.SessionKeyVersion{
SessionKeyId: sessionKeyId,
RootKeyVersionId: rootKeyVersionId,
Key: key,
},
}
return k, nil
}
// AllocSessionKeyVersion allocates a key version
func AllocSessionKeyVersion() SessionKeyVersion {
return SessionKeyVersion{
SessionKeyVersion: &store.SessionKeyVersion{},
}
}
// Clone creates a clone of the key version
func (k *SessionKeyVersion) Clone() interface{} {
cp := proto.Clone(k.SessionKeyVersion)
return &SessionKeyVersion{
SessionKeyVersion: cp.(*store.SessionKeyVersion),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// version before it's written.
func (k *SessionKeyVersion) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "kms.(SessionKeyVersion).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if opType == db.CreateOp {
if k.CtKey == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
if k.SessionKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing session key id")
}
if k.RootKeyVersionId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *SessionKeyVersion) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultSessionKeyVersionTableName
}
// 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 (k *SessionKeyVersion) SetTableName(n string) {
k.tableName = n
}
// Encrypt will encrypt the key version's key
func (k *SessionKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(SessionKeyVersion).Encrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.SessionKeyVersion directly
if err := structwrapping.WrapStruct(ctx, cipher, k.SessionKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
}
return nil
}
// Decrypt will decrypt the key version's key
func (k *SessionKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(SessionKeyVersion).Decrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.SessionKeyVersion directly
if err := structwrapping.UnwrapStruct(ctx, cipher, k.SessionKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
-249
View File
@@ -1,249 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the essionKeyVersion attributes are
// immutable and those tests are covered by TestSessionKey_ImmutableFields
func TestSessionKeyVersion_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rkv, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestSessionKey(t, conn, rk.PrivateId)
type args struct {
sessionKeyId string
key []byte
rootKeyVersionId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.SessionKeyVersion
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-id",
args: args{
key: []byte("test key"),
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
sessionKeyId: dk.PrivateId,
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-root-key-version-id",
args: args{
sessionKeyId: dk.PrivateId,
key: []byte("test key"),
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
sessionKeyId: dk.PrivateId,
key: []byte("test key"),
rootKeyVersionId: rkv.PrivateId,
},
want: func() *kms.SessionKeyVersion {
k := kms.AllocSessionKeyVersion()
k.RootKeyVersionId = rkv.PrivateId
k.Key = []byte("test key")
k.SessionKeyId = dk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocSessionKeyVersion(); return &i }(), "1=1")
got, err := kms.NewSessionKeyVersion(tt.args.sessionKeyId, tt.args.key, tt.args.rootKeyVersionId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.SessionKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = got.Encrypt(context.Background(), rkvWrapper)
require.NoError(err)
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestSessionKeyVersion_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
tk := kms.TestSessionKey(t, conn, rk.PrivateId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocSessionKeyVersion(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.SessionKeyVersion
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestSessionKeyVersion(t, conn, rkvWrapper, tk.PrivateId, []byte("test key")),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.SessionKeyVersion {
k := kms.AllocSessionKeyVersion()
id, err := db.NewPublicId(kms.SessionKeyVersionPrefix)
require.NoError(t, err)
k.PrivateId = id
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocSessionKeyVersion()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocSessionKeyVersion()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestSessionKeyVersion_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
k := kms.TestSessionKey(t, conn, rk.PrivateId)
kv := kms.TestSessionKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test key"))
cp := kv.Clone()
assert.True(proto.Equal(cp.(*kms.SessionKeyVersion).SessionKeyVersion, kv.SessionKeyVersion))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
_, rkvWrapper2 := kms.TestRootKeyVersion(t, conn, wrapper, rk2.PrivateId)
k := kms.TestSessionKey(t, conn, rk.PrivateId)
k2 := kms.TestSessionKey(t, conn, rk2.PrivateId)
kv := kms.TestSessionKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test key"))
kv2 := kms.TestSessionKeyVersion(t, conn, rkvWrapper2, k2.PrivateId, []byte("test key 2"))
cp := kv.Clone()
assert.True(!proto.Equal(cp.(*kms.SessionKeyVersion).SessionKeyVersion, kv2.SessionKeyVersion))
})
}
func TestSessionKeyVersion_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultSessionKeyVersionTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocSessionKeyVersion()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocSessionKeyVersion()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
+13 -251
View File
@@ -2,276 +2,38 @@ package kms
import (
"context"
"fmt"
"testing"
"github.com/hashicorp/boundary/internal/db"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/stretchr/testify/require"
)
func TestRootKey(t testing.TB, conn *db.DB, scopeId string) *RootKey {
t.Helper()
require := require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocRootKey(); return &i }(), "scope_id = ?", scopeId)
rw := db.New(conn)
k, err := NewRootKey(scopeId)
require.NoError(err)
id, err := newRootKeyId()
require.NoError(err)
k.PrivateId = id
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestRootKeyVersion(t testing.TB, conn *db.DB, wrapper wrapping.Wrapper, rootId string) (kv *RootKeyVersion, kvWrapper wrapping.Wrapper) {
t.Helper()
require := require.New(t)
rw := db.New(conn)
rootKeyVersionWrapper := db.TestWrapper(t)
key, err := rootKeyVersionWrapper.(*aead.Wrapper).KeyBytes(context.Background())
require.NoError(err)
k, err := NewRootKeyVersion(rootId, key)
require.NoError(err)
id, err := newRootKeyVersionId()
require.NoError(err)
k.PrivateId = id
err = k.Encrypt(context.Background(), wrapper)
require.NoError(err)
err = rw.Create(context.Background(), k)
require.NoError(err)
_, err = rootKeyVersionWrapper.(*aead.Wrapper).SetConfig(context.Background(), wrapping.WithKeyId(k.GetPrivateId()))
require.NoError(err)
return k, rootKeyVersionWrapper
}
// TestKms creates a kms for testing.
func TestKms(t testing.TB, conn *db.DB, rootWrapper wrapping.Wrapper) *Kms {
t.Helper()
require := require.New(t)
rw := db.New(conn)
kmsRepo, err := NewRepository(rw, rw)
require.NoError(err)
kms, err := NewKms(kmsRepo)
kms, err := New(context.Background(), rw, rw)
require.NoError(err)
err = kms.AddExternalWrappers(context.Background(), WithRootWrapper(rootWrapper))
require.NoError(err)
_, err = kms.underlying.GetExternalRootWrapper()
require.NoError(err)
return kms
}
func TestDatabaseKey(t testing.TB, conn *db.DB, rootKeyId string) *DatabaseKey {
t.Helper()
require := require.New(t)
rw := db.New(conn)
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocDatabaseKey(); return &i }(), "root_key_id = ?", rootKeyId)
k, err := NewDatabaseKey(rootKeyId)
require.NoError(err)
id, err := newDatabaseKeyId()
require.NoError(err)
k.PrivateId = id
k.RootKeyId = rootKeyId
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
// TestKmsDeleteKeyPurpose allows you to delete a KeyPurpose for testing.
func TestKmsDeleteKeyPurpose(t testing.TB, conn *db.DB, purpose KeyPurpose) {
db.TestDeleteWhere(t, conn, func() interface{} { i := dataKey{}; return &i }(), fmt.Sprintf("purpose='%s'", purpose.String()))
}
func TestDatabaseKeyVersion(t testing.TB, conn *db.DB, rootKeyVersionWrapper wrapping.Wrapper, databaseKeyId string, key []byte) *DatabaseKeyVersion {
t.Helper()
require := require.New(t)
rw := db.New(conn)
rootKeyId, err := rootKeyVersionWrapper.KeyId(context.Background())
require.NoError(err)
rootKeyVersionId := rootKeyId
require.NotEmpty(rootKeyVersionId)
k, err := NewDatabaseKeyVersion(databaseKeyId, key, rootKeyVersionId)
require.NoError(err)
id, err := newDatabaseKeyVersionId()
require.NoError(err)
k.PrivateId = id
err = k.Encrypt(context.Background(), rootKeyVersionWrapper)
require.NoError(err)
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
// TestKmsDeleteAllKeys allows you to delete all the keys for testing.
func TestKmsDeleteAllKeys(t testing.TB, conn *db.DB) {
db.TestDeleteWhere(t, conn, func() interface{} { i := rootKey{}; return &i }(), "1=1")
}
func TestOplogKey(t testing.TB, conn *db.DB, rootKeyId string) *OplogKey {
t.Helper()
require := require.New(t)
rw := db.New(conn)
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocOplogKey(); return &i }(), "root_key_id = ?", rootKeyId)
k, err := NewOplogKey(rootKeyId)
require.NoError(err)
id, err := newOplogKeyId()
require.NoError(err)
k.PrivateId = id
k.RootKeyId = rootKeyId
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
type dataKey struct{}
func TestOplogKeyVersion(t testing.TB, conn *db.DB, rootKeyVersionWrapper wrapping.Wrapper, oplogKeyId string, key []byte) *OplogKeyVersion {
t.Helper()
require := require.New(t)
rw := db.New(conn)
rootKeyId, err := rootKeyVersionWrapper.KeyId(context.Background())
require.NoError(err)
rootKeyVersionId := rootKeyId
require.NotEmpty(rootKeyVersionId)
k, err := NewOplogKeyVersion(oplogKeyId, key, rootKeyVersionId)
require.NoError(err)
id, err := newOplogKeyVersionId()
require.NoError(err)
k.PrivateId = id
err = k.Encrypt(context.Background(), rootKeyVersionWrapper)
require.NoError(err)
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestTokenKey(t testing.TB, conn *db.DB, rootKeyId string) *TokenKey {
t.Helper()
require := require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocTokenKey(); return &i }(), "root_key_id = ?", rootKeyId)
rw := db.New(conn)
k, err := NewTokenKey(rootKeyId)
require.NoError(err)
id, err := newTokenKeyId()
require.NoError(err)
k.PrivateId = id
k.RootKeyId = rootKeyId
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestTokenKeyVersion(t testing.TB, conn *db.DB, rootKeyVersionWrapper wrapping.Wrapper, tokenKeyId string, key []byte) *TokenKeyVersion {
t.Helper()
require := require.New(t)
rw := db.New(conn)
rootKeyId, err := rootKeyVersionWrapper.KeyId(context.Background())
require.NoError(err)
rootKeyVersionId := rootKeyId
require.NotEmpty(rootKeyVersionId)
k, err := NewTokenKeyVersion(tokenKeyId, key, rootKeyVersionId)
require.NoError(err)
id, err := newTokenKeyVersionId()
require.NoError(err)
k.PrivateId = id
err = k.Encrypt(context.Background(), rootKeyVersionWrapper)
require.NoError(err)
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestSessionKey(t testing.TB, conn *db.DB, rootKeyId string) *SessionKey {
t.Helper()
require := require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocSessionKey(); return &i }(), "root_key_id = ?", rootKeyId)
rw := db.New(conn)
k, err := NewSessionKey(rootKeyId)
require.NoError(err)
id, err := newSessionKeyId()
require.NoError(err)
k.PrivateId = id
k.RootKeyId = rootKeyId
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestSessionKeyVersion(t testing.TB, conn *db.DB, rootKeyVersionWrapper wrapping.Wrapper, sessionKeyId string, key []byte) *SessionKeyVersion {
t.Helper()
require := require.New(t)
rw := db.New(conn)
rootKeyId, err := rootKeyVersionWrapper.KeyId(context.Background())
require.NoError(err)
rootKeyVersionId := rootKeyId
require.NotEmpty(rootKeyVersionId)
k, err := NewSessionKeyVersion(sessionKeyId, key, rootKeyVersionId)
require.NoError(err)
id, err := newSessionKeyVersionId()
require.NoError(err)
k.PrivateId = id
err = k.Encrypt(context.Background(), rootKeyVersionWrapper)
require.NoError(err)
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestOidcKey(t testing.TB, conn *db.DB, rootKeyId string) *OidcKey {
t.Helper()
require := require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocOidcKey(); return &i }(), "root_key_id = ?", rootKeyId)
rw := db.New(conn)
k, err := NewOidcKey(rootKeyId)
require.NoError(err)
id, err := newOidcKeyId()
require.NoError(err)
k.PrivateId = id
k.RootKeyId = rootKeyId
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestOidcKeyVersion(t testing.TB, conn *db.DB, rootKeyVersionWrapper wrapping.Wrapper, oidcKeyId string, key []byte) *OidcKeyVersion {
t.Helper()
require := require.New(t)
rw := db.New(conn)
rootKeyId, err := rootKeyVersionWrapper.KeyId(context.Background())
require.NoError(err)
rootKeyVersionId := rootKeyId
require.NotEmpty(rootKeyVersionId)
k, err := NewOidcKeyVersion(oidcKeyId, key, rootKeyVersionId)
require.NoError(err)
id, err := newOidcKeyVersionId()
require.NoError(err)
k.PrivateId = id
err = k.Encrypt(context.Background(), rootKeyVersionWrapper)
require.NoError(err)
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestAuditKey(t testing.TB, conn *db.DB, rootKeyId string) *AuditKey {
t.Helper()
ctx := context.Background()
require := require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocAuditKey(); return &i }(), "root_key_id = ?", rootKeyId)
rw := db.New(conn)
k, err := NewAuditKey(ctx, rootKeyId)
require.NoError(err)
id, err := newAuditKeyId(ctx)
require.NoError(err)
k.PrivateId = id
k.RootKeyId = rootKeyId
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestAuditKeyVersion(t testing.TB, conn *db.DB, rootKeyVersionWrapper wrapping.Wrapper, auditKeyId string, key []byte) *AuditKeyVersion {
t.Helper()
ctx := context.Background()
require := require.New(t)
rw := db.New(conn)
rootKeyVersionId, err := rootKeyVersionWrapper.KeyId(ctx)
require.NoError(err)
require.NotEmpty(rootKeyVersionId)
k, err := NewAuditKeyVersion(ctx, auditKeyId, key, rootKeyVersionId)
require.NoError(err)
id, err := newAuditKeyVersionId(ctx)
require.NoError(err)
k.PrivateId = id
err = k.Encrypt(context.Background(), rootKeyVersionWrapper)
require.NoError(err)
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func (*dataKey) TableName() string { return "kms_data_key" }
-150
View File
@@ -1,150 +0,0 @@
package kms_test
import (
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_TestRootKey(t *testing.T) {
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
k := kms.TestRootKey(t, conn, org.PublicId)
require.NotNil(k)
assert.NotEmpty(k.PrivateId)
}
func Test_TestRootKeyVersion(t *testing.T) {
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k, _ := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
require.NotNil(k)
assert.NotEmpty(k.PrivateId)
}
func Test_TestDatabaseKey(t *testing.T) {
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
k := kms.TestRootKey(t, conn, org.PublicId)
require.NotNil(k)
assert.NotEmpty(k.PrivateId)
dk := kms.TestDatabaseKey(t, conn, k.PrivateId)
require.NotNil(dk)
assert.NotEmpty(dk.PrivateId)
}
func Test_TestDatabaseKeyVersion(t *testing.T) {
require := require.New(t)
conn, _ := db.TestSetup(t, "postgres")
kmsWrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, kmsWrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rootKeyVersionWrapper := kms.TestRootKeyVersion(t, conn, kmsWrapper, rk.PrivateId)
dk := kms.TestDatabaseKey(t, conn, rk.PrivateId)
dv := kms.TestDatabaseKeyVersion(t, conn, rootKeyVersionWrapper, dk.PrivateId, []byte("test dek key"))
require.NotNil(dv)
require.NotEmpty(dv.PrivateId)
}
func Test_TestOplogKey(t *testing.T) {
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
k := kms.TestRootKey(t, conn, org.PublicId)
require.NotNil(k)
assert.NotEmpty(k.PrivateId)
opk := kms.TestOplogKey(t, conn, k.PrivateId)
require.NotNil(opk)
assert.NotEmpty(opk.PrivateId)
}
func Test_TestOplogKeyVersion(t *testing.T) {
require := require.New(t)
conn, _ := db.TestSetup(t, "postgres")
kmsWrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, kmsWrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rootKeyVersionWrapper := kms.TestRootKeyVersion(t, conn, kmsWrapper, rk.PrivateId)
opk := kms.TestOplogKey(t, conn, rk.PrivateId)
opv := kms.TestOplogKeyVersion(t, conn, rootKeyVersionWrapper, opk.PrivateId, []byte("test dek key"))
require.NotNil(opv)
require.NotEmpty(opv.PrivateId)
}
func Test_TestTokenKey(t *testing.T) {
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
k := kms.TestRootKey(t, conn, org.PublicId)
require.NotNil(k)
assert.NotEmpty(k.PrivateId)
tk := kms.TestTokenKey(t, conn, k.PrivateId)
require.NotNil(tk)
assert.NotEmpty(tk.PrivateId)
}
func Test_TestTokenKeyVersion(t *testing.T) {
require := require.New(t)
conn, _ := db.TestSetup(t, "postgres")
kmsWrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, kmsWrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rootKeyVersionWrapper := kms.TestRootKeyVersion(t, conn, kmsWrapper, rk.PrivateId)
tk := kms.TestTokenKey(t, conn, rk.PrivateId)
tv := kms.TestTokenKeyVersion(t, conn, rootKeyVersionWrapper, tk.PrivateId, []byte("test dek key"))
require.NotNil(tv)
require.NotEmpty(tv.PrivateId)
}
func Test_TestSessionKey(t *testing.T) {
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
k := kms.TestRootKey(t, conn, org.PublicId)
require.NotNil(k)
assert.NotEmpty(k.PrivateId)
sk := kms.TestSessionKey(t, conn, k.PrivateId)
require.NotNil(sk)
assert.NotEmpty(sk.PrivateId)
}
func Test_TestSessionKeyVersion(t *testing.T) {
require := require.New(t)
conn, _ := db.TestSetup(t, "postgres")
kmsWrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, kmsWrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rootKeyVersionWrapper := kms.TestRootKeyVersion(t, conn, kmsWrapper, rk.PrivateId)
sk := kms.TestSessionKey(t, conn, rk.PrivateId)
sv := kms.TestSessionKeyVersion(t, conn, rootKeyVersionWrapper, sk.PrivateId, []byte("test dek key"))
require.NotNil(sv)
require.NotEmpty(sv.PrivateId)
}
-79
View File
@@ -1,79 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
"google.golang.org/protobuf/proto"
)
const (
DefaultTokenKeyTableName = "kms_token_key"
)
type TokenKey struct {
*store.TokenKey
tableName string `gorm:"-"`
}
// NewTokenKey creates a new in memory key. No options
// are currently supported.
func NewTokenKey(rootKeyId string, _ ...Option) (*TokenKey, error) {
const op = "kms.NewTokenKey"
if rootKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key id")
}
c := &TokenKey{
TokenKey: &store.TokenKey{
RootKeyId: rootKeyId,
},
}
return c, nil
}
// AllocTokenKey will allocate a key
func AllocTokenKey() TokenKey {
return TokenKey{
TokenKey: &store.TokenKey{},
}
}
// Clone creates a clone of the key
func (k *TokenKey) Clone() interface{} {
cp := proto.Clone(k.TokenKey)
return &TokenKey{
TokenKey: cp.(*store.TokenKey),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// before it's written.
func (k *TokenKey) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "kms.(TokenKey).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if opType == db.CreateOp {
if k.RootKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *TokenKey) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultTokenKeyTableName
}
// 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 (k *TokenKey) SetTableName(n string) {
k.tableName = n
}
-206
View File
@@ -1,206 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the TokenKey attributes are
// immutable and those tests are covered by TestTokenKey_ImmutableFields
func TestTokenKey_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootKeyId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.TokenKey
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-rootKeyId",
args: args{},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
rootKeyId: rk.PrivateId,
},
want: func() *kms.TokenKey {
k := kms.AllocTokenKey()
k.RootKeyId = rk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocTokenKey(); return &i }(), "1=1")
got, err := kms.NewTokenKey(tt.args.rootKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.TokenKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestTokenKey_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocTokenKey(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.TokenKey
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestTokenKey(t, conn, rk.PrivateId),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.TokenKey {
k := kms.AllocTokenKey()
id, err := db.NewPublicId(kms.TokenKeyPrefix)
require.NoError(t, err)
k.PrivateId = id
k.RootKeyId = rk.PrivateId
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocTokenKey()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocTokenKey()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestTokenKey_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k := kms.TestTokenKey(t, conn, rk.PrivateId)
cp := k.Clone()
assert.True(proto.Equal(cp.(*kms.TokenKey).TokenKey, k.TokenKey))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
k := kms.TestTokenKey(t, conn, rk.PrivateId)
k2 := kms.TestTokenKey(t, conn, rk2.PrivateId)
cp := k.Clone()
assert.True(!proto.Equal(cp.(*kms.TokenKey).TokenKey, k2.TokenKey))
})
}
func TestTokenKey_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultTokenKeyTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocTokenKey()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocTokenKey()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-118
View File
@@ -1,118 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
const (
DefaultTokenKeyVersionTableName = "kms_token_key_version"
)
type TokenKeyVersion struct {
*store.TokenKeyVersion
tableName string `gorm:"-"`
}
// TokenKeyVersion creates a new in memory key version. No options are
// currently supported.
func NewTokenKeyVersion(tokenKeyId string, key []byte, rootKeyVersionId string, _ ...Option) (*TokenKeyVersion, error) {
const op = "kms.NewTokenKeyVersion"
if tokenKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing token key id")
}
if len(key) == 0 {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing key")
}
if rootKeyVersionId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key version id")
}
k := &TokenKeyVersion{
TokenKeyVersion: &store.TokenKeyVersion{
TokenKeyId: tokenKeyId,
RootKeyVersionId: rootKeyVersionId,
Key: key,
},
}
return k, nil
}
// AllocTokenKeyVersion allocates a key version
func AllocTokenKeyVersion() TokenKeyVersion {
return TokenKeyVersion{
TokenKeyVersion: &store.TokenKeyVersion{},
}
}
// Clone creates a clone of the key version
func (k *TokenKeyVersion) Clone() interface{} {
cp := proto.Clone(k.TokenKeyVersion)
return &TokenKeyVersion{
TokenKeyVersion: cp.(*store.TokenKeyVersion),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// version before it's written.
func (k *TokenKeyVersion) VetForWrite(ctx context.Context, _r db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "kms.(TokenKeyVersion).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if opType == db.CreateOp {
if k.CtKey == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
if k.TokenKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing token key id")
}
if k.RootKeyVersionId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *TokenKeyVersion) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultTokenKeyVersionTableName
}
// 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 (k *TokenKeyVersion) SetTableName(n string) {
k.tableName = n
}
// Encrypt will encrypt the key version's key
func (k *TokenKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(TokenKeyVersion).Encrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.TokenKeyVersion directly
if err := structwrapping.WrapStruct(ctx, cipher, k.TokenKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
}
return nil
}
// Decrypt will decrypt the key version's key
func (k *TokenKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(TokenKeyVersion).Decrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.TokenKeyVersion directly
if err := structwrapping.UnwrapStruct(ctx, cipher, k.TokenKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
-249
View File
@@ -1,249 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the TokenKeyVersion attributes are
// immutable and those tests are covered by TestTokenKey_ImmutableFields
func TestTokenKeyVersion_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rkv, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestTokenKey(t, conn, rk.PrivateId)
type args struct {
tokenKeyId string
key []byte
rootKeyVersionId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.TokenKeyVersion
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-id",
args: args{
key: []byte("test key"),
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
tokenKeyId: dk.PrivateId,
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-root-key-version-id",
args: args{
tokenKeyId: dk.PrivateId,
key: []byte("test key"),
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
tokenKeyId: dk.PrivateId,
key: []byte("test key"),
rootKeyVersionId: rkv.PrivateId,
},
want: func() *kms.TokenKeyVersion {
k := kms.AllocTokenKeyVersion()
k.RootKeyVersionId = rkv.PrivateId
k.Key = []byte("test key")
k.TokenKeyId = dk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocTokenKeyVersion(); return &i }(), "1=1")
got, err := kms.NewTokenKeyVersion(tt.args.tokenKeyId, tt.args.key, tt.args.rootKeyVersionId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.TokenKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = got.Encrypt(context.Background(), rkvWrapper)
require.NoError(err)
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestTokenKeyVersion_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
tk := kms.TestTokenKey(t, conn, rk.PrivateId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocTokenKeyVersion(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.TokenKeyVersion
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestTokenKeyVersion(t, conn, rkvWrapper, tk.PrivateId, []byte("test key")),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.TokenKeyVersion {
k := kms.AllocTokenKeyVersion()
id, err := db.NewPublicId(kms.TokenKeyVersionPrefix)
require.NoError(t, err)
k.PrivateId = id
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocTokenKeyVersion()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocTokenKeyVersion()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestTokenKeyVersion_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
k := kms.TestTokenKey(t, conn, rk.PrivateId)
kv := kms.TestTokenKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test key"))
cp := kv.Clone()
assert.True(proto.Equal(cp.(*kms.TokenKeyVersion).TokenKeyVersion, kv.TokenKeyVersion))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
_, rkvWrapper2 := kms.TestRootKeyVersion(t, conn, wrapper, rk2.PrivateId)
k := kms.TestTokenKey(t, conn, rk.PrivateId)
k2 := kms.TestTokenKey(t, conn, rk2.PrivateId)
kv := kms.TestTokenKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test key"))
kv2 := kms.TestTokenKeyVersion(t, conn, rkvWrapper2, k2.PrivateId, []byte("test key 2"))
cp := kv.Clone()
assert.True(!proto.Equal(cp.(*kms.TokenKeyVersion).TokenKeyVersion, kv2.TokenKeyVersion))
})
}
func TestTokenKeyVersion_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultTokenKeyVersionTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocTokenKeyVersion()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocTokenKeyVersion()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}