Merge branch 'main' into llb-byow
This commit is contained in:
commit
2f06513a3a
105 files changed
+2716
-14710
No files matched your search
@@ -9,6 +9,8 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
|
||||
* controller: Do not shut down cluster listener when it receives an invalid
|
||||
packet ([Issue](https://github.com/hashicorp/boundary/issues/2072),
|
||||
[PR](https://github.com/hashicorp/boundary/pull/2073))
|
||||
* session: update cancel_session() function to check for terminated state ([Issue](https://github.com/hashicorp/boundary/issues/2064),
|
||||
[PR](https://github.com/hashicorp/boundary/pull/2065))
|
||||
|
||||
## 0.8.0 (2022/05/03)
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ update-ui-version:
|
||||
else \
|
||||
echo "==> Setting to latest commit matching '$(UI_COMMITISH)'"; \
|
||||
fi; \
|
||||
./scripts/uiclone.sh && ./scripts/uiupdate.sh
|
||||
./scripts/uiupdate.sh
|
||||
|
||||
.PHONY: build-ui
|
||||
build-ui:
|
||||
|
||||
@@ -14,7 +14,7 @@ require (
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/favadi/protoc-go-inject-tag v1.3.0
|
||||
github.com/godbus/dbus/v5 v5.0.6 // indirect
|
||||
github.com/golang-migrate/migrate/v4 v4.14.1
|
||||
github.com/golang-migrate/migrate/v4 v4.15.1
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/google/go-cmp v0.5.7
|
||||
@@ -30,7 +30,7 @@ require (
|
||||
github.com/hashicorp/go-bexpr v0.1.10
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2
|
||||
github.com/hashicorp/go-hclog v1.1.0
|
||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.5
|
||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.6-0.20220512190651-85bdcdd684e6
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0
|
||||
github.com/hashicorp/go-rootcerts v1.0.2
|
||||
@@ -87,19 +87,23 @@ require (
|
||||
nhooyr.io/websocket v1.8.7
|
||||
)
|
||||
|
||||
require github.com/hashicorp/go-dbw v0.0.0-20211215222256-2ff0d37184ff // this is a branch and should be updated before merging
|
||||
require github.com/hashicorp/go-dbw v0.0.0-20220412153211-c470aec9369f // this is a branch and should be updated before merging
|
||||
|
||||
require github.com/hashicorp/nodeenrollment v0.0.0-20220513140413-5b932ff6843f
|
||||
require (
|
||||
github.com/hashicorp/go-kms-wrapping/extras/kms/v2 v2.0.0-20220512190651-85bdcdd684e6
|
||||
github.com/hashicorp/nodeenrollment v0.0.0-20220513140413-5b932ff6843f
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
||||
github.com/AlecAivazis/survey/v2 v2.2.9 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/Microsoft/go-winio v0.4.16 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.0 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/alessio/shellescape v1.4.1 // indirect
|
||||
github.com/apex/log v1.9.0 // indirect
|
||||
@@ -109,14 +113,13 @@ require (
|
||||
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc // indirect
|
||||
github.com/containerd/continuity v0.1.0 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.0.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dhui/dktest v0.3.4 // indirect
|
||||
github.com/docker/cli v20.10.7+incompatible // indirect
|
||||
github.com/docker/docker v20.10.7+incompatible // indirect
|
||||
github.com/docker/docker v20.10.9+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
|
||||
@@ -148,31 +151,32 @@ require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.3 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.13.5 // indirect
|
||||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/lib/pq v1.10.2 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.1+incompatible // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/mtibben/percent v0.2.1 // indirect
|
||||
github.com/oklog/run v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/opencontainers/runc v1.0.0-rc9 // indirect
|
||||
github.com/opencontainers/runc v1.0.2 // indirect
|
||||
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||
github.com/sethvargo/go-diceware v0.3.0 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/sirupsen/logrus v1.7.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/urfave/cli/v2 v2.3.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
@@ -190,6 +194,14 @@ require (
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
gorm.io/driver/sqlite v1.2.4 // indirect
|
||||
gotest.tools/v3 v3.0.3 // indirect
|
||||
modernc.org/cc/v3 v3.32.4 // indirect
|
||||
modernc.org/ccgo/v3 v3.9.2 // indirect
|
||||
modernc.org/libc v1.9.5 // indirect
|
||||
modernc.org/mathutil v1.2.2 // indirect
|
||||
modernc.org/memory v1.0.4 // indirect
|
||||
modernc.org/opt v0.1.1 // indirect
|
||||
modernc.org/sqlite v1.10.6 // indirect
|
||||
modernc.org/strutil v1.1.0 // indirect
|
||||
modernc.org/token v1.0.0 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
@@ -328,11 +328,7 @@ func (b *Server) CreateDevOidcAuthMethod(ctx context.Context) error {
|
||||
func (b *Server) createInitialOidcAuthMethod(ctx context.Context) (*oidc.AuthMethod, error) {
|
||||
rw := db.New(b.Database)
|
||||
|
||||
kmsRepo, err := kms.NewRepository(rw, rw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating kms repository: %w", err)
|
||||
}
|
||||
kmsCache, err := kms.NewKms(kmsRepo)
|
||||
kmsCache, err := kms.New(ctx, rw, rw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating kms cache: %w", err)
|
||||
}
|
||||
|
||||
@@ -22,11 +22,7 @@ import (
|
||||
func (b *Server) CreateInitialLoginRole(ctx context.Context) (*iam.Role, error) {
|
||||
rw := db.New(b.Database)
|
||||
|
||||
kmsRepo, err := kms.NewRepository(rw, rw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating kms repository: %w", err)
|
||||
}
|
||||
kmsCache, err := kms.NewKms(kmsRepo)
|
||||
kmsCache, err := kms.New(ctx, rw, rw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating kms cache: %w", err)
|
||||
}
|
||||
@@ -80,11 +76,7 @@ func (b *Server) CreateInitialLoginRole(ctx context.Context) (*iam.Role, error)
|
||||
func (b *Server) CreateInitialPasswordAuthMethod(ctx context.Context) (*password.AuthMethod, *iam.User, error) {
|
||||
rw := db.New(b.Database)
|
||||
|
||||
kmsRepo, err := kms.NewRepository(rw, rw)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating kms repository: %w", err)
|
||||
}
|
||||
kmsCache, err := kms.NewKms(kmsRepo)
|
||||
kmsCache, err := kms.New(ctx, rw, rw)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating kms cache: %w", err)
|
||||
}
|
||||
@@ -279,11 +271,7 @@ func (b *Server) CreateInitialPasswordAuthMethod(ctx context.Context) (*password
|
||||
func (b *Server) CreateInitialScopes(ctx context.Context) (*iam.Scope, *iam.Scope, error) {
|
||||
rw := db.New(b.Database)
|
||||
|
||||
kmsRepo, err := kms.NewRepository(rw, rw)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating kms repository: %w", err)
|
||||
}
|
||||
kmsCache, err := kms.NewKms(kmsRepo)
|
||||
kmsCache, err := kms.New(ctx, rw, rw)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating kms cache: %w", err)
|
||||
}
|
||||
@@ -362,11 +350,7 @@ func (b *Server) CreateInitialScopes(ctx context.Context) (*iam.Scope, *iam.Scop
|
||||
func (b *Server) CreateInitialHostResources(ctx context.Context) (*static.HostCatalog, *static.HostSet, *static.Host, error) {
|
||||
rw := db.New(b.Database)
|
||||
|
||||
kmsRepo, err := kms.NewRepository(rw, rw)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("error creating kms repository: %w", err)
|
||||
}
|
||||
kmsCache, err := kms.NewKms(kmsRepo)
|
||||
kmsCache, err := kms.New(ctx, rw, rw)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("error creating kms cache: %w", err)
|
||||
}
|
||||
@@ -473,11 +457,7 @@ func (b *Server) CreateInitialHostResources(ctx context.Context) (*static.HostCa
|
||||
func (b *Server) CreateInitialTarget(ctx context.Context) (target.Target, error) {
|
||||
rw := db.New(b.Database)
|
||||
|
||||
kmsRepo, err := kms.NewRepository(rw, rw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating kms repository: %w", err)
|
||||
}
|
||||
kmsCache, err := kms.NewKms(kmsRepo)
|
||||
kmsCache, err := kms.New(ctx, rw, rw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating kms cache: %w", err)
|
||||
}
|
||||
@@ -608,11 +588,7 @@ func (b *Server) RegisterHostPlugin(ctx context.Context, name string, plg plgpb.
|
||||
}
|
||||
rw := db.New(b.Database)
|
||||
|
||||
kmsRepo, err := kms.NewRepository(rw, rw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating kms repository: %w", err)
|
||||
}
|
||||
kmsCache, err := kms.NewKms(kmsRepo)
|
||||
kmsCache, err := kms.New(ctx, rw, rw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating kms cache: %w", err)
|
||||
}
|
||||
|
||||
@@ -655,11 +655,7 @@ func (b *Server) ConnectToDatabase(ctx context.Context, dialect string) error {
|
||||
func (b *Server) CreateGlobalKmsKeys(ctx context.Context) error {
|
||||
rw := db.New(b.Database)
|
||||
|
||||
kmsRepo, err := kms.NewRepository(rw, rw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating kms repository: %w", err)
|
||||
}
|
||||
kmsCache, err := kms.NewKms(kmsRepo)
|
||||
kmsCache, err := kms.New(ctx, rw, rw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating kms cache: %w", err)
|
||||
}
|
||||
@@ -680,8 +676,7 @@ func (b *Server) CreateGlobalKmsKeys(ctx context.Context) error {
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = kms.CreateKeysTx(cancelCtx, rw, rw, b.RootKms, b.SecureRandomReader, scope.Global.String())
|
||||
if err != nil {
|
||||
if err = kmsCache.CreateKeys(cancelCtx, scope.Global.String(), kms.WithRandomReader(b.SecureRandomReader)); err != nil {
|
||||
return fmt.Errorf("error creating global scope kms keys: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/observability/event"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
kms_plugin_assets "github.com/hashicorp/boundary/plugins/kms"
|
||||
"github.com/hashicorp/boundary/sdk/wrapper"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
@@ -800,18 +799,13 @@ func (c *Command) verifyKmsSetup() error {
|
||||
const op = "server.(Command).verifyKmsExists"
|
||||
rw := db.New(c.Database)
|
||||
|
||||
kmsRepo, err := kms.NewRepository(rw, rw)
|
||||
ctx := context.Background()
|
||||
kmsCache, err := kms.New(ctx, rw, rw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating kms repository: %w", err)
|
||||
return fmt.Errorf("error creating kms: %w", err)
|
||||
}
|
||||
rks, err := kmsRepo.ListRootKeys(c.Context, kms.WithLimit(1))
|
||||
if err != nil {
|
||||
if err := kmsCache.VerifyGlobalRoot(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, rk := range rks {
|
||||
if rk.GetScopeId() == scope.Global.String() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.NewDeprecated(errors.MigrationIntegrity, op, "can't find global scoped root key")
|
||||
return nil
|
||||
}
|
||||
@@ -396,7 +396,7 @@ func (v *verifier) decryptToken(ctx context.Context) {
|
||||
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
|
||||
return
|
||||
}
|
||||
wrapper := v.kms.GetExternalWrappers().Recovery()
|
||||
wrapper := v.kms.GetExternalWrappers(ctx).Recovery()
|
||||
if wrapper == nil {
|
||||
event.WriteError(ctx, op, stderrors.New("no recovery KMS is available"))
|
||||
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
|
||||
|
||||
@@ -210,11 +210,7 @@ func New(ctx context.Context, conf *Config) (*Controller, error) {
|
||||
|
||||
// Set up repo stuff
|
||||
dbase := db.New(c.conf.Database)
|
||||
kmsRepo, err := kms.NewRepository(dbase, dbase)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating kms repository: %w", err)
|
||||
}
|
||||
c.kms, err = kms.NewKms(kmsRepo)
|
||||
c.kms, err = kms.New(ctx, dbase, dbase)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating kms cache: %w", err)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
@@ -51,8 +50,8 @@ func TestController_New(t *testing.T) {
|
||||
verifyFn()
|
||||
|
||||
// this tests a scenario where there is NOT an audit DEK
|
||||
db.TestDeleteWhere(t, conf.Server.Database, func() interface{} { i := kms.AllocAuditKey(); return &i }(), "1=1")
|
||||
db.TestDeleteWhere(t, conf.Server.Database, func() interface{} { i := kms.AllocOidcKey(); return &i }(), "1=1")
|
||||
kms.TestKmsDeleteKeyPurpose(t, conf.Database, kms.KeyPurposeAudit)
|
||||
kms.TestKmsDeleteKeyPurpose(t, conf.Database, kms.KeyPurposeOidc)
|
||||
|
||||
// re-init an empty cache and assert that the DEKs are not there.
|
||||
kmsCache := kms.TestKms(t, conf.Server.Database, conf.RootKms)
|
||||
|
||||
@@ -74,9 +74,7 @@ func BenchmarkSessionList(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(b, "postgres", db.WithTemplate(template.name))
|
||||
rw := db.New(conn)
|
||||
kmsRepo, err := kms.NewRepository(rw, rw)
|
||||
require.NoError(b, err)
|
||||
kmsThing, err := kms.NewKms(kmsRepo)
|
||||
kmsThing, err := kms.New(ctx, rw, rw)
|
||||
require.NoError(b, err)
|
||||
wrap, err := dbtest.GetBoundaryBenchmarksRootKeyWrapper(ctx)
|
||||
require.NoError(b, err)
|
||||
|
||||
@@ -208,6 +208,11 @@ func New(underlying *DB) *Db {
|
||||
return &Db{underlying: underlying}
|
||||
}
|
||||
|
||||
// UnderlyingDB returns the underlying *dbw.DB
|
||||
func (rw *Db) UnderlyingDB() *dbw.DB {
|
||||
return rw.underlying.wrapped
|
||||
}
|
||||
|
||||
// Exec will execute the sql with the values as parameters. The int returned
|
||||
// is the number of rows affected by the sql. WithDebug is supported.
|
||||
func (rw *Db) Exec(ctx context.Context, sql string, values []interface{}, opt ...Option) (int, error) {
|
||||
|
||||
@@ -290,6 +290,7 @@ begin;
|
||||
for each row execute procedure update_session_state_on_termination_reason();
|
||||
|
||||
|
||||
-- Updated in 29/01_cancel_session_null_fkey
|
||||
-- cancel_session will insert a cancel state for the session, if there's isn't
|
||||
-- a canceled state already. It's used by cancel_session_with_null_fk.
|
||||
create or replace function
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
begin;
|
||||
|
||||
drop function cancel_session(in sessionId text);
|
||||
-- Updates cancel_session() from 0/50_session to check if a session is either terminated or canceling
|
||||
-- Sessions can progress directly to terminated without going through the canceling state
|
||||
-- cancel_session will insert a cancel state for the session, if there's isn't
|
||||
-- a canceled or terminated state already. It's used by cancel_session_with_null_fk.
|
||||
create function
|
||||
cancel_session(in sessionId text) returns void
|
||||
as $$
|
||||
declare
|
||||
rows_affected numeric;
|
||||
begin
|
||||
insert into session_state(session_id, state)
|
||||
select
|
||||
sessionId::text, 'canceling'
|
||||
from
|
||||
session s
|
||||
where
|
||||
s.public_id = sessionId::text and
|
||||
s.public_id not in (
|
||||
select
|
||||
session_id
|
||||
from
|
||||
session_state
|
||||
where
|
||||
session_id = sessionId::text and
|
||||
state in('canceling','terminated')
|
||||
) limit 1;
|
||||
get diagnostics rows_affected = row_count;
|
||||
if rows_affected > 1 then
|
||||
raise exception 'cancel session: more than one row affected: %', rows_affected;
|
||||
end if;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
commit;
|
||||
@@ -0,0 +1,103 @@
|
||||
begin;
|
||||
|
||||
-- make the required schema changes to adopt
|
||||
-- github.com/hashicorp/go-kms-wrapping/extras/kms/v2
|
||||
-- this migration is from:
|
||||
-- https://github.com/hashicorp/go-kms-wrapping/blob/main/extras/kms/migrations/postgres/01_domain_types.up.sql
|
||||
|
||||
create domain kms_private_id as text
|
||||
not null
|
||||
check(
|
||||
length(trim(value)) > 0
|
||||
);
|
||||
comment on domain kms_private_id is
|
||||
'standard column for private id';
|
||||
|
||||
create domain kms_scope_id as text
|
||||
check(
|
||||
length(trim(value)) > 0
|
||||
);
|
||||
comment on domain kms_scope_id is
|
||||
'standard column for scope id';
|
||||
|
||||
create domain kms_timestamp as
|
||||
timestamp with time zone
|
||||
default current_timestamp;
|
||||
comment on domain kms_timestamp is
|
||||
'Standard timestamp for all create_time and update_time columns';
|
||||
|
||||
create domain kms_version as bigint
|
||||
default 1
|
||||
not null
|
||||
check(
|
||||
value > 0
|
||||
);
|
||||
comment on domain kms_version is
|
||||
'standard column for row version';
|
||||
|
||||
-- kms_immutable_columns() will make the column names immutable which are passed as
|
||||
-- parameters when the trigger is created. It raises error code 23601 which is a
|
||||
-- class 23 integrity constraint violation: immutable column
|
||||
create function kms_immutable_columns()
|
||||
returns trigger
|
||||
as $$
|
||||
declare
|
||||
col_name text;
|
||||
new_value text;
|
||||
old_value text;
|
||||
begin
|
||||
foreach col_name in array tg_argv loop
|
||||
execute format('SELECT $1.%I', col_name) into new_value using new;
|
||||
execute format('SELECT $1.%I', col_name) into old_value using old;
|
||||
if new_value is distinct from old_value then
|
||||
raise exception 'immutable column: %.%', tg_table_name, col_name using
|
||||
errcode = '23601',
|
||||
schema = tg_table_schema,
|
||||
table = tg_table_name,
|
||||
column = col_name;
|
||||
end if;
|
||||
end loop;
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
comment on function
|
||||
kms_immutable_columns()
|
||||
is
|
||||
'function used in before update triggers to make columns immutable';
|
||||
|
||||
|
||||
create function kms_default_create_time()
|
||||
returns trigger
|
||||
as $$
|
||||
begin
|
||||
if new.create_time is distinct from now() then
|
||||
new.create_time = now();
|
||||
end if;
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
comment on function
|
||||
kms_default_create_time()
|
||||
is
|
||||
'function used to properly set create_time columns';
|
||||
|
||||
create function
|
||||
kms_update_time_column()
|
||||
returns trigger
|
||||
as $$
|
||||
begin
|
||||
if row(new.*) is distinct from row(old.*) then
|
||||
new.update_time = now();
|
||||
return new;
|
||||
else
|
||||
return old;
|
||||
end if;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
comment on function
|
||||
kms_update_time_column()
|
||||
is
|
||||
'function used in before update triggers to properly set update_time columns';
|
||||
|
||||
commit;
|
||||
@@ -0,0 +1,40 @@
|
||||
begin;
|
||||
|
||||
-- make the required schema changes to adopt
|
||||
-- github.com/hashicorp/go-kms-wrapping/extras/kms/v2
|
||||
-- this migration is from:
|
||||
-- https://github.com/hashicorp/go-kms-wrapping/blob/main/extras/kms/migrations/postgres/02_version.up.sql
|
||||
|
||||
create table kms_schema_version(
|
||||
version text not null,
|
||||
create_time kms_timestamp,
|
||||
update_time kms_timestamp
|
||||
);
|
||||
comment on table kms_schema_version is
|
||||
'kms_schema_version contains the kms schema version';
|
||||
|
||||
-- this index ensures that there will only ever be one row in the
|
||||
-- table. see:
|
||||
-- https://www.postgresql.org/docs/current/indexes-expressional.html
|
||||
create unique index kms_schema_version_one_row
|
||||
ON kms_schema_version((version is not null));
|
||||
|
||||
-- define the immutable fields for kms_root_key (all of them)
|
||||
create trigger kms_immutable_columns
|
||||
before
|
||||
update on kms_schema_version
|
||||
for each row execute procedure kms_immutable_columns('create_time');
|
||||
|
||||
create trigger kms_default_create_time_column
|
||||
before
|
||||
insert on kms_schema_version
|
||||
for each row execute procedure kms_default_create_time();
|
||||
|
||||
create trigger kms_update_time_column
|
||||
before
|
||||
update on kms_schema_version
|
||||
for each row execute procedure kms_update_time_column();
|
||||
|
||||
insert into kms_schema_version(version) values('v0.0.1');
|
||||
|
||||
commit;
|
||||
@@ -0,0 +1,37 @@
|
||||
begin;
|
||||
|
||||
-- make the required schema changes to adopt
|
||||
-- github.com/hashicorp/go-kms-wrapping/extras/kms/v2
|
||||
-- this migration is from:
|
||||
-- https://github.com/hashicorp/go-kms-wrapping/blob/main/extras/kms/migrations/postgres/03_keys.up.sql
|
||||
|
||||
-- we need to replace the existing function and there are existing tables that
|
||||
-- depend on it.. so let's just create/replace it
|
||||
|
||||
-- kms_version_column() will increment the version column whenever row data
|
||||
-- is inserted and should only be used in an before insert trigger. This
|
||||
-- function will overwrite any explicit values to the version column.
|
||||
create or replace function kms_version_column()
|
||||
returns trigger
|
||||
as $$
|
||||
declare
|
||||
_key_id text;
|
||||
_max bigint;
|
||||
begin
|
||||
execute format('SELECT $1.%I', tg_argv[0]) into _key_id using new;
|
||||
execute format('select max(version) + 1 from %I where %I = $1', tg_relid::regclass, tg_argv[0]) using _key_id into _max;
|
||||
if _max is null then
|
||||
_max = 1;
|
||||
end if;
|
||||
new.version = _max;
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
comment on function
|
||||
kms_version_column()
|
||||
is
|
||||
'function used in before insert triggers to properly set version columns for kms_* tables with a version column';
|
||||
|
||||
|
||||
commit;
|
||||
@@ -0,0 +1,281 @@
|
||||
begin;
|
||||
|
||||
-- make the required schema changes to adopt
|
||||
-- github.com/hashicorp/go-kms-wrapping/extras/kms/v2
|
||||
-- this migration is from:
|
||||
-- https://github.com/hashicorp/go-kms-wrapping/blob/main/extras/kms/migrations/postgres/04_keys.up.sql
|
||||
|
||||
|
||||
alter table kms_root_key rename to kms_deprecated_root_key;
|
||||
alter table kms_root_key_version rename to kms_deprecated_root_key_version;
|
||||
|
||||
create table kms_root_key (
|
||||
private_id kms_private_id primary key,
|
||||
scope_id kms_scope_id not null unique -- there can only be one root key for a scope.
|
||||
references iam_scope(public_id)
|
||||
on delete cascade
|
||||
on update cascade,
|
||||
create_time kms_timestamp
|
||||
);
|
||||
comment on table kms_root_key is
|
||||
'kms_root_key defines a root key for a scope';
|
||||
|
||||
-- define the immutable fields for kms_root_key (all of them)
|
||||
create trigger kms_immutable_columns
|
||||
before
|
||||
update on kms_root_key
|
||||
for each row execute procedure kms_immutable_columns('private_id', 'scope_id', 'create_time');
|
||||
|
||||
-- not adding the kms_default_create_time_column trigger until after we've
|
||||
-- converted the data (we don't want the timestamp to change)
|
||||
|
||||
create table kms_root_key_version (
|
||||
private_id kms_private_id primary key,
|
||||
root_key_id kms_private_id not null
|
||||
constraint kms_root_key_fkey
|
||||
references kms_root_key(private_id)
|
||||
on delete cascade
|
||||
on update cascade,
|
||||
version kms_version,
|
||||
key bytea not null
|
||||
constraint not_empty_key
|
||||
check (
|
||||
length(key) > 0
|
||||
),
|
||||
create_time kms_timestamp,
|
||||
constraint kms_root_key_version_root_key_id_version_uq
|
||||
unique(root_key_id, version)
|
||||
);
|
||||
comment on table kms_root_key_version is
|
||||
'kms_root_key_version contains versions of a kms_root_key';
|
||||
|
||||
-- define the immutable fields for kms_root_key_version (all of them)
|
||||
create trigger kms_immutable_columns
|
||||
before
|
||||
update on kms_root_key_version
|
||||
for each row execute procedure kms_immutable_columns('private_id', 'root_key_id', 'version', 'key', 'create_time');
|
||||
|
||||
-- not adding the kms_default_create_time_column or kms_version_column triggers
|
||||
-- until after we've converted the data (we don't want the timestamp or versions
|
||||
-- to change)
|
||||
|
||||
create table kms_data_key (
|
||||
private_id kms_private_id primary key,
|
||||
root_key_id kms_private_id not null
|
||||
constraint kms_root_key_fkey
|
||||
references kms_root_key(private_id)
|
||||
on delete cascade
|
||||
on update cascade,
|
||||
purpose text not null
|
||||
constraint not_start_end_whitespace_purpose
|
||||
check (length(trim(purpose)) = length(purpose)),
|
||||
create_time kms_timestamp,
|
||||
constraint kms_data_key_root_key_id_purpose_uq
|
||||
unique (root_key_id, purpose) -- there can only be one dek for a specific purpose per root key
|
||||
);
|
||||
comment on table kms_data_key is
|
||||
'kms_data_key contains deks (data keys) for specific purposes';
|
||||
|
||||
-- define the immutable fields for kms_data_key (all of them)
|
||||
create trigger kms_immutable_columns
|
||||
before
|
||||
update on kms_data_key
|
||||
for each row execute procedure kms_immutable_columns('private_id', 'root_key_id', 'purpose', 'create_time');
|
||||
|
||||
-- not adding the kms_default_create_time_column trigger until after we've
|
||||
-- converted the data (we don't want the timestamp to change)
|
||||
|
||||
create table kms_data_key_version (
|
||||
private_id kms_private_id primary key,
|
||||
data_key_id kms_private_id not null
|
||||
constraint kms_data_key_fkey
|
||||
references kms_data_key(private_id)
|
||||
on delete cascade
|
||||
on update cascade,
|
||||
root_key_version_id kms_private_id not null
|
||||
constraint kms_root_key_version_fkey
|
||||
references kms_root_key_version(private_id)
|
||||
on delete cascade
|
||||
on update cascade,
|
||||
version kms_version,
|
||||
key bytea not null
|
||||
constraint not_empty_key
|
||||
check (
|
||||
length(key) > 0
|
||||
),
|
||||
create_time kms_timestamp,
|
||||
constraint kms_data_key_version_data_key_id_version_uq
|
||||
unique(data_key_id, version)
|
||||
);
|
||||
comment on table kms_data_key is
|
||||
'kms_data_key_version contains versions of a kms_data_key (dek aka data keys)';
|
||||
|
||||
-- define the immutable fields for kms_data_key_version (all of them)
|
||||
create trigger kms_immutable_columns
|
||||
before
|
||||
update on kms_data_key_version
|
||||
for each row execute procedure immutable_columns('private_id', 'data_key_id', 'root_key_version_id', 'version', 'key', 'create_time');
|
||||
|
||||
-- not adding the kms_default_create_time_column or kms_version_column triggers
|
||||
-- until after we've converted the data (we don't want the timestamp or versions
|
||||
-- to change)
|
||||
|
||||
-- ############################################################################
|
||||
-- Next: we will convert all the existing KEKs/DEKs into the new schema model
|
||||
-- before adding triggers for create_time, and version
|
||||
-- this is important, especially for the version column!
|
||||
|
||||
-- convert KEK tables
|
||||
insert into kms_root_key (private_id, scope_id, create_time)
|
||||
select private_id, scope_id, create_time
|
||||
from kms_deprecated_root_key;
|
||||
|
||||
insert into kms_root_key_version (private_id, root_key_id, version, key, create_time)
|
||||
select private_id, root_key_id, version, key, create_time
|
||||
from kms_deprecated_root_key_version;
|
||||
|
||||
-- convert database DEK tables
|
||||
insert into kms_data_key (private_id, root_key_id, purpose, create_time)
|
||||
select private_id, root_key_id, 'database', create_time
|
||||
from kms_database_key;
|
||||
|
||||
insert into kms_data_key_version (private_id, data_key_id, root_key_version_id, version, key, create_time)
|
||||
select private_id, database_key_id, root_key_version_id, version, key, create_time
|
||||
from kms_database_key_version;
|
||||
|
||||
-- convert oplog DEK tables
|
||||
insert into kms_data_key (private_id, root_key_id, purpose, create_time)
|
||||
select private_id, root_key_id, 'oplog', create_time
|
||||
from kms_oplog_key;
|
||||
|
||||
insert into kms_data_key_version (private_id, data_key_id, root_key_version_id, version, key, create_time)
|
||||
select private_id, oplog_key_id, root_key_version_id, version, key, create_time
|
||||
from kms_oplog_key_version;
|
||||
|
||||
-- convert the audit DEK tables
|
||||
insert into kms_data_key (private_id, root_key_id, purpose, create_time)
|
||||
select private_id, root_key_id, 'audit', create_time
|
||||
from kms_audit_key;
|
||||
|
||||
insert into kms_data_key_version (private_id, data_key_id, root_key_version_id, version, key, create_time)
|
||||
select private_id, audit_key_id, root_key_version_id, version, key, create_time
|
||||
from kms_audit_key_version;
|
||||
|
||||
-- convert the oidc DEK tables
|
||||
insert into kms_data_key (private_id, root_key_id, purpose, create_time)
|
||||
select private_id, root_key_id, 'oidc', create_time
|
||||
from kms_oidc_key;
|
||||
|
||||
insert into kms_data_key_version (private_id, data_key_id, root_key_version_id, version, key, create_time)
|
||||
select private_id, oidc_key_id, root_key_version_id, version, key, create_time
|
||||
from kms_oidc_key_version;
|
||||
|
||||
-- convert the token DEK tables
|
||||
insert into kms_data_key (private_id, root_key_id, purpose, create_time)
|
||||
select private_id, root_key_id, 'tokens', create_time
|
||||
from kms_token_key;
|
||||
|
||||
insert into kms_data_key_version (private_id, data_key_id, root_key_version_id, version, key, create_time)
|
||||
select private_id, token_key_id, root_key_version_id, version, key, create_time
|
||||
from kms_token_key_version;
|
||||
|
||||
-- convert the session DEK tables
|
||||
insert into kms_data_key (private_id, root_key_id, purpose, create_time)
|
||||
select private_id, root_key_id, 'sessions', create_time
|
||||
from kms_session_key;
|
||||
|
||||
insert into kms_data_key_version (private_id, data_key_id, root_key_version_id, version, key, create_time)
|
||||
select private_id, session_key_id, root_key_version_id, version, key, create_time
|
||||
from kms_session_key_version;
|
||||
|
||||
-- ############################################################################
|
||||
-- post conversion, we add the required triggers
|
||||
create trigger kms_default_create_time_column
|
||||
before
|
||||
insert on kms_root_key
|
||||
for each row execute procedure kms_default_create_time();
|
||||
|
||||
create trigger kms_default_create_time_column
|
||||
before
|
||||
insert on kms_root_key_version
|
||||
for each row execute procedure kms_default_create_time();
|
||||
|
||||
create trigger kms_version_column
|
||||
before insert on kms_root_key_version
|
||||
for each row execute procedure kms_version_column('root_key_id');
|
||||
|
||||
create trigger default_create_time_column
|
||||
before
|
||||
insert on kms_data_key
|
||||
for each row execute procedure kms_default_create_time();
|
||||
|
||||
create trigger default_create_time
|
||||
before
|
||||
insert on kms_data_key_version
|
||||
for each row execute procedure kms_default_create_time();
|
||||
|
||||
create trigger kms_version_column
|
||||
before insert on kms_data_key_version
|
||||
for each row execute procedure kms_version_column('data_key_id');
|
||||
|
||||
-- Next: we will convert all the existing DEKs FKs to the new model
|
||||
alter table credential_vault_token drop constraint kms_database_key_version_fkey;
|
||||
alter table credential_vault_token
|
||||
add constraint kms_data_key_version_fkey
|
||||
foreign key(key_id)
|
||||
references kms_data_key_version (private_id)
|
||||
on delete restrict
|
||||
on update cascade;
|
||||
|
||||
alter table credential_vault_client_certificate drop constraint kms_database_key_version_fkey;
|
||||
alter table credential_vault_client_certificate
|
||||
add constraint kms_data_key_version_fkey
|
||||
foreign key(key_id)
|
||||
references kms_data_key_version (private_id)
|
||||
on delete restrict
|
||||
on update cascade;
|
||||
|
||||
alter table auth_oidc_method drop constraint kms_database_key_version_fkey;
|
||||
alter table auth_oidc_method
|
||||
add constraint kms_data_key_version_fkey
|
||||
foreign key(key_id)
|
||||
references kms_data_key_version (private_id)
|
||||
on delete restrict
|
||||
on update cascade;
|
||||
|
||||
alter table host_plugin_catalog_secret drop constraint kms_database_key_version_fkey;
|
||||
alter table host_plugin_catalog_secret
|
||||
add constraint kms_data_key_version_fkey
|
||||
foreign key(key_id)
|
||||
references kms_data_key_version (private_id)
|
||||
on delete restrict
|
||||
on update cascade;
|
||||
|
||||
alter table session_credential drop constraint kms_database_key_version_fkey;
|
||||
alter table session_credential
|
||||
add constraint kms_data_key_version_fkey
|
||||
foreign key(key_id)
|
||||
references kms_data_key_version (private_id)
|
||||
on delete restrict
|
||||
on update cascade;
|
||||
|
||||
-- rename tables so we know that they won't be used by mistake
|
||||
alter table kms_database_key_version rename to kms_deprecated_database_key_version;
|
||||
alter table kms_database_key rename to kms_deprecated_database_key;
|
||||
|
||||
alter table kms_oplog_key_version rename to kms_deprecated_oplog_key_version;
|
||||
alter table kms_oplog_key rename to kms_deprecated_oplog_key;
|
||||
|
||||
alter table kms_audit_key_version rename to kms_deprecated_audit_key_version;
|
||||
alter table kms_audit_key rename to kms_audit_database_key;
|
||||
|
||||
alter table kms_oidc_key_version rename to kms_deprecated_oidc_key_version;
|
||||
alter table kms_oidc_key rename to kms_deprecated_oidc_key;
|
||||
|
||||
alter table kms_session_key_version rename to kms_deprecated_session_key_version;
|
||||
alter table kms_session_key rename to kms_deprecated_session_key;
|
||||
|
||||
alter table kms_token_key_version rename to kms_deprecated_token_key_version;
|
||||
alter table kms_token_key rename to kms_deprecated_token_key;
|
||||
|
||||
commit;
|
||||
@@ -1,162 +0,0 @@
|
||||
package oss_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/common"
|
||||
"github.com/hashicorp/boundary/internal/db/schema"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/servers"
|
||||
"github.com/hashicorp/boundary/testing/dbtest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMigrations_Nonce(t *testing.T) {
|
||||
const (
|
||||
priorMigration = 16005
|
||||
currentMigration = 18001
|
||||
)
|
||||
|
||||
t.Parallel()
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
dialect := dbtest.Postgres
|
||||
|
||||
c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1))
|
||||
require.NoError(err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(c())
|
||||
})
|
||||
d, err := common.SqlOpen(dialect, u)
|
||||
require.NoError(err)
|
||||
|
||||
// migration to the prior migration (before the one we want to test)
|
||||
m, err := schema.NewManager(ctx, schema.Dialect(dialect), d, schema.WithEditions(
|
||||
schema.TestCreatePartialEditions(schema.Dialect(dialect), schema.PartialEditions{"oss": priorMigration}),
|
||||
))
|
||||
require.NoError(err)
|
||||
|
||||
require.NoError(m.ApplyMigrations(ctx))
|
||||
state, err := m.CurrentState(ctx)
|
||||
require.NoError(err)
|
||||
want := &schema.State{
|
||||
Initialized: true,
|
||||
Editions: []schema.EditionState{
|
||||
{
|
||||
Name: "oss",
|
||||
BinarySchemaVersion: priorMigration,
|
||||
DatabaseSchemaVersion: priorMigration,
|
||||
DatabaseSchemaState: schema.Equal,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.Equal(want, state)
|
||||
|
||||
// get a connection
|
||||
dbType, err := db.StringToDbType(dialect)
|
||||
require.NoError(err)
|
||||
conn, err := db.Open(dbType, u)
|
||||
require.NoError(err)
|
||||
rw := db.New(conn)
|
||||
|
||||
// okay, now we can seed the database with test data and validate existing
|
||||
// functionality. At the end of this we will have two values in the table
|
||||
// with no purpose.
|
||||
{
|
||||
// Create an old version of the struct
|
||||
type RecoveryNonce struct {
|
||||
Nonce string
|
||||
}
|
||||
|
||||
require.NoError(rw.CreateItems(ctx, []interface{}{
|
||||
&RecoveryNonce{Nonce: "abcd"},
|
||||
&RecoveryNonce{Nonce: "dcba"},
|
||||
}))
|
||||
|
||||
var results []*RecoveryNonce
|
||||
require.NoError(rw.SearchWhere(ctx, &results, "", nil))
|
||||
require.Len(results, 2)
|
||||
}
|
||||
|
||||
// now we're ready for the migration we want to test.
|
||||
m, err = schema.NewManager(ctx, schema.Dialect(dialect), d, schema.WithEditions(
|
||||
schema.TestCreatePartialEditions(schema.Dialect(dialect), schema.PartialEditions{"oss": currentMigration}),
|
||||
))
|
||||
require.NoError(err)
|
||||
|
||||
require.NoError(m.ApplyMigrations(ctx))
|
||||
state, err = m.CurrentState(ctx)
|
||||
require.NoError(err)
|
||||
want = &schema.State{
|
||||
Initialized: true,
|
||||
Editions: []schema.EditionState{
|
||||
{
|
||||
Name: "oss",
|
||||
BinarySchemaVersion: currentMigration,
|
||||
DatabaseSchemaVersion: currentMigration,
|
||||
DatabaseSchemaState: schema.Equal,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.Equal(want, state)
|
||||
|
||||
// now we have migrated and we will validate functionality; we'll do this
|
||||
// via the repo
|
||||
{
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
repo, err := servers.NewRepository(rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
|
||||
// First list and validate that the upgrade adjusted purpose correctly
|
||||
results, err := repo.ListNonces(ctx, servers.NoncePurposeRecovery)
|
||||
require.NoError(err)
|
||||
assert.Len(results, 2)
|
||||
for _, n := range results {
|
||||
assert.Equal(servers.NoncePurposeRecovery, n.Purpose)
|
||||
}
|
||||
|
||||
// Test the constraint, first via the repo, then directly
|
||||
const badPurpose = "foobar"
|
||||
err = repo.AddNonce(ctx, "xyz", badPurpose)
|
||||
require.Error(err)
|
||||
assert.Contains(err.Error(), "unknown nonce purpose")
|
||||
|
||||
// Check that null errors
|
||||
require.Error(rw.CreateItems(ctx, []interface{}{
|
||||
&servers.Nonce{Nonce: "dcba"},
|
||||
}))
|
||||
// Check that bad purpose errors
|
||||
require.Error(rw.CreateItems(ctx, []interface{}{
|
||||
&servers.Nonce{Nonce: "dcba", Purpose: badPurpose},
|
||||
}))
|
||||
|
||||
// Add some valid values, ensure that we can then list for both purposes
|
||||
// and get different values
|
||||
require.NoError(repo.AddNonce(ctx, "wxyz", servers.NoncePurposeWorkerAuth))
|
||||
require.NoError(repo.AddNonce(ctx, "zyxw", servers.NoncePurposeWorkerAuth))
|
||||
results, err = repo.ListNonces(ctx, servers.NoncePurposeRecovery)
|
||||
require.NoError(err)
|
||||
assert.Len(results, 2)
|
||||
for _, n := range results {
|
||||
assert.Equal(servers.NoncePurposeRecovery, n.Purpose)
|
||||
}
|
||||
results, err = repo.ListNonces(ctx, servers.NoncePurposeWorkerAuth)
|
||||
require.NoError(err)
|
||||
assert.Len(results, 2)
|
||||
for _, n := range results {
|
||||
assert.Equal(servers.NoncePurposeWorkerAuth, n.Purpose)
|
||||
}
|
||||
|
||||
// Last: add the same nonce, ensure we error
|
||||
require.NoError(repo.AddNonce(ctx, "double", servers.NoncePurposeWorkerAuth))
|
||||
err = repo.AddNonce(ctx, "double", servers.NoncePurposeWorkerAuth)
|
||||
require.Error(err)
|
||||
require.Contains(err.Error(), "duplicate key value")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,587 @@
|
||||
package oss_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/common"
|
||||
"github.com/hashicorp/boundary/internal/db/schema"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/testing/dbtest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMigrations_KMS_Refactor(t *testing.T) {
|
||||
const (
|
||||
priorMigration = 28002
|
||||
currentMigration = 30004
|
||||
)
|
||||
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
dialect := dbtest.Postgres
|
||||
|
||||
c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1))
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, c())
|
||||
})
|
||||
d, err := common.SqlOpen(dialect, u)
|
||||
require.NoError(t, err)
|
||||
|
||||
// migration to the prior migration (before the one we want to test)
|
||||
m, err := schema.NewManager(ctx, schema.Dialect(dialect), d, schema.WithEditions(
|
||||
schema.TestCreatePartialEditions(schema.Dialect(dialect), schema.PartialEditions{"oss": priorMigration}),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, m.ApplyMigrations(ctx))
|
||||
state, err := m.CurrentState(ctx)
|
||||
require.NoError(t, err)
|
||||
want := &schema.State{
|
||||
Initialized: true,
|
||||
Editions: []schema.EditionState{
|
||||
{
|
||||
Name: "oss",
|
||||
BinarySchemaVersion: priorMigration,
|
||||
DatabaseSchemaVersion: priorMigration,
|
||||
DatabaseSchemaState: schema.Equal,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.Equal(t, want, state)
|
||||
|
||||
// get a connection
|
||||
dbType, err := db.StringToDbType(dialect)
|
||||
require.NoError(t, err)
|
||||
conn, err := db.Open(dbType, u)
|
||||
require.NoError(t, err)
|
||||
rw := db.New(conn)
|
||||
|
||||
generateTestKeys(t, rw)
|
||||
// load up the current KEKs and their versions
|
||||
existingKeks := loadKeks(t, rw)
|
||||
existingKeksVersions := loadKekVersions(t, rw)
|
||||
|
||||
// load up the current DEKs and their versions
|
||||
existingDeks := loadCurrentDeks(t, rw)
|
||||
existingKeyVersions := loadCurrentDekVersions(t, rw)
|
||||
|
||||
// now we're ready for the migration we want to test.
|
||||
m, err = schema.NewManager(ctx, schema.Dialect(dialect), d, schema.WithEditions(
|
||||
schema.TestCreatePartialEditions(schema.Dialect(dialect), schema.PartialEditions{"oss": currentMigration}),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, m.ApplyMigrations(ctx))
|
||||
state, err = m.CurrentState(ctx)
|
||||
require.NoError(t, err)
|
||||
want = &schema.State{
|
||||
Initialized: true,
|
||||
Editions: []schema.EditionState{
|
||||
{
|
||||
Name: "oss",
|
||||
BinarySchemaVersion: currentMigration,
|
||||
DatabaseSchemaVersion: currentMigration,
|
||||
DatabaseSchemaState: schema.Equal,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.Equal(t, want, state)
|
||||
// Now read all the converted keys and see if we have transformed them correctly
|
||||
{
|
||||
// get a new connection
|
||||
dbType, err := db.StringToDbType(dialect)
|
||||
require.NoError(t, err)
|
||||
conn, err := db.Open(dbType, u)
|
||||
require.NoError(t, err)
|
||||
rw := db.New(conn)
|
||||
|
||||
newKeks := loadKeks(t, rw)
|
||||
assert.ElementsMatch(t, newKeks, existingKeks)
|
||||
|
||||
newKeksVersions := loadKekVersions(t, rw)
|
||||
assert.ElementsMatch(t, newKeksVersions, existingKeksVersions)
|
||||
|
||||
newDeks := loadNewDeks(t, rw)
|
||||
assert.ElementsMatch(t, newDeks, existingDeks)
|
||||
|
||||
newKeyVersions := loadNewDekVersions(t, rw)
|
||||
assert.ElementsMatch(t, newKeyVersions, existingKeyVersions)
|
||||
}
|
||||
}
|
||||
|
||||
// we can't use the new KMS bits since all the tables have changed, so we'll
|
||||
// generate a bit of test data by hand
|
||||
func generateTestKeys(t *testing.T, rw *db.Db) {
|
||||
t.Helper()
|
||||
require := require.New(t)
|
||||
testCtx := context.Background()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
k := kek{
|
||||
PrivateId: testId(t, "rk"),
|
||||
ScopeId: testScope(t, rw).PublicId,
|
||||
}
|
||||
require.NoError(rw.Create(testCtx, &k))
|
||||
|
||||
var kv kekVersion
|
||||
for i := 0; i < 2; i++ {
|
||||
kv = kekVersion{
|
||||
PrivateId: testId(t, "rkv"),
|
||||
RootKeyId: k.PrivateId,
|
||||
Version: 1 + uint32(i),
|
||||
Key: []byte("test-key"),
|
||||
}
|
||||
require.NoError(rw.Create(testCtx, &kv))
|
||||
}
|
||||
|
||||
{
|
||||
dbk := kmsDatabaseKey{
|
||||
PrivateId: testId(t, "dk"),
|
||||
RootKeyId: k.PrivateId,
|
||||
}
|
||||
require.NoError(rw.Create(testCtx, &dbk))
|
||||
dbkv := kmsDatabaseKeyVersion{
|
||||
PrivateId: testId(t, "dkv"),
|
||||
DatabaseKeyId: dbk.PrivateId,
|
||||
RootKeyVersionId: kv.PrivateId,
|
||||
Key: []byte("test-key"),
|
||||
Version: 1,
|
||||
}
|
||||
require.NoError(rw.Create(testCtx, &dbkv))
|
||||
}
|
||||
{
|
||||
ak := kmsAuditKey{
|
||||
PrivateId: testId(t, "ak"),
|
||||
RootKeyId: k.PrivateId,
|
||||
}
|
||||
require.NoError(rw.Create(testCtx, &ak))
|
||||
akv := kmsAuditKeyVersion{
|
||||
PrivateId: testId(t, "dkv"),
|
||||
AuditKeyId: ak.PrivateId,
|
||||
RootKeyVersionId: kv.PrivateId,
|
||||
Key: []byte("test-key"),
|
||||
Version: 1,
|
||||
}
|
||||
require.NoError(rw.Create(testCtx, &akv))
|
||||
}
|
||||
{
|
||||
oidck := kmsOidcKey{
|
||||
PrivateId: testId(t, "oidck"),
|
||||
RootKeyId: k.PrivateId,
|
||||
}
|
||||
require.NoError(rw.Create(testCtx, &oidck))
|
||||
oidckv := kmsOidcKeyVersion{
|
||||
PrivateId: testId(t, "dkv"),
|
||||
OidcKeyId: oidck.PrivateId,
|
||||
RootKeyVersionId: kv.PrivateId,
|
||||
Key: []byte("test-key"),
|
||||
Version: 1,
|
||||
}
|
||||
require.NoError(rw.Create(testCtx, &oidckv))
|
||||
}
|
||||
{
|
||||
opk := kmsOplogKey{
|
||||
PrivateId: testId(t, "opk"),
|
||||
RootKeyId: k.PrivateId,
|
||||
}
|
||||
require.NoError(rw.Create(testCtx, &opk))
|
||||
opkv := kmsOplogKeyVersion{
|
||||
PrivateId: testId(t, "dkv"),
|
||||
OplogKeyId: opk.PrivateId,
|
||||
RootKeyVersionId: kv.PrivateId,
|
||||
Key: []byte("test-key"),
|
||||
Version: 1,
|
||||
}
|
||||
require.NoError(rw.Create(testCtx, &opkv))
|
||||
}
|
||||
{
|
||||
sk := kmsSessionKey{
|
||||
PrivateId: testId(t, "sk"),
|
||||
RootKeyId: k.PrivateId,
|
||||
}
|
||||
require.NoError(rw.Create(testCtx, &sk))
|
||||
skv := kmsSessionKeyVersion{
|
||||
PrivateId: testId(t, "dkv"),
|
||||
SessionKeyId: sk.PrivateId,
|
||||
RootKeyVersionId: kv.PrivateId,
|
||||
Key: []byte("test-key"),
|
||||
Version: 1,
|
||||
}
|
||||
require.NoError(rw.Create(testCtx, &skv))
|
||||
}
|
||||
{
|
||||
tk := kmsTokenKey{
|
||||
PrivateId: testId(t, "tk"),
|
||||
RootKeyId: k.PrivateId,
|
||||
}
|
||||
require.NoError(rw.Create(testCtx, &tk))
|
||||
tkv := kmsTokenKeyVersion{
|
||||
PrivateId: testId(t, "dkv"),
|
||||
TokenKeyId: tk.PrivateId,
|
||||
RootKeyVersionId: kv.PrivateId,
|
||||
Key: []byte("test-key"),
|
||||
Version: 1,
|
||||
}
|
||||
require.NoError(rw.Create(testCtx, &tkv))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func loadKeks(t *testing.T, rw *db.Db) []kek {
|
||||
t.Helper()
|
||||
testCtx := context.Background()
|
||||
rows, err := rw.Query(testCtx, `select private_id, scope_id, create_time from kms_root_key order by private_id`, nil)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
var keks []kek
|
||||
for rows.Next() {
|
||||
var key kek
|
||||
require.NoError(t, rw.ScanRows(context.Background(), rows, &key))
|
||||
keks = append(keks, key)
|
||||
}
|
||||
return keks
|
||||
}
|
||||
|
||||
func loadKekVersions(t *testing.T, rw *db.Db) []kekVersion {
|
||||
t.Helper()
|
||||
testCtx := context.Background()
|
||||
rows, err := rw.Query(testCtx, `select private_id, root_key_id, version, key, create_time from kms_root_key_version order by private_id`, nil)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
var kekVersions []kekVersion
|
||||
for rows.Next() {
|
||||
var v kekVersion
|
||||
require.NoError(t, rw.ScanRows(context.Background(), rows, &v))
|
||||
kekVersions = append(kekVersions, v)
|
||||
}
|
||||
return kekVersions
|
||||
}
|
||||
|
||||
func loadNewDeks(t *testing.T, rw *db.Db) []dek {
|
||||
t.Helper()
|
||||
testCtx := context.Background()
|
||||
rows, err := rw.Query(testCtx, `select private_id, root_key_id, create_time, purpose from kms_data_key order by private_id`, nil)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
var deks []dek
|
||||
for rows.Next() {
|
||||
var key dek
|
||||
require.NoError(t, rw.ScanRows(context.Background(), rows, &key))
|
||||
deks = append(deks, key)
|
||||
}
|
||||
return deks
|
||||
}
|
||||
|
||||
func loadCurrentDeks(t *testing.T, rw *db.Db) []dek {
|
||||
t.Helper()
|
||||
testCtx := context.Background()
|
||||
var deks []dek
|
||||
for _, purpose := range []string{"audit", "database", "oidc", "oplog", "sessions", "tokens"} {
|
||||
var table string
|
||||
switch purpose {
|
||||
case "audit":
|
||||
table = "kms_audit_key"
|
||||
case "database":
|
||||
table = "kms_database_key"
|
||||
case "oidc":
|
||||
table = "kms_oidc_key"
|
||||
case "oplog":
|
||||
table = "kms_oplog_key"
|
||||
case "sessions":
|
||||
table = "kms_session_key"
|
||||
case "tokens":
|
||||
table = "kms_token_key"
|
||||
default:
|
||||
t.Fatalf("not a supported dek %q", purpose)
|
||||
}
|
||||
sql := fmt.Sprintf(`select private_id, root_key_id, create_time, '%s' as purpose from %s order by private_id`, purpose, table)
|
||||
rows, err := rw.Query(testCtx, sql, nil)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
for rows.Next() {
|
||||
var key dek
|
||||
require.NoError(t, rw.ScanRows(context.Background(), rows, &key))
|
||||
deks = append(deks, key)
|
||||
}
|
||||
}
|
||||
return deks
|
||||
}
|
||||
|
||||
func loadNewDekVersions(t *testing.T, rw *db.Db) []dekVersion {
|
||||
t.Helper()
|
||||
testCtx := context.Background()
|
||||
rows, err := rw.Query(testCtx, `select private_id, data_key_id, root_key_version_id, version, key, create_time from kms_data_key_version order by private_id`, nil)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
var dekVersions []dekVersion
|
||||
for rows.Next() {
|
||||
var v dekVersion
|
||||
require.NoError(t, rw.ScanRows(context.Background(), rows, &v))
|
||||
dekVersions = append(dekVersions, v)
|
||||
}
|
||||
return dekVersions
|
||||
}
|
||||
|
||||
func loadCurrentDekVersions(t *testing.T, rw *db.Db) []dekVersion {
|
||||
t.Helper()
|
||||
testCtx := context.Background()
|
||||
var dekVersions []dekVersion
|
||||
for _, versionType := range []string{"audit", "database", "oidc", "oplog", "session", "token"} {
|
||||
var table string
|
||||
switch versionType {
|
||||
case "audit":
|
||||
table = "kms_audit_key_version"
|
||||
case "database":
|
||||
table = "kms_database_key_version"
|
||||
case "oidc":
|
||||
table = "kms_oidc_key_version"
|
||||
case "oplog":
|
||||
table = "kms_oplog_key_version"
|
||||
case "session":
|
||||
table = "kms_session_key_version"
|
||||
case "token":
|
||||
table = "kms_token_key_version"
|
||||
}
|
||||
sql := fmt.Sprintf(`select private_id, %s_key_id, root_key_version_id, key, version, create_time from %s order by private_id`, versionType, table)
|
||||
rows, err := rw.Query(testCtx, sql, nil)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
for rows.Next() {
|
||||
result := map[string]interface{}{}
|
||||
require.NoError(t, rw.ScanRows(context.Background(), rows, &result))
|
||||
var v dekVersion
|
||||
switch versionType {
|
||||
case "audit":
|
||||
v = dekVersion{
|
||||
PrivateId: result["private_id"].(string),
|
||||
DataKeyId: result["audit_key_id"].(string),
|
||||
RootKeyVersionId: result["root_key_version_id"].(string),
|
||||
Key: result["key"].([]byte),
|
||||
Version: uint32(result["version"].(int64)),
|
||||
CreateTime: result["create_time"].(time.Time),
|
||||
}
|
||||
case "database":
|
||||
v = dekVersion{
|
||||
PrivateId: result["private_id"].(string),
|
||||
DataKeyId: result["database_key_id"].(string),
|
||||
RootKeyVersionId: result["root_key_version_id"].(string),
|
||||
Key: result["key"].([]byte),
|
||||
Version: uint32(result["version"].(int64)),
|
||||
CreateTime: result["create_time"].(time.Time),
|
||||
}
|
||||
case "oidc":
|
||||
v = dekVersion{
|
||||
PrivateId: result["private_id"].(string),
|
||||
DataKeyId: result["oidc_key_id"].(string),
|
||||
RootKeyVersionId: result["root_key_version_id"].(string),
|
||||
Key: result["key"].([]byte),
|
||||
Version: uint32(result["version"].(int64)),
|
||||
CreateTime: result["create_time"].(time.Time),
|
||||
}
|
||||
case "oplog":
|
||||
v = dekVersion{
|
||||
PrivateId: result["private_id"].(string),
|
||||
DataKeyId: result["oplog_key_id"].(string),
|
||||
RootKeyVersionId: result["root_key_version_id"].(string),
|
||||
Key: result["key"].([]byte),
|
||||
Version: uint32(result["version"].(int64)),
|
||||
CreateTime: result["create_time"].(time.Time),
|
||||
}
|
||||
case "session":
|
||||
v = dekVersion{
|
||||
PrivateId: result["private_id"].(string),
|
||||
DataKeyId: result["session_key_id"].(string),
|
||||
RootKeyVersionId: result["root_key_version_id"].(string),
|
||||
Key: result["key"].([]byte),
|
||||
Version: uint32(result["version"].(int64)),
|
||||
CreateTime: result["create_time"].(time.Time),
|
||||
}
|
||||
case "token":
|
||||
v = dekVersion{
|
||||
PrivateId: result["private_id"].(string),
|
||||
DataKeyId: result["token_key_id"].(string),
|
||||
RootKeyVersionId: result["root_key_version_id"].(string),
|
||||
Key: result["key"].([]byte),
|
||||
Version: uint32(result["version"].(int64)),
|
||||
CreateTime: result["create_time"].(time.Time),
|
||||
}
|
||||
}
|
||||
dekVersions = append(dekVersions, v)
|
||||
}
|
||||
}
|
||||
return dekVersions
|
||||
}
|
||||
|
||||
func testId(t testing.TB, prefix string) string {
|
||||
t.Helper()
|
||||
id, err := db.NewPublicId(prefix)
|
||||
require.NoError(t, err)
|
||||
return id
|
||||
}
|
||||
|
||||
func testScope(t *testing.T, rw *db.Db) *iam.Scope {
|
||||
t.Helper()
|
||||
require := require.New(t)
|
||||
testCtx := context.Background()
|
||||
|
||||
s, err := iam.NewOrg()
|
||||
require.NoError(err)
|
||||
s.PublicId = testId(t, "o")
|
||||
|
||||
require.NoError(rw.Create(testCtx, &s))
|
||||
return s
|
||||
}
|
||||
|
||||
type kek struct {
|
||||
PrivateId string
|
||||
ScopeId string
|
||||
CreateTime time.Time
|
||||
}
|
||||
|
||||
func (*kek) TableName() string { return "kms_root_key" }
|
||||
|
||||
type kekVersion struct {
|
||||
PrivateId string
|
||||
RootKeyId string
|
||||
Version uint32
|
||||
Key []byte
|
||||
CreateTime time.Time
|
||||
}
|
||||
|
||||
func (*kekVersion) TableName() string { return "kms_root_key_version" }
|
||||
|
||||
type dek struct {
|
||||
PrivateId string
|
||||
RootKeyId string
|
||||
CreateTime time.Time
|
||||
Purpose string
|
||||
}
|
||||
type dekVersion struct {
|
||||
PrivateId string
|
||||
DataKeyId string
|
||||
RootKeyVersionId string
|
||||
Key []byte
|
||||
Version uint32
|
||||
CreateTime time.Time
|
||||
}
|
||||
|
||||
type kmsDatabaseKey struct {
|
||||
PrivateId string
|
||||
RootKeyId string
|
||||
CreateTime time.Time
|
||||
}
|
||||
|
||||
func (*kmsDatabaseKey) TableName() string { return "kms_database_key" }
|
||||
|
||||
type kmsDatabaseKeyVersion struct {
|
||||
PrivateId string
|
||||
DatabaseKeyId string
|
||||
RootKeyVersionId string
|
||||
Key []byte
|
||||
Version uint32
|
||||
CreateTime time.Time
|
||||
}
|
||||
|
||||
func (*kmsDatabaseKeyVersion) TableName() string { return "kms_database_key_version" }
|
||||
|
||||
type kmsAuditKey struct {
|
||||
PrivateId string
|
||||
RootKeyId string
|
||||
CreateTime time.Time
|
||||
}
|
||||
|
||||
func (*kmsAuditKey) TableName() string { return "kms_audit_key" }
|
||||
|
||||
type kmsAuditKeyVersion struct {
|
||||
PrivateId string
|
||||
AuditKeyId string
|
||||
RootKeyVersionId string
|
||||
Key []byte
|
||||
Version uint32
|
||||
CreateTime time.Time
|
||||
}
|
||||
|
||||
func (*kmsAuditKeyVersion) TableName() string { return "kms_audit_key_version" }
|
||||
|
||||
type kmsOidcKey struct {
|
||||
PrivateId string
|
||||
RootKeyId string
|
||||
CreateTime time.Time
|
||||
}
|
||||
|
||||
func (*kmsOidcKey) TableName() string { return "kms_oidc_key" }
|
||||
|
||||
type kmsOidcKeyVersion struct {
|
||||
PrivateId string
|
||||
OidcKeyId string
|
||||
RootKeyVersionId string
|
||||
Key []byte
|
||||
Version uint32
|
||||
CreateTime time.Time
|
||||
}
|
||||
|
||||
func (*kmsOidcKeyVersion) TableName() string { return "kms_oidc_key_version" }
|
||||
|
||||
type kmsOplogKey struct {
|
||||
PrivateId string
|
||||
RootKeyId string
|
||||
CreateTime time.Time
|
||||
}
|
||||
|
||||
func (*kmsOplogKey) TableName() string { return "kms_oplog_key" }
|
||||
|
||||
type kmsOplogKeyVersion struct {
|
||||
PrivateId string
|
||||
OplogKeyId string
|
||||
RootKeyVersionId string
|
||||
Key []byte
|
||||
Version uint32
|
||||
CreateTime time.Time
|
||||
}
|
||||
|
||||
func (*kmsOplogKeyVersion) TableName() string { return "kms_oplog_key_version" }
|
||||
|
||||
type kmsSessionKey struct {
|
||||
PrivateId string
|
||||
RootKeyId string
|
||||
CreateTime time.Time
|
||||
}
|
||||
|
||||
func (*kmsSessionKey) TableName() string { return "kms_session_key" }
|
||||
|
||||
type kmsSessionKeyVersion struct {
|
||||
PrivateId string
|
||||
SessionKeyId string
|
||||
RootKeyVersionId string
|
||||
Key []byte
|
||||
Version uint32
|
||||
CreateTime time.Time
|
||||
}
|
||||
|
||||
func (*kmsSessionKeyVersion) TableName() string { return "kms_session_key_version" }
|
||||
|
||||
type kmsTokenKey struct {
|
||||
PrivateId string
|
||||
RootKeyId string
|
||||
CreateTime time.Time
|
||||
}
|
||||
|
||||
func (*kmsTokenKey) TableName() string { return "kms_token_key" }
|
||||
|
||||
type kmsTokenKeyVersion struct {
|
||||
PrivateId string
|
||||
TokenKeyId string
|
||||
RootKeyVersionId string
|
||||
Key []byte
|
||||
Version uint32
|
||||
CreateTime time.Time
|
||||
}
|
||||
|
||||
func (*kmsTokenKeyVersion) TableName() string { return "kms_token_key_version" }
|
||||
@@ -105,13 +105,13 @@ begin;
|
||||
values
|
||||
('krkv___widget', 'krk___widget', 'krk___widget'::bytea);
|
||||
|
||||
insert into kms_database_key
|
||||
(private_id, root_key_id)
|
||||
insert into kms_data_key
|
||||
(private_id, root_key_id, purpose)
|
||||
values
|
||||
('kdk____widget', 'krk___widget');
|
||||
('kdk____widget', 'krk___widget', 'database');
|
||||
|
||||
insert into kms_database_key_version
|
||||
(private_id, database_key_id, root_key_version_id, key)
|
||||
insert into kms_data_key_version
|
||||
(private_id, data_key_id, root_key_version_id, key)
|
||||
values
|
||||
('kdkv___widget', 'kdk____widget', 'krkv___widget', 'kdk____widget'::bytea);
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
begin;
|
||||
select plan(9);
|
||||
|
||||
-- Ensure session state table is populated
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____clare' and state='pending';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____cindy' and state='terminated';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____ciara' and state='canceling';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____carly' and state='active';
|
||||
|
||||
-- Check that we have 4 sessions using this target
|
||||
select is(count(*), 4::bigint) from session where target_id = 't_________cb';
|
||||
|
||||
-- Delete target, expect no errors
|
||||
delete from target where public_id='t_________cb';
|
||||
select is(count(*), 0::bigint) from target where public_id='t_________cb';
|
||||
|
||||
-- Ensure we no longer have sessions associated with this target
|
||||
select is(count(*), 0::bigint) from session where target_id = 't_________cb';
|
||||
|
||||
-- Ensure sessions that were pending or active are now in canceling state
|
||||
select is(count(*), 1::bigint) from session_state where state = 'canceling' and session_id = 's1_____clare';
|
||||
select is(count(*), 1::bigint) from session_state where state = 'canceling' and session_id = 's1_____carly';
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@@ -4742,7 +4742,7 @@
|
||||
},
|
||||
"closed_reason": {
|
||||
"type": "string",
|
||||
"title": "closed_reason of the conneciont"
|
||||
"title": "closed_reason of the connection"
|
||||
}
|
||||
},
|
||||
"title": "Connection contains information about a specific connection in a session"
|
||||
|
||||
@@ -31,7 +31,6 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
|
||||
}
|
||||
|
||||
var parentOplogWrapper wrapping.Wrapper
|
||||
var externalWrappers *kms.ExternalWrappers
|
||||
var err error
|
||||
switch s.Type {
|
||||
case scope.Unknown.String():
|
||||
@@ -47,7 +46,6 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
|
||||
default:
|
||||
parentOplogWrapper, err = r.kms.GetWrapper(ctx, s.ParentId, kms.KeyPurposeOplog)
|
||||
}
|
||||
externalWrappers = r.kms.GetExternalWrappers()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "unable to get oplog wrapper")
|
||||
@@ -174,17 +172,17 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
|
||||
|
||||
s := scopeRaw.(*Scope)
|
||||
|
||||
// Create the scope's keys
|
||||
_, err = kms.CreateKeysTx(ctx, dbr, w, externalWrappers.Root(), reader, s.PublicId)
|
||||
txnKms, err := kms.NewUsingReaderWriter(ctx, dbr, w)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("error creating transaction's kms"))
|
||||
}
|
||||
if err := txnKms.AddExternalWrappers(ctx, kms.WithRootWrapper(r.kms.GetExternalWrappers(ctx).Root())); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("error adding external root wrapper to transaction's kms"))
|
||||
}
|
||||
if err := txnKms.CreateKeys(ctx, s.PublicId, kms.WithRandomReader(reader), kms.WithReaderWriter(dbr, w)); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("error creating scope keys"))
|
||||
}
|
||||
|
||||
kmsRepo, err := kms.NewRepository(dbr, w)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("error creating new kms repo"))
|
||||
}
|
||||
childOplogWrapper, err := r.kms.GetWrapper(ctx, s.PublicId, kms.KeyPurposeOplog, kms.WithRepository(kmsRepo))
|
||||
childOplogWrapper, err := txnKms.GetWrapper(ctx, s.PublicId, kms.KeyPurposeOplog)
|
||||
if err != nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "unable to get oplog wrapper")
|
||||
}
|
||||
|
||||
@@ -160,8 +160,8 @@ func Test_Repository_delete(t *testing.T) {
|
||||
err = db.TestVerifyOplog(t, rw, s.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(5*time.Second))
|
||||
require.NoError(err)
|
||||
})
|
||||
rk := kms.AllocRootKey()
|
||||
db.TestDeleteWhere(t, conn, &rk, "1=1")
|
||||
kms.TestKmsDeleteAllKeys(t, conn)
|
||||
|
||||
t.Run("nil-resource", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestRepo(t testing.TB, conn *db.DB, rootWrapper wrapping.Wrapper, opt ...Op
|
||||
kmsCache := kms.TestKms(t, conn, rootWrapper)
|
||||
wrapper, err := kmsCache.GetWrapper(context.Background(), scope.Global.String(), kms.KeyPurposeOplog)
|
||||
if err != nil {
|
||||
_, err = kms.CreateKeysTx(context.Background(), rw, rw, rootWrapper, rand.Reader, scope.Global.String())
|
||||
err = kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))
|
||||
require.NoError(err)
|
||||
wrapper, err = kmsCache.GetWrapper(context.Background(), scope.Global.String(), kms.KeyPurposeOplog)
|
||||
if err != nil {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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" }
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ message Connection {
|
||||
// bytes_down of the connection
|
||||
uint64 bytes_down = 8; // @gotags: `class:"public"`
|
||||
|
||||
// closed_reason of the conneciont
|
||||
// closed_reason of the connection
|
||||
string closed_reason = 9; // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,10 @@ func TestRecoveryNonces(t *testing.T) {
|
||||
})
|
||||
defer tc.Shutdown()
|
||||
|
||||
externalWrappers := tc.Kms().GetExternalWrappers(context.Background())
|
||||
externalRecovery := externalWrappers.Recovery()
|
||||
require.Equal(externalRecovery, wrapper)
|
||||
|
||||
client := tc.Client()
|
||||
repo := tc.ServersRepo()
|
||||
|
||||
|
||||
Loaded 100 of 105 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user