Merge branch 'main' into llb-byow

This commit is contained in:
Jeff Mitchell committed 2022-05-13 17:05:36 -04:00
commit 2f06513a3a
105 files changed
+2716 -14710

No files matched your search

+2
View File
@@ -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)
+1 -1
View File
@@ -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:
+27 -15
View File
@@ -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
)
+798 -66
View File
File diff suppressed because it is too large. Load diff
+1 -5
View File
@@ -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)
}
+6 -30
View File
@@ -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)
}
+2 -7
View File
@@ -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)
}
+5 -11
View File
@@ -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
}
+1 -1
View File
@@ -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)
+1 -5
View File
@@ -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)
+5
View File
@@ -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;
+1 -1
View File
@@ -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"
+8 -10
View File
@@ -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")
}
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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 {
-82
View File
@@ -1,82 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
"google.golang.org/protobuf/proto"
)
const (
DefaultAuditKeyTableName = "kms_audit_key"
)
type AuditKey struct {
*store.AuditKey
tableName string `gorm:"-"`
}
// NewAuditKey creates a new in memory audit key. This key is used for crypto
// operations on audit entries. No options are currently supported.
func NewAuditKey(ctx context.Context, rootKeyId string, _ ...Option) (*AuditKey, error) {
const op = "kms.NewAuditKey"
if rootKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
k := &AuditKey{
AuditKey: &store.AuditKey{
RootKeyId: rootKeyId,
},
}
return k, nil
}
// AllocAuditKey will allocate a AuditKey
func AllocAuditKey() AuditKey {
return AuditKey{
AuditKey: &store.AuditKey{},
}
}
// Clone creates a clone of the AuditKey
func (k *AuditKey) Clone() interface{} {
cp := proto.Clone(k.AuditKey)
return &AuditKey{
AuditKey: cp.(*store.AuditKey),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// before it's written.
func (k *AuditKey) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "kms.(AuditKey).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
switch opType {
case db.CreateOp:
if k.RootKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
case db.UpdateOp:
return errors.New(ctx, errors.InvalidParameter, op, "key is immutable")
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *AuditKey) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultAuditKeyTableName
}
// SetTableName sets the tablename and satisfies the ReplayableMessage
// interface. If the caller attempts to set the name to "" the name will be
// reset to the default name.
func (k *AuditKey) SetTableName(n string) {
k.tableName = n
}
-209
View File
@@ -1,209 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the AuditKey attributes are
// immutable and those tests are covered by TestAuditKey_ImmutableFields
func TestAuditKey_Create(t *testing.T) {
t.Parallel()
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootKeyId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.AuditKey
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-rootKeyId",
args: args{},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
rootKeyId: rk.PrivateId,
},
want: func() *kms.AuditKey {
k := kms.AllocAuditKey()
k.RootKeyId = rk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocAuditKey(); return &i }(), "1=1")
got, err := kms.NewAuditKey(testCtx, tt.args.rootKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.AuditKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestAuditKey_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocAuditKey(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.AuditKey
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestAuditKey(t, conn, rk.PrivateId),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.AuditKey {
k := kms.AllocAuditKey()
id, err := db.NewPublicId(kms.AuditKeyPrefix)
require.NoError(t, err)
k.PrivateId = id
k.RootKeyId = rk.PrivateId
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocAuditKey()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocAuditKey()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestAuditKey_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k := kms.TestAuditKey(t, conn, rk.PrivateId)
cp := k.Clone()
assert.True(proto.Equal(cp.(*kms.AuditKey).AuditKey, k.AuditKey))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
k := kms.TestAuditKey(t, conn, rk.PrivateId)
k2 := kms.TestAuditKey(t, conn, rk2.PrivateId)
cp := k.Clone()
assert.True(!proto.Equal(cp.(*kms.AuditKey).AuditKey, k2.AuditKey))
})
}
func TestAuditKey_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultAuditKeyTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocAuditKey()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocAuditKey()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-122
View File
@@ -1,122 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
const (
DefaultAuditKeyVersionTableName = "kms_audit_key_version"
)
type AuditKeyVersion struct {
*store.AuditKeyVersion
tableName string `gorm:"-"`
}
// NewAuditKeyVersion creates a new in memory audit key version. This key is
// used for crypto operations on audit entries. No options are currently
// supported.
func NewAuditKeyVersion(ctx context.Context, auditKeyId string, key []byte, rootKeyVersionId string, _ ...Option) (*AuditKeyVersion, error) {
const op = "kms.NewAuditKeyVersion"
if auditKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing audit key id")
}
if len(key) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
if rootKeyVersionId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
k := &AuditKeyVersion{
AuditKeyVersion: &store.AuditKeyVersion{
AuditKeyId: auditKeyId,
RootKeyVersionId: rootKeyVersionId,
Key: key,
},
}
return k, nil
}
// AllocAuditKeyVersion allocates a AuditKeyVersion
func AllocAuditKeyVersion() AuditKeyVersion {
return AuditKeyVersion{
AuditKeyVersion: &store.AuditKeyVersion{},
}
}
// Clone creates a clone of the AuditKeyVersion
func (k *AuditKeyVersion) Clone() interface{} {
cp := proto.Clone(k.AuditKeyVersion)
return &AuditKeyVersion{
AuditKeyVersion: cp.(*store.AuditKeyVersion),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the audit key
// version before it's written.
func (k *AuditKeyVersion) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "kms.(AuditKeyVersion).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
switch opType {
case db.CreateOp:
if k.CtKey == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
if k.AuditKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing audit key id")
}
if k.RootKeyVersionId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
case db.UpdateOp:
return errors.New(ctx, errors.InvalidParameter, op, "key is immutable")
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *AuditKeyVersion) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultAuditKeyVersionTableName
}
// SetTableName sets the tablename and satisfies the ReplayableMessage
// interface. If the caller attempts to set the name to "" the name will be
// reset to the default name.
func (k *AuditKeyVersion) SetTableName(n string) {
k.tableName = n
}
// Encrypt will encrypt the audit key version's key
func (k *AuditKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(AuditKeyVersion).Encrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.AuditKeyVersion directly
if err := structwrapping.WrapStruct(ctx, cipher, k.AuditKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
}
return nil
}
// Decrypt will decrypt the audit key version's key
func (k *AuditKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(AuditKeyVersion).Decrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.AuditKeyVersion directly
if err := structwrapping.UnwrapStruct(ctx, cipher, k.AuditKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
-250
View File
@@ -1,250 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the AuditKeyVersion attributes are
// immutable and those tests are covered by TestAuditKey_ImmutableFields
func TestAuditKeyVersion_Create(t *testing.T) {
t.Parallel()
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rkv, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestAuditKey(t, conn, rk.PrivateId)
type args struct {
AuditKeyId string
key []byte
rootKeyVersionId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.AuditKeyVersion
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-audit-id",
args: args{
key: []byte("audit key"),
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
AuditKeyId: dk.PrivateId,
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-root-key-version-id",
args: args{
AuditKeyId: dk.PrivateId,
key: []byte("audit key"),
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
AuditKeyId: dk.PrivateId,
key: []byte("audit key"),
rootKeyVersionId: rkv.PrivateId,
},
want: func() *kms.AuditKeyVersion {
k := kms.AllocAuditKeyVersion()
k.RootKeyVersionId = rkv.PrivateId
k.Key = []byte("audit key")
k.AuditKeyId = dk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocAuditKeyVersion(); return &i }(), "1=1")
got, err := kms.NewAuditKeyVersion(testCtx, tt.args.AuditKeyId, tt.args.key, tt.args.rootKeyVersionId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.AuditKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = got.Encrypt(context.Background(), rkvWrapper)
require.NoError(err)
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestAuditKeyVersion_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestAuditKey(t, conn, rk.PrivateId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocAuditKeyVersion(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.AuditKeyVersion
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestAuditKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("audit key")),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.AuditKeyVersion {
k := kms.AllocAuditKeyVersion()
id, err := db.NewPublicId(kms.AuditKeyVersionPrefix)
require.NoError(t, err)
k.PrivateId = id
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocAuditKeyVersion()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocAuditKeyVersion()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestAuditKeyVersion_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
k := kms.TestAuditKey(t, conn, rk.PrivateId)
kv := kms.TestAuditKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test db key"))
cp := kv.Clone()
assert.True(proto.Equal(cp.(*kms.AuditKeyVersion).AuditKeyVersion, kv.AuditKeyVersion))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
_, rkvWrapper2 := kms.TestRootKeyVersion(t, conn, wrapper, rk2.PrivateId)
k := kms.TestAuditKey(t, conn, rk.PrivateId)
k2 := kms.TestAuditKey(t, conn, rk2.PrivateId)
kv := kms.TestAuditKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test db key"))
kv2 := kms.TestAuditKeyVersion(t, conn, rkvWrapper2, k2.PrivateId, []byte("test db key 2"))
cp := kv.Clone()
assert.True(!proto.Equal(cp.(*kms.AuditKeyVersion).AuditKeyVersion, kv2.AuditKeyVersion))
})
}
func TestAuditKeyVersion_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultAuditKeyVersionTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocAuditKeyVersion()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocAuditKeyVersion()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
+21 -59
View File
@@ -24,6 +24,9 @@ const (
// KeyPurposeRecovery is used for recovery access
KeyPurposeRecovery
// KeyPurposeWorkerAuth is used for worker auth
KeyPurposeWorkerAuth
// KeyPurposeTokens is used for token encryption
KeyPurposeTokens
@@ -36,6 +39,9 @@ const (
// KeyPurposeAudit is used for audit operations
KeyPurposeAudit
// KeyPurposeRootKey is used as the root key
KeyPurposeRootKey
)
// String returns the key purpose cast as a string, just so it can be called as
@@ -48,6 +54,8 @@ func (k KeyPurpose) String() string {
return "oplog"
case KeyPurposeRecovery:
return "recovery"
case KeyPurposeWorkerAuth:
return "workerauth"
case KeyPurposeTokens:
return "tokens"
case KeyPurposeSessions:
@@ -56,67 +64,21 @@ func (k KeyPurpose) String() string {
return "oidc"
case KeyPurposeAudit:
return "audit"
default:
return "unknown"
}
}
// KeyType allows the kms repo to return a map[KeyType]Key which can be easily
// used without type casting.
type KeyType uint
const (
KeyTypeUnknown KeyType = iota
KeyTypeRootKey
KeyTypeRootKeyVersion
KeyTypeDatabaseKey
KeyTypeDatabaseKeyVersion
KeyTypeOplogKey
KeyTypeOplogKeyVersion
KeyTypeTokenKey
KeyTypeTokenKeyVersion
KeyTypeSessionKey
KeyTypeSessionKeyVersion
KeyTypeOidcKey
KeyTypeOidcKeyVersion
KeyTypeAuditKey
KeyTypeAuditKeyVersion
)
// String returns the key type cast as a string, just so it can be called as
// a function instead of direct casting elsewhere, yw
func (k KeyType) String() string {
switch k {
case KeyTypeRootKey:
case KeyPurposeRootKey:
return "rootKey"
case KeyTypeRootKeyVersion:
return "rootKeyVersion"
case KeyTypeDatabaseKey:
return "databaseKey"
case KeyTypeDatabaseKeyVersion:
return "databaseKeyVersion"
case KeyTypeOplogKey:
return "oplogKey"
case KeyTypeOplogKeyVersion:
return "oplogKeyVersion"
case KeyTypeTokenKey:
return "tokenKey"
case KeyTypeTokenKeyVersion:
return "tokenKeyVersion"
case KeyTypeSessionKey:
return "sessionKey"
case KeyTypeSessionKeyVersion:
return "sessionKeyVersion"
case KeyTypeOidcKey:
return "oidcKey"
case KeyTypeOidcKeyVersion:
return "oidcKeyVersion"
case KeyTypeAuditKey:
return "auditKey"
case KeyTypeAuditKeyVersion:
return "auditKeyVersion"
default:
return "unknown"
}
}
// ValidDekPurposes returns the current list of valid DEK key purposes
func ValidDekPurposes() []KeyPurpose {
return []KeyPurpose{
KeyPurposeDatabase,
KeyPurposeOplog,
KeyPurposeTokens,
KeyPurposeSessions,
KeyPurposeOidc,
KeyPurposeAudit,
}
}
-82
View File
@@ -1,82 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
"google.golang.org/protobuf/proto"
)
const (
DefaultDatabaseKeyTableName = "kms_database_key"
)
type DatabaseKey struct {
*store.DatabaseKey
tableName string `gorm:"-"`
}
// NewDatabaseKey creates a new in memory database key. No options
// are currently supported.
func NewDatabaseKey(rootKeyId string, _ ...Option) (*DatabaseKey, error) {
const op = "kms.NewDatabaseKey"
if rootKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key id")
}
c := &DatabaseKey{
DatabaseKey: &store.DatabaseKey{
RootKeyId: rootKeyId,
},
}
return c, nil
}
// AllocDatabaseKey will allocate a DatabaseKey
func AllocDatabaseKey() DatabaseKey {
return DatabaseKey{
DatabaseKey: &store.DatabaseKey{},
}
}
// Clone creates a clone of the DatabaseKey
func (k *DatabaseKey) Clone() interface{} {
cp := proto.Clone(k.DatabaseKey)
return &DatabaseKey{
DatabaseKey: cp.(*store.DatabaseKey),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// before it's written.
func (k *DatabaseKey) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "kms.(DatabaseKey).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
switch opType {
case db.CreateOp:
if k.RootKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
case db.UpdateOp:
return errors.New(ctx, errors.InvalidParameter, op, "key is immutable")
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *DatabaseKey) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultDatabaseKeyTableName
}
// SetTableName sets the tablename and satisfies the ReplayableMessage
// interface. If the caller attempts to set the name to "" the name will be
// reset to the default name.
func (k *DatabaseKey) SetTableName(n string) {
k.tableName = n
}
-206
View File
@@ -1,206 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the DatabaseKey attributes are
// immutable and those tests are covered by TestDatabaseKey_ImmutableFields
func TestDatabaseKey_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootKeyId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.DatabaseKey
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-rootKeyId",
args: args{},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
rootKeyId: rk.PrivateId,
},
want: func() *kms.DatabaseKey {
k := kms.AllocDatabaseKey()
k.RootKeyId = rk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocDatabaseKey(); return &i }(), "1=1")
got, err := kms.NewDatabaseKey(tt.args.rootKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.DatabaseKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestDatabaseKey_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocDatabaseKey(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.DatabaseKey
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestDatabaseKey(t, conn, rk.PrivateId),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.DatabaseKey {
k := kms.AllocDatabaseKey()
id, err := db.NewPublicId(kms.DatabaseKeyPrefix)
require.NoError(t, err)
k.PrivateId = id
k.RootKeyId = rk.PrivateId
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocDatabaseKey()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocDatabaseKey()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestDatabaseKey_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k := kms.TestDatabaseKey(t, conn, rk.PrivateId)
cp := k.Clone()
assert.True(proto.Equal(cp.(*kms.DatabaseKey).DatabaseKey, k.DatabaseKey))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
k := kms.TestDatabaseKey(t, conn, rk.PrivateId)
k2 := kms.TestDatabaseKey(t, conn, rk2.PrivateId)
cp := k.Clone()
assert.True(!proto.Equal(cp.(*kms.DatabaseKey).DatabaseKey, k2.DatabaseKey))
})
}
func TestDatabaseKey_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultDatabaseKeyTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocDatabaseKey()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocDatabaseKey()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-121
View File
@@ -1,121 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
const (
DefaultDatabaseKeyVersionTableName = "kms_database_key_version"
)
type DatabaseKeyVersion struct {
*store.DatabaseKeyVersion
tableName string `gorm:"-"`
}
// NewDatabaseKeyVersion creates a new in memory database key version. No options are
// currently supported.
func NewDatabaseKeyVersion(databaseKeyId string, key []byte, rootKeyVersionId string, _ ...Option) (*DatabaseKeyVersion, error) {
const op = "kms.NewDatabaseKeyVersion"
if databaseKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing database key id")
}
if len(key) == 0 {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing key")
}
if rootKeyVersionId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key version id")
}
k := &DatabaseKeyVersion{
DatabaseKeyVersion: &store.DatabaseKeyVersion{
DatabaseKeyId: databaseKeyId,
RootKeyVersionId: rootKeyVersionId,
Key: key,
},
}
return k, nil
}
// AllocDatabaseKeyVersion allocates a DatabaseKeyVersion
func AllocDatabaseKeyVersion() DatabaseKeyVersion {
return DatabaseKeyVersion{
DatabaseKeyVersion: &store.DatabaseKeyVersion{},
}
}
// Clone creates a clone of the DatabaseKeyVersion
func (k *DatabaseKeyVersion) Clone() interface{} {
cp := proto.Clone(k.DatabaseKeyVersion)
return &DatabaseKeyVersion{
DatabaseKeyVersion: cp.(*store.DatabaseKeyVersion),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the database key
// version before it's written.
func (k *DatabaseKeyVersion) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "kms.(DatabaseKeyVersion).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
switch opType {
case db.CreateOp:
if k.CtKey == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
if k.DatabaseKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing database key id")
}
if k.RootKeyVersionId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
case db.UpdateOp:
return errors.New(ctx, errors.InvalidParameter, op, "key is immutable")
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *DatabaseKeyVersion) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultDatabaseKeyVersionTableName
}
// SetTableName sets the tablename and satisfies the ReplayableMessage
// interface. If the caller attempts to set the name to "" the name will be
// reset to the default name.
func (k *DatabaseKeyVersion) SetTableName(n string) {
k.tableName = n
}
// Encrypt will encrypt the database key version's key
func (k *DatabaseKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(DatabaseKeyVersion).Encrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.DatabaseKeyVersion directly
if err := structwrapping.WrapStruct(ctx, cipher, k.DatabaseKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
}
return nil
}
// Decrypt will decrypt the database key version's key
func (k *DatabaseKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(DatabaseKeyVersion).Decrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.DatabaseKeyVersion directly
if err := structwrapping.UnwrapStruct(ctx, cipher, k.DatabaseKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
-249
View File
@@ -1,249 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the DatabaseKeyVersion attributes are
// immutable and those tests are covered by TestDatabaseKey_ImmutableFields
func TestDatabaseKeyVersion_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rkv, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestDatabaseKey(t, conn, rk.PrivateId)
type args struct {
databaseKeyId string
key []byte
rootKeyVersionId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.DatabaseKeyVersion
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-database-id",
args: args{
key: []byte("database key"),
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
databaseKeyId: dk.PrivateId,
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-root-key-version-id",
args: args{
databaseKeyId: dk.PrivateId,
key: []byte("database key"),
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
databaseKeyId: dk.PrivateId,
key: []byte("database key"),
rootKeyVersionId: rkv.PrivateId,
},
want: func() *kms.DatabaseKeyVersion {
k := kms.AllocDatabaseKeyVersion()
k.RootKeyVersionId = rkv.PrivateId
k.Key = []byte("database key")
k.DatabaseKeyId = dk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocDatabaseKeyVersion(); return &i }(), "1=1")
got, err := kms.NewDatabaseKeyVersion(tt.args.databaseKeyId, tt.args.key, tt.args.rootKeyVersionId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.DatabaseKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = got.Encrypt(context.Background(), rkvWrapper)
require.NoError(err)
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestDatabaseKeyVersion_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestDatabaseKey(t, conn, rk.PrivateId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocDatabaseKeyVersion(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.DatabaseKeyVersion
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestDatabaseKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("database key")),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.DatabaseKeyVersion {
k := kms.AllocDatabaseKeyVersion()
id, err := db.NewPublicId(kms.DatabaseKeyVersionPrefix)
require.NoError(t, err)
k.PrivateId = id
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocDatabaseKeyVersion()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocDatabaseKeyVersion()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestDatabaseKeyVersion_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
k := kms.TestDatabaseKey(t, conn, rk.PrivateId)
kv := kms.TestDatabaseKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test db key"))
cp := kv.Clone()
assert.True(proto.Equal(cp.(*kms.DatabaseKeyVersion).DatabaseKeyVersion, kv.DatabaseKeyVersion))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
_, rkvWrapper2 := kms.TestRootKeyVersion(t, conn, wrapper, rk2.PrivateId)
k := kms.TestDatabaseKey(t, conn, rk.PrivateId)
k2 := kms.TestDatabaseKey(t, conn, rk2.PrivateId)
kv := kms.TestDatabaseKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test db key"))
kv2 := kms.TestDatabaseKeyVersion(t, conn, rkvWrapper2, k2.PrivateId, []byte("test db key 2"))
cp := kv.Clone()
assert.True(!proto.Equal(cp.(*kms.DatabaseKeyVersion).DatabaseKeyVersion, kv2.DatabaseKeyVersion))
})
}
func TestDatabaseKeyVersion_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultDatabaseKeyVersionTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocDatabaseKeyVersion()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocDatabaseKeyVersion()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
+28
View File
@@ -0,0 +1,28 @@
package kms
import (
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// ExternalWrappers holds wrappers defined outside of Boundary, e.g. in its
// configuration file.
type ExternalWrappers struct {
root wrapping.Wrapper
workerAuth wrapping.Wrapper
recovery wrapping.Wrapper
}
// Root returns the wrapper for root keys
func (e *ExternalWrappers) Root() wrapping.Wrapper {
return e.root
}
// WorkerAuth returns the wrapper for worker authentication
func (e *ExternalWrappers) WorkerAuth() wrapping.Wrapper {
return e.workerAuth
}
// Recovery returns the wrapper for recovery operations
func (e *ExternalWrappers) Recovery() wrapping.Wrapper {
return e.recovery
}
+46
View File
@@ -0,0 +1,46 @@
package kms
import (
"context"
"fmt"
"testing"
"github.com/hashicorp/boundary/internal/db"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestExternalWrappers(t *testing.T) {
t.Parallel()
assert, require := assert.New(t), require.New(t)
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rootWrapper := db.TestWrapper(t)
recoveryWrapper := db.TestWrapper(t)
workerAuthWrapper := db.TestWrapper(t)
k := TestKms(t, conn, rootWrapper)
err := k.AddExternalWrappers(testCtx, WithRecoveryWrapper(recoveryWrapper), WithWorkerAuthWrapper(workerAuthWrapper))
require.NoError(err)
assert.Equal(rootWrapper, k.GetExternalWrappers(testCtx).Root())
assert.Equal(recoveryWrapper, k.GetExternalWrappers(testCtx).Recovery())
assert.Equal(workerAuthWrapper, k.GetExternalWrappers(testCtx).WorkerAuth())
err = k.AddExternalWrappers(testCtx, WithRootWrapper(&invalidWrapper{}))
assert.Error(err)
err = k.AddExternalWrappers(testCtx, WithRecoveryWrapper(&invalidWrapper{}))
assert.Error(err)
err = k.AddExternalWrappers(testCtx, WithWorkerAuthWrapper(&invalidWrapper{}))
assert.Error(err)
}
type invalidWrapper struct {
wrapping.Wrapper
}
func (w *invalidWrapper) KeyId(context.Context) (string, error) {
return "", fmt.Errorf("invalid-error")
}
-151
View File
@@ -1,151 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
)
const (
RootKeyPrefix = "krk"
RootKeyVersionPrefix = "krkv"
DatabaseKeyPrefix = "kdk"
DatabaseKeyVersionPrefix = "kdkv"
OplogKeyPrefix = "kopk"
OplogKeyVersionPrefix = "kopkv"
TokenKeyPrefix = "ktk"
TokenKeyVersionPrefix = "ktv"
SessionKeyPrefix = "ksk"
SessionKeyVersionPrefix = "kskv"
OidcKeyPrefix = "koidck"
OidcKeyVersionPrefix = "koidckv"
AuditKeyPrefix = "kak"
AuditKeyVersionPrefix = "kakv"
)
func newRootKeyId() (string, error) {
const op = "kms.newRootKeyId"
id, err := db.NewPublicId(RootKeyPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newRootKeyVersionId() (string, error) {
const op = "kms.newRootKeyVersionId"
id, err := db.NewPublicId(RootKeyVersionPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newDatabaseKeyId() (string, error) {
const op = "kms.newDatabaseKeyId"
id, err := db.NewPublicId(DatabaseKeyPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newDatabaseKeyVersionId() (string, error) {
const op = "kms.newDatabaseKeyVersionId"
id, err := db.NewPublicId(DatabaseKeyVersionPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newOplogKeyId() (string, error) {
const op = "kms.newOplogKeyId"
id, err := db.NewPublicId(OplogKeyPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newOplogKeyVersionId() (string, error) {
const op = "kms.newOplogKeyVersionId"
id, err := db.NewPublicId(OplogKeyVersionPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newTokenKeyId() (string, error) {
const op = "kms.newTokenKeyId"
id, err := db.NewPublicId(TokenKeyPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newTokenKeyVersionId() (string, error) {
const op = "kms.newTokenKeyVersionId"
id, err := db.NewPublicId(TokenKeyVersionPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newSessionKeyId() (string, error) {
const op = "kms.newSessionKeyId"
id, err := db.NewPublicId(SessionKeyPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newSessionKeyVersionId() (string, error) {
const op = "kms.newSessionKeyVersionId"
id, err := db.NewPublicId(SessionKeyVersionPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newOidcKeyId() (string, error) {
const op = "kms.newOidcKeyId"
id, err := db.NewPublicId(OidcKeyPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newOidcKeyVersionId() (string, error) {
const op = "kms.newOidcKeyVersionId"
id, err := db.NewPublicId(OidcKeyVersionPrefix)
if err != nil {
return "", errors.WrapDeprecated(err, op)
}
return id, nil
}
func newAuditKeyId(ctx context.Context) (string, error) {
const op = "kms.newAuditKeyId"
id, err := db.NewPublicId(AuditKeyPrefix)
if err != nil {
return "", errors.Wrap(ctx, err, op)
}
return id, nil
}
func newAuditKeyVersionId(ctx context.Context) (string, error) {
const op = "kms.newAuditKeyVersionId"
id, err := db.NewPublicId(AuditKeyVersionPrefix)
if err != nil {
return "", errors.Wrap(ctx, err, op)
}
return id, nil
}
-84
View File
@@ -1,84 +0,0 @@
package kms
import (
"context"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_Ids(t *testing.T) {
t.Parallel()
t.Run("krk", func(t *testing.T) {
id, err := newRootKeyId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, RootKeyPrefix+"_"))
})
t.Run("krkv", func(t *testing.T) {
id, err := newRootKeyVersionId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, RootKeyVersionPrefix+"_"))
})
t.Run("kdk", func(t *testing.T) {
id, err := newDatabaseKeyId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, DatabaseKeyPrefix+"_"))
})
t.Run("kdkv", func(t *testing.T) {
id, err := newDatabaseKeyVersionId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, DatabaseKeyVersionPrefix+"_"))
})
t.Run("kopk", func(t *testing.T) {
id, err := newOplogKeyId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, OplogKeyPrefix+"_"))
})
t.Run("kopkv", func(t *testing.T) {
id, err := newOplogKeyVersionId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, OplogKeyVersionPrefix+"_"))
})
t.Run("ktk", func(t *testing.T) {
id, err := newTokenKeyId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, TokenKeyPrefix+"_"))
})
t.Run("ktkv", func(t *testing.T) {
id, err := newTokenKeyVersionId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, TokenKeyVersionPrefix+"_"))
})
t.Run("ksk", func(t *testing.T) {
id, err := newSessionKeyId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, SessionKeyPrefix+"_"))
})
t.Run("kskv", func(t *testing.T) {
id, err := newSessionKeyVersionId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, SessionKeyVersionPrefix+"_"))
})
t.Run("koidck", func(t *testing.T) {
id, err := newOidcKeyId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, OidcKeyPrefix+"_"))
})
t.Run("koidckv", func(t *testing.T) {
id, err := newOidcKeyVersionId()
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, OidcKeyVersionPrefix+"_"))
})
t.Run("kak", func(t *testing.T) {
id, err := newAuditKeyId(context.Background())
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, AuditKeyPrefix+"_"))
})
t.Run("kakv", func(t *testing.T) {
id, err := newAuditKeyVersionId(context.Background())
require.NoError(t, err)
assert.True(t, strings.HasPrefix(id, AuditKeyVersionPrefix+"_"))
})
}
File diff suppressed because it is too large. Load diff
+179 -378
View File
@@ -6,285 +6,150 @@ import (
"io"
"reflect"
"sync"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/go-dbw"
wrappingKms "github.com/hashicorp/go-kms-wrapping/extras/kms/v2"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/hashicorp/go-kms-wrapping/v2/extras/multi"
)
// ExternalWrappers holds wrappers defined outside of Boundary, e.g. in its
// configuration file.
type ExternalWrappers struct {
m sync.RWMutex
root wrapping.Wrapper
workerAuth wrapping.Wrapper
recovery wrapping.Wrapper
}
// Root returns the wrapper for root keys
func (e *ExternalWrappers) Root() wrapping.Wrapper {
e.m.RLock()
defer e.m.RUnlock()
return e.root
}
// WorkerAuth returns the wrapper for worker authentication
func (e *ExternalWrappers) WorkerAuth() wrapping.Wrapper {
e.m.RLock()
defer e.m.RUnlock()
return e.workerAuth
}
// Recovery returns the wrapper for recovery operations
func (e *ExternalWrappers) Recovery() wrapping.Wrapper {
e.m.RLock()
defer e.m.RUnlock()
return e.recovery
}
// Kms is a way to access wrappers for a given scope and purpose. Since keys can
// never change, only be added or (eventually) removed, it opportunistically
// caches, going to the database as needed.
type Kms struct {
// scopePurposeCache holds a per-scope-purpose multiwrapper containing the
// current encrypting key and all previous key versions, for decryption
scopePurposeCache sync.Map
externalScopeCache map[string]*ExternalWrappers
externalScopeCacheMutex sync.RWMutex
underlying *wrappingKms.Kms
reader db.Reader
derivedPurposeCache sync.Map
repo *Repository
}
// NewKms takes in a repo and returns a Kms.
func NewKms(repo *Repository, opt ...Option) (*Kms, error) {
const op = "kms.NewKms"
if repo == nil {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing underlying repo")
// New creates a Kms using the provided reader and writer. No options are
// currently supported.
func New(ctx context.Context, reader *db.Db, writer *db.Db, _ ...Option) (*Kms, error) {
const op = "kms.(Kms).New"
if isNil(reader) {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing reader")
}
if isNil(writer) {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing writer")
}
purposes := stdNewKmsPurposes()
r := dbw.New(reader.UnderlyingDB())
w := dbw.New(writer.UnderlyingDB())
k, err := wrappingKms.New(r, w, purposes, wrappingKms.WithCache(true))
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error creating new in-memory kms"))
}
return &Kms{
externalScopeCache: make(map[string]*ExternalWrappers),
repo: repo,
underlying: k,
reader: reader,
}, nil
}
// GetScopePurposeCache is used in test functions for validation. Since the
// tests need to be in a different package to avoid circular dependencies, this
// is exported.
func (k *Kms) GetScopePurposeCache() *sync.Map {
return &k.scopePurposeCache
// NewUsingReaderWriter creates a Kms using the provided reader and writer. No
// options are currently supported.
func NewUsingReaderWriter(ctx context.Context, reader db.Reader, writer db.Writer, _ ...Option) (*Kms, error) {
const op = "kms.(Kms).New"
if isNil(reader) {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing reader")
}
if isNil(writer) {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing writer")
}
purposes := stdNewKmsPurposes()
r, err := convertToRW(ctx, reader)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
w, err := convertToRW(ctx, writer)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
k, err := wrappingKms.New(r, w, purposes, wrappingKms.WithCache(true))
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error creating new in-memory kms"))
}
return &Kms{
underlying: k,
reader: reader,
}, nil
}
func (k *Kms) GetDerivedPurposeCache() *sync.Map {
return &k.derivedPurposeCache
func convertToRW(ctx context.Context, r interface{}) (*dbw.RW, error) {
const op = "kms.convertToDb"
d, ok := r.(*db.Db)
if !ok {
return nil, errors.New(ctx, errors.InvalidParameter, op, "unable to convert to db.DB")
}
return dbw.New(d.UnderlyingDB()), nil
}
// AddExternalWrappers allows setting the external keys.
//
// TODO: If we support more than one, e.g. for encrypting against many in case
// of a key loss, there will need to be some refactoring here to have the values
// being stored in the struct be a multiwrapper, but that's for a later project.
func (k *Kms) AddExternalWrappers(ctx context.Context, opt ...Option) error {
const op = "kms.AddExternalWrappers"
k.externalScopeCacheMutex.Lock()
defer k.externalScopeCacheMutex.Unlock()
ext := k.externalScopeCache[scope.Global.String()]
if ext == nil {
ext = &ExternalWrappers{}
}
ext.m.Lock()
defer ext.m.Unlock()
const op = "kms.(Kms).AddExternalWrappers"
opts := getOpts(opt...)
if opts.withRootWrapper != nil {
ext.root = opts.withRootWrapper
keyId, err := ext.root.KeyId(ctx)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("error reading root wrapper key ID"))
}
if keyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "root wrapper has no key ID")
if err := k.underlying.AddExternalWrapper(ctx, wrappingKms.KeyPurpose(KeyPurposeRootKey.String()), opts.withRootWrapper); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add root wrapper"))
}
}
if opts.withWorkerAuthWrapper != nil {
ext.workerAuth = opts.withWorkerAuthWrapper
keyId, err := ext.workerAuth.KeyId(ctx)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("error reading worker auth wrapper key ID"))
}
if keyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "worker auth wrapper has no key ID")
if err := k.underlying.AddExternalWrapper(ctx, wrappingKms.KeyPurpose(KeyPurposeWorkerAuth.String()), opts.withWorkerAuthWrapper); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add worker auth wrapper"))
}
}
if opts.withRecoveryWrapper != nil {
ext.recovery = opts.withRecoveryWrapper
keyId, err := ext.root.KeyId(ctx)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("error reading recovery wrapper key ID"))
}
if keyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "recovery wrapper has no key ID")
if err := k.underlying.AddExternalWrapper(ctx, wrappingKms.KeyPurpose(KeyPurposeRecovery.String()), opts.withRecoveryWrapper); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add recovery wrapper"))
}
}
k.externalScopeCache[scope.Global.String()] = ext
return nil
}
func (k *Kms) GetExternalWrappers() *ExternalWrappers {
k.externalScopeCacheMutex.RLock()
defer k.externalScopeCacheMutex.RUnlock()
ext := k.externalScopeCache[scope.Global.String()]
ext.m.RLock()
defer ext.m.RUnlock()
ret := &ExternalWrappers{
root: ext.root,
workerAuth: ext.workerAuth,
recovery: ext.recovery,
}
return ret
}
// GetWrapper returns a wrapper for the given scope and purpose. When a keyId is
// passed, it will ensure that the returning wrapper has that key ID in the
// multiwrapper. This is not necesary for encryption but should be supplied for
// multiwrapper. This is not necessary for encryption but should be supplied for
// decryption.
func (k *Kms) GetWrapper(ctx context.Context, scopeId string, purpose KeyPurpose, opt ...Option) (wrapping.Wrapper, error) {
const op = "kms.GetWrapper"
const op = "kms.(Kms).GetWrapper"
if scopeId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
}
switch purpose {
case KeyPurposeOplog, KeyPurposeDatabase, KeyPurposeTokens, KeyPurposeSessions, KeyPurposeOidc, KeyPurposeAudit:
case KeyPurposeUnknown:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key purpose")
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported purpose %q", purpose))
if purpose == KeyPurposeUnknown {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing purpose")
}
opts := getOpts(opt...)
// Fast-path: we have a valid key at the scope/purpose. Verify the key with
// that ID is in the multiwrapper; if not, fall through to reload from the
// DB.
val, ok := k.scopePurposeCache.Load(scopeId + purpose.String())
if ok {
wrapper := val.(*multi.PooledWrapper)
if opts.withKeyId == "" {
return wrapper, nil
}
if keyIdWrapper := wrapper.WrapperForKeyId(opts.withKeyId); keyIdWrapper != nil {
return keyIdWrapper, nil
}
// Fall through to refresh our multiwrapper for this scope/purpose from the DB
}
// We don't have it cached, so we'll need to read from the database. Get the
// root for the scope as we'll need it to decrypt the value coming from the
// DB. We don't cache the roots as we expect that after a few calls the
// scope-purpose cache will catch everything in steady-state.
rootWrapper, rootKeyId, err := k.loadRoot(ctx, scopeId, opt...)
w, err := k.underlying.GetWrapper(ctx, scopeId, wrappingKms.KeyPurpose(purpose.String()), wrappingKms.WithKeyId(opts.withKeyId))
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error loading root key for scope %s", scopeId)))
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get wrapper"))
}
if rootWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("got nil root wrapper for scope %s", scopeId))
}
wrapper, err := k.loadDek(ctx, scopeId, purpose, rootWrapper, rootKeyId, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error loading %s for scope %s", purpose.String(), scopeId)))
}
k.scopePurposeCache.Store(scopeId+purpose.String(), wrapper)
if opts.withKeyId != "" {
if keyIdWrapper := wrapper.WrapperForKeyId(opts.withKeyId); keyIdWrapper != nil {
return keyIdWrapper, nil
}
return nil, errors.New(ctx, errors.KeyNotFound, op, "unable to find specified key ID")
}
return wrapper, nil
return w, err
}
func (k *Kms) loadRoot(ctx context.Context, scopeId string, opt ...Option) (*multi.PooledWrapper, string, error) {
const op = "kms.loadRoot"
opts := getOpts(opt...)
repo := opts.withRepository
if repo == nil {
repo = k.repo
// GetExternalWrappers returns the Kms' ExternalWrappers
func (k *Kms) GetExternalWrappers(ctx context.Context) *ExternalWrappers {
const op = "kms.(Kms).GetExternalWrappers"
ret := &ExternalWrappers{}
if root, err := k.underlying.GetExternalRootWrapper(); err == nil {
ret.root = root
}
rootKeys, err := repo.ListRootKeys(ctx)
if err != nil {
return nil, "", errors.Wrap(ctx, err, op)
if workerAuth, err := k.underlying.GetExternalWrapper(ctx, wrappingKms.KeyPurpose(KeyPurposeWorkerAuth.String())); err == nil {
ret.workerAuth = workerAuth
}
var rootKeyId string
for _, k := range rootKeys {
if k.GetScopeId() == scopeId {
rootKeyId = k.GetPrivateId()
break
}
}
if rootKeyId == "" {
return nil, "", errors.New(ctx, errors.KeyNotFound, op, fmt.Sprintf("missing root key for scope %s", scopeId))
if recovery, err := k.underlying.GetExternalWrapper(ctx, wrappingKms.KeyPurpose(KeyPurposeRecovery.String())); err == nil {
ret.recovery = recovery
}
return ret
}
// Now: find the external KMS that can be used to decrypt the root values
// from the DB.
k.externalScopeCacheMutex.Lock()
externalWrappers := k.externalScopeCache[scope.Global.String()]
k.externalScopeCacheMutex.Unlock()
if externalWrappers == nil {
return nil, "", errors.New(ctx, errors.KeyNotFound, op, "could not find kms information at either the needed scope or global fallback")
}
externalWrappers.m.RLock()
defer externalWrappers.m.RUnlock()
if externalWrappers.root == nil {
return nil, "", errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key wrapper for scope %s is nil", scopeId))
}
rootKeyVersions, err := repo.ListRootKeyVersions(ctx, externalWrappers.root, rootKeyId, WithOrderByVersion(db.DescendingOrderBy))
if err != nil {
return nil, "", errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error looking up root key versions for scope %s", scopeId)))
}
if len(rootKeyVersions) == 0 {
return nil, "", errors.New(ctx, errors.KeyNotFound, op, fmt.Sprintf("no root key versions found for scope %s", scopeId))
}
var pooled *multi.PooledWrapper
for i, key := range rootKeyVersions {
var err error
wrapper := aead.NewWrapper()
if _, err = wrapper.SetConfig(ctx, wrapping.WithKeyId(key.GetPrivateId())); err != nil {
return nil, "", errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error setting config on aead root wrapper in scope %s", scopeId)))
}
if err = wrapper.SetAesGcmKeyBytes(key.GetKey()); err != nil {
return nil, "", errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error setting key bytes on aead root wrapper in scope %s", scopeId)))
}
if i == 0 {
pooled, err = multi.NewPooledWrapper(ctx, wrapper)
if err != nil {
return nil, "", errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error getting root pooled wrapper for key version 0 in scope %s", scopeId)))
}
} else {
_, err = pooled.AddWrapper(ctx, wrapper)
if err != nil {
return nil, "", errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error adding pooled wrapper for key version %d in scope %s", i, scopeId)))
}
}
}
return pooled, rootKeyId, nil
// GetDerivedPurposeCache returns the raw derived purpose cache
func (k *Kms) GetDerivedPurposeCache() *sync.Map {
return &k.derivedPurposeCache
}
// ReconcileKeys will reconcile the keys in the kms against known possible
@@ -295,71 +160,108 @@ func (k *Kms) ReconcileKeys(ctx context.Context, randomReader io.Reader, opt ...
if isNil(randomReader) {
return errors.New(ctx, errors.InvalidParameter, op, "missing rand reader")
}
// it's possible that the global audit key was created after this instance's
// database was initialized... so check if the audit wrapper is available
// for the global scope and if not, then add one to the global scope
if _, err := k.GetWrapper(ctx, scope.Global.String(), KeyPurposeAudit); err != nil {
switch {
case errors.Match(errors.T(errors.KeyNotFound), err):
globalRootWrapper, _, err := k.loadRoot(ctx, scope.Global.String())
if err != nil {
return errors.Wrap(ctx, err, op)
}
key, err := generateKey(ctx, randomReader)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for database key in scope %s", scope.Global.String())))
}
if _, _, err := k.repo.CreateAuditKey(ctx, globalRootWrapper, key); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error creating audit key in scope %s", scope.Global.String())))
}
default:
errors.Wrap(ctx, err, op)
if err := k.underlying.ReconcileKeys(
ctx,
[]string{scope.Global.String()},
[]wrappingKms.KeyPurpose{wrappingKms.KeyPurpose(KeyPurposeAudit.String())},
wrappingKms.WithRandomReader(randomReader),
); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error creating audit key in scope %s", scope.Global.String())))
}
}
opts := getOpts(opt...)
for _, id := range opts.withScopeIds {
var scopeRootWrapper *multi.PooledWrapper
for _, purpose := range []KeyPurpose{
if len(opts.withScopeIds) > 0 {
if err := k.underlying.ReconcileKeys(
ctx,
opts.withScopeIds,
// just add additional purposes as needed going forward to reconcile
// new keys as they are added.
//
// NOTE: don't add an audit key here since it can only be created in
// the global scope.
KeyPurposeOidc,
} {
if _, err := k.GetWrapper(ctx, id, purpose); err != nil {
switch {
case errors.Match(errors.T(errors.KeyNotFound), err):
if scopeRootWrapper == nil {
if scopeRootWrapper, _, err = k.loadRoot(ctx, id); err != nil {
return errors.Wrap(ctx, err, op)
}
}
key, err := generateKey(ctx, randomReader)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for %q key in scope %q", purpose.String(), id)))
}
switch purpose {
case KeyPurposeOidc:
if _, _, err := k.repo.CreateOidcKey(ctx, scopeRootWrapper, key); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error creating %q key in scope %s", purpose.String(), id)))
}
default:
return errors.New(ctx, errors.Internal, op, fmt.Sprintf("error creating %q key in scope %s since it's not a supported KeyPurpose", purpose.String(), id))
}
default:
errors.Wrap(ctx, err, op)
}
}
[]wrappingKms.KeyPurpose{wrappingKms.KeyPurpose(KeyPurposeOidc.String())},
wrappingKms.WithRandomReader(randomReader),
); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error creating keys in scopes %s", opts.withScopeIds)))
}
}
return nil
}
// CreateKeys creates the root key and DEKs returns a map of the new keys.
// Supports the WithRandomReader(...) and WithReaderWriter(...) options.
// When WithReaderWriter(...) is used the caller is responsible for managing the
// transaction which allows this capability to be shared with the iam repo when
// it's creating Scopes.
func (k *Kms) CreateKeys(ctx context.Context, scopeId string, opt ...Option) error {
const op = "kms.(Kms).CreateKeysTx"
if scopeId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
}
opts := getOpts(opt...)
kmsOpts := []wrappingKms.Option{wrappingKms.WithRandomReader(opts.withRandomReader)}
switch {
case !isNil(opts.withReader) && isNil(opts.withWriter):
return errors.New(ctx, errors.InvalidParameter, op, "missing writer")
case isNil(opts.withReader) && !isNil(opts.withWriter):
return errors.New(ctx, errors.InvalidParameter, op, "missing reader")
case !isNil(opts.withReader) && !isNil(opts.withWriter):
r, err := convertToRW(ctx, opts.withReader)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to convert reader"))
}
w, err := convertToRW(ctx, opts.withWriter)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to convert writer"))
}
kmsOpts = append(kmsOpts, wrappingKms.WithReaderWriter(r, w))
}
purposes := make([]wrappingKms.KeyPurpose, 0, len(ValidDekPurposes()))
for _, p := range ValidDekPurposes() {
purposes = append(purposes, wrappingKms.KeyPurpose(p.String()))
}
if err := k.underlying.CreateKeys(ctx, scopeId, purposes, kmsOpts...); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
}
// ClearCache will clear the kms's cache which is useful after a scope has been
// deleted.
func (k *Kms) ClearCache(ctx context.Context) error {
const op = "kms.(Kms).ClearCache"
if err := k.underlying.ClearCache(ctx); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
}
// VerifyGlobalRoot will verify that the global root wrapper is reasonable.
func (k *Kms) VerifyGlobalRoot(ctx context.Context) error {
const op = "kms.(Kms).VerifyGlobalRoot"
var keys []*rootKey
if err := k.reader.SearchWhere(ctx, &keys, "1=1", nil, db.WithLimit(1), db.WithOrder("create_time asc")); err != nil {
return errors.Wrap(ctx, err, op)
}
for _, rk := range keys {
if rk.ScopeId == scope.Global.String() {
return nil
}
}
return errors.New(ctx, errors.MigrationIntegrity, op, "can't find global scoped root key")
}
func stdNewKmsPurposes() []wrappingKms.KeyPurpose {
purposes := make([]wrappingKms.KeyPurpose, 0, len(ValidDekPurposes()))
for _, p := range ValidDekPurposes() {
purposes = append(purposes, wrappingKms.KeyPurpose(p.String()))
}
purposes = append(purposes, wrappingKms.KeyPurpose(KeyPurposeWorkerAuth.String()), wrappingKms.KeyPurpose(KeyPurposeRecovery.String()))
return purposes
}
func isNil(i interface{}) bool {
if i == nil {
return true
@@ -371,111 +273,10 @@ func isNil(i interface{}) bool {
return false
}
// Dek is an interface wrapping dek types to allow a lot less switching in loadDek
type Dek interface {
GetRootKeyId() string
GetPrivateId() string
type rootKey struct {
PrivateId string `gorm:"primary_key"`
ScopeId string `gorm:"default:null"`
CreateTime time.Time `gorm:"default:current_timestamp"`
}
// DekVersion is an interface wrapping versioned dek types to allow a lot less switching in loadDek
type DekVersion interface {
GetPrivateId() string
GetKey() []byte
}
func (k *Kms) loadDek(ctx context.Context, scopeId string, purpose KeyPurpose, rootWrapper wrapping.Wrapper, rootKeyId string, opt ...Option) (*multi.PooledWrapper, error) {
const op = "kms.loadDek"
if rootWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("nil root wrapper for scope %s", scopeId))
}
if rootKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("missing root key ID for scope %s", scopeId))
}
opts := getOpts(opt...)
repo := opts.withRepository
if repo == nil {
repo = k.repo
}
var keys []Dek
var err error
switch purpose {
case KeyPurposeDatabase:
keys, err = repo.ListDatabaseKeys(ctx)
case KeyPurposeOplog:
keys, err = repo.ListOplogKeys(ctx)
case KeyPurposeTokens:
keys, err = repo.ListTokenKeys(ctx)
case KeyPurposeSessions:
keys, err = repo.ListSessionKeys(ctx)
case KeyPurposeOidc:
keys, err = repo.ListOidcKeys(ctx)
case KeyPurposeAudit:
keys, err = repo.ListAuditKeys(ctx)
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, "unknown or invalid DEK purpose specified")
}
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error listing root keys"))
}
var keyId string
for _, k := range keys {
if k.GetRootKeyId() == rootKeyId {
keyId = k.GetPrivateId()
break
}
}
if keyId == "" {
return nil, errors.New(ctx, errors.KeyNotFound, op, fmt.Sprintf("error finding %s key for scope %s", purpose.String(), scopeId))
}
var keyVersions []DekVersion
switch purpose {
case KeyPurposeDatabase:
keyVersions, err = repo.ListDatabaseKeyVersions(ctx, rootWrapper, keyId, WithOrderByVersion(db.DescendingOrderBy))
case KeyPurposeOplog:
keyVersions, err = repo.ListOplogKeyVersions(ctx, rootWrapper, keyId, WithOrderByVersion(db.DescendingOrderBy))
case KeyPurposeTokens:
keyVersions, err = repo.ListTokenKeyVersions(ctx, rootWrapper, keyId, WithOrderByVersion(db.DescendingOrderBy))
case KeyPurposeSessions:
keyVersions, err = repo.ListSessionKeyVersions(ctx, rootWrapper, keyId, WithOrderByVersion(db.DescendingOrderBy))
case KeyPurposeOidc:
keyVersions, err = repo.ListOidcKeyVersions(ctx, rootWrapper, keyId, WithOrderByVersion(db.DescendingOrderBy))
case KeyPurposeAudit:
keyVersions, err = repo.ListAuditKeyVersions(ctx, rootWrapper, keyId, WithOrderByVersion(db.DescendingOrderBy))
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, "unknown or invalid DEK purpose specified")
}
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error looking up %s key versions for scope %s", purpose.String(), scopeId)))
}
if len(keyVersions) == 0 {
return nil, errors.New(ctx, errors.KeyNotFound, op, fmt.Sprintf("no %s key versions found for scope %s", purpose.String(), scopeId))
}
var pooled *multi.PooledWrapper
for i, keyVersion := range keyVersions {
var err error
wrapper := aead.NewWrapper()
if _, err = wrapper.SetConfig(ctx, wrapping.WithKeyId(keyVersion.GetPrivateId())); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error setting config on aead %s wrapper in scope %s", purpose.String(), scopeId)))
}
if err = wrapper.SetAesGcmKeyBytes(keyVersion.GetKey()); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error setting key bytes on aead %s wrapper in scope %s", purpose.String(), scopeId)))
}
if i == 0 {
pooled, err = multi.NewPooledWrapper(ctx, wrapper)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error getting %s pooled wrapper for key version 0 in scope %s", purpose.String(), scopeId)))
}
} else {
_, err = pooled.AddWrapper(ctx, wrapper)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error getting %s pooled wrapper for key version %d in scope %s", purpose.String(), i, scopeId)))
}
}
}
return pooled, nil
}
func (*rootKey) TableName() string { return "kms_root_key" }
+180 -65
View File
@@ -26,55 +26,15 @@ func TestKms(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
extWrapper := db.TestWrapper(t)
badExtWrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
kmsCache := kms.TestKms(t, conn, extWrapper)
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, extWrapper))
// Verify that the cache is empty, so we can show that by the end of the
// test sequence we did actually look up keys and store them in the cache
t.Run("verify cache empty", func(t *testing.T) {
var count int
kmsCache.GetScopePurposeCache().Range(func(key interface{}, value interface{}) bool {
count++
return true
})
assert.Equal(t, 0, count)
})
// Verify that the root keys are all in the database and can be decrypted
// with the correct wrapper from the KMS object
t.Run("verify root keys", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
rootKeys, err := repo.ListRootKeys(ctx)
require.NoError(err)
wrappers := kmsCache.GetExternalWrappers()
for _, key := range rootKeys {
kvs, err := repo.ListRootKeyVersions(ctx, wrappers.Root(), key.GetPrivateId())
require.NoError(err)
assert.Len(kvs, 1)
assert.Len(kvs[0].GetKey(), 32)
}
})
// Verify that the wrong wrapper causes decryption to fail
t.Run("bad external keys", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
rootKeys, err := repo.ListRootKeys(ctx)
require.NoError(err)
for _, key := range rootKeys {
_, err := repo.ListRootKeyVersions(ctx, badExtWrapper, key.GetPrivateId())
require.Error(err)
assert.True(strings.Contains(err.Error(), "message authentication failed"), err.Error())
}
})
// This next sequence is run twice to ensure that calling for the keys twice
// returns the same value each time and doesn't simply populate more keys
// into the KMS object
keyBytes := map[string]bool{}
keyIds := map[string]bool{}
scopePurposeMap := map[string]interface{}{}
for i := 1; i < 3; i++ {
// This iterates through wrappers for all three scopes and four purposes,
// ensuring that the key bytes and IDs are different for each of them,
@@ -116,24 +76,6 @@ func TestKms(t *testing.T) {
}
}
})
// Verify that the cache has been populated with unique values. The
// second time we validate that the items we find when going through the
// cache a second time are the same as the first. If they were recreated
// the pointers would be different.
t.Run(fmt.Sprintf("verify cache populated x %d", i), func(t *testing.T) {
var count int
kmsCache.GetScopePurposeCache().Range(func(key interface{}, value interface{}) bool {
count++
if i == 1 {
scopePurposeMap[key.(string)] = value
} else {
assert.Same(t, scopePurposeMap[key.(string)], value)
}
return true
})
// four purposes x 3 scopes
assert.Equal(t, 12, count)
})
}
}
@@ -141,7 +83,6 @@ func TestKms_ReconcileKeys(t *testing.T) {
t.Parallel()
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
@@ -183,9 +124,10 @@ func TestKms_ReconcileKeys(t *testing.T) {
kms: kms.TestKms(t, conn, wrapper),
reader: rand.Reader,
setup: func(k *kms.Kms) {
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocAuditKey(); return &i }(), "1=1")
kms.TestKmsDeleteKeyPurpose(t, conn, kms.KeyPurposeAudit)
// make sure the kms is in the proper state for the unit test
// before proceeding.
k.ClearCache(testCtx)
_, err := k.GetWrapper(testCtx, scope.Global.String(), kms.KeyPurposeAudit)
require.Error(t, err)
},
@@ -200,14 +142,15 @@ func TestKms_ReconcileKeys(t *testing.T) {
setup: func(k *kms.Kms) {
// create initial keys for the test scope ids...
for _, id := range []string{org.PublicId, org2.PublicId} {
_, err := kms.CreateKeysTx(context.Background(), rw, rw, wrapper, rand.Reader, id)
err := k.CreateKeys(testCtx, id)
require.NoError(t, err)
}
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOidcKey(); return &i }(), "1=1")
kms.TestKmsDeleteKeyPurpose(t, conn, kms.KeyPurposeOidc)
// make sure the kms is in the proper state for the unit test
// before proceeding.
for _, id := range []string{org.PublicId, org2.PublicId} {
_, err := k.GetWrapper(testCtx, id, kms.KeyPurposeAudit)
_, err := k.GetWrapper(testCtx, id, kms.KeyPurposeOidc)
require.Error(t, err)
}
},
@@ -218,10 +161,10 @@ func TestKms_ReconcileKeys(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
// start with no keys...
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
kms.TestKmsDeleteAllKeys(t, conn)
// create initial keys for the global scope...
_, err := kms.CreateKeysTx(context.Background(), rw, rw, wrapper, rand.Reader, scope.Global.String())
err := tt.kms.CreateKeys(context.Background(), scope.Global.String())
require.NoError(err)
if tt.setup != nil {
@@ -252,3 +195,175 @@ func TestKms_ReconcileKeys(t *testing.T) {
})
}
}
func TestKms_GetDerivedPurposeCache(t *testing.T) {
t.Parallel()
assert := assert.New(t)
conn, _ := db.TestSetup(t, "postgres")
rootWrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, rootWrapper)
derivedWrapper := db.TestWrapper(t)
kmsCache.GetDerivedPurposeCache().Store(1, derivedWrapper)
v, ok := kmsCache.GetDerivedPurposeCache().Load(1)
assert.True(ok)
assert.Equal(derivedWrapper, v)
}
func TestKms_VerifyGlobalRoot(t *testing.T) {
t.Parallel()
testCtx := context.Background()
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
rootWrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, rootWrapper)
assert.Error(kmsCache.VerifyGlobalRoot(testCtx))
require.NoError(kmsCache.CreateKeys(testCtx, "global"))
assert.NoError(kmsCache.VerifyGlobalRoot(testCtx))
}
func TestKms_GetWrapper(t *testing.T) {
t.Parallel()
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rootWrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, rootWrapper)
kmsCache.CreateKeys(testCtx, "global")
tests := []struct {
name string
kms *kms.Kms
purpose kms.KeyPurpose
scopeId string
opt []kms.Option
wantErr bool
wantErrMatch *errors.Template
wantErrContains string
}{
{
name: "missing-purpose",
kms: kmsCache,
scopeId: "global",
wantErr: true,
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing purpose",
},
{
name: "missing-scope-id",
kms: kmsCache,
purpose: kms.KeyPurposeDatabase,
wantErr: true,
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing scope id",
},
{
name: "success",
kms: kmsCache,
purpose: kms.KeyPurposeDatabase,
scopeId: "global",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := tc.kms.GetWrapper(testCtx, tc.scopeId, tc.purpose, tc.opt...)
if tc.wantErr {
require.Error(err)
if tc.wantErrMatch != nil {
assert.Truef(errors.Match(tc.wantErrMatch, err), "expected %q and got err: %+v", tc.wantErrMatch.Code, err)
}
if tc.wantErrContains != "" {
assert.True(strings.Contains(err.Error(), tc.wantErrContains))
}
return
}
require.NoError(err)
assert.NotNil(got)
})
}
}
func TestKms_CreateKeys(t *testing.T) {
t.Parallel()
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rootWrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, rootWrapper)
kmsCache.CreateKeys(testCtx, "global")
rw := db.New(conn)
tests := []struct {
name string
kms *kms.Kms
scopeId string
opt []kms.Option
wantErr bool
wantErrMatch *errors.Template
wantErrContains string
}{
{
name: "missing-scope-id",
kms: kmsCache,
wantErr: true,
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing scope id",
},
{
name: "invalid-scope",
kms: kmsCache,
scopeId: "o_1234567890",
wantErr: true,
wantErrContains: "violates foreign key constraint",
},
{
name: "missing-writer-opt",
kms: kmsCache,
scopeId: "global",
opt: []kms.Option{kms.WithReaderWriter(rw, nil)},
wantErr: true,
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing writer",
},
{
name: "missing-reader-opt",
kms: kmsCache,
scopeId: "global",
opt: []kms.Option{kms.WithReaderWriter(nil, rw)},
wantErr: true,
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing reader",
},
{
name: "success",
kms: kmsCache,
scopeId: "global",
},
{
name: "success-with-reader-writer",
kms: kmsCache,
opt: []kms.Option{kms.WithReaderWriter(rw, rw)},
scopeId: "global",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
kms.TestKmsDeleteAllKeys(t, conn)
err := tc.kms.CreateKeys(testCtx, tc.scopeId, tc.opt...)
if tc.wantErr {
require.Error(err)
if tc.wantErrMatch != nil {
assert.Truef(errors.Match(tc.wantErrMatch, err), "expected %q and got err: %+v", tc.wantErrMatch.Code, err)
}
if tc.wantErrContains != "" {
assert.True(strings.Contains(err.Error(), tc.wantErrContains))
}
return
}
require.NoError(err)
})
}
}
+159 -57
View File
@@ -2,73 +2,175 @@ package kms
import (
"context"
"crypto/rand"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/go-dbw"
wrappingKms "github.com/hashicorp/go-kms-wrapping/extras/kms/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestKms_KeyId(t *testing.T) {
func Test_New(t *testing.T) {
t.Parallel()
require := require.New(t)
ctx := context.Background()
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
extWrapper := db.TestWrapper(t)
repo, err := NewRepository(rw, rw)
require.NoError(err)
// Make the global scope base keys
_, err = CreateKeysTx(ctx, rw, rw, extWrapper, rand.Reader, scope.Global.String())
require.NoError(err)
tests := []struct {
name string
r *db.Db
w *db.Db
want *Kms
wantErr bool
wantErrMatch *errors.Template
wantErrContains string
}{
{
name: "nil-reader",
w: rw,
wantErr: true,
wantErrContains: "missing reader",
wantErrMatch: errors.T(errors.InvalidParameter),
},
{
name: "nil-writer",
r: rw,
wantErr: true,
wantErrContains: "missing writer",
wantErrMatch: errors.T(errors.InvalidParameter),
},
{
name: "success",
r: rw,
w: rw,
want: &Kms{
reader: rw,
underlying: func() *wrappingKms.Kms {
purposes := make([]wrappingKms.KeyPurpose, 0, len(ValidDekPurposes()))
for _, p := range ValidDekPurposes() {
purposes = append(purposes, wrappingKms.KeyPurpose(p.String()))
}
purposes = append(purposes, wrappingKms.KeyPurpose(KeyPurposeWorkerAuth.String()), wrappingKms.KeyPurpose(KeyPurposeRecovery.String()))
// Get the global scope's root wrapper
kmsCache, err := NewKms(repo)
require.NoError(err)
require.NoError(kmsCache.AddExternalWrappers(ctx, WithRootWrapper(extWrapper)))
globalRootWrapper, _, err := kmsCache.loadRoot(ctx, scope.Global.String())
require.NoError(err)
r := dbw.New(rw.UnderlyingDB())
w := dbw.New(rw.UnderlyingDB())
wrapped, err := wrappingKms.New(r, w, purposes, wrappingKms.WithCache(true))
require.NoError(t, err)
return wrapped
}(),
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
dks, err := repo.ListDatabaseKeys(ctx)
require.NoError(err)
require.Len(dks, 1)
// Create another key version
newKeyBytes, err := uuid.GenerateRandomBytes(32)
require.NoError(err)
_, err = repo.CreateDatabaseKeyVersion(ctx, globalRootWrapper, dks[0].GetPrivateId(), newKeyBytes)
require.NoError(err)
dkvs, err := repo.ListDatabaseKeyVersions(ctx, globalRootWrapper, dks[0].GetPrivateId())
require.NoError(err)
require.Len(dkvs, 2)
keyId1 := dkvs[0].GetPrivateId()
keyId2 := dkvs[1].GetPrivateId()
// First test: just getting the key should return the latest
wrapper, err := kmsCache.GetWrapper(ctx, scope.Global.String(), KeyPurposeDatabase)
require.NoError(err)
tKeyId, err := wrapper.KeyId(context.Background())
require.NoError(err)
require.Equal(keyId2, tKeyId)
// Second: ask for each in turn
wrapper, err = kmsCache.GetWrapper(ctx, scope.Global.String(), KeyPurposeDatabase, WithKeyId(keyId1))
require.NoError(err)
tKeyId, err = wrapper.KeyId(context.Background())
require.NoError(err)
require.Equal(keyId1, tKeyId)
wrapper, err = kmsCache.GetWrapper(ctx, scope.Global.String(), KeyPurposeDatabase, WithKeyId(keyId2))
require.NoError(err)
tKeyId, err = wrapper.KeyId(context.Background())
require.NoError(err)
require.Equal(keyId2, tKeyId)
// Last: verify something bogus finds nothing
_, err = kmsCache.GetWrapper(ctx, scope.Global.String(), KeyPurposeDatabase, WithKeyId("foo"))
require.Error(err)
got, err := New(testCtx, tc.r, tc.w)
if tc.wantErr {
require.Error(err)
if tc.wantErrMatch != nil {
assert.Truef(errors.Match(tc.wantErrMatch, err), "expected %q and got err: %+v", tc.wantErrMatch.Code, err)
}
if tc.wantErrContains != "" {
assert.Contains(err.Error(), tc.wantErrContains)
}
return
}
require.NoError(err)
assert.Equal(tc.want, got)
})
}
}
func Test_NewUsingReaderWriter(t *testing.T) {
t.Parallel()
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
tests := []struct {
name string
r db.Reader
w db.Writer
want *Kms
wantErr bool
wantErrMatch *errors.Template
wantErrContains string
}{
{
name: "nil-reader",
w: rw,
wantErr: true,
wantErrContains: "missing reader",
wantErrMatch: errors.T(errors.InvalidParameter),
},
{
name: "nil-writer",
r: rw,
wantErr: true,
wantErrContains: "missing writer",
wantErrMatch: errors.T(errors.InvalidParameter),
},
{
name: "invalid-reader",
r: &invalidReader{},
w: rw,
wantErr: true,
wantErrContains: "unable to convert to db.DB",
wantErrMatch: errors.T(errors.InvalidParameter),
},
{
name: "invalid-writer",
r: rw,
w: &invalidWriter{},
wantErr: true,
wantErrContains: "unable to convert to db.DB",
wantErrMatch: errors.T(errors.InvalidParameter),
},
{
name: "success",
r: rw,
w: rw,
want: &Kms{
reader: rw,
underlying: func() *wrappingKms.Kms {
purposes := stdNewKmsPurposes()
r := dbw.New(rw.UnderlyingDB())
w := dbw.New(rw.UnderlyingDB())
wrapped, err := wrappingKms.New(r, w, purposes, wrappingKms.WithCache(true))
require.NoError(t, err)
return wrapped
}(),
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := NewUsingReaderWriter(testCtx, tc.r, tc.w)
if tc.wantErr {
require.Error(err)
if tc.wantErrMatch != nil {
assert.Truef(errors.Match(tc.wantErrMatch, err), "expected %q and got err: %+v", tc.wantErrMatch.Code, err)
}
if tc.wantErrContains != "" {
assert.Contains(err.Error(), tc.wantErrContains)
}
return
}
require.NoError(err)
assert.Equal(tc.want, got)
})
}
}
type invalidReader struct {
db.Reader
}
type invalidWriter struct {
db.Writer
}
-83
View File
@@ -1,83 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
"google.golang.org/protobuf/proto"
)
const (
DefaultOidcKeyTableName = "kms_oidc_key"
)
type OidcKey struct {
*store.OidcKey
tableName string `gorm:"-"`
}
// NewOidcKey creates a new in memory oidc key. This key is used to encrypt
// oidc state before it's included in the oidc auth url. No options are
// currently supported.
func NewOidcKey(rootKeyId string, _ ...Option) (*OidcKey, error) {
const op = "kms.NewOidcKey"
if rootKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key id")
}
c := &OidcKey{
OidcKey: &store.OidcKey{
RootKeyId: rootKeyId,
},
}
return c, nil
}
// AllocOidcKey will allocate a OidcKey
func AllocOidcKey() OidcKey {
return OidcKey{
OidcKey: &store.OidcKey{},
}
}
// Clone creates a clone of the OidcKey
func (k *OidcKey) Clone() interface{} {
cp := proto.Clone(k.OidcKey)
return &OidcKey{
OidcKey: cp.(*store.OidcKey),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// before it's written.
func (k *OidcKey) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "kms.(OidcKey).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
switch opType {
case db.CreateOp:
if k.RootKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
case db.UpdateOp:
return errors.New(ctx, errors.InvalidParameter, op, "key is immutable")
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *OidcKey) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultOidcKeyTableName
}
// SetTableName sets the tablename and satisfies the ReplayableMessage
// interface. If the caller attempts to set the name to "" the name will be
// reset to the default name.
func (k *OidcKey) SetTableName(n string) {
k.tableName = n
}
-206
View File
@@ -1,206 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the OidcKey attributes are
// immutable and those tests are covered by TestOidcKey_ImmutableFields
func TestOidcKey_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootKeyId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.OidcKey
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-rootKeyId",
args: args{},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
rootKeyId: rk.PrivateId,
},
want: func() *kms.OidcKey {
k := kms.AllocOidcKey()
k.RootKeyId = rk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOidcKey(); return &i }(), "1=1")
got, err := kms.NewOidcKey(tt.args.rootKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.OidcKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestOidcKey_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOidcKey(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.OidcKey
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestOidcKey(t, conn, rk.PrivateId),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.OidcKey {
k := kms.AllocOidcKey()
id, err := db.NewPublicId(kms.OidcKeyPrefix)
require.NoError(t, err)
k.PrivateId = id
k.RootKeyId = rk.PrivateId
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocOidcKey()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocOidcKey()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestOidcKey_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k := kms.TestOidcKey(t, conn, rk.PrivateId)
cp := k.Clone()
assert.True(proto.Equal(cp.(*kms.OidcKey).OidcKey, k.OidcKey))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
k := kms.TestOidcKey(t, conn, rk.PrivateId)
k2 := kms.TestOidcKey(t, conn, rk2.PrivateId)
cp := k.Clone()
assert.True(!proto.Equal(cp.(*kms.OidcKey).OidcKey, k2.OidcKey))
})
}
func TestOidcKey_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultOidcKeyTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocOidcKey()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocOidcKey()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-122
View File
@@ -1,122 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
const (
DefaultOidcKeyVersionTableName = "kms_oidc_key_version"
)
type OidcKeyVersion struct {
*store.OidcKeyVersion
tableName string `gorm:"-"`
}
// NewOidcKeyVersion creates a new in memory oidc key version. This key is used
// to encrypt oidc state before it's included in the oidc auth url. No options
// are currently supported.
func NewOidcKeyVersion(oidcKeyId string, key []byte, rootKeyVersionId string, _ ...Option) (*OidcKeyVersion, error) {
const op = "kms.NewOidcKeyVersion"
if oidcKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing oidc key id")
}
if len(key) == 0 {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing key")
}
if rootKeyVersionId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key version id")
}
k := &OidcKeyVersion{
OidcKeyVersion: &store.OidcKeyVersion{
OidcKeyId: oidcKeyId,
RootKeyVersionId: rootKeyVersionId,
Key: key,
},
}
return k, nil
}
// AllocOidcKeyVersion allocates a OidcKeyVersion
func AllocOidcKeyVersion() OidcKeyVersion {
return OidcKeyVersion{
OidcKeyVersion: &store.OidcKeyVersion{},
}
}
// Clone creates a clone of the OidcKeyVersion
func (k *OidcKeyVersion) Clone() interface{} {
cp := proto.Clone(k.OidcKeyVersion)
return &OidcKeyVersion{
OidcKeyVersion: cp.(*store.OidcKeyVersion),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the oidc key
// version before it's written.
func (k *OidcKeyVersion) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "kms.(OidcKeyVersion).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
switch opType {
case db.CreateOp:
if k.CtKey == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
if k.OidcKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing oidc key id")
}
if k.RootKeyVersionId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
case db.UpdateOp:
return errors.New(ctx, errors.InvalidParameter, op, "key is immutable")
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *OidcKeyVersion) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultOidcKeyVersionTableName
}
// SetTableName sets the tablename and satisfies the ReplayableMessage
// interface. If the caller attempts to set the name to "" the name will be
// reset to the default name.
func (k *OidcKeyVersion) SetTableName(n string) {
k.tableName = n
}
// Encrypt will encrypt the oidc key version's key
func (k *OidcKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(OidcKeyVersion).Encrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.OidcKeyVersion directly
if err := structwrapping.WrapStruct(ctx, cipher, k.OidcKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
}
return nil
}
// Decrypt will decrypt the oidc key version's key
func (k *OidcKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(OidcKeyVersion).Decrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.OidcKeyVersion directly
if err := structwrapping.UnwrapStruct(ctx, cipher, k.OidcKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
-249
View File
@@ -1,249 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the OidcKeyVersion attributes are
// immutable and those tests are covered by TestOidcKey_ImmutableFields
func TestOidcKeyVersion_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rkv, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOidcKey(t, conn, rk.PrivateId)
type args struct {
OidcKeyId string
key []byte
rootKeyVersionId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.OidcKeyVersion
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-oidc-id",
args: args{
key: []byte("oidc key"),
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
OidcKeyId: dk.PrivateId,
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-root-key-version-id",
args: args{
OidcKeyId: dk.PrivateId,
key: []byte("oidc key"),
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
OidcKeyId: dk.PrivateId,
key: []byte("oidc key"),
rootKeyVersionId: rkv.PrivateId,
},
want: func() *kms.OidcKeyVersion {
k := kms.AllocOidcKeyVersion()
k.RootKeyVersionId = rkv.PrivateId
k.Key = []byte("oidc key")
k.OidcKeyId = dk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOidcKeyVersion(); return &i }(), "1=1")
got, err := kms.NewOidcKeyVersion(tt.args.OidcKeyId, tt.args.key, tt.args.rootKeyVersionId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.OidcKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = got.Encrypt(context.Background(), rkvWrapper)
require.NoError(err)
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestOidcKeyVersion_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOidcKey(t, conn, rk.PrivateId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOidcKeyVersion(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.OidcKeyVersion
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestOidcKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("oidc key")),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.OidcKeyVersion {
k := kms.AllocOidcKeyVersion()
id, err := db.NewPublicId(kms.OidcKeyVersionPrefix)
require.NoError(t, err)
k.PrivateId = id
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocOidcKeyVersion()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocOidcKeyVersion()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestOidcKeyVersion_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
k := kms.TestOidcKey(t, conn, rk.PrivateId)
kv := kms.TestOidcKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test db key"))
cp := kv.Clone()
assert.True(proto.Equal(cp.(*kms.OidcKeyVersion).OidcKeyVersion, kv.OidcKeyVersion))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
_, rkvWrapper2 := kms.TestRootKeyVersion(t, conn, wrapper, rk2.PrivateId)
k := kms.TestOidcKey(t, conn, rk.PrivateId)
k2 := kms.TestOidcKey(t, conn, rk2.PrivateId)
kv := kms.TestOidcKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test db key"))
kv2 := kms.TestOidcKeyVersion(t, conn, rkvWrapper2, k2.PrivateId, []byte("test db key 2"))
cp := kv.Clone()
assert.True(!proto.Equal(cp.(*kms.OidcKeyVersion).OidcKeyVersion, kv2.OidcKeyVersion))
})
}
func TestOidcKeyVersion_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultOidcKeyVersionTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocOidcKeyVersion()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocOidcKeyVersion()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-79
View File
@@ -1,79 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
"google.golang.org/protobuf/proto"
)
const (
DefaultOplogKeyTableName = "kms_oplog_key"
)
type OplogKey struct {
*store.OplogKey
tableName string `gorm:"-"`
}
// NewOplogKey creates a new in memory key. No options
// are currently supported.
func NewOplogKey(rootKeyId string, _ ...Option) (*OplogKey, error) {
const op = "kms.NewOplogKey"
if rootKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key id")
}
c := &OplogKey{
OplogKey: &store.OplogKey{
RootKeyId: rootKeyId,
},
}
return c, nil
}
// AllocOplogKey will allocate a key
func AllocOplogKey() OplogKey {
return OplogKey{
OplogKey: &store.OplogKey{},
}
}
// Clone creates a clone of the key
func (k *OplogKey) Clone() interface{} {
cp := proto.Clone(k.OplogKey)
return &OplogKey{
OplogKey: cp.(*store.OplogKey),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// before it's written.
func (k *OplogKey) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "kms.(OplogKey).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if opType == db.CreateOp {
if k.RootKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *OplogKey) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultOplogKeyTableName
}
// SetTableName sets the tablename and satisfies the ReplayableMessage
// interface. If the caller attempts to set the name to "" the name will be
// reset to the default name.
func (k *OplogKey) SetTableName(n string) {
k.tableName = n
}
-206
View File
@@ -1,206 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the OplogKey attributes are
// immutable and those tests are covered by TestOplogKey_ImmutableFields
func TestOplogKey_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootKeyId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.OplogKey
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-rootKeyId",
args: args{},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
rootKeyId: rk.PrivateId,
},
want: func() *kms.OplogKey {
k := kms.AllocOplogKey()
k.RootKeyId = rk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOplogKey(); return &i }(), "1=1")
got, err := kms.NewOplogKey(tt.args.rootKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.OplogKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestOplogKey_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOplogKey(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.OplogKey
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestOplogKey(t, conn, rk.PrivateId),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.OplogKey {
k := kms.AllocOplogKey()
id, err := db.NewPublicId(kms.OplogKeyPrefix)
require.NoError(t, err)
k.PrivateId = id
k.RootKeyId = rk.PrivateId
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocOplogKey()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocOplogKey()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestOplogKey_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k := kms.TestOplogKey(t, conn, rk.PrivateId)
cp := k.Clone()
assert.True(proto.Equal(cp.(*kms.OplogKey).OplogKey, k.OplogKey))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
k := kms.TestOplogKey(t, conn, rk.PrivateId)
k2 := kms.TestOplogKey(t, conn, rk2.PrivateId)
cp := k.Clone()
assert.True(!proto.Equal(cp.(*kms.OplogKey).OplogKey, k2.OplogKey))
})
}
func TestOplogKey_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultOplogKeyTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocOplogKey()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocOplogKey()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-118
View File
@@ -1,118 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
const (
DefaultOplogKeyVersionTableName = "kms_oplog_key_version"
)
type OplogKeyVersion struct {
*store.OplogKeyVersion
tableName string `gorm:"-"`
}
// OplogKeyVersion creates a new in memory key version. No options are
// currently supported.
func NewOplogKeyVersion(oplogKeyId string, key []byte, rootKeyVersionId string, _ ...Option) (*OplogKeyVersion, error) {
const op = "kms.NewOplogKeyVersion"
if oplogKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing oplog key id")
}
if len(key) == 0 {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing key")
}
if rootKeyVersionId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key version id")
}
k := &OplogKeyVersion{
OplogKeyVersion: &store.OplogKeyVersion{
OplogKeyId: oplogKeyId,
RootKeyVersionId: rootKeyVersionId,
Key: key,
},
}
return k, nil
}
// AllocOplogKeyVersion allocates a key version
func AllocOplogKeyVersion() OplogKeyVersion {
return OplogKeyVersion{
OplogKeyVersion: &store.OplogKeyVersion{},
}
}
// Clone creates a clone of the key version
func (k *OplogKeyVersion) Clone() interface{} {
cp := proto.Clone(k.OplogKeyVersion)
return &OplogKeyVersion{
OplogKeyVersion: cp.(*store.OplogKeyVersion),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// version before it's written.
func (k *OplogKeyVersion) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "kms.(OplogKeyVersion).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if opType == db.CreateOp {
if k.CtKey == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
if k.OplogKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing oplog key id")
}
if k.RootKeyVersionId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *OplogKeyVersion) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultOplogKeyVersionTableName
}
// SetTableName sets the tablename and satisfies the ReplayableMessage
// interface. If the caller attempts to set the name to "" the name will be
// reset to the default name.
func (k *OplogKeyVersion) SetTableName(n string) {
k.tableName = n
}
// Encrypt will encrypt the key version's key
func (k *OplogKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(OplogKeyVersion).Encrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.OplogKeyVersion directly
if err := structwrapping.WrapStruct(ctx, cipher, k.OplogKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
}
return nil
}
// Decrypt will decrypt the key version's key
func (k *OplogKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(OplogKeyVersion).Decrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.OplogKeyVersion directly
if err := structwrapping.UnwrapStruct(ctx, cipher, k.OplogKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
-249
View File
@@ -1,249 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the OplogKeyVersion attributes are
// immutable and those tests are covered by TestOplogKey_ImmutableFields
func TestOplogKeyVersion_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rkv, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOplogKey(t, conn, rk.PrivateId)
type args struct {
oplogKeyId string
key []byte
rootKeyVersionId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.OplogKeyVersion
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-id",
args: args{
key: []byte("test key"),
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
oplogKeyId: dk.PrivateId,
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-root-key-version-id",
args: args{
oplogKeyId: dk.PrivateId,
key: []byte("test key"),
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
oplogKeyId: dk.PrivateId,
key: []byte("test key"),
rootKeyVersionId: rkv.PrivateId,
},
want: func() *kms.OplogKeyVersion {
k := kms.AllocOplogKeyVersion()
k.RootKeyVersionId = rkv.PrivateId
k.Key = []byte("test key")
k.OplogKeyId = dk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOplogKeyVersion(); return &i }(), "1=1")
got, err := kms.NewOplogKeyVersion(tt.args.oplogKeyId, tt.args.key, tt.args.rootKeyVersionId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.OplogKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = got.Encrypt(context.Background(), rkvWrapper)
require.NoError(err)
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestOplogKeyVersion_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
opk := kms.TestOplogKey(t, conn, rk.PrivateId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOplogKeyVersion(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.OplogKeyVersion
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestOplogKeyVersion(t, conn, rkvWrapper, opk.PrivateId, []byte("test key")),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.OplogKeyVersion {
k := kms.AllocOplogKeyVersion()
id, err := db.NewPublicId(kms.OplogKeyVersionPrefix)
require.NoError(t, err)
k.PrivateId = id
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocOplogKeyVersion()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocOplogKeyVersion()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestOplogKeyVersion_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
k := kms.TestOplogKey(t, conn, rk.PrivateId)
kv := kms.TestOplogKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test key"))
cp := kv.Clone()
assert.True(proto.Equal(cp.(*kms.OplogKeyVersion).OplogKeyVersion, kv.OplogKeyVersion))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
_, rkvWrapper2 := kms.TestRootKeyVersion(t, conn, wrapper, rk2.PrivateId)
k := kms.TestOplogKey(t, conn, rk.PrivateId)
k2 := kms.TestOplogKey(t, conn, rk2.PrivateId)
kv := kms.TestOplogKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test key"))
kv2 := kms.TestOplogKeyVersion(t, conn, rkvWrapper2, k2.PrivateId, []byte("test key 2"))
cp := kv.Clone()
assert.True(!proto.Equal(cp.(*kms.OplogKeyVersion).OplogKeyVersion, kv2.OplogKeyVersion))
})
}
func TestOplogKeyVersion_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultOplogKeyVersionTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocOplogKeyVersion()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocOplogKeyVersion()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
+7
View File
@@ -35,4 +35,11 @@ func Test_GetOpts(t *testing.T) {
testOpts.withOrderByVersion = db.DescendingOrderBy
assert.Equal(opts, testOpts)
})
t.Run("WithKeyId", func(t *testing.T) {
assert := assert.New(t)
opts := getOpts(WithKeyId("100"))
testOpts := getDefaultOptions()
testOpts.withKeyId = "100"
assert.Equal(opts, testOpts)
})
}
+25 -9
View File
@@ -1,6 +1,8 @@
package kms
import (
"io"
"github.com/hashicorp/boundary/internal/db"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
@@ -23,10 +25,12 @@ type options struct {
withRootWrapper wrapping.Wrapper
withWorkerAuthWrapper wrapping.Wrapper
withRecoveryWrapper wrapping.Wrapper
withRepository *Repository
withOrderByVersion db.OrderBy
withKeyId string
withScopeIds []string
withRandomReader io.Reader
withReader db.Reader
withWriter db.Writer
}
func getDefaultOptions() options {
@@ -64,14 +68,6 @@ func WithRecoveryWrapper(w wrapping.Wrapper) Option {
}
}
// WithRepository sets a repository for a given wrapper lookup, useful if in the
// middle of a transaction where the reader/writer need to be specified
func WithRepository(repo *Repository) Option {
return func(o *options) {
o.withRepository = repo
}
}
// WithOrderByVersion provides an option to specify ordering by the
// CreateTime field.
func WithOrderByVersion(orderBy db.OrderBy) Option {
@@ -95,3 +91,23 @@ func WithScopeIds(scopeId ...string) Option {
o.withScopeIds = scopeId
}
}
// WithRandomReader(...) option allows an optional random reader to be
// provided. By default the reader from crypto/rand will be used.
func WithRandomReader(randomReader io.Reader) Option {
return func(o *options) {
o.withRandomReader = randomReader
}
}
// WithReaderWriter allows the caller to pass an inflight transaction to be used
// for all database operations. If WithReaderWriter(...) is used, then the
// caller is responsible for managing the transaction. The purpose of the
// WithReaderWriter(...) option is to allow the caller to create the scope and
// all of its keys in the same transaction.
func WithReaderWriter(r db.Reader, w db.Writer) Option {
return func(o *options) {
o.withReader = r
o.withWriter = w
}
}
-200
View File
@@ -1,200 +0,0 @@
package kms
import (
"context"
"fmt"
"io"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/types/scope"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/hashicorp/go-uuid"
)
// Repository is the iam database repository
type Repository struct {
reader db.Reader
writer db.Writer
// defaultLimit provides a default for limiting the number of results returned from the repo
defaultLimit int
}
// NewRepository creates a new kms Repository. Supports the options: WithLimit
// which sets a default limit on results returned by repo operations.
func NewRepository(r db.Reader, w db.Writer, opt ...Option) (*Repository, error) {
const op = "kms.NewRepository"
if r == nil {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "nil reader")
}
if w == nil {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "nil writer")
}
opts := getOpts(opt...)
if opts.withLimit == 0 {
// zero signals the boundary defaults should be used.
opts.withLimit = db.DefaultLimit
}
return &Repository{
reader: r,
writer: w,
defaultLimit: opts.withLimit,
}, nil
}
// list will return a listing of resources and honor the WithLimit option or the
// repo defaultLimit
func (r *Repository) list(ctx context.Context, resources interface{}, where string, args []interface{}, opt ...Option) error {
opts := getOpts(opt...)
limit := r.defaultLimit
var dbOpts []db.Option
if opts.withLimit != 0 {
// non-zero signals an override of the default limit for the repo.
limit = opts.withLimit
}
dbOpts = append(dbOpts, db.WithLimit(limit))
switch opts.withOrderByVersion {
case db.AscendingOrderBy:
dbOpts = append(dbOpts, db.WithOrder("version asc"))
case db.DescendingOrderBy:
dbOpts = append(dbOpts, db.WithOrder("version desc"))
}
return r.reader.SearchWhere(ctx, resources, where, args, dbOpts...)
}
// DefaultLimit returns the default limit for listing as set on the repo
func (r *Repository) DefaultLimit() int {
return r.defaultLimit
}
// KeyIder defines a common interface for all keys returned from CreateKeysTx in
// a Keys map
type KeyIder interface {
GetPrivateId() string
}
// Keys defines a return type for CreateKeysTx so the returned keys can be
// easily accessed via their KeyType
type Keys map[KeyType]KeyIder
// CreateKeysTx creates the root key and DEKs returns a map of the new keys.
// This function encapsulates all the work required within a db.TxHandler and
// allows this capability to be shared with the iam repo.
func CreateKeysTx(ctx context.Context, dbReader db.Reader, dbWriter db.Writer, rootWrapper wrapping.Wrapper, randomReader io.Reader, scopeId string) (Keys, error) {
const op = "kms.CreateKeysTx"
if dbReader == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing db reader")
}
if dbWriter == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing db writer")
}
if rootWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root wrapper")
}
if randomReader == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing random reader")
}
if scopeId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
}
k, err := generateKey(ctx, randomReader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for root key in scope %s", scopeId)))
}
rootKey, rootKeyVersion, err := createRootKeyTx(ctx, dbWriter, rootWrapper, scopeId, k)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to create root key in scope %s", scopeId)))
}
rkvWrapper := aead.NewWrapper()
if _, err := rkvWrapper.SetConfig(ctx, wrapping.WithKeyId(rootKeyVersion.GetPrivateId())); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error setting config on aead root wrapper in scope %s", scopeId)))
}
if err := rkvWrapper.SetAesGcmKeyBytes(rootKeyVersion.GetKey()); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error setting key bytes on aead root wrapper in scope %s", scopeId)))
}
k, err = generateKey(ctx, randomReader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for database key in scope %s", scopeId)))
}
dbKey, dbKeyVersion, err := createDatabaseKeyTx(ctx, dbReader, dbWriter, rkvWrapper, k)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to create database key in scope %s", scopeId)))
}
k, err = generateKey(ctx, randomReader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for oplog key in scope %s", scopeId)))
}
oplogKey, oplogKeyVersion, err := createOplogKeyTx(ctx, dbReader, dbWriter, rkvWrapper, k)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to create oplog key in scope %s", scopeId)))
}
k, err = generateKey(ctx, randomReader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for session key in scope %s", scopeId)))
}
sessionKey, sessionKeyVersion, err := createSessionKeyTx(ctx, dbReader, dbWriter, rkvWrapper, k)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to create session key in scope %s", scopeId)))
}
k, err = generateKey(ctx, randomReader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for token key in scope %s", scopeId)))
}
tokenKey, tokenKeyVersion, err := createTokenKeyTx(ctx, dbReader, dbWriter, rkvWrapper, k)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to create token key in scope %s", scopeId)))
}
k, err = generateKey(ctx, randomReader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for oidc key in scope %s", scopeId)))
}
oidcKey, oidcKeyVersion, err := createOidcKeyTx(ctx, dbReader, dbWriter, rkvWrapper, k)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to create oidc key in scope %s", scopeId)))
}
keys := Keys{
KeyTypeRootKey: rootKey,
KeyTypeRootKeyVersion: rootKeyVersion,
KeyTypeDatabaseKey: dbKey,
KeyTypeDatabaseKeyVersion: dbKeyVersion,
KeyTypeOplogKey: oplogKey,
KeyTypeOplogKeyVersion: oplogKeyVersion,
KeyTypeSessionKey: sessionKey,
KeyTypeSessionKeyVersion: sessionKeyVersion,
KeyTypeTokenKey: tokenKey,
KeyTypeTokenKeyVersion: tokenKeyVersion,
KeyTypeOidcKey: oidcKey,
KeyTypeOidcKeyVersion: oidcKeyVersion,
}
if scopeId == scope.Global.String() {
k, err = generateKey(ctx, randomReader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error generating random bytes for oidc key in scope %s", scopeId)))
}
auditKey, auditKeyVersion, err := createAuditKeyTx(ctx, dbReader, dbWriter, rkvWrapper, k)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to create audit key in scope %s", scopeId)))
}
keys[KeyTypeAuditKey] = auditKey
keys[KeyTypeAuditKeyVersion] = auditKeyVersion
}
return keys, nil
}
func generateKey(ctx context.Context, randomReader io.Reader) ([]byte, error) {
const op = "kms.generateKey"
k, err := uuid.GenerateRandomBytesWithReader(32, randomReader)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return k, nil
}
-163
View File
@@ -1,163 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateAuditKey inserts into the repository and returns the new audit key and
// audit key version. There are no valid options at this time.
func (r *Repository) CreateAuditKey(ctx context.Context, rkvWrapper wrapping.Wrapper, key []byte, _ ...Option) (*AuditKey, *AuditKeyVersion, error) {
const op = "kms.(Repository).CreateAuditKey"
var returnedDk, returnedDv interface{}
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
var err error
if returnedDk, returnedDv, err = createAuditKeyTx(ctx, reader, w, rkvWrapper, key); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
return returnedDk.(*AuditKey), returnedDv.(*AuditKeyVersion), nil
}
// createAuditKeyTx inserts into the db (via db.Writer) and returns the new audit key
// and audit key version. This function encapsulates all the work required within
// a db.TxHandler and allows this capability to be shared with the iam repo.
func createAuditKeyTx(ctx context.Context, r db.Reader, w db.Writer, rkvWrapper wrapping.Wrapper, key []byte) (*AuditKey, *AuditKeyVersion, error) {
const op = "kms.createAuditKeyTx"
if rkvWrapper == nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
if len(key) == 0 {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup root key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
rv := AllocRootKeyVersion()
rv.PrivateId = rootKeyVersionId
err = r.LookupById(ctx, &rv)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to lookup root key version %s", rootKeyVersionId)))
}
dk := AllocAuditKey()
dv := AllocAuditKeyVersion()
id, err := newAuditKeyId(ctx)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
dk.PrivateId = id
dk.RootKeyId = rv.RootKeyId
id, err = newAuditKeyVersionId(ctx)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
dv.PrivateId = id
dv.AuditKeyId = dk.PrivateId
dv.RootKeyVersionId = rootKeyVersionId
dv.Key = key
if err := dv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
// no oplog entries for keys
if err := w.Create(ctx, &dk); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("keys create"))
}
// no oplog entries for key versions
if err := w.Create(ctx, &dv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("key versions create"))
}
return &dk, &dv, nil
}
// LookupAuditKey will look up a key in the repository. If the key is not
// found, it will return nil, nil.
func (r *Repository) LookupAuditKey(ctx context.Context, privateId string, _ ...Option) (*AuditKey, error) {
const op = "kms.(Repository).LookupAuditKey"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocAuditKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return &k, nil
}
// DeleteAuditKey deletes the key for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteAuditKey(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteAuditKey"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocAuditKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for root keys
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// ListAuditKeys will list the keys. Supports the WithLimit option.
func (r *Repository) ListAuditKeys(ctx context.Context, opt ...Option) ([]Dek, error) {
const op = "kms.(Repository).ListAuditKeys"
var keys []*AuditKey
err := r.list(ctx, &keys, "1=1", nil, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
deks := make([]Dek, 0, len(keys))
for _, key := range keys {
deks = append(deks, key)
}
return deks, nil
}
-298
View File
@@ -1,298 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateAuditKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
type args struct {
scopeId string
key []byte
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid-org",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: rkvWrapper,
},
wantErr: false,
},
{
name: "nil-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
key: []byte(""),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-rkv-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: wrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "wrapper-missing-id",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: func() wrapping.Wrapper {
w := db.TestWrapper(t)
_, err = w.(*aead.Wrapper).SetConfig(
context.Background(),
wrapping.WithKeyId(""),
)
require.NoError(t, err)
return w
}(),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
dk, dv, err := repo.CreateAuditKey(context.Background(), tt.args.keyWrapper, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(dk)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(dk.CreateTime)
foundKey, err := repo.LookupAuditKey(context.Background(), dk.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, dk))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, dk.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
assert.NotNil(dv.CreateTime)
foundKeyVersion, err := repo.LookupAuditKeyVersion(context.Background(), tt.args.keyWrapper, dv.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKeyVersion, dv))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, dv.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteAuditKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
key *kms.AuditKey
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestAuditKey(t, conn, rk.PrivateId),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.AuditKey {
k := kms.AllocAuditKey()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.AuditKey {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k := kms.AllocAuditKey()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteAuditKey(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupAuditKey(context.Background(), tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_ListAuditKeys(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
wrapper := db.TestWrapper(t)
type args struct {
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1, // org and project both have keys, plus global scope
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
}
for _, tt := range tests {
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
for i := 0; i < tt.createCnt; i++ {
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "scope_id in(?)", "global", org.PublicId, proj.PublicId)
rk := kms.TestRootKey(t, conn, proj.PublicId)
kms.TestAuditKey(t, conn, rk.PrivateId)
require.NoError(err)
}
got, err := repo.ListAuditKeys(context.Background(), tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
@@ -1,176 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateAuditKeyVersion inserts into the repository and returns the new key
// version with its PrivateId. There are no valid options at this time.
func (r *Repository) CreateAuditKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, auditKeyId string, key []byte, _ ...Option) (*AuditKeyVersion, error) {
const op = "kms.(Repository).CreateAuditKeyVersion"
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup root key version ID"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
if auditKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing audit key id")
}
if len(key) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
kv := AllocAuditKeyVersion()
id, err := newAuditKeyVersionId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
kv.PrivateId = id
kv.RootKeyVersionId = rootKeyVersionId
kv.Key = key
kv.AuditKeyId = auditKeyId
if err := kv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var returnedKey interface{}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedKey = kv.Clone()
// no oplog entries for root key version
if err := w.Create(ctx, returnedKey); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s audit key id", kv.AuditKeyId)))
}
return returnedKey.(*AuditKeyVersion), nil
}
// LookupAuditKeyVersion will look up a key version in the repository. If
// the key version is not found, it will return nil, nil.
func (r *Repository) LookupAuditKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*AuditKeyVersion, error) {
const op = "kms.(Repository).LookupAuditKeyVersion"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
k := AllocAuditKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
if err := k.Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &k, nil
}
// DeleteAuditKeyVersion deletes the key version for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteAuditKeyVersion(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteAuditKeyVersion"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocAuditKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for the key version
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// LatestAuditKeyVersion searches for the key version with the highest
// version number. When no results are found, it returns nil with an
// errors.RecordNotFound error.
func (r *Repository) LatestAuditKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, AuditKeyId string, _ ...Option) (*AuditKeyVersion, error) {
const op = "kms.(Repository).LatestAuditKeyVersion"
if AuditKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing audit key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var foundKeys []*AuditKeyVersion
if err := r.reader.SearchWhere(ctx, &foundKeys, "audit_key_id = ?", []interface{}{AuditKeyId}, db.WithLimit(1), db.WithOrder("version desc")); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", AuditKeyId)))
}
if len(foundKeys) == 0 {
return nil, errors.E(ctx, errors.WithCode(errors.RecordNotFound), errors.WithOp(op))
}
if err := foundKeys[0].Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return foundKeys[0], nil
}
// ListAuditKeyVersions will lists versions of a key. Supports the WithLimit option.
func (r *Repository) ListAuditKeyVersions(ctx context.Context, rkvWrapper wrapping.Wrapper, AuditKeyId string, opt ...Option) ([]DekVersion, error) {
const op = "kms.(Repository).ListAuditKeyVersions"
if AuditKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing audit key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var versions []*AuditKeyVersion
err := r.list(ctx, &versions, "audit_key_id = ?", []interface{}{AuditKeyId}, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
for i, k := range versions {
if err := k.Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error decrypting key num %d", i)))
}
}
dekVersions := make([]DekVersion, 0, len(versions))
for _, version := range versions {
dekVersions = append(dekVersions, version)
}
return dekVersions, nil
}
@@ -1,367 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateAuditKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestAuditKey(t, conn, rk.PrivateId)
type args struct {
key []byte
AuditKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: []byte("test key"),
keyWrapper: rkvWrapper,
AuditKeyId: dk.PrivateId,
},
wantErr: false,
},
{
name: "invalid-rkv-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: wrapper,
AuditKeyId: dk.PrivateId,
},
wantErr: true,
},
{
name: "empty-key",
args: args{
key: nil,
keyWrapper: wrapper,
AuditKeyId: dk.PrivateId,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
k, err := repo.CreateAuditKeyVersion(context.Background(), tt.args.keyWrapper, tt.args.AuditKeyId, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(k)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(k.CreateTime)
foundKey, err := repo.LookupAuditKeyVersion(context.Background(), tt.args.keyWrapper, k.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, k))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, k.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteAuditKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestAuditKey(t, conn, rk.PrivateId)
type args struct {
key *kms.AuditKeyVersion
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestAuditKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("audit key")),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.AuditKeyVersion {
k := kms.AllocAuditKeyVersion()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.AuditKeyVersion {
id, err := db.NewPublicId(kms.AuditKeyPrefix)
require.NoError(t, err)
k := kms.AllocAuditKeyVersion()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteAuditKeyVersion(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupAuditKeyVersion(context.Background(), wrapper, tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_LatestAuditKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestAuditKey(t, conn, rk.PrivateId)
tests := []struct {
name string
createCnt int
keyWrapper wrapping.Wrapper
wantVersion uint32
wantErr bool
wantIsError errors.Code
}{
{
name: "5",
createCnt: 5,
keyWrapper: rkvWrapper,
wantVersion: 5,
wantErr: false,
},
{
name: "1",
createCnt: 1,
keyWrapper: rkvWrapper,
wantVersion: 1,
wantErr: false,
},
{
name: "0",
createCnt: 0,
keyWrapper: rkvWrapper,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
{
name: "nil-wrapper",
createCnt: 5,
keyWrapper: nil,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocAuditKeyVersion(); return &i }(), "1=1")
testKeys := []*kms.AuditKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestAuditKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("test audit key"))
testKeys = append(testKeys, k)
}
assert.Equal(tt.createCnt, len(testKeys))
got, err := repo.LatestAuditKeyVersion(context.Background(), tt.keyWrapper, dk.PrivateId)
if tt.wantErr {
require.Error(err)
assert.Nil(got)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
require.NotNil(got)
assert.Equal(tt.wantVersion, got.Version)
})
}
}
func TestRepository_ListAuditKeyVersions(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestAuditKey(t, conn, rk.PrivateId)
type args struct {
AuditKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
AuditKeyId: dk.PrivateId,
keyWrapper: rkvWrapper,
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1,
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
AuditKeyId: dk.PrivateId,
},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
AuditKeyId: dk.PrivateId,
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "bad-org",
createCnt: 1,
args: args{
keyWrapper: rkvWrapper,
AuditKeyId: "bad-id",
},
wantCnt: 0,
wantErr: false,
},
{
name: "nil-wrapper",
createCnt: 1,
args: args{
keyWrapper: nil,
AuditKeyId: dk.PrivateId,
},
wantCnt: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocAuditKeyVersion(); return &i }(), "1=1")
keyVersions := []*kms.AuditKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestAuditKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("audit key"))
keyVersions = append(keyVersions, k)
}
assert.Equal(tt.createCnt, len(keyVersions))
got, err := repo.ListAuditKeyVersions(context.Background(), tt.args.keyWrapper, tt.args.AuditKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-163
View File
@@ -1,163 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateDatabaseKey inserts into the repository and returns the new database key and
// database key version. There are no valid options at this time.
func (r *Repository) CreateDatabaseKey(ctx context.Context, rkvWrapper wrapping.Wrapper, key []byte, _ ...Option) (*DatabaseKey, *DatabaseKeyVersion, error) {
const op = "kms.(Repository).CreateDatabaseKey"
var returnedDk, returnedDv interface{}
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
var err error
if returnedDk, returnedDv, err = createDatabaseKeyTx(ctx, reader, w, rkvWrapper, key); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
return returnedDk.(*DatabaseKey), returnedDv.(*DatabaseKeyVersion), nil
}
// createDatabaseKeyTx inserts into the db (via db.Writer) and returns the new database key
// and database key version. This function encapsulates all the work required within
// a db.TxHandler and allows this capability to be shared with the iam repo.
func createDatabaseKeyTx(ctx context.Context, r db.Reader, w db.Writer, rkvWrapper wrapping.Wrapper, key []byte) (*DatabaseKey, *DatabaseKeyVersion, error) {
const op = "kms.createDatabaseKeyTx"
if rkvWrapper == nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
if len(key) == 0 {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup root key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
rv := AllocRootKeyVersion()
rv.PrivateId = rootKeyVersionId
err = r.LookupById(ctx, &rv)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to lookup root key version %s", rootKeyVersionId)))
}
dk := AllocDatabaseKey()
dv := AllocDatabaseKeyVersion()
id, err := newDatabaseKeyId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
dk.PrivateId = id
dk.RootKeyId = rv.RootKeyId
id, err = newDatabaseKeyVersionId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
dv.PrivateId = id
dv.DatabaseKeyId = dk.PrivateId
dv.RootKeyVersionId = rootKeyVersionId
dv.Key = key
if err := dv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
// no oplog entries for keys
if err := w.Create(ctx, &dk); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("keys create"))
}
// no oplog entries for key versions
if err := w.Create(ctx, &dv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("key versions create"))
}
return &dk, &dv, nil
}
// LookupDatabaseKey will look up a key in the repository. If the key is not
// found, it will return nil, nil.
func (r *Repository) LookupDatabaseKey(ctx context.Context, privateId string, _ ...Option) (*DatabaseKey, error) {
const op = "kms.(Repository).LookupDatabaseKey"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocDatabaseKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return &k, nil
}
// DeleteDatabaseKey deletes the key for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteDatabaseKey(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteDatabaseKey"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocDatabaseKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for root keys
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// ListDatabaseKeys will list the keys. Supports the WithLimit option.
func (r *Repository) ListDatabaseKeys(ctx context.Context, opt ...Option) ([]Dek, error) {
const op = "kms.(Repository).ListDatabaseKeys"
var keys []*DatabaseKey
err := r.list(ctx, &keys, "1=1", nil, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
deks := make([]Dek, 0, len(keys))
for _, key := range keys {
deks = append(deks, key)
}
return deks, nil
}
@@ -1,295 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateDatabaseKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
type args struct {
scopeId string
key []byte
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid-org",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: rkvWrapper,
},
wantErr: false,
},
{
name: "nil-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
key: []byte(""),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-rkv-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: wrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "wrapper-missing-id",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: func() wrapping.Wrapper {
w := db.TestWrapper(t)
_, err = w.(*aead.Wrapper).SetConfig(context.Background())
require.NoError(t, err)
return w
}(),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
dk, dv, err := repo.CreateDatabaseKey(context.Background(), tt.args.keyWrapper, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(dk)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(dk.CreateTime)
foundKey, err := repo.LookupDatabaseKey(context.Background(), dk.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, dk))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, dk.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
assert.NotNil(dv.CreateTime)
foundKeyVersion, err := repo.LookupDatabaseKeyVersion(context.Background(), tt.args.keyWrapper, dv.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKeyVersion, dv))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, dv.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteDatabaseKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
key *kms.DatabaseKey
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestDatabaseKey(t, conn, rk.PrivateId),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.DatabaseKey {
k := kms.AllocDatabaseKey()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.DatabaseKey {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k := kms.AllocDatabaseKey()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteDatabaseKey(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupDatabaseKey(context.Background(), tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_ListDatabaseKeys(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
wrapper := db.TestWrapper(t)
type args struct {
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1, // org and project both have keys, plus global scope
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
}
for _, tt := range tests {
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
for i := 0; i < tt.createCnt; i++ {
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "scope_id in(?)", "global", org.PublicId, proj.PublicId)
rk := kms.TestRootKey(t, conn, proj.PublicId)
kms.TestDatabaseKey(t, conn, rk.PrivateId)
require.NoError(err)
}
got, err := repo.ListDatabaseKeys(context.Background(), tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
@@ -1,176 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateDatabaseKeyVersion inserts into the repository and returns the new key
// version with its PrivateId. There are no valid options at this time.
func (r *Repository) CreateDatabaseKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, databaseKeyId string, key []byte, _ ...Option) (*DatabaseKeyVersion, error) {
const op = "kms.(Repository).CreateDatabaseKeyVersion"
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
if databaseKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing database key id")
}
if len(key) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
kv := AllocDatabaseKeyVersion()
id, err := newDatabaseKeyVersionId()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
kv.PrivateId = id
kv.RootKeyVersionId = rootKeyVersionId
kv.Key = key
kv.DatabaseKeyId = databaseKeyId
if err := kv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var returnedKey interface{}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedKey = kv.Clone()
// no oplog entries for root key version
if err := w.Create(ctx, returnedKey); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s database key id", kv.DatabaseKeyId)))
}
return returnedKey.(*DatabaseKeyVersion), nil
}
// LookupDatabaseKeyVersion will look up a key version in the repository. If
// the key version is not found, it will return nil, nil.
func (r *Repository) LookupDatabaseKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*DatabaseKeyVersion, error) {
const op = "kms.(Repository).LookupDatabaseKeyVersion"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
k := AllocDatabaseKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
if err := k.Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &k, nil
}
// DeleteDatabaseKeyVersion deletes the key version for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteDatabaseKeyVersion(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteDatabaseKeyVersion"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocDatabaseKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for the key version
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// LatestDatabaseKeyVersion searches for the key version with the highest
// version number. When no results are found, it returns nil with an
// errors.RecordNotFound error.
func (r *Repository) LatestDatabaseKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, databaseKeyId string, _ ...Option) (*DatabaseKeyVersion, error) {
const op = "kms.(Repository).LatestDatabaseKeyVersion"
if databaseKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing database key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var foundKeys []*DatabaseKeyVersion
if err := r.reader.SearchWhere(ctx, &foundKeys, "database_key_id = ?", []interface{}{databaseKeyId}, db.WithLimit(1), db.WithOrder("version desc")); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", databaseKeyId)))
}
if len(foundKeys) == 0 {
return nil, errors.E(ctx, errors.WithCode(errors.RecordNotFound), errors.WithOp(op))
}
if err := foundKeys[0].Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return foundKeys[0], nil
}
// ListDatabaseKeyVersions will lists versions of a key. Supports the WithLimit option.
func (r *Repository) ListDatabaseKeyVersions(ctx context.Context, rkvWrapper wrapping.Wrapper, databaseKeyId string, opt ...Option) ([]DekVersion, error) {
const op = "kms.(Repository).ListDatabaseKeyVersions"
if databaseKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing database key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var versions []*DatabaseKeyVersion
err := r.list(ctx, &versions, "database_key_id = ?", []interface{}{databaseKeyId}, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
for i, k := range versions {
if err := k.Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error decrypting key num %d", i)))
}
}
dekVersions := make([]DekVersion, 0, len(versions))
for _, version := range versions {
dekVersions = append(dekVersions, version)
}
return dekVersions, nil
}
@@ -1,367 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateDatabaseKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestDatabaseKey(t, conn, rk.PrivateId)
type args struct {
key []byte
databaseKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: []byte("test key"),
keyWrapper: rkvWrapper,
databaseKeyId: dk.PrivateId,
},
wantErr: false,
},
{
name: "invalid-rkv-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: wrapper,
databaseKeyId: dk.PrivateId,
},
wantErr: true,
},
{
name: "empty-key",
args: args{
key: nil,
keyWrapper: wrapper,
databaseKeyId: dk.PrivateId,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
k, err := repo.CreateDatabaseKeyVersion(context.Background(), tt.args.keyWrapper, tt.args.databaseKeyId, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(k)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(k.CreateTime)
foundKey, err := repo.LookupDatabaseKeyVersion(context.Background(), tt.args.keyWrapper, k.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, k))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, k.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteDatabaseKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestDatabaseKey(t, conn, rk.PrivateId)
type args struct {
key *kms.DatabaseKeyVersion
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestDatabaseKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("database key")),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.DatabaseKeyVersion {
k := kms.AllocDatabaseKeyVersion()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.DatabaseKeyVersion {
id, err := db.NewPublicId(kms.DatabaseKeyPrefix)
require.NoError(t, err)
k := kms.AllocDatabaseKeyVersion()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteDatabaseKeyVersion(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupDatabaseKeyVersion(context.Background(), wrapper, tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_LatestDatabaseKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestDatabaseKey(t, conn, rk.PrivateId)
tests := []struct {
name string
createCnt int
keyWrapper wrapping.Wrapper
wantVersion uint32
wantErr bool
wantIsError errors.Code
}{
{
name: "5",
createCnt: 5,
keyWrapper: rkvWrapper,
wantVersion: 5,
wantErr: false,
},
{
name: "1",
createCnt: 1,
keyWrapper: rkvWrapper,
wantVersion: 1,
wantErr: false,
},
{
name: "0",
createCnt: 0,
keyWrapper: rkvWrapper,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
{
name: "nil-wrapper",
createCnt: 5,
keyWrapper: nil,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocDatabaseKeyVersion(); return &i }(), "1=1")
testKeys := []*kms.DatabaseKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestDatabaseKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("test database key"))
testKeys = append(testKeys, k)
}
assert.Equal(tt.createCnt, len(testKeys))
got, err := repo.LatestDatabaseKeyVersion(context.Background(), tt.keyWrapper, dk.PrivateId)
if tt.wantErr {
require.Error(err)
assert.Nil(got)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
require.NotNil(got)
assert.Equal(tt.wantVersion, got.Version)
})
}
}
func TestRepository_ListDatabaseKeyVersions(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestDatabaseKey(t, conn, rk.PrivateId)
type args struct {
databaseKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
databaseKeyId: dk.PrivateId,
keyWrapper: rkvWrapper,
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1,
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
databaseKeyId: dk.PrivateId,
},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
databaseKeyId: dk.PrivateId,
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "bad-org",
createCnt: 1,
args: args{
keyWrapper: rkvWrapper,
databaseKeyId: "bad-id",
},
wantCnt: 0,
wantErr: false,
},
{
name: "nil-wrapper",
createCnt: 1,
args: args{
keyWrapper: nil,
databaseKeyId: dk.PrivateId,
},
wantCnt: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocDatabaseKeyVersion(); return &i }(), "1=1")
keyVersions := []*kms.DatabaseKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestDatabaseKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("database key"))
keyVersions = append(keyVersions, k)
}
assert.Equal(tt.createCnt, len(keyVersions))
got, err := repo.ListDatabaseKeyVersions(context.Background(), tt.args.keyWrapper, tt.args.databaseKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-162
View File
@@ -1,162 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateOidcKey inserts into the repository and returns the new oidc key and
// oidc key version. There are no valid options at this time.
func (r *Repository) CreateOidcKey(ctx context.Context, rkvWrapper wrapping.Wrapper, key []byte, _ ...Option) (*OidcKey, *OidcKeyVersion, error) {
const op = "kms.(Repository).CreateOidcKey"
var returnedDk, returnedDv interface{}
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
var err error
if returnedDk, returnedDv, err = createOidcKeyTx(ctx, reader, w, rkvWrapper, key); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
return returnedDk.(*OidcKey), returnedDv.(*OidcKeyVersion), nil
}
// createOidcKeyTx inserts into the db (via db.Writer) and returns the new oidc key
// and oidc key version. This function encapsulates all the work required within
// a db.TxHandler and allows this capability to be shared with the iam repo.
func createOidcKeyTx(ctx context.Context, r db.Reader, w db.Writer, rkvWrapper wrapping.Wrapper, key []byte) (*OidcKey, *OidcKeyVersion, error) {
const op = "kms.createOidcKeyTx"
if rkvWrapper == nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
if len(key) == 0 {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup root key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
rv := AllocRootKeyVersion()
rv.PrivateId = rootKeyVersionId
if err := r.LookupById(ctx, &rv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to lookup root key version %s", rootKeyVersionId)))
}
dk := AllocOidcKey()
dv := AllocOidcKeyVersion()
id, err := newOidcKeyId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
dk.PrivateId = id
dk.RootKeyId = rv.RootKeyId
id, err = newOidcKeyVersionId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
dv.PrivateId = id
dv.OidcKeyId = dk.PrivateId
dv.RootKeyVersionId = rootKeyVersionId
dv.Key = key
if err := dv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
// no oplog entries for keys
if err := w.Create(ctx, &dk); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("keys create"))
}
// no oplog entries for key versions
if err := w.Create(ctx, &dv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("key versions create"))
}
return &dk, &dv, nil
}
// LookupOidcKey will look up a key in the repository. If the key is not
// found, it will return nil, nil.
func (r *Repository) LookupOidcKey(ctx context.Context, privateId string, _ ...Option) (*OidcKey, error) {
const op = "kms.(Repository).LookupOidcKey"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocOidcKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return &k, nil
}
// DeleteOidcKey deletes the key for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteOidcKey(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteOidcKey"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocOidcKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for root keys
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// ListOidcKeys will list the keys. Supports the WithLimit option.
func (r *Repository) ListOidcKeys(ctx context.Context, opt ...Option) ([]Dek, error) {
const op = "kms.(Repository).ListOidcKeys"
var keys []*OidcKey
err := r.list(ctx, &keys, "1=1", nil, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
deks := make([]Dek, 0, len(keys))
for _, key := range keys {
deks = append(deks, key)
}
return deks, nil
}
-295
View File
@@ -1,295 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateOidcKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
type args struct {
scopeId string
key []byte
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid-org",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: rkvWrapper,
},
wantErr: false,
},
{
name: "nil-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
key: []byte(""),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-rkv-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: wrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "wrapper-missing-id",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: func() wrapping.Wrapper {
w := db.TestWrapper(t)
_, err = w.(*aead.Wrapper).SetConfig(context.Background())
require.NoError(t, err)
return w
}(),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
dk, dv, err := repo.CreateOidcKey(context.Background(), tt.args.keyWrapper, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(dk)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(dk.CreateTime)
foundKey, err := repo.LookupOidcKey(context.Background(), dk.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, dk))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, dk.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
assert.NotNil(dv.CreateTime)
foundKeyVersion, err := repo.LookupOidcKeyVersion(context.Background(), tt.args.keyWrapper, dv.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKeyVersion, dv))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, dv.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteOidcKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
key *kms.OidcKey
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestOidcKey(t, conn, rk.PrivateId),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.OidcKey {
k := kms.AllocOidcKey()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.OidcKey {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k := kms.AllocOidcKey()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteOidcKey(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupOidcKey(context.Background(), tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_ListOidcKeys(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
wrapper := db.TestWrapper(t)
type args struct {
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1, // org and project both have keys, plus global scope
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
}
for _, tt := range tests {
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
for i := 0; i < tt.createCnt; i++ {
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "scope_id in(?)", "global", org.PublicId, proj.PublicId)
rk := kms.TestRootKey(t, conn, proj.PublicId)
kms.TestOidcKey(t, conn, rk.PrivateId)
require.NoError(err)
}
got, err := repo.ListOidcKeys(context.Background(), tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-176
View File
@@ -1,176 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateOidcKeyVersion inserts into the repository and returns the new key
// version with its PrivateId. There are no valid options at this time.
func (r *Repository) CreateOidcKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, oidcKeyId string, key []byte, _ ...Option) (*OidcKeyVersion, error) {
const op = "kms.(Repository).CreateOidcKeyVersion"
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to fetch key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
if oidcKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing oidc key id")
}
if len(key) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
kv := AllocOidcKeyVersion()
id, err := newOidcKeyVersionId()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
kv.PrivateId = id
kv.RootKeyVersionId = rootKeyVersionId
kv.Key = key
kv.OidcKeyId = oidcKeyId
if err := kv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var returnedKey interface{}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedKey = kv.Clone()
// no oplog entries for root key version
if err := w.Create(ctx, returnedKey); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s oidc key id", kv.OidcKeyId)))
}
return returnedKey.(*OidcKeyVersion), nil
}
// LookupOidcKeyVersion will look up a key version in the repository. If
// the key version is not found, it will return nil, nil.
func (r *Repository) LookupOidcKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*OidcKeyVersion, error) {
const op = "kms.(Repository).LookupOidcKeyVersion"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
k := AllocOidcKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
if err := k.Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &k, nil
}
// DeleteOidcKeyVersion deletes the key version for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteOidcKeyVersion(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteOidcKeyVersion"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocOidcKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for the key version
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// LatestOidcKeyVersion searches for the key version with the highest
// version number. When no results are found, it returns nil with an
// errors.RecordNotFound error.
func (r *Repository) LatestOidcKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, OidcKeyId string, _ ...Option) (*OidcKeyVersion, error) {
const op = "kms.(Repository).LatestOidcKeyVersion"
if OidcKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing oidc key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var foundKeys []*OidcKeyVersion
if err := r.reader.SearchWhere(ctx, &foundKeys, "oidc_key_id = ?", []interface{}{OidcKeyId}, db.WithLimit(1), db.WithOrder("version desc")); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", OidcKeyId)))
}
if len(foundKeys) == 0 {
return nil, errors.E(ctx, errors.WithCode(errors.RecordNotFound), errors.WithOp(op))
}
if err := foundKeys[0].Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return foundKeys[0], nil
}
// ListOidcKeyVersions will lists versions of a key. Supports the WithLimit option.
func (r *Repository) ListOidcKeyVersions(ctx context.Context, rkvWrapper wrapping.Wrapper, OidcKeyId string, opt ...Option) ([]DekVersion, error) {
const op = "kms.(Repository).ListOidcKeyVersions"
if OidcKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing oidc key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var versions []*OidcKeyVersion
err := r.list(ctx, &versions, "oidc_key_id = ?", []interface{}{OidcKeyId}, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
for i, k := range versions {
if err := k.Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error decrypting key num %d", i)))
}
}
dekVersions := make([]DekVersion, 0, len(versions))
for _, version := range versions {
dekVersions = append(dekVersions, version)
}
return dekVersions, nil
}
@@ -1,367 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateOidcKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOidcKey(t, conn, rk.PrivateId)
type args struct {
key []byte
OidcKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: []byte("test key"),
keyWrapper: rkvWrapper,
OidcKeyId: dk.PrivateId,
},
wantErr: false,
},
{
name: "invalid-rkv-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: wrapper,
OidcKeyId: dk.PrivateId,
},
wantErr: true,
},
{
name: "empty-key",
args: args{
key: nil,
keyWrapper: wrapper,
OidcKeyId: dk.PrivateId,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
k, err := repo.CreateOidcKeyVersion(context.Background(), tt.args.keyWrapper, tt.args.OidcKeyId, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(k)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(k.CreateTime)
foundKey, err := repo.LookupOidcKeyVersion(context.Background(), tt.args.keyWrapper, k.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, k))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, k.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteOidcKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOidcKey(t, conn, rk.PrivateId)
type args struct {
key *kms.OidcKeyVersion
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestOidcKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("oidc key")),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.OidcKeyVersion {
k := kms.AllocOidcKeyVersion()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.OidcKeyVersion {
id, err := db.NewPublicId(kms.OidcKeyPrefix)
require.NoError(t, err)
k := kms.AllocOidcKeyVersion()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteOidcKeyVersion(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupOidcKeyVersion(context.Background(), wrapper, tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_LatestOidcKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOidcKey(t, conn, rk.PrivateId)
tests := []struct {
name string
createCnt int
keyWrapper wrapping.Wrapper
wantVersion uint32
wantErr bool
wantIsError errors.Code
}{
{
name: "5",
createCnt: 5,
keyWrapper: rkvWrapper,
wantVersion: 5,
wantErr: false,
},
{
name: "1",
createCnt: 1,
keyWrapper: rkvWrapper,
wantVersion: 1,
wantErr: false,
},
{
name: "0",
createCnt: 0,
keyWrapper: rkvWrapper,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
{
name: "nil-wrapper",
createCnt: 5,
keyWrapper: nil,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOidcKeyVersion(); return &i }(), "1=1")
testKeys := []*kms.OidcKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestOidcKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("test oidc key"))
testKeys = append(testKeys, k)
}
assert.Equal(tt.createCnt, len(testKeys))
got, err := repo.LatestOidcKeyVersion(context.Background(), tt.keyWrapper, dk.PrivateId)
if tt.wantErr {
require.Error(err)
assert.Nil(got)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
require.NotNil(got)
assert.Equal(tt.wantVersion, got.Version)
})
}
}
func TestRepository_ListOidcKeyVersions(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOidcKey(t, conn, rk.PrivateId)
type args struct {
OidcKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
OidcKeyId: dk.PrivateId,
keyWrapper: rkvWrapper,
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1,
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
OidcKeyId: dk.PrivateId,
},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
OidcKeyId: dk.PrivateId,
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "bad-org",
createCnt: 1,
args: args{
keyWrapper: rkvWrapper,
OidcKeyId: "bad-id",
},
wantCnt: 0,
wantErr: false,
},
{
name: "nil-wrapper",
createCnt: 1,
args: args{
keyWrapper: nil,
OidcKeyId: dk.PrivateId,
},
wantCnt: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOidcKeyVersion(); return &i }(), "1=1")
keyVersions := []*kms.OidcKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestOidcKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("oidc key"))
keyVersions = append(keyVersions, k)
}
assert.Equal(tt.createCnt, len(keyVersions))
got, err := repo.ListOidcKeyVersions(context.Background(), tt.args.keyWrapper, tt.args.OidcKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-162
View File
@@ -1,162 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateOplogKey inserts into the repository and returns the new key and the
// key version. There are no valid options at this time.
func (r *Repository) CreateOplogKey(ctx context.Context, rkvWrapper wrapping.Wrapper, key []byte, _ ...Option) (*OplogKey, *OplogKeyVersion, error) {
const op = "kms.(Repository).CreateOplogKey"
var returnedDk, returnedDv interface{}
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
var err error
if returnedDk, returnedDv, err = createOplogKeyTx(ctx, reader, w, rkvWrapper, key); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
return returnedDk.(*OplogKey), returnedDv.(*OplogKeyVersion), nil
}
// createOplogKeyTx inserts into the db (via db.Writer) and returns the new key
// and the key version. This function encapsulates all the work required within
// a db.TxHandler and allows this capability to be shared with the iam repo.
func createOplogKeyTx(ctx context.Context, r db.Reader, w db.Writer, rkvWrapper wrapping.Wrapper, key []byte) (*OplogKey, *OplogKeyVersion, error) {
const op = "kms.createOplogKeyTx"
if rkvWrapper == nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
if len(key) == 0 {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup root key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
rv := AllocRootKeyVersion()
rv.PrivateId = rootKeyVersionId
if err := r.LookupById(ctx, &rv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to lookup root key version %s", rootKeyVersionId)))
}
opk := AllocOplogKey()
opv := AllocOplogKeyVersion()
id, err := newOplogKeyId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
opk.PrivateId = id
opk.RootKeyId = rv.RootKeyId
id, err = newOplogKeyVersionId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
opv.PrivateId = id
opv.OplogKeyId = opk.PrivateId
opv.RootKeyVersionId = rootKeyVersionId
opv.Key = key
if err := opv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
// no oplog entries for keys
if err := w.Create(ctx, &opk); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("key create"))
}
// no oplog entries for key versions
if err := w.Create(ctx, &opv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("key versions create"))
}
return &opk, &opv, nil
}
// LookupOplogKey will look up a key in the repository. If the key is not
// found, it will return nil, nil.
func (r *Repository) LookupOplogKey(ctx context.Context, privateId string, _ ...Option) (*OplogKey, error) {
const op = "kms.(Repository).LookupOplogKey"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocOplogKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return &k, nil
}
// DeleteOplogKey deletes the key for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteOplogKey(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteOplogKey"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocOplogKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for root keys
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// ListOplogKeys will list the keys. Supports the WithLimit option.
func (r *Repository) ListOplogKeys(ctx context.Context, opt ...Option) ([]Dek, error) {
const op = "kms.(Repository).ListOplogKeys"
var keys []*OplogKey
err := r.list(ctx, &keys, "1=1", nil, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
deks := make([]Dek, 0, len(keys))
for _, key := range keys {
deks = append(deks, key)
}
return deks, nil
}
-295
View File
@@ -1,295 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateOplogKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
type args struct {
scopeId string
key []byte
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid-org",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: rkvWrapper,
},
wantErr: false,
},
{
name: "nil-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
key: []byte(""),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-rkv-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: wrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "wrapper-missing-id",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: func() wrapping.Wrapper {
w := db.TestWrapper(t)
_, err = w.(*aead.Wrapper).SetConfig(context.Background())
require.NoError(t, err)
return w
}(),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
opk, opv, err := repo.CreateOplogKey(context.Background(), tt.args.keyWrapper, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(opk)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(opk.CreateTime)
foundKey, err := repo.LookupOplogKey(context.Background(), opk.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, opk))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, opk.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
assert.NotNil(opv.CreateTime)
foundKeyVersion, err := repo.LookupOplogKeyVersion(context.Background(), tt.args.keyWrapper, opv.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKeyVersion, opv))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, opv.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteOplogKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
key *kms.OplogKey
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestOplogKey(t, conn, rk.PrivateId),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.OplogKey {
k := kms.AllocOplogKey()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.OplogKey {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k := kms.AllocOplogKey()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteOplogKey(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupOplogKey(context.Background(), tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_ListOplogKeys(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
wrapper := db.TestWrapper(t)
type args struct {
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1, // org and project both have keys, plus global scope
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
}
for _, tt := range tests {
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
for i := 0; i < tt.createCnt; i++ {
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "scope_id in(?)", "global", org.PublicId, proj.PublicId)
rk := kms.TestRootKey(t, conn, proj.PublicId)
kms.TestOplogKey(t, conn, rk.PrivateId)
require.NoError(err)
}
got, err := repo.ListOplogKeys(context.Background(), tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
@@ -1,176 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateOplogKeyVersion inserts into the repository and returns the new key
// version with its PrivateId. There are no valid options at this time.
func (r *Repository) CreateOplogKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, oplogKeyId string, key []byte, _ ...Option) (*OplogKeyVersion, error) {
const op = "kms.(Repository).CreateOplogKeyVersion"
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to fetch key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
if oplogKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing oplog key id")
}
if len(key) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
kv := AllocOplogKeyVersion()
id, err := newOplogKeyVersionId()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
kv.PrivateId = id
kv.RootKeyVersionId = rootKeyVersionId
kv.Key = key
kv.OplogKeyId = oplogKeyId
if err := kv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var returnedKey interface{}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedKey = kv.Clone()
// no oplog entries for root key version
if err := w.Create(ctx, returnedKey); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s oplog key id", kv.OplogKeyId)))
}
return returnedKey.(*OplogKeyVersion), nil
}
// LookupOplogKeyVersion will look up a key version in the repository. If
// the key version is not found, it will return nil, nil.
func (r *Repository) LookupOplogKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*OplogKeyVersion, error) {
const op = "kms.(Repository).LookupOplogKeyVersion"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
k := AllocOplogKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
if err := k.Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &k, nil
}
// DeleteOplogKeyVersion deletes the key version for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteOplogKeyVersion(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteOplogKeyVersion"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocOplogKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for the key version
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// LatestOplogKeyVersion searches for the key version with the highest
// version number. When no results are found, it returns nil with an
// errors.RecordNotFound error.
func (r *Repository) LatestOplogKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, oplogKeyId string, _ ...Option) (*OplogKeyVersion, error) {
const op = "kms.(Repository).LatestOplogKeyVersion"
if oplogKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing oplog key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var foundKeys []*OplogKeyVersion
if err := r.reader.SearchWhere(ctx, &foundKeys, "oplog_key_id = ?", []interface{}{oplogKeyId}, db.WithLimit(1), db.WithOrder("version desc")); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", oplogKeyId)))
}
if len(foundKeys) == 0 {
return nil, errors.E(ctx, errors.WithCode(errors.RecordNotFound), errors.WithOp(op))
}
if err := foundKeys[0].Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return foundKeys[0], nil
}
// ListOplogKeyVersions will lists versions of a key. Supports the WithLimit option.
func (r *Repository) ListOplogKeyVersions(ctx context.Context, rkvWrapper wrapping.Wrapper, oplogKeyId string, opt ...Option) ([]DekVersion, error) {
const op = "kms.(Repository).ListOplogKeyVersions"
if oplogKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing oplog key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var versions []*OplogKeyVersion
err := r.list(ctx, &versions, "oplog_key_id = ?", []interface{}{oplogKeyId}, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
for i, k := range versions {
if err := k.Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error decrypting key num %d", i)))
}
}
dekVersions := make([]DekVersion, 0, len(versions))
for _, version := range versions {
dekVersions = append(dekVersions, version)
}
return dekVersions, nil
}
@@ -1,367 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateOplogKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOplogKey(t, conn, rk.PrivateId)
type args struct {
key []byte
oplogKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: []byte("test key"),
keyWrapper: rkvWrapper,
oplogKeyId: dk.PrivateId,
},
wantErr: false,
},
{
name: "invalid-rkv-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: wrapper,
oplogKeyId: dk.PrivateId,
},
wantErr: true,
},
{
name: "empty-key",
args: args{
key: nil,
keyWrapper: wrapper,
oplogKeyId: dk.PrivateId,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
k, err := repo.CreateOplogKeyVersion(context.Background(), tt.args.keyWrapper, tt.args.oplogKeyId, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(k)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(k.CreateTime)
foundKey, err := repo.LookupOplogKeyVersion(context.Background(), tt.args.keyWrapper, k.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, k))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, k.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteOplogKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOplogKey(t, conn, rk.PrivateId)
type args struct {
key *kms.OplogKeyVersion
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestOplogKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("oplog key")),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.OplogKeyVersion {
k := kms.AllocOplogKeyVersion()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.OplogKeyVersion {
id, err := db.NewPublicId(kms.OplogKeyPrefix)
require.NoError(t, err)
k := kms.AllocOplogKeyVersion()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteOplogKeyVersion(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupOplogKeyVersion(context.Background(), wrapper, tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_LatestOplogKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOplogKey(t, conn, rk.PrivateId)
tests := []struct {
name string
createCnt int
keyWrapper wrapping.Wrapper
wantVersion uint32
wantErr bool
wantIsError errors.Code
}{
{
name: "5",
createCnt: 5,
keyWrapper: rkvWrapper,
wantVersion: 5,
wantErr: false,
},
{
name: "1",
createCnt: 1,
keyWrapper: rkvWrapper,
wantVersion: 1,
wantErr: false,
},
{
name: "0",
createCnt: 0,
keyWrapper: rkvWrapper,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
{
name: "nil-wrapper",
createCnt: 5,
keyWrapper: nil,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOplogKeyVersion(); ; return &i }(), "1=1")
testKeys := []*kms.OplogKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestOplogKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("test oplog key"))
testKeys = append(testKeys, k)
}
assert.Equal(tt.createCnt, len(testKeys))
got, err := repo.LatestOplogKeyVersion(context.Background(), tt.keyWrapper, dk.PrivateId)
if tt.wantErr {
require.Error(err)
assert.Nil(got)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
require.NotNil(got)
assert.Equal(tt.wantVersion, got.Version)
})
}
}
func TestRepository_ListOplogKeyVersions(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestOplogKey(t, conn, rk.PrivateId)
type args struct {
oplogKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
oplogKeyId: dk.PrivateId,
keyWrapper: rkvWrapper,
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1,
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
oplogKeyId: dk.PrivateId,
},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
oplogKeyId: dk.PrivateId,
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "bad-org",
createCnt: 1,
args: args{
keyWrapper: rkvWrapper,
oplogKeyId: "bad-id",
},
wantCnt: 0,
wantErr: false,
},
{
name: "nil-wrapper",
createCnt: 1,
args: args{
keyWrapper: nil,
oplogKeyId: dk.PrivateId,
},
wantCnt: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocOplogKeyVersion(); return &i }(), "1=1")
keyVersions := []*kms.OplogKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestOplogKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("oplog key"))
keyVersions = append(keyVersions, k)
}
assert.Equal(tt.createCnt, len(keyVersions))
got, err := repo.ListOplogKeyVersions(context.Background(), tt.args.keyWrapper, tt.args.oplogKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-146
View File
@@ -1,146 +0,0 @@
package kms
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateRootKey inserts into the repository and returns the new root key and
// root key version. There are no valid options at this time.
func (r *Repository) CreateRootKey(ctx context.Context, keyWrapper wrapping.Wrapper, scopeId string, key []byte, _ ...Option) (*RootKey, *RootKeyVersion, error) {
const op = "kms.(Repository).CreateRootKey"
var returnedRk, returnedKv interface{}
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
var err error
if returnedRk, returnedKv, err = createRootKeyTx(ctx, w, keyWrapper, scopeId, key); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", scopeId)))
}
return returnedRk.(*RootKey), returnedKv.(*RootKeyVersion), nil
}
// createRootKeyTx inserts into the db (via db.Writer) and returns the new root key
// and root key version. This function encapsulates all the work required within
// a db.TxHandler and allows this capability to be shared with the iam repo.
func createRootKeyTx(ctx context.Context, w db.Writer, keyWrapper wrapping.Wrapper, scopeId string, key []byte) (*RootKey, *RootKeyVersion, error) {
const op = "kms.createRootKeyTx"
if scopeId == "" {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
}
if keyWrapper == nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
if len(key) == 0 {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
rk := AllocRootKey()
kv := AllocRootKeyVersion()
id, err := newRootKeyId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
rk.PrivateId = id
rk.ScopeId = scopeId
id, err = newRootKeyVersionId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
kv.PrivateId = id
kv.RootKeyId = rk.PrivateId
kv.Key = key
if err := kv.Encrypt(ctx, keyWrapper); err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
// no oplog entries for root keys
if err := w.Create(ctx, &rk); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("root keys"))
}
// no oplog entries for root key versions
if err := w.Create(ctx, &kv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("key versions"))
}
return &rk, &kv, nil
}
// LookupRootKey will look up a root key in the repository. If the key is not
// found, it will return nil, nil.
func (r *Repository) LookupRootKey(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*RootKey, error) {
const op = "kms.(Repository).LookupRootKey"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
k := AllocRootKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return &k, nil
}
// DeleteRootKey deletes the root key for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteRootKey(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteRootKey"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocRootKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for root keys
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// ListRootKeys will list the root keys. Supports the WithLimit option.
func (r *Repository) ListRootKeys(ctx context.Context, opt ...Option) ([]*RootKey, error) {
const op = "kms.(Repository).ListRootKeys"
var keys []*RootKey
err := r.list(ctx, &keys, "1=1", nil, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return keys, nil
}
-270
View File
@@ -1,270 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateRootKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
type args struct {
scopeId string
key []byte
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid-org",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: wrapper,
},
wantErr: false,
},
{
name: "valid-global",
args: args{
scopeId: "global",
key: []byte("valid-global"),
keyWrapper: wrapper,
},
wantErr: false,
},
{
name: "invalid-scope",
args: args{
scopeId: "o_notAValidScopeId",
key: []byte("invalid-scope"),
keyWrapper: wrapper,
},
wantErr: true,
},
{
name: "empty-scope",
args: args{
key: []byte("empty-scope"),
keyWrapper: wrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
rk, kv, err := repo.CreateRootKey(context.Background(), tt.args.keyWrapper, tt.args.scopeId, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(rk)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(rk.CreateTime)
foundKey, err := repo.LookupRootKey(context.Background(), tt.args.keyWrapper, rk.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, rk))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, rk.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
assert.NotNil(kv.CreateTime)
foundKeyVersion, err := repo.LookupRootKeyVersion(context.Background(), tt.args.keyWrapper, kv.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKeyVersion, kv))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, kv.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteRootKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
type args struct {
key *kms.RootKey
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestRootKey(t, conn, org.PublicId),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.RootKey {
k := kms.AllocRootKey()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.RootKey {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k := kms.AllocRootKey()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteRootKey(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupRootKey(context.Background(), wrapper, tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_ListRootKeys(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
wrapper := db.TestWrapper(t)
type args struct {
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: (repo.DefaultLimit()+1)*2 + 1, // org and project both have keys, plus global scope
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
}
for _, tt := range tests {
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
for i := 0; i < tt.createCnt; i++ {
_, _ = iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
}
got, err := repo.ListRootKeys(context.Background(), tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-160
View File
@@ -1,160 +0,0 @@
package kms
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateRootKeyVersion inserts into the repository and returns the new root key
// version with its PrivateId. There are no valid options at this time.
func (r *Repository) CreateRootKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, rootKeyId string, key []byte, _ ...Option) (*RootKeyVersion, error) {
const op = "kms.(Repository).CreateRootKeyVersion"
if rootKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
if len(key) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
kv := AllocRootKeyVersion()
id, err := newRootKeyVersionId()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
kv.PrivateId = id
kv.RootKeyId = rootKeyId
kv.Key = key
if err := kv.Encrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var returnedKey interface{}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedKey = kv.Clone()
// no oplog entries for root key version
if err := w.Create(ctx, returnedKey); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s root key id", kv.RootKeyId)))
}
return returnedKey.(*RootKeyVersion), nil
}
// LookupRootKeyVersion will look up a root key version in the repository. If
// the key version is not found, it will return nil, nil.
func (r *Repository) LookupRootKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*RootKeyVersion, error) {
const op = "kms.(Repository).LookupRootKeyVersion"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
k := AllocRootKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
if err := k.Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &k, nil
}
// DeleteRootKeyVersion deletes the root key version for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteRootKeyVersion(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteRootKeyVersion"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocRootKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no oplog entries for root key version
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// LatestRootKeyVersion searches for the root key version with the highest
// version number. When no results are found, it returns nil with an
// errors.RecordNotFound error.
func (r *Repository) LatestRootKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, rootKeyId string, _ ...Option) (*RootKeyVersion, error) {
const op = "kms.(Repository).LatestRootKeyVersion"
if rootKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
var foundKeys []RootKeyVersion
if err := r.reader.SearchWhere(ctx, &foundKeys, "root_key_id = ?", []interface{}{rootKeyId}, db.WithLimit(1), db.WithOrder("version desc")); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", rootKeyId)))
}
if len(foundKeys) == 0 {
return nil, errors.E(ctx, errors.WithCode(errors.RecordNotFound), errors.WithOp(op))
}
if err := foundKeys[0].Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &foundKeys[0], nil
}
// ListRootKeyVersions in versions of a root key. Supports the WithLimit option.
func (r *Repository) ListRootKeyVersions(ctx context.Context, keyWrapper wrapping.Wrapper, rootKeyId string, opt ...Option) ([]*RootKeyVersion, error) {
const op = "kms.(Repository).ListRootKeyVersions"
if rootKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
var versions []*RootKeyVersion
err := r.list(ctx, &versions, "root_key_id = ?", []interface{}{rootKeyId}, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
for i, k := range versions {
if err := k.Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error decrypting key num %d", i)))
}
}
return versions, nil
}
@@ -1,363 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateRootKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootKeyId string
key []byte
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: []byte("test key"),
rootKeyId: rk.PrivateId,
keyWrapper: wrapper,
},
wantErr: false,
},
{
name: "invalid-root-key",
args: args{
key: []byte("test key"),
rootKeyId: "krk_thisIsNotValid",
keyWrapper: wrapper,
},
wantErr: true,
},
{
name: "empty-key",
args: args{
key: nil,
rootKeyId: rk.PrivateId,
keyWrapper: wrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
key: []byte("test key"),
rootKeyId: rk.PrivateId,
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
k, err := repo.CreateRootKeyVersion(context.Background(), tt.args.keyWrapper, tt.args.rootKeyId, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(k)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(k.CreateTime)
foundKey, err := repo.LookupRootKeyVersion(context.Background(), tt.args.keyWrapper, k.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, k))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, k.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteRootKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
key *kms.RootKeyVersion
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: func() *kms.RootKeyVersion {
k, _ := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
return k
}(),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.RootKeyVersion {
k := kms.AllocRootKeyVersion()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.RootKeyVersion {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k := kms.AllocRootKeyVersion()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteRootKeyVersion(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupRootKeyVersion(context.Background(), wrapper, tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_LatestRootKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
tests := []struct {
name string
createCnt int
keyWrapper wrapping.Wrapper
wantVersion uint32
wantErr bool
wantIsError errors.Code
}{
{
name: "5",
createCnt: 5,
keyWrapper: wrapper,
wantVersion: 5,
wantErr: false,
},
{
name: "1",
createCnt: 1,
keyWrapper: wrapper,
wantVersion: 1,
wantErr: false,
},
{
name: "0",
createCnt: 0,
keyWrapper: wrapper,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
{
name: "nil-wrapper",
createCnt: 5,
keyWrapper: nil,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKeyVersion(); return &i }(), "1=1")
testKeys := []*kms.RootKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k, _ := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
testKeys = append(testKeys, k)
}
assert.Equal(tt.createCnt, len(testKeys))
got, err := repo.LatestRootKeyVersion(context.Background(), tt.keyWrapper, rk.PrivateId)
if tt.wantErr {
require.Error(err)
assert.Nil(got)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
require.NotNil(got)
assert.Equal(tt.wantVersion, got.Version)
})
}
}
func TestRepository_ListRootKeyVersions(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
rootKeyId: rk.PrivateId,
keyWrapper: wrapper,
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1,
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: wrapper,
rootKeyId: rk.PrivateId,
},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: wrapper,
rootKeyId: rk.PrivateId,
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "bad-org",
createCnt: 1,
args: args{
keyWrapper: wrapper,
rootKeyId: "bad-id",
},
wantCnt: 0,
wantErr: false,
},
{
name: "nil-wrapper",
createCnt: 1,
args: args{
keyWrapper: nil,
rootKeyId: rk.PrivateId,
},
wantCnt: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKeyVersion(); return &i }(), "1=1")
testRootKeyVersions := []*kms.RootKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k, _ := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
testRootKeyVersions = append(testRootKeyVersions, k)
}
assert.Equal(tt.createCnt, len(testRootKeyVersions))
got, err := repo.ListRootKeyVersions(context.Background(), tt.args.keyWrapper, tt.args.rootKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-162
View File
@@ -1,162 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateSessionKey inserts into the repository and returns the new key and the
// key version. There are no valid options at this time.
func (r *Repository) CreateSessionKey(ctx context.Context, rkvWrapper wrapping.Wrapper, key []byte, _ ...Option) (*SessionKey, *SessionKeyVersion, error) {
const op = "kms.(Repository).CreateSessionKey"
var returnedDk, returnedDv interface{}
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
var err error
if returnedDk, returnedDv, err = createSessionKeyTx(ctx, reader, w, rkvWrapper, key); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
return returnedDk.(*SessionKey), returnedDv.(*SessionKeyVersion), nil
}
// createSessionKeyTx inserts into the db (via db.Writer) and returns the new key
// and the key version. This function encapsulates all the work required within
// a db.TxHandler and allows this capability to be shared with the iam repo.
func createSessionKeyTx(ctx context.Context, r db.Reader, w db.Writer, rkvWrapper wrapping.Wrapper, key []byte) (*SessionKey, *SessionKeyVersion, error) {
const op = "kms.createSessionKeyTx"
if rkvWrapper == nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
if len(key) == 0 {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup root key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
rv := AllocRootKeyVersion()
rv.PrivateId = rootKeyVersionId
if err := r.LookupById(ctx, &rv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to lookup root key version %s", rootKeyVersionId)))
}
tk := AllocSessionKey()
tv := AllocSessionKeyVersion()
id, err := newSessionKeyId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
tk.PrivateId = id
tk.RootKeyId = rv.RootKeyId
id, err = newSessionKeyVersionId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
tv.PrivateId = id
tv.SessionKeyId = tk.PrivateId
tv.RootKeyVersionId = rootKeyVersionId
tv.Key = key
if err := tv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
// no session entries for keys
if err := w.Create(ctx, &tk); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("keys create"))
}
// no session entries for key versions
if err := w.Create(ctx, &tv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("key versions create"))
}
return &tk, &tv, nil
}
// LookupSessionKey will look up a key in the repository. If the key is not
// found, it will return nil, nil.
func (r *Repository) LookupSessionKey(ctx context.Context, privateId string, _ ...Option) (*SessionKey, error) {
const op = "kms.(Repository).LookupSessionKey"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocSessionKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return &k, nil
}
// DeleteSessionKey deletes the key for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteSessionKey(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteSessionKey"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocSessionKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no session entries for root keys
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// ListSessionKeys will list the keys. Supports the WithLimit option.
func (r *Repository) ListSessionKeys(ctx context.Context, opt ...Option) ([]Dek, error) {
const op = "kms.(Repository).ListSessionKeys"
var keys []*SessionKey
err := r.list(ctx, &keys, "1=1", nil, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
deks := make([]Dek, 0, len(keys))
for _, key := range keys {
deks = append(deks, key)
}
return deks, nil
}
-296
View File
@@ -1,296 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateSessionKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
type args struct {
scopeId string
key []byte
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid-org",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: rkvWrapper,
},
wantErr: false,
},
{
name: "nil-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
key: []byte(""),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-rkv-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: wrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "wrapper-missing-id",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: func() wrapping.Wrapper {
w := db.TestWrapper(t)
_, err = w.(*aead.Wrapper).SetConfig(context.Background())
require.NoError(t, err)
return w
}(),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tk, tv, err := repo.CreateSessionKey(context.Background(), tt.args.keyWrapper, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(tk)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(tk.CreateTime)
foundKey, err := repo.LookupSessionKey(context.Background(), tk.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, tk))
// make sure there was no session written
err = db.TestVerifyOplog(t, rw, tk.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
assert.NotNil(tv.CreateTime)
foundKeyVersion, err := repo.LookupSessionKeyVersion(context.Background(), tt.args.keyWrapper, tv.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKeyVersion, tv))
// make sure there was no session written
err = db.TestVerifyOplog(t, rw, tv.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteSessionKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
key *kms.SessionKey
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestSessionKey(t, conn, rk.PrivateId),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.SessionKey {
k := kms.AllocSessionKey()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.SessionKey {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k := kms.AllocSessionKey()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteSessionKey(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
// make sure there was no session written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupSessionKey(context.Background(), tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no session written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_ListSessionKeys(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
wrapper := db.TestWrapper(t)
type args struct {
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1, // org and project both have keys, plus global scope
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
}
for _, tt := range tests {
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
for i := 0; i < tt.createCnt; i++ {
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "scope_id in(?)", "global", org.PublicId, proj.PublicId)
rk := kms.TestRootKey(t, conn, proj.PublicId)
kms.TestSessionKey(t, conn, rk.PrivateId)
require.NoError(err)
}
got, err := repo.ListSessionKeys(context.Background(), tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
@@ -1,176 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateSessionKeyVersion inserts into the repository and returns the new key
// version with its PrivateId. There are no valid options at this time.
func (r *Repository) CreateSessionKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, sessionKeyId string, key []byte, _ ...Option) (*SessionKeyVersion, error) {
const op = "kms.(Repository).CreateSessionKeyVersion"
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to fetch key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
if sessionKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing session key id")
}
if len(key) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
kv := AllocSessionKeyVersion()
id, err := newSessionKeyVersionId()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
kv.PrivateId = id
kv.RootKeyVersionId = rootKeyVersionId
kv.Key = key
kv.SessionKeyId = sessionKeyId
if err := kv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var returnedKey interface{}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedKey = kv.Clone()
// no session entries for root key version
if err := w.Create(ctx, returnedKey); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s session key id", kv.SessionKeyId)))
}
return returnedKey.(*SessionKeyVersion), nil
}
// LookupSessionKeyVersion will look up a key version in the repository. If
// the key version is not found, it will return nil, nil.
func (r *Repository) LookupSessionKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*SessionKeyVersion, error) {
const op = "kms.(Repository).LookupSessionKeyVersion"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
k := AllocSessionKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
if err := k.Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &k, nil
}
// DeleteSessionKeyVersion deletes the key version for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteSessionKeyVersion(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteSessionKeyVersion"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocSessionKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no session entries for the key version
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// LatestSessionKeyVersion searches for the key version with the highest
// version number. When no results are found, it returns nil with an
// errors.RecordNotFound error.
func (r *Repository) LatestSessionKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, sessionKeyId string, _ ...Option) (*SessionKeyVersion, error) {
const op = "kms.(Repository).LatestSessionKeyVersion"
if sessionKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing session key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var foundKeys []*SessionKeyVersion
if err := r.reader.SearchWhere(ctx, &foundKeys, "session_key_id = ?", []interface{}{sessionKeyId}, db.WithLimit(1), db.WithOrder("version desc")); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", sessionKeyId)))
}
if len(foundKeys) == 0 {
return nil, errors.E(ctx, errors.WithCode(errors.RecordNotFound), errors.WithOp(op))
}
if err := foundKeys[0].Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return foundKeys[0], nil
}
// ListSessionKeyVersions will lists versions of a key. Supports the WithLimit option.
func (r *Repository) ListSessionKeyVersions(ctx context.Context, rkvWrapper wrapping.Wrapper, sessionKeyId string, opt ...Option) ([]DekVersion, error) {
const op = "kms.(Repository).ListSessionKeyVersions"
if sessionKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing session key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var versions []*SessionKeyVersion
err := r.list(ctx, &versions, "session_key_id = ?", []interface{}{sessionKeyId}, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
for i, k := range versions {
if err := k.Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error decrypting key num %d", i)))
}
}
dekVersions := make([]DekVersion, 0, len(versions))
for _, version := range versions {
dekVersions = append(dekVersions, version)
}
return dekVersions, nil
}
@@ -1,367 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateSessionKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
sk := kms.TestSessionKey(t, conn, rk.PrivateId)
type args struct {
key []byte
sessionKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: []byte("test key"),
keyWrapper: rkvWrapper,
sessionKeyId: sk.PrivateId,
},
wantErr: false,
},
{
name: "invalid-rkv-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: wrapper,
sessionKeyId: sk.PrivateId,
},
wantErr: true,
},
{
name: "empty-key",
args: args{
key: nil,
keyWrapper: wrapper,
sessionKeyId: sk.PrivateId,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
k, err := repo.CreateSessionKeyVersion(context.Background(), tt.args.keyWrapper, tt.args.sessionKeyId, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(k)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(k.CreateTime)
foundKey, err := repo.LookupSessionKeyVersion(context.Background(), tt.args.keyWrapper, k.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, k))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, k.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteSessionKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
sk := kms.TestSessionKey(t, conn, rk.PrivateId)
type args struct {
key *kms.SessionKeyVersion
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestSessionKeyVersion(t, conn, rkvWrapper, sk.PrivateId, []byte("session key")),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.SessionKeyVersion {
k := kms.AllocSessionKeyVersion()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.SessionKeyVersion {
id, err := db.NewPublicId(kms.SessionKeyPrefix)
require.NoError(t, err)
k := kms.AllocSessionKeyVersion()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteSessionKeyVersion(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupSessionKeyVersion(context.Background(), wrapper, tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_LatestSessionKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
sk := kms.TestSessionKey(t, conn, rk.PrivateId)
tests := []struct {
name string
createCnt int
keyWrapper wrapping.Wrapper
wantVersion uint32
wantErr bool
wantIsError errors.Code
}{
{
name: "5",
createCnt: 5,
keyWrapper: rkvWrapper,
wantVersion: 5,
wantErr: false,
},
{
name: "1",
createCnt: 1,
keyWrapper: rkvWrapper,
wantVersion: 1,
wantErr: false,
},
{
name: "0",
createCnt: 0,
keyWrapper: rkvWrapper,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
{
name: "nil-wrapper",
createCnt: 5,
keyWrapper: nil,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocSessionKeyVersion(); return &i }(), "1=1")
testKeys := []*kms.SessionKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestSessionKeyVersion(t, conn, rkvWrapper, sk.PrivateId, []byte("test key"))
testKeys = append(testKeys, k)
}
assert.Equal(tt.createCnt, len(testKeys))
got, err := repo.LatestSessionKeyVersion(context.Background(), tt.keyWrapper, sk.PrivateId)
if tt.wantErr {
require.Error(err)
assert.Nil(got)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
require.NotNil(got)
assert.Equal(tt.wantVersion, got.Version)
})
}
}
func TestRepository_ListSessionKeyVersions(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
sk := kms.TestSessionKey(t, conn, rk.PrivateId)
type args struct {
sessionKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
sessionKeyId: sk.PrivateId,
keyWrapper: rkvWrapper,
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1,
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
sessionKeyId: sk.PrivateId,
},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
sessionKeyId: sk.PrivateId,
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "bad-org",
createCnt: 1,
args: args{
keyWrapper: rkvWrapper,
sessionKeyId: "bad-id",
},
wantCnt: 0,
wantErr: false,
},
{
name: "nil-wrapper",
createCnt: 1,
args: args{
keyWrapper: nil,
sessionKeyId: sk.PrivateId,
},
wantCnt: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocSessionKeyVersion(); return &i }(), "1=1")
keyVersions := []*kms.SessionKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestSessionKeyVersion(t, conn, rkvWrapper, sk.PrivateId, []byte("session key"))
keyVersions = append(keyVersions, k)
}
assert.Equal(tt.createCnt, len(keyVersions))
got, err := repo.ListSessionKeyVersions(context.Background(), tt.args.keyWrapper, tt.args.sessionKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-288
View File
@@ -1,288 +0,0 @@
package kms_test
import (
"context"
"crypto/rand"
"io"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/types/scope"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewRepository(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
type args struct {
r db.Reader
w db.Writer
}
tests := []struct {
name string
args args
want *kms.Repository
wantErr bool
wantErrString string
}{
{
name: "valid",
args: args{
r: rw,
w: rw,
},
want: func() *kms.Repository {
ret, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
return ret
}(),
wantErr: false,
},
{
name: "nil-writer",
args: args{
r: rw,
w: nil,
},
want: nil,
wantErr: true,
wantErrString: "kms.NewRepository: nil writer: parameter violation: error #100",
},
{
name: "nil-reader",
args: args{
r: nil,
w: rw,
},
want: nil,
wantErr: true,
wantErrString: "kms.NewRepository: nil reader: parameter violation: error #100",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := kms.NewRepository(tt.args.r, tt.args.w)
if tt.wantErr {
require.Error(err)
assert.Equal(tt.wantErrString, err.Error())
return
}
require.NoError(err)
assert.Equal(tt.want, got)
})
}
}
func TestCreateKeysTx(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
type args struct {
ctx context.Context
dbReader db.Reader
dbWriter db.Writer
rootWrapper wrapping.Wrapper
randomReader io.Reader
scopeId string
}
tests := []struct {
name string
args args
wantErr bool
wantErrIs errors.Code
}{
{
name: "valid",
args: args{
dbReader: rw,
dbWriter: rw,
rootWrapper: wrapper,
randomReader: rand.Reader,
scopeId: org.PublicId,
},
},
{
name: "valid-at-global",
args: args{
dbReader: rw,
dbWriter: rw,
rootWrapper: wrapper,
randomReader: rand.Reader,
scopeId: scope.Global.String(),
},
},
{
name: "nil-reader",
args: args{
dbReader: nil,
dbWriter: rw,
rootWrapper: wrapper,
randomReader: rand.Reader,
scopeId: org.PublicId,
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "nil-writer",
args: args{
dbReader: rw,
dbWriter: nil,
rootWrapper: wrapper,
randomReader: rand.Reader,
scopeId: org.PublicId,
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
dbReader: rw,
dbWriter: rw,
rootWrapper: nil,
randomReader: rand.Reader,
scopeId: org.PublicId,
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "empty-scope",
args: args{
dbReader: rw,
dbWriter: rw,
rootWrapper: wrapper,
randomReader: rand.Reader,
scopeId: "",
},
wantErr: true,
wantErrIs: errors.InvalidParameter,
},
{
name: "bad-scope",
args: args{
dbReader: rw,
dbWriter: rw,
rootWrapper: wrapper,
randomReader: rand.Reader,
scopeId: "o_thisIsAnInvalidScopeId",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
keys, err := kms.CreateKeysTx(tt.args.ctx, tt.args.dbReader, tt.args.dbWriter, tt.args.rootWrapper, tt.args.randomReader, tt.args.scopeId)
if tt.wantErr {
require.Error(err)
assert.Truef(errors.Match(errors.T(tt.wantErrIs), err), "unexpected error: %s", err.Error())
return
}
require.NoError(err)
rk := kms.AllocRootKey()
rk.PrivateId = keys[kms.KeyTypeRootKey].GetPrivateId()
err = rw.LookupById(context.Background(), &rk)
require.NoError(err)
assert.Equal(rk.ScopeId, tt.args.scopeId)
rkv := kms.AllocRootKeyVersion()
rkv.PrivateId = keys[kms.KeyTypeRootKeyVersion].GetPrivateId()
err = rw.LookupById(context.Background(), &rkv)
require.NoError(err)
assert.Equal(rkv.RootKeyId, rk.PrivateId)
dk := kms.AllocDatabaseKey()
dk.PrivateId = keys[kms.KeyTypeDatabaseKey].GetPrivateId()
err = rw.LookupById(context.Background(), &dk)
require.NoError(err)
assert.Equal(rk.PrivateId, dk.RootKeyId)
dkv := kms.AllocDatabaseKeyVersion()
dkv.PrivateId = keys[kms.KeyTypeDatabaseKeyVersion].GetPrivateId()
err = rw.LookupById(context.Background(), &dkv)
require.NoError(err)
assert.Equal(dk.PrivateId, dkv.DatabaseKeyId)
assert.Equal(rkv.PrivateId, dkv.RootKeyVersionId)
opk := kms.AllocOplogKey()
opk.PrivateId = keys[kms.KeyTypeOplogKey].GetPrivateId()
err = rw.LookupById(context.Background(), &opk)
require.NoError(err)
assert.Equal(rk.PrivateId, opk.RootKeyId)
opkv := kms.AllocOplogKeyVersion()
opkv.PrivateId = keys[kms.KeyTypeOplogKeyVersion].GetPrivateId()
err = rw.LookupById(context.Background(), &opkv)
require.NoError(err)
assert.Equal(opk.PrivateId, opkv.OplogKeyId)
assert.Equal(rkv.PrivateId, opkv.RootKeyVersionId)
sk := kms.AllocSessionKey()
sk.PrivateId = keys[kms.KeyTypeSessionKey].GetPrivateId()
err = rw.LookupById(context.Background(), &sk)
require.NoError(err)
assert.Equal(rk.PrivateId, sk.RootKeyId)
skv := kms.AllocSessionKeyVersion()
skv.PrivateId = keys[kms.KeyTypeSessionKeyVersion].GetPrivateId()
err = rw.LookupById(context.Background(), &skv)
require.NoError(err)
assert.Equal(sk.PrivateId, skv.SessionKeyId)
assert.Equal(rkv.PrivateId, skv.RootKeyVersionId)
tk := kms.AllocTokenKey()
tk.PrivateId = keys[kms.KeyTypeTokenKey].GetPrivateId()
err = rw.LookupById(context.Background(), &tk)
require.NoError(err)
assert.Equal(rk.PrivateId, tk.RootKeyId)
tkv := kms.AllocTokenKeyVersion()
tkv.PrivateId = keys[kms.KeyTypeTokenKeyVersion].GetPrivateId()
err = rw.LookupById(context.Background(), &tkv)
require.NoError(err)
assert.Equal(tk.PrivateId, tkv.TokenKeyId)
assert.Equal(rkv.PrivateId, tkv.RootKeyVersionId)
oidcK := kms.AllocOidcKey()
oidcK.PrivateId = keys[kms.KeyTypeOidcKey].GetPrivateId()
err = rw.LookupById(context.Background(), &oidcK)
require.NoError(err)
assert.Equal(rk.PrivateId, oidcK.RootKeyId)
oidcKv := kms.AllocOidcKeyVersion()
oidcKv.PrivateId = keys[kms.KeyTypeOidcKeyVersion].GetPrivateId()
err = rw.LookupById(context.Background(), &oidcKv)
require.NoError(err)
assert.Equal(oidcK.PrivateId, oidcKv.OidcKeyId)
assert.Equal(rkv.PrivateId, oidcKv.RootKeyVersionId)
if tt.args.scopeId == scope.Global.String() {
auditK := kms.AllocAuditKey()
auditK.PrivateId = keys[kms.KeyTypeAuditKey].GetPrivateId()
err = rw.LookupById(context.Background(), &auditK)
require.NoError(err)
assert.Equal(rk.PrivateId, auditK.RootKeyId)
auditKv := kms.AllocAuditKeyVersion()
auditKv.PrivateId = keys[kms.KeyTypeAuditKeyVersion].GetPrivateId()
err = rw.LookupById(context.Background(), &auditKv)
require.NoError(err)
assert.Equal(auditK.PrivateId, auditKv.AuditKeyId)
assert.Equal(rkv.PrivateId, auditKv.RootKeyVersionId)
}
})
}
}
-162
View File
@@ -1,162 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateTokenKey inserts into the repository and returns the new key and the
// key version. There are no valid options at this time.
func (r *Repository) CreateTokenKey(ctx context.Context, rkvWrapper wrapping.Wrapper, key []byte, _ ...Option) (*TokenKey, *TokenKeyVersion, error) {
const op = "kms.(Repository).CreateTokenKey"
var returnedDk, returnedDv interface{}
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
var err error
if returnedDk, returnedDv, err = createTokenKeyTx(ctx, reader, w, rkvWrapper, key); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
return returnedDk.(*TokenKey), returnedDv.(*TokenKeyVersion), nil
}
// createTokenKeyTx inserts into the db (via db.Writer) and returns the new key
// and the key version. This function encapsulates all the work required within
// a db.TxHandler and allows this capability to be shared with the iam repo.
func createTokenKeyTx(ctx context.Context, r db.Reader, w db.Writer, rkvWrapper wrapping.Wrapper, key []byte) (*TokenKey, *TokenKeyVersion, error) {
const op = "kms.createTokenKeyTx"
if rkvWrapper == nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
if len(key) == 0 {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup root key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
rv := AllocRootKeyVersion()
rv.PrivateId = rootKeyVersionId
if err := r.LookupById(ctx, &rv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to lookup root key version %s", rootKeyVersionId)))
}
tk := AllocTokenKey()
tv := AllocTokenKeyVersion()
id, err := newTokenKeyId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
tk.PrivateId = id
tk.RootKeyId = rv.RootKeyId
id, err = newTokenKeyVersionId()
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
tv.PrivateId = id
tv.TokenKeyId = tk.PrivateId
tv.RootKeyVersionId = rootKeyVersionId
tv.Key = key
if err := tv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, nil, errors.Wrap(ctx, err, op)
}
// no token entries for keys
if err := w.Create(ctx, &tk); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("keys create"))
}
// no token entries for key versions
if err := w.Create(ctx, &tv); err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("key versions create"))
}
return &tk, &tv, nil
}
// LookupTokenKey will look up a key in the repository. If the key is not
// found, it will return nil, nil.
func (r *Repository) LookupTokenKey(ctx context.Context, privateId string, _ ...Option) (*TokenKey, error) {
const op = "kms.(Repository).LookupTokenKey"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocTokenKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return &k, nil
}
// DeleteTokenKey deletes the key for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteTokenKey(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteTokenKey"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocTokenKey()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no token entries for root keys
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// ListTokenKeys will list the keys. Supports the WithLimit option.
func (r *Repository) ListTokenKeys(ctx context.Context, opt ...Option) ([]Dek, error) {
const op = "kms.(Repository).ListTokenKeys"
var keys []*TokenKey
err := r.list(ctx, &keys, "1=1", nil, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
deks := make([]Dek, 0, len(keys))
for _, key := range keys {
deks = append(deks, key)
}
return deks, nil
}
-295
View File
@@ -1,295 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateTokenKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
type args struct {
scopeId string
key []byte
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid-org",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: rkvWrapper,
},
wantErr: false,
},
{
name: "nil-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
scopeId: org.PublicId,
keyWrapper: rkvWrapper,
key: []byte(""),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-rkv-wrapper",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: wrapper,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "wrapper-missing-id",
args: args{
scopeId: org.PublicId,
key: []byte("test key"),
keyWrapper: func() wrapping.Wrapper {
w := db.TestWrapper(t)
_, err = w.(*aead.Wrapper).SetConfig(context.Background())
require.NoError(t, err)
return w
}(),
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tk, tv, err := repo.CreateTokenKey(context.Background(), tt.args.keyWrapper, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(tk)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(tk.CreateTime)
foundKey, err := repo.LookupTokenKey(context.Background(), tk.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, tk))
// make sure there was no token written
err = db.TestVerifyOplog(t, rw, tk.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
assert.NotNil(tv.CreateTime)
foundKeyVersion, err := repo.LookupTokenKeyVersion(context.Background(), tt.args.keyWrapper, tv.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKeyVersion, tv))
// make sure there was no token written
err = db.TestVerifyOplog(t, rw, tv.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteTokenKey(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
key *kms.TokenKey
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestTokenKey(t, conn, rk.PrivateId),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.TokenKey {
k := kms.AllocTokenKey()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.TokenKey {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k := kms.AllocTokenKey()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteTokenKey(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
// make sure there was no token written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupTokenKey(context.Background(), tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no token written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_ListTokenKeys(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
wrapper := db.TestWrapper(t)
type args struct {
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1, // org and project both have keys, plus global scope
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
}
for _, tt := range tests {
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
for i := 0; i < tt.createCnt; i++ {
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "scope_id in(?)", "global", org.PublicId, proj.PublicId)
rk := kms.TestRootKey(t, conn, proj.PublicId)
kms.TestTokenKey(t, conn, rk.PrivateId)
require.NoError(err)
}
got, err := repo.ListTokenKeys(context.Background(), tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
@@ -1,176 +0,0 @@
package kms
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
)
// CreateTokenKeyVersion inserts into the repository and returns the new key
// version with its PrivateId. There are no valid options at this time.
func (r *Repository) CreateTokenKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, tokenKeyId string, key []byte, _ ...Option) (*TokenKeyVersion, error) {
const op = "kms.(Repository).CreateTokenKeyVersion"
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
rootKeyVersionId, err := rkvWrapper.KeyId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to fetch key id"))
}
switch {
case !strings.HasPrefix(rootKeyVersionId, RootKeyVersionPrefix):
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("root key version id %s doesn't start with prefix %s", rootKeyVersionId, RootKeyVersionPrefix))
case rootKeyVersionId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
if tokenKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token key id")
}
if len(key) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
kv := AllocTokenKeyVersion()
id, err := newTokenKeyVersionId()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
kv.PrivateId = id
kv.RootKeyVersionId = rootKeyVersionId
kv.Key = key
kv.TokenKeyId = tokenKeyId
if err := kv.Encrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var returnedKey interface{}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedKey = kv.Clone()
// no token entries for root key version
if err := w.Create(ctx, returnedKey); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s token key id", kv.TokenKeyId)))
}
return returnedKey.(*TokenKeyVersion), nil
}
// LookupTokenKeyVersion will look up a key version in the repository. If
// the key version is not found, it will return nil, nil.
func (r *Repository) LookupTokenKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*TokenKeyVersion, error) {
const op = "kms.(Repository).LookupTokenKeyVersion"
if privateId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if keyWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key wrapper")
}
k := AllocTokenKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
if err := k.Decrypt(ctx, keyWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return &k, nil
}
// DeleteTokenKeyVersion deletes the key version for the provided id from the
// repository returning a count of the number of records deleted. All options
// are ignored.
func (r *Repository) DeleteTokenKeyVersion(ctx context.Context, privateId string, _ ...Option) (int, error) {
const op = "kms.(Repository).DeleteTokenKeyVersion"
if privateId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
k := AllocTokenKeyVersion()
k.PrivateId = privateId
if err := r.reader.LookupById(ctx, &k); err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
var rowsDeleted int
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
dk := k.Clone()
// no token entries for the key version
rowsDeleted, err = w.Delete(ctx, dk)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
}
return rowsDeleted, nil
}
// LatestTokenKeyVersion searches for the key version with the highest
// version number. When no results are found, it returns nil with an
// errors.RecordNotFound error.
func (r *Repository) LatestTokenKeyVersion(ctx context.Context, rkvWrapper wrapping.Wrapper, tokenKeyId string, _ ...Option) (*TokenKeyVersion, error) {
const op = "kms.(Repository).LatestTokenKeyVersion"
if tokenKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var foundKeys []*TokenKeyVersion
if err := r.reader.SearchWhere(ctx, &foundKeys, "token_key_id = ?", []interface{}{tokenKeyId}, db.WithLimit(1), db.WithOrder("version desc")); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", tokenKeyId)))
}
if len(foundKeys) == 0 {
return nil, errors.E(ctx, errors.WithCode(errors.RecordNotFound), errors.WithOp(op))
}
if err := foundKeys[0].Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return foundKeys[0], nil
}
// ListTokenKeyVersions will lists versions of a key. Supports the WithLimit option.
func (r *Repository) ListTokenKeyVersions(ctx context.Context, rkvWrapper wrapping.Wrapper, tokenKeyId string, opt ...Option) ([]DekVersion, error) {
const op = "kms.(Repository).ListTokenKeyVersions"
if tokenKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token key id")
}
if rkvWrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing root key version wrapper")
}
var versions []*TokenKeyVersion
err := r.list(ctx, &versions, "token_key_id = ?", []interface{}{tokenKeyId}, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
for i, k := range versions {
if err := k.Decrypt(ctx, rkvWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("error decrypting key num %d", i)))
}
}
dekVersions := make([]DekVersion, 0, len(versions))
for _, version := range versions {
dekVersions = append(dekVersions, version)
}
return dekVersions, nil
}
@@ -1,367 +0,0 @@
package kms_test
import (
"context"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateTokenKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestTokenKey(t, conn, rk.PrivateId)
type args struct {
key []byte
tokenKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
args args
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: []byte("test key"),
keyWrapper: rkvWrapper,
tokenKeyId: dk.PrivateId,
},
wantErr: false,
},
{
name: "invalid-rkv-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: wrapper,
tokenKeyId: dk.PrivateId,
},
wantErr: true,
},
{
name: "empty-key",
args: args{
key: nil,
keyWrapper: wrapper,
tokenKeyId: dk.PrivateId,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "nil-wrapper",
args: args{
key: []byte("test key"),
keyWrapper: nil,
},
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
k, err := repo.CreateTokenKeyVersion(context.Background(), tt.args.keyWrapper, tt.args.tokenKeyId, tt.args.key, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(k)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
assert.NotNil(k.CreateTime)
foundKey, err := repo.LookupTokenKeyVersion(context.Background(), tt.args.keyWrapper, k.PrivateId)
assert.NoError(err)
assert.True(proto.Equal(foundKey, k))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, k.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_DeleteTokenKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestTokenKey(t, conn, rk.PrivateId)
type args struct {
key *kms.TokenKeyVersion
opt []kms.Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantIsError errors.Code
}{
{
name: "valid",
args: args{
key: kms.TestTokenKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("token key")),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-private-id",
args: args{
key: func() *kms.TokenKeyVersion {
k := kms.AllocTokenKeyVersion()
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
{
name: "not-found",
args: args{
key: func() *kms.TokenKeyVersion {
id, err := db.NewPublicId(kms.TokenKeyPrefix)
require.NoError(t, err)
k := kms.AllocTokenKeyVersion()
k.PrivateId = id
require.NoError(t, err)
return &k
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deletedRows, err := repo.DeleteTokenKeyVersion(context.Background(), tt.args.key.PrivateId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Equal(0, deletedRows)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey, err := repo.LookupTokenKeyVersion(context.Background(), wrapper, tt.args.key.PrivateId)
assert.Error(err)
assert.Nil(foundKey)
assert.True(errors.IsNotFoundError(err))
// make sure there was no oplog written
err = db.TestVerifyOplog(t, rw, tt.args.key.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRepository_LatestTokenKeyVersion(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestTokenKey(t, conn, rk.PrivateId)
tests := []struct {
name string
createCnt int
keyWrapper wrapping.Wrapper
wantVersion uint32
wantErr bool
wantIsError errors.Code
}{
{
name: "5",
createCnt: 5,
keyWrapper: rkvWrapper,
wantVersion: 5,
wantErr: false,
},
{
name: "1",
createCnt: 1,
keyWrapper: rkvWrapper,
wantVersion: 1,
wantErr: false,
},
{
name: "0",
createCnt: 0,
keyWrapper: rkvWrapper,
wantErr: true,
wantIsError: errors.RecordNotFound,
},
{
name: "nil-wrapper",
createCnt: 5,
keyWrapper: nil,
wantErr: true,
wantIsError: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocTokenKeyVersion(); return &i }(), "1=1")
testKeys := []*kms.TokenKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestTokenKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("test key"))
testKeys = append(testKeys, k)
}
assert.Equal(tt.createCnt, len(testKeys))
got, err := repo.LatestTokenKeyVersion(context.Background(), tt.keyWrapper, dk.PrivateId)
if tt.wantErr {
require.Error(err)
assert.Nil(got)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
require.NoError(err)
require.NotNil(got)
assert.Equal(tt.wantVersion, got.Version)
})
}
}
func TestRepository_ListTokenKeyVersions(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit))
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestTokenKey(t, conn, rk.PrivateId)
type args struct {
tokenKeyId string
keyWrapper wrapping.Wrapper
opt []kms.Option
}
tests := []struct {
name string
createCnt int
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
tokenKeyId: dk.PrivateId,
keyWrapper: rkvWrapper,
opt: []kms.Option{kms.WithLimit(-1)},
},
wantCnt: repo.DefaultLimit() + 1,
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
tokenKeyId: dk.PrivateId,
},
wantCnt: repo.DefaultLimit(),
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.DefaultLimit() + 1,
args: args{
keyWrapper: rkvWrapper,
tokenKeyId: dk.PrivateId,
opt: []kms.Option{kms.WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "bad-org",
createCnt: 1,
args: args{
keyWrapper: rkvWrapper,
tokenKeyId: "bad-id",
},
wantCnt: 0,
wantErr: false,
},
{
name: "nil-wrapper",
createCnt: 1,
args: args{
keyWrapper: nil,
tokenKeyId: dk.PrivateId,
},
wantCnt: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocTokenKeyVersion(); return &i }(), "1=1")
keyVersions := []*kms.TokenKeyVersion{}
for i := 0; i < tt.createCnt; i++ {
k := kms.TestTokenKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("token key"))
keyVersions = append(keyVersions, k)
}
assert.Equal(tt.createCnt, len(keyVersions))
got, err := repo.ListTokenKeyVersions(context.Background(), tt.args.keyWrapper, tt.args.tokenKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
-84
View File
@@ -1,84 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
"google.golang.org/protobuf/proto"
)
const (
DefaultRootKeyTableName = "kms_root_key"
)
type RootKey struct {
*store.RootKey
tableName string `gorm:"-"`
}
// NewRootKey creates a new in memory root key. ScopeId must be
// for a global or org scope, but the scope type validation will be deferred
// until the in memory root key is written to the database. No options
// are currently supported.
func NewRootKey(scopeId string, _ ...Option) (*RootKey, error) {
const op = "kms.NewRootKey"
if scopeId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing scope id")
}
c := &RootKey{
RootKey: &store.RootKey{
ScopeId: scopeId,
},
}
return c, nil
}
// AllocRootKey will allocate a root key
func AllocRootKey() RootKey {
return RootKey{
RootKey: &store.RootKey{},
}
}
// Clone creates a clone of the RootKey
func (k *RootKey) Clone() interface{} {
cp := proto.Clone(k.RootKey)
return &RootKey{
RootKey: cp.(*store.RootKey),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// before it's written.
func (k *RootKey) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "kms.(RootKey).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
switch opType {
case db.CreateOp:
if k.ScopeId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
}
case db.UpdateOp:
return errors.New(ctx, errors.InvalidParameter, op, "key is immutable")
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *RootKey) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultRootKeyTableName
}
// SetTableName sets the tablename and satisfies the ReplayableMessage
// interface. If the caller attempts to set the name to "" the name will be
// reset to the default name.
func (c *RootKey) SetTableName(n string) {
c.tableName = n
}
-210
View File
@@ -1,210 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the RootKey attributes are
// immutable and those tests are covered by TestRootKey_ImmutableFields
func TestRootKey_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
type args struct {
scopeId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.RootKey
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-scopeId",
args: args{},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid-org-config",
args: args{
scopeId: org.PublicId,
},
want: func() *kms.RootKey {
k := kms.AllocRootKey()
k.ScopeId = org.PublicId
return &k
}(),
create: true,
},
{
name: "valid-global-config",
args: args{
scopeId: "global",
},
want: func() *kms.RootKey {
k := kms.AllocRootKey()
k.ScopeId = "global"
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
got, err := kms.NewRootKey(tt.args.scopeId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestRootKey_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.RootKey
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestRootKey(t, conn, org.PublicId),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.RootKey {
k := kms.AllocRootKey()
id, err := db.NewPublicId(kms.RootKeyPrefix)
require.NoError(t, err)
k.PrivateId = id
k.ScopeId = org.PublicId
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocRootKey()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocRootKey()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRootKey_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
k := kms.TestRootKey(t, conn, org.PublicId)
cp := k.Clone()
assert.True(proto.Equal(cp.(*kms.RootKey).RootKey, k.RootKey))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
k := kms.TestRootKey(t, conn, org.PublicId)
k2 := kms.TestRootKey(t, conn, org2.PublicId)
cp := k.Clone()
assert.True(!proto.Equal(cp.(*kms.RootKey).RootKey, k2.RootKey))
})
}
func TestRootKey_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultRootKeyTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocRootKey()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocRootKey()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-114
View File
@@ -1,114 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
const (
DefaultRootKeyVersionTableName = "kms_root_key_version"
)
type RootKeyVersion struct {
*store.RootKeyVersion
tableName string `gorm:"-"`
}
// NewRootKeyVersion creates a new in memory root key version. No options are
// currently supported.
func NewRootKeyVersion(rootKeyId string, key []byte, _ ...Option) (*RootKeyVersion, error) {
const op = "kms.NewRootKeyVersion"
if rootKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key id")
}
if len(key) == 0 {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing key")
}
k := &RootKeyVersion{
RootKeyVersion: &store.RootKeyVersion{
RootKeyId: rootKeyId,
Key: key,
},
}
return k, nil
}
// AllocRootKeyVersion allocates a RootKeyVersion
func AllocRootKeyVersion() RootKeyVersion {
return RootKeyVersion{
RootKeyVersion: &store.RootKeyVersion{},
}
}
// Clone creates a clone of the RootKeyVersion
func (k *RootKeyVersion) Clone() interface{} {
cp := proto.Clone(k.RootKeyVersion)
return &RootKeyVersion{
RootKeyVersion: cp.(*store.RootKeyVersion),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the root key
// version before it's written.
func (k *RootKeyVersion) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "kms.(RootKeyVersion).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
switch opType {
case db.CreateOp:
if k.CtKey == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
if k.RootKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
case db.UpdateOp:
return errors.New(ctx, errors.InvalidParameter, op, "key is immutable")
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *RootKeyVersion) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultRootKeyVersionTableName
}
// SetTableName sets the tablename and satisfies the ReplayableMessage
// interface. If the caller attempts to set the name to "" the name will be
// reset to the default name.
func (k *RootKeyVersion) SetTableName(n string) {
k.tableName = n
}
// Encrypt will encrypt the root key version's key
func (k *RootKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(RootKeyVersion).Encrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.RootKey directly
if err := structwrapping.WrapStruct(ctx, cipher, k.RootKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
}
return nil
}
// Decrypt will decrypt the root key version's key
func (k *RootKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(RootKeyVersion).Decrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.RootKeyVersion directly
if err := structwrapping.UnwrapStruct(ctx, cipher, k.RootKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
-219
View File
@@ -1,219 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/kms/store"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the RootKeyVersion attributes are
// immutable and those tests are covered by TestRootKeyVersion_ImmutableFields
func TestRootKeyVersion_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rootKey := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootId string
key []byte
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.RootKeyVersion
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-root-id",
args: args{
key: []byte("test key"),
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
rootId: rootKey.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
rootId: rootKey.PrivateId,
key: []byte("test key"),
},
want: func() *kms.RootKeyVersion {
k := kms.AllocRootKeyVersion()
k.RootKeyId = rootKey.PrivateId
k.Key = []byte("test key")
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKeyVersion(); return &i }(), "1=1")
got, err := kms.NewRootKeyVersion(tt.args.rootId, tt.args.key, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.RootKeyVersionPrefix)
require.NoError(err)
got.PrivateId = id
err = got.Encrypt(context.Background(), wrapper)
require.NoError(err)
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
assert.Equal(uint32(1), got.Version)
}
})
}
}
func TestRootKeyVersion_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
rw := db.New(conn)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
tests := []struct {
name string
key *kms.RootKeyVersion
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: func() *kms.RootKeyVersion { k, _ := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId); return k }(),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.RootKeyVersion {
k := kms.AllocRootKeyVersion()
id, err := db.NewPublicId(kms.RootKeyVersionPrefix)
require.NoError(t, err)
k.PrivateId = id
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocRootKeyVersion()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocRootKey()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestRootKeyVersion_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k, _ := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
cp := k.Clone()
assert.True(proto.Equal(cp.(*kms.RootKeyVersion).RootKeyVersion, k.RootKeyVersion))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k, _ := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
k2, _ := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
cp := k.Clone()
assert.True(!proto.Equal(cp.(*kms.RootKeyVersion).RootKeyVersion, k2.RootKeyVersion))
})
}
func TestRootKeyVersion_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultRootKeyVersionTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocRootKeyVersion()
require.Equal(defaultTableName, def.TableName())
s := &kms.RootKeyVersion{
RootKeyVersion: &store.RootKeyVersion{},
}
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-79
View File
@@ -1,79 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
"google.golang.org/protobuf/proto"
)
const (
DefaultSessionKeyTableName = "kms_session_key"
)
type SessionKey struct {
*store.SessionKey
tableName string `gorm:"-"`
}
// NewSessionKey creates a new in memory key. No options
// are currently supported.
func NewSessionKey(rootKeyId string, _ ...Option) (*SessionKey, error) {
const op = "kms.NewSessionKey"
if rootKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key id")
}
c := &SessionKey{
SessionKey: &store.SessionKey{
RootKeyId: rootKeyId,
},
}
return c, nil
}
// AllocSessionKey will allocate a key
func AllocSessionKey() SessionKey {
return SessionKey{
SessionKey: &store.SessionKey{},
}
}
// Clone creates a clone of the key
func (k *SessionKey) Clone() interface{} {
cp := proto.Clone(k.SessionKey)
return &SessionKey{
SessionKey: cp.(*store.SessionKey),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// before it's written.
func (k *SessionKey) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "kms.(SessionKey).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if opType == db.CreateOp {
if k.RootKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *SessionKey) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultSessionKeyTableName
}
// SetTableName sets the tablename and satisfies the ReplayableMessage
// interface. If the caller attempts to set the name to "" the name will be
// reset to the default name.
func (k *SessionKey) SetTableName(n string) {
k.tableName = n
}
-206
View File
@@ -1,206 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the SessionKey attributes are
// immutable and those tests are covered by TestSessionKey_ImmutableFields
func TestSessionKey_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootKeyId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.SessionKey
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-rootKeyId",
args: args{},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
rootKeyId: rk.PrivateId,
},
want: func() *kms.SessionKey {
k := kms.AllocSessionKey()
k.RootKeyId = rk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocSessionKey(); return &i }(), "1=1")
got, err := kms.NewSessionKey(tt.args.rootKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.SessionKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestSessionKey_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocSessionKey(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.SessionKey
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestSessionKey(t, conn, rk.PrivateId),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.SessionKey {
k := kms.AllocSessionKey()
id, err := db.NewPublicId(kms.SessionKeyPrefix)
require.NoError(t, err)
k.PrivateId = id
k.RootKeyId = rk.PrivateId
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocSessionKey()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocSessionKey()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestSessionKey_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k := kms.TestSessionKey(t, conn, rk.PrivateId)
cp := k.Clone()
assert.True(proto.Equal(cp.(*kms.SessionKey).SessionKey, k.SessionKey))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
k := kms.TestSessionKey(t, conn, rk.PrivateId)
k2 := kms.TestSessionKey(t, conn, rk2.PrivateId)
cp := k.Clone()
assert.True(!proto.Equal(cp.(*kms.SessionKey).SessionKey, k2.SessionKey))
})
}
func TestSessionKey_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultSessionKeyTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocSessionKey()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocSessionKey()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-118
View File
@@ -1,118 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
const (
DefaultSessionKeyVersionTableName = "kms_session_key_version"
)
type SessionKeyVersion struct {
*store.SessionKeyVersion
tableName string `gorm:"-"`
}
// SessionKeyVersion creates a new in memory key version. No options are
// currently supported.
func NewSessionKeyVersion(sessionKeyId string, key []byte, rootKeyVersionId string, _ ...Option) (*SessionKeyVersion, error) {
const op = "kms.NewSessionKeyVersion"
if sessionKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing session key id")
}
if len(key) == 0 {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing key")
}
if rootKeyVersionId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key version id")
}
k := &SessionKeyVersion{
SessionKeyVersion: &store.SessionKeyVersion{
SessionKeyId: sessionKeyId,
RootKeyVersionId: rootKeyVersionId,
Key: key,
},
}
return k, nil
}
// AllocSessionKeyVersion allocates a key version
func AllocSessionKeyVersion() SessionKeyVersion {
return SessionKeyVersion{
SessionKeyVersion: &store.SessionKeyVersion{},
}
}
// Clone creates a clone of the key version
func (k *SessionKeyVersion) Clone() interface{} {
cp := proto.Clone(k.SessionKeyVersion)
return &SessionKeyVersion{
SessionKeyVersion: cp.(*store.SessionKeyVersion),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// version before it's written.
func (k *SessionKeyVersion) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
const op = "kms.(SessionKeyVersion).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if opType == db.CreateOp {
if k.CtKey == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
if k.SessionKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing session key id")
}
if k.RootKeyVersionId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *SessionKeyVersion) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultSessionKeyVersionTableName
}
// SetTableName sets the tablename and satisfies the ReplayableMessage
// interface. If the caller attempts to set the name to "" the name will be
// reset to the default name.
func (k *SessionKeyVersion) SetTableName(n string) {
k.tableName = n
}
// Encrypt will encrypt the key version's key
func (k *SessionKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(SessionKeyVersion).Encrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.SessionKeyVersion directly
if err := structwrapping.WrapStruct(ctx, cipher, k.SessionKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
}
return nil
}
// Decrypt will decrypt the key version's key
func (k *SessionKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(SessionKeyVersion).Decrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.SessionKeyVersion directly
if err := structwrapping.UnwrapStruct(ctx, cipher, k.SessionKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
-249
View File
@@ -1,249 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the essionKeyVersion attributes are
// immutable and those tests are covered by TestSessionKey_ImmutableFields
func TestSessionKeyVersion_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rkv, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestSessionKey(t, conn, rk.PrivateId)
type args struct {
sessionKeyId string
key []byte
rootKeyVersionId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.SessionKeyVersion
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-id",
args: args{
key: []byte("test key"),
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
sessionKeyId: dk.PrivateId,
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-root-key-version-id",
args: args{
sessionKeyId: dk.PrivateId,
key: []byte("test key"),
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
sessionKeyId: dk.PrivateId,
key: []byte("test key"),
rootKeyVersionId: rkv.PrivateId,
},
want: func() *kms.SessionKeyVersion {
k := kms.AllocSessionKeyVersion()
k.RootKeyVersionId = rkv.PrivateId
k.Key = []byte("test key")
k.SessionKeyId = dk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocSessionKeyVersion(); return &i }(), "1=1")
got, err := kms.NewSessionKeyVersion(tt.args.sessionKeyId, tt.args.key, tt.args.rootKeyVersionId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.SessionKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = got.Encrypt(context.Background(), rkvWrapper)
require.NoError(err)
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestSessionKeyVersion_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
tk := kms.TestSessionKey(t, conn, rk.PrivateId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocSessionKeyVersion(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.SessionKeyVersion
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestSessionKeyVersion(t, conn, rkvWrapper, tk.PrivateId, []byte("test key")),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.SessionKeyVersion {
k := kms.AllocSessionKeyVersion()
id, err := db.NewPublicId(kms.SessionKeyVersionPrefix)
require.NoError(t, err)
k.PrivateId = id
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocSessionKeyVersion()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocSessionKeyVersion()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestSessionKeyVersion_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
k := kms.TestSessionKey(t, conn, rk.PrivateId)
kv := kms.TestSessionKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test key"))
cp := kv.Clone()
assert.True(proto.Equal(cp.(*kms.SessionKeyVersion).SessionKeyVersion, kv.SessionKeyVersion))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
_, rkvWrapper2 := kms.TestRootKeyVersion(t, conn, wrapper, rk2.PrivateId)
k := kms.TestSessionKey(t, conn, rk.PrivateId)
k2 := kms.TestSessionKey(t, conn, rk2.PrivateId)
kv := kms.TestSessionKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test key"))
kv2 := kms.TestSessionKeyVersion(t, conn, rkvWrapper2, k2.PrivateId, []byte("test key 2"))
cp := kv.Clone()
assert.True(!proto.Equal(cp.(*kms.SessionKeyVersion).SessionKeyVersion, kv2.SessionKeyVersion))
})
}
func TestSessionKeyVersion_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultSessionKeyVersionTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocSessionKeyVersion()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocSessionKeyVersion()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
+13 -251
View File
@@ -2,276 +2,38 @@ package kms
import (
"context"
"fmt"
"testing"
"github.com/hashicorp/boundary/internal/db"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/stretchr/testify/require"
)
func TestRootKey(t testing.TB, conn *db.DB, scopeId string) *RootKey {
t.Helper()
require := require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocRootKey(); return &i }(), "scope_id = ?", scopeId)
rw := db.New(conn)
k, err := NewRootKey(scopeId)
require.NoError(err)
id, err := newRootKeyId()
require.NoError(err)
k.PrivateId = id
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestRootKeyVersion(t testing.TB, conn *db.DB, wrapper wrapping.Wrapper, rootId string) (kv *RootKeyVersion, kvWrapper wrapping.Wrapper) {
t.Helper()
require := require.New(t)
rw := db.New(conn)
rootKeyVersionWrapper := db.TestWrapper(t)
key, err := rootKeyVersionWrapper.(*aead.Wrapper).KeyBytes(context.Background())
require.NoError(err)
k, err := NewRootKeyVersion(rootId, key)
require.NoError(err)
id, err := newRootKeyVersionId()
require.NoError(err)
k.PrivateId = id
err = k.Encrypt(context.Background(), wrapper)
require.NoError(err)
err = rw.Create(context.Background(), k)
require.NoError(err)
_, err = rootKeyVersionWrapper.(*aead.Wrapper).SetConfig(context.Background(), wrapping.WithKeyId(k.GetPrivateId()))
require.NoError(err)
return k, rootKeyVersionWrapper
}
// TestKms creates a kms for testing.
func TestKms(t testing.TB, conn *db.DB, rootWrapper wrapping.Wrapper) *Kms {
t.Helper()
require := require.New(t)
rw := db.New(conn)
kmsRepo, err := NewRepository(rw, rw)
require.NoError(err)
kms, err := NewKms(kmsRepo)
kms, err := New(context.Background(), rw, rw)
require.NoError(err)
err = kms.AddExternalWrappers(context.Background(), WithRootWrapper(rootWrapper))
require.NoError(err)
_, err = kms.underlying.GetExternalRootWrapper()
require.NoError(err)
return kms
}
func TestDatabaseKey(t testing.TB, conn *db.DB, rootKeyId string) *DatabaseKey {
t.Helper()
require := require.New(t)
rw := db.New(conn)
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocDatabaseKey(); return &i }(), "root_key_id = ?", rootKeyId)
k, err := NewDatabaseKey(rootKeyId)
require.NoError(err)
id, err := newDatabaseKeyId()
require.NoError(err)
k.PrivateId = id
k.RootKeyId = rootKeyId
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
// TestKmsDeleteKeyPurpose allows you to delete a KeyPurpose for testing.
func TestKmsDeleteKeyPurpose(t testing.TB, conn *db.DB, purpose KeyPurpose) {
db.TestDeleteWhere(t, conn, func() interface{} { i := dataKey{}; return &i }(), fmt.Sprintf("purpose='%s'", purpose.String()))
}
func TestDatabaseKeyVersion(t testing.TB, conn *db.DB, rootKeyVersionWrapper wrapping.Wrapper, databaseKeyId string, key []byte) *DatabaseKeyVersion {
t.Helper()
require := require.New(t)
rw := db.New(conn)
rootKeyId, err := rootKeyVersionWrapper.KeyId(context.Background())
require.NoError(err)
rootKeyVersionId := rootKeyId
require.NotEmpty(rootKeyVersionId)
k, err := NewDatabaseKeyVersion(databaseKeyId, key, rootKeyVersionId)
require.NoError(err)
id, err := newDatabaseKeyVersionId()
require.NoError(err)
k.PrivateId = id
err = k.Encrypt(context.Background(), rootKeyVersionWrapper)
require.NoError(err)
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
// TestKmsDeleteAllKeys allows you to delete all the keys for testing.
func TestKmsDeleteAllKeys(t testing.TB, conn *db.DB) {
db.TestDeleteWhere(t, conn, func() interface{} { i := rootKey{}; return &i }(), "1=1")
}
func TestOplogKey(t testing.TB, conn *db.DB, rootKeyId string) *OplogKey {
t.Helper()
require := require.New(t)
rw := db.New(conn)
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocOplogKey(); return &i }(), "root_key_id = ?", rootKeyId)
k, err := NewOplogKey(rootKeyId)
require.NoError(err)
id, err := newOplogKeyId()
require.NoError(err)
k.PrivateId = id
k.RootKeyId = rootKeyId
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
type dataKey struct{}
func TestOplogKeyVersion(t testing.TB, conn *db.DB, rootKeyVersionWrapper wrapping.Wrapper, oplogKeyId string, key []byte) *OplogKeyVersion {
t.Helper()
require := require.New(t)
rw := db.New(conn)
rootKeyId, err := rootKeyVersionWrapper.KeyId(context.Background())
require.NoError(err)
rootKeyVersionId := rootKeyId
require.NotEmpty(rootKeyVersionId)
k, err := NewOplogKeyVersion(oplogKeyId, key, rootKeyVersionId)
require.NoError(err)
id, err := newOplogKeyVersionId()
require.NoError(err)
k.PrivateId = id
err = k.Encrypt(context.Background(), rootKeyVersionWrapper)
require.NoError(err)
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestTokenKey(t testing.TB, conn *db.DB, rootKeyId string) *TokenKey {
t.Helper()
require := require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocTokenKey(); return &i }(), "root_key_id = ?", rootKeyId)
rw := db.New(conn)
k, err := NewTokenKey(rootKeyId)
require.NoError(err)
id, err := newTokenKeyId()
require.NoError(err)
k.PrivateId = id
k.RootKeyId = rootKeyId
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestTokenKeyVersion(t testing.TB, conn *db.DB, rootKeyVersionWrapper wrapping.Wrapper, tokenKeyId string, key []byte) *TokenKeyVersion {
t.Helper()
require := require.New(t)
rw := db.New(conn)
rootKeyId, err := rootKeyVersionWrapper.KeyId(context.Background())
require.NoError(err)
rootKeyVersionId := rootKeyId
require.NotEmpty(rootKeyVersionId)
k, err := NewTokenKeyVersion(tokenKeyId, key, rootKeyVersionId)
require.NoError(err)
id, err := newTokenKeyVersionId()
require.NoError(err)
k.PrivateId = id
err = k.Encrypt(context.Background(), rootKeyVersionWrapper)
require.NoError(err)
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestSessionKey(t testing.TB, conn *db.DB, rootKeyId string) *SessionKey {
t.Helper()
require := require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocSessionKey(); return &i }(), "root_key_id = ?", rootKeyId)
rw := db.New(conn)
k, err := NewSessionKey(rootKeyId)
require.NoError(err)
id, err := newSessionKeyId()
require.NoError(err)
k.PrivateId = id
k.RootKeyId = rootKeyId
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestSessionKeyVersion(t testing.TB, conn *db.DB, rootKeyVersionWrapper wrapping.Wrapper, sessionKeyId string, key []byte) *SessionKeyVersion {
t.Helper()
require := require.New(t)
rw := db.New(conn)
rootKeyId, err := rootKeyVersionWrapper.KeyId(context.Background())
require.NoError(err)
rootKeyVersionId := rootKeyId
require.NotEmpty(rootKeyVersionId)
k, err := NewSessionKeyVersion(sessionKeyId, key, rootKeyVersionId)
require.NoError(err)
id, err := newSessionKeyVersionId()
require.NoError(err)
k.PrivateId = id
err = k.Encrypt(context.Background(), rootKeyVersionWrapper)
require.NoError(err)
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestOidcKey(t testing.TB, conn *db.DB, rootKeyId string) *OidcKey {
t.Helper()
require := require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocOidcKey(); return &i }(), "root_key_id = ?", rootKeyId)
rw := db.New(conn)
k, err := NewOidcKey(rootKeyId)
require.NoError(err)
id, err := newOidcKeyId()
require.NoError(err)
k.PrivateId = id
k.RootKeyId = rootKeyId
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestOidcKeyVersion(t testing.TB, conn *db.DB, rootKeyVersionWrapper wrapping.Wrapper, oidcKeyId string, key []byte) *OidcKeyVersion {
t.Helper()
require := require.New(t)
rw := db.New(conn)
rootKeyId, err := rootKeyVersionWrapper.KeyId(context.Background())
require.NoError(err)
rootKeyVersionId := rootKeyId
require.NotEmpty(rootKeyVersionId)
k, err := NewOidcKeyVersion(oidcKeyId, key, rootKeyVersionId)
require.NoError(err)
id, err := newOidcKeyVersionId()
require.NoError(err)
k.PrivateId = id
err = k.Encrypt(context.Background(), rootKeyVersionWrapper)
require.NoError(err)
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestAuditKey(t testing.TB, conn *db.DB, rootKeyId string) *AuditKey {
t.Helper()
ctx := context.Background()
require := require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := AllocAuditKey(); return &i }(), "root_key_id = ?", rootKeyId)
rw := db.New(conn)
k, err := NewAuditKey(ctx, rootKeyId)
require.NoError(err)
id, err := newAuditKeyId(ctx)
require.NoError(err)
k.PrivateId = id
k.RootKeyId = rootKeyId
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func TestAuditKeyVersion(t testing.TB, conn *db.DB, rootKeyVersionWrapper wrapping.Wrapper, auditKeyId string, key []byte) *AuditKeyVersion {
t.Helper()
ctx := context.Background()
require := require.New(t)
rw := db.New(conn)
rootKeyVersionId, err := rootKeyVersionWrapper.KeyId(ctx)
require.NoError(err)
require.NotEmpty(rootKeyVersionId)
k, err := NewAuditKeyVersion(ctx, auditKeyId, key, rootKeyVersionId)
require.NoError(err)
id, err := newAuditKeyVersionId(ctx)
require.NoError(err)
k.PrivateId = id
err = k.Encrypt(context.Background(), rootKeyVersionWrapper)
require.NoError(err)
err = rw.Create(context.Background(), k)
require.NoError(err)
return k
}
func (*dataKey) TableName() string { return "kms_data_key" }
-150
View File
@@ -1,150 +0,0 @@
package kms_test
import (
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_TestRootKey(t *testing.T) {
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
k := kms.TestRootKey(t, conn, org.PublicId)
require.NotNil(k)
assert.NotEmpty(k.PrivateId)
}
func Test_TestRootKeyVersion(t *testing.T) {
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k, _ := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
require.NotNil(k)
assert.NotEmpty(k.PrivateId)
}
func Test_TestDatabaseKey(t *testing.T) {
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
k := kms.TestRootKey(t, conn, org.PublicId)
require.NotNil(k)
assert.NotEmpty(k.PrivateId)
dk := kms.TestDatabaseKey(t, conn, k.PrivateId)
require.NotNil(dk)
assert.NotEmpty(dk.PrivateId)
}
func Test_TestDatabaseKeyVersion(t *testing.T) {
require := require.New(t)
conn, _ := db.TestSetup(t, "postgres")
kmsWrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, kmsWrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rootKeyVersionWrapper := kms.TestRootKeyVersion(t, conn, kmsWrapper, rk.PrivateId)
dk := kms.TestDatabaseKey(t, conn, rk.PrivateId)
dv := kms.TestDatabaseKeyVersion(t, conn, rootKeyVersionWrapper, dk.PrivateId, []byte("test dek key"))
require.NotNil(dv)
require.NotEmpty(dv.PrivateId)
}
func Test_TestOplogKey(t *testing.T) {
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
k := kms.TestRootKey(t, conn, org.PublicId)
require.NotNil(k)
assert.NotEmpty(k.PrivateId)
opk := kms.TestOplogKey(t, conn, k.PrivateId)
require.NotNil(opk)
assert.NotEmpty(opk.PrivateId)
}
func Test_TestOplogKeyVersion(t *testing.T) {
require := require.New(t)
conn, _ := db.TestSetup(t, "postgres")
kmsWrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, kmsWrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rootKeyVersionWrapper := kms.TestRootKeyVersion(t, conn, kmsWrapper, rk.PrivateId)
opk := kms.TestOplogKey(t, conn, rk.PrivateId)
opv := kms.TestOplogKeyVersion(t, conn, rootKeyVersionWrapper, opk.PrivateId, []byte("test dek key"))
require.NotNil(opv)
require.NotEmpty(opv.PrivateId)
}
func Test_TestTokenKey(t *testing.T) {
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
k := kms.TestRootKey(t, conn, org.PublicId)
require.NotNil(k)
assert.NotEmpty(k.PrivateId)
tk := kms.TestTokenKey(t, conn, k.PrivateId)
require.NotNil(tk)
assert.NotEmpty(tk.PrivateId)
}
func Test_TestTokenKeyVersion(t *testing.T) {
require := require.New(t)
conn, _ := db.TestSetup(t, "postgres")
kmsWrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, kmsWrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rootKeyVersionWrapper := kms.TestRootKeyVersion(t, conn, kmsWrapper, rk.PrivateId)
tk := kms.TestTokenKey(t, conn, rk.PrivateId)
tv := kms.TestTokenKeyVersion(t, conn, rootKeyVersionWrapper, tk.PrivateId, []byte("test dek key"))
require.NotNil(tv)
require.NotEmpty(tv.PrivateId)
}
func Test_TestSessionKey(t *testing.T) {
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
k := kms.TestRootKey(t, conn, org.PublicId)
require.NotNil(k)
assert.NotEmpty(k.PrivateId)
sk := kms.TestSessionKey(t, conn, k.PrivateId)
require.NotNil(sk)
assert.NotEmpty(sk.PrivateId)
}
func Test_TestSessionKeyVersion(t *testing.T) {
require := require.New(t)
conn, _ := db.TestSetup(t, "postgres")
kmsWrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, kmsWrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rootKeyVersionWrapper := kms.TestRootKeyVersion(t, conn, kmsWrapper, rk.PrivateId)
sk := kms.TestSessionKey(t, conn, rk.PrivateId)
sv := kms.TestSessionKeyVersion(t, conn, rootKeyVersionWrapper, sk.PrivateId, []byte("test dek key"))
require.NotNil(sv)
require.NotEmpty(sv.PrivateId)
}
-79
View File
@@ -1,79 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
"google.golang.org/protobuf/proto"
)
const (
DefaultTokenKeyTableName = "kms_token_key"
)
type TokenKey struct {
*store.TokenKey
tableName string `gorm:"-"`
}
// NewTokenKey creates a new in memory key. No options
// are currently supported.
func NewTokenKey(rootKeyId string, _ ...Option) (*TokenKey, error) {
const op = "kms.NewTokenKey"
if rootKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key id")
}
c := &TokenKey{
TokenKey: &store.TokenKey{
RootKeyId: rootKeyId,
},
}
return c, nil
}
// AllocTokenKey will allocate a key
func AllocTokenKey() TokenKey {
return TokenKey{
TokenKey: &store.TokenKey{},
}
}
// Clone creates a clone of the key
func (k *TokenKey) Clone() interface{} {
cp := proto.Clone(k.TokenKey)
return &TokenKey{
TokenKey: cp.(*store.TokenKey),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// before it's written.
func (k *TokenKey) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "kms.(TokenKey).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if opType == db.CreateOp {
if k.RootKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key id")
}
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *TokenKey) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultTokenKeyTableName
}
// SetTableName sets the tablename and satisfies the ReplayableMessage
// interface. If the caller attempts to set the name to "" the name will be
// reset to the default name.
func (k *TokenKey) SetTableName(n string) {
k.tableName = n
}
-206
View File
@@ -1,206 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the TokenKey attributes are
// immutable and those tests are covered by TestTokenKey_ImmutableFields
func TestTokenKey_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
type args struct {
rootKeyId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.TokenKey
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-rootKeyId",
args: args{},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
rootKeyId: rk.PrivateId,
},
want: func() *kms.TokenKey {
k := kms.AllocTokenKey()
k.RootKeyId = rk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocTokenKey(); return &i }(), "1=1")
got, err := kms.NewTokenKey(tt.args.rootKeyId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.TokenKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestTokenKey_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocTokenKey(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.TokenKey
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestTokenKey(t, conn, rk.PrivateId),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.TokenKey {
k := kms.AllocTokenKey()
id, err := db.NewPublicId(kms.TokenKeyPrefix)
require.NoError(t, err)
k.PrivateId = id
k.RootKeyId = rk.PrivateId
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocTokenKey()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocTokenKey()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestTokenKey_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
k := kms.TestTokenKey(t, conn, rk.PrivateId)
cp := k.Clone()
assert.True(proto.Equal(cp.(*kms.TokenKey).TokenKey, k.TokenKey))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
k := kms.TestTokenKey(t, conn, rk.PrivateId)
k2 := kms.TestTokenKey(t, conn, rk2.PrivateId)
cp := k.Clone()
assert.True(!proto.Equal(cp.(*kms.TokenKey).TokenKey, k2.TokenKey))
})
}
func TestTokenKey_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultTokenKeyTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocTokenKey()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocTokenKey()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
-118
View File
@@ -1,118 +0,0 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms/store"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
const (
DefaultTokenKeyVersionTableName = "kms_token_key_version"
)
type TokenKeyVersion struct {
*store.TokenKeyVersion
tableName string `gorm:"-"`
}
// TokenKeyVersion creates a new in memory key version. No options are
// currently supported.
func NewTokenKeyVersion(tokenKeyId string, key []byte, rootKeyVersionId string, _ ...Option) (*TokenKeyVersion, error) {
const op = "kms.NewTokenKeyVersion"
if tokenKeyId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing token key id")
}
if len(key) == 0 {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing key")
}
if rootKeyVersionId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing root key version id")
}
k := &TokenKeyVersion{
TokenKeyVersion: &store.TokenKeyVersion{
TokenKeyId: tokenKeyId,
RootKeyVersionId: rootKeyVersionId,
Key: key,
},
}
return k, nil
}
// AllocTokenKeyVersion allocates a key version
func AllocTokenKeyVersion() TokenKeyVersion {
return TokenKeyVersion{
TokenKeyVersion: &store.TokenKeyVersion{},
}
}
// Clone creates a clone of the key version
func (k *TokenKeyVersion) Clone() interface{} {
cp := proto.Clone(k.TokenKeyVersion)
return &TokenKeyVersion{
TokenKeyVersion: cp.(*store.TokenKeyVersion),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the key
// version before it's written.
func (k *TokenKeyVersion) VetForWrite(ctx context.Context, _r db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "kms.(TokenKeyVersion).VetForWrite"
if k.PrivateId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing private id")
}
if opType == db.CreateOp {
if k.CtKey == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing key")
}
if k.TokenKeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing token key id")
}
if k.RootKeyVersionId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing root key version id")
}
}
return nil
}
// TableName returns the tablename to override the default gorm table name
func (k *TokenKeyVersion) TableName() string {
if k.tableName != "" {
return k.tableName
}
return DefaultTokenKeyVersionTableName
}
// SetTableName sets the tablename and satisfies the ReplayableMessage
// interface. If the caller attempts to set the name to "" the name will be
// reset to the default name.
func (k *TokenKeyVersion) SetTableName(n string) {
k.tableName = n
}
// Encrypt will encrypt the key version's key
func (k *TokenKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(TokenKeyVersion).Encrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.TokenKeyVersion directly
if err := structwrapping.WrapStruct(ctx, cipher, k.TokenKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
}
return nil
}
// Decrypt will decrypt the key version's key
func (k *TokenKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "kms.(TokenKeyVersion).Decrypt"
// structwrapping doesn't support embedding, so we'll pass in the
// store.TokenKeyVersion directly
if err := structwrapping.UnwrapStruct(ctx, cipher, k.TokenKeyVersion, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
-249
View File
@@ -1,249 +0,0 @@
package kms_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// NOTE: there are no update tests since all the TokenKeyVersion attributes are
// immutable and those tests are covered by TestTokenKey_ImmutableFields
func TestTokenKeyVersion_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rkv, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestTokenKey(t, conn, rk.PrivateId)
type args struct {
tokenKeyId string
key []byte
rootKeyVersionId string
opt []kms.Option
}
tests := []struct {
name string
args args
want *kms.TokenKeyVersion
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
}{
{
name: "empty-id",
args: args{
key: []byte("test key"),
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-key",
args: args{
tokenKeyId: dk.PrivateId,
rootKeyVersionId: rkv.PrivateId,
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-root-key-version-id",
args: args{
tokenKeyId: dk.PrivateId,
key: []byte("test key"),
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "valid",
args: args{
tokenKeyId: dk.PrivateId,
key: []byte("test key"),
rootKeyVersionId: rkv.PrivateId,
},
want: func() *kms.TokenKeyVersion {
k := kms.AllocTokenKeyVersion()
k.RootKeyVersionId = rkv.PrivateId
k.Key = []byte("test key")
k.TokenKeyId = dk.PrivateId
return &k
}(),
create: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocTokenKeyVersion(); return &i }(), "1=1")
got, err := kms.NewTokenKeyVersion(tt.args.tokenKeyId, tt.args.key, tt.args.rootKeyVersionId, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
id, err := db.NewPublicId(kms.TokenKeyPrefix)
require.NoError(err)
got.PrivateId = id
err = got.Encrypt(context.Background(), rkvWrapper)
require.NoError(err)
err = db.New(conn).Create(context.Background(), got)
if tt.wantCreateErr {
assert.Error(err)
return
} else {
assert.NoError(err)
}
}
})
}
}
func TestTokenKeyVersion_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
tk := kms.TestTokenKey(t, conn, rk.PrivateId)
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocTokenKeyVersion(); return &i }(), "1=1")
tests := []struct {
name string
key *kms.TokenKeyVersion
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
key: kms.TestTokenKeyVersion(t, conn, rkvWrapper, tk.PrivateId, []byte("test key")),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-id",
key: func() *kms.TokenKeyVersion {
k := kms.AllocTokenKeyVersion()
id, err := db.NewPublicId(kms.TokenKeyVersionPrefix)
require.NoError(t, err)
k.PrivateId = id
return &k
}(),
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
deleteKey := kms.AllocTokenKeyVersion()
deleteKey.PrivateId = tt.key.PrivateId
deletedRows, err := rw.Delete(context.Background(), &deleteKey)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundKey := kms.AllocTokenKeyVersion()
foundKey.PrivateId = tt.key.PrivateId
err = rw.LookupById(context.Background(), &foundKey)
require.Error(err)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestTokenKeyVersion_Clone(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
t.Run("valid", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
k := kms.TestTokenKey(t, conn, rk.PrivateId)
kv := kms.TestTokenKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test key"))
cp := kv.Clone()
assert.True(proto.Equal(cp.(*kms.TokenKeyVersion).TokenKeyVersion, kv.TokenKeyVersion))
})
t.Run("not-equal", func(t *testing.T) {
assert := assert.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
db.TestDeleteWhere(t, conn, func() interface{} { i := kms.AllocRootKey(); return &i }(), "1=1")
rk := kms.TestRootKey(t, conn, org.PublicId)
rk2 := kms.TestRootKey(t, conn, org2.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
_, rkvWrapper2 := kms.TestRootKeyVersion(t, conn, wrapper, rk2.PrivateId)
k := kms.TestTokenKey(t, conn, rk.PrivateId)
k2 := kms.TestTokenKey(t, conn, rk2.PrivateId)
kv := kms.TestTokenKeyVersion(t, conn, rkvWrapper, k.PrivateId, []byte("test key"))
kv2 := kms.TestTokenKeyVersion(t, conn, rkvWrapper2, k2.PrivateId, []byte("test key 2"))
cp := kv.Clone()
assert.True(!proto.Equal(cp.(*kms.TokenKeyVersion).TokenKeyVersion, kv2.TokenKeyVersion))
})
}
func TestTokenKeyVersion_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := kms.DefaultTokenKeyVersionTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := kms.AllocTokenKeyVersion()
require.Equal(defaultTableName, def.TableName())
s := kms.AllocTokenKeyVersion()
s.SetTableName(tt.setNameTo)
assert.Equal(tt.want, s.TableName())
})
}
}
@@ -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"`
}
+4
View File
@@ -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