feature (kms/audit): Add base audit key type (#1651)

* feature (db): Add the ability to enable debug output for a connection

* refactor: Use scope.Global instead of "global"

* feature (db/migrations): Add kms audit keys

Add schema changes to support a new DEK for audit crypto operations.

* feature (kms/audit): Add audit key type

All the bits required to define a new DEK concrete type for
operating on audit entries.

* refactor (db): Remove deprecated migration unit tests.

We added a new audit DEK to kms.CreateKeysTx(...).  Unfortunately,
unit tests like TestMigrations_UserDimension,
Test_PrimaryAuthMethodChanges and TestMigrations_CredentialDimension
have an indirect dependency on a specific historical set of DEKs via
an indirect call kms.CreateKeysTx.  This of course fails because the
new DEK's tables is not a part of pinned migration versions that these
tests depend on.
This commit is contained in:
Jim authored and GitHub committed 2021-11-04 09:34:01 -04:00
1 parent dd2c3807cd
commit f63ca1313c
33 files changed
+2424 -512

No files matched your search

+1
View File
@@ -136,6 +136,7 @@ protobuild:
@protoc-go-inject-tag -input=./internal/credential/store/credential.pb.go
@protoc-go-inject-tag -input=./internal/credential/vault/store/vault.pb.go
@protoc-go-inject-tag -input=./internal/servers/servers.pb.go
@protoc-go-inject-tag -input=./internal/kms/store/audit_key.pb.go
# inject classification tags (see: https://github.com/hashicorp/go-eventlogger/tree/main/filters/encrypt)
@protoc-go-inject-tag -input=./internal/gen/controller/api/services/auth_method_service.pb.go
+2 -2
View File
@@ -193,7 +193,7 @@ func (b *Server) CreateInitialPasswordAuthMethod(ctx context.Context) (*password
if admin {
opts = append(opts,
iam.WithName("admin"),
iam.WithDescription(`Initial admin user within the "global" scope`),
iam.WithDescription(fmt.Sprintf(`Initial admin user within the "%s" scope`, scope.Global.String())),
)
} else {
opts = append(opts,
@@ -217,7 +217,7 @@ func (b *Server) CreateInitialPasswordAuthMethod(ctx context.Context) (*password
// Create a role tying them together
pr, err := iam.NewRole(scope.Global.String(),
iam.WithName("Administration"),
iam.WithDescription(`Provides admin grants within the "global" scope to the initial user`),
iam.WithDescription(fmt.Sprintf(`Provides admin grants within the "%s" scope to the initial user`, scope.Global.String())),
)
if err != nil {
return nil, fmt.Errorf("error creating in memory role for generated grants: %w", err)
+2 -1
View File
@@ -9,6 +9,7 @@ import (
"github.com/hashicorp/boundary/api/roles"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/mitchellh/go-wordwrap"
)
@@ -214,7 +215,7 @@ func extraFlagsHandlingFuncImpl(c *Command, _ *base.FlagSets, opts *[]roles.Opti
if len(c.flagGrants) > 0 {
for _, grant := range c.flagGrants {
_, err := perms.Parse("global", grant)
_, err := perms.Parse(scope.Global.String(), grant)
if err != nil {
c.UI.Error(fmt.Errorf("Grant %q could not be parsed successfully: %w", grant, err).Error())
return false
+3 -2
View File
@@ -14,6 +14,7 @@ import (
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/go-secure-stdlib/strutil"
"github.com/mitchellh/go-wordwrap"
"github.com/posener/complete"
@@ -912,9 +913,9 @@ var keySubstMap = map[string]string{
func exampleOutput() string {
item := &targets.Target{
Id: "ttcp_1234567890",
ScopeId: "global",
ScopeId: scope.Global.String(),
Scope: &scopes.ScopeInfo{
Id: "global",
Id: scope.Global.String(),
},
Name: "foo",
Description: "The bar of foos",
+2 -1
View File
@@ -10,6 +10,7 @@ import (
"strings"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/go-secure-stdlib/parseutil"
"github.com/posener/complete"
)
@@ -22,7 +23,7 @@ func PopulateCommonFlags(c *base.Command, f *base.FlagSet, resourceType string,
Name: "scope-id",
Target: &c.FlagScopeId,
EnvVar: "BOUNDARY_SCOPE_ID",
Default: "global",
Default: scope.Global.String(),
Completion: complete.PredictAnything,
Usage: `Scope in which to make the request.`,
})
+11
View File
@@ -44,6 +44,17 @@ type DB struct {
*gorm.DB
}
// Debug will enable/disable debug info for the connection
func (db *DB) Debug(on bool) {
if on {
// info level in the Gorm domain which maps to a debug level in the boundary domain
db.Logger = logger.Default.LogMode(logger.Info)
} else {
// the default level in the gorm domain is: error level
db.Logger = logger.Default.LogMode(logger.Error)
}
}
// SqlDB returns the underlying sql.DB
//
// Note: This func is not named DB(), because our choice of ORM (gorm) which is
@@ -0,0 +1,66 @@
begin;
-- kms_audit_key entries are DEKs for crypto operations on audit entries.
create table kms_audit_key (
private_id wt_private_id primary key,
root_key_id wt_private_id not null unique -- there can be only one audit dek per root key
references kms_root_key(private_id)
on delete cascade
on update cascade,
create_time wt_timestamp
);
-- define the immutable fields for kms_audit_key (all of them)
create trigger
immutable_columns
before
update on kms_audit_key
for each row execute procedure immutable_columns('private_id', 'root_key_id', 'create_time');
-- define the value of kms_audit_key's create_time
create trigger
default_create_time_column
before
insert on kms_audit_key
for each row execute procedure default_create_time();
-- kms_audit_key_version entries are version of DEK keys used crypto operations on audit entries.
-- entries.
create table kms_audit_key_version (
private_id wt_private_id primary key,
audit_key_id wt_private_id not null
references kms_audit_key(private_id)
on delete cascade
on update cascade,
root_key_version_id wt_private_id not null
references kms_root_key_version(private_id)
on delete cascade
on update cascade,
version wt_version,
key bytea not null,
create_time wt_timestamp,
constraint kms_audit_key_version_audit_key_id_uq
unique(audit_key_id, version)
);
-- define the immutable fields for kms_audit_key_version (all of them)
create trigger
immutable_columns
before
update on kms_audit_key_version
for each row execute procedure immutable_columns('private_id', 'audit_key_id', 'root_key_version_id', 'version', 'key', 'create_time');
-- define the value of kms_audit_key_version's create_time
create trigger
default_create_time_column
before
insert on kms_audit_key_version
for each row execute procedure default_create_time();
-- define the value of kms_audit_key_version's version column
create trigger
kms_version_column
before insert on kms_audit_key_version
for each row execute procedure kms_version_column('audit_key_id');
commit;
@@ -1,183 +0,0 @@
package oss_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/authtoken"
"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/host/static"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/session"
"github.com/hashicorp/boundary/internal/target"
"github.com/hashicorp/boundary/internal/target/tcp"
"github.com/hashicorp/boundary/testing/dbtest"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMigrations_UserDimension(t *testing.T) {
const (
priorMigration = 13001
currentMigration = 14001
)
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)
assert.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)
// okay, now we can seed the database with test data
dbType, err := db.StringToDbType(dialect)
require.NoError(err)
conn, err := db.Open(dbType, u)
require.NoError(err)
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
require.NotNil(prj)
assert.NotEmpty(prj.GetPublicId())
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
var sessions []*session.Session
kmsCache := kms.TestKms(t, conn, wrapper)
databaseWrapper, err := kmsCache.GetWrapper(ctx, org.GetPublicId(), kms.KeyPurposeDatabase)
require.NoError(err)
{
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
uId := at.GetIamUserId()
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
UserId: uId,
HostId: h.GetPublicId(),
TargetId: tar.GetPublicId(),
HostSetId: hs.GetPublicId(),
AuthTokenId: at.GetPublicId(),
ScopeId: prj.GetPublicId(),
Endpoint: "tcp://127.0.0.1:22",
})
sessions = append(sessions, sess)
}
{
at := testOidcAuthToken(t, conn, kmsCache, databaseWrapper, org.GetPublicId())
uId := at.GetIamUserId()
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
UserId: uId,
HostId: h.GetPublicId(),
TargetId: tar.GetPublicId(),
HostSetId: hs.GetPublicId(),
AuthTokenId: at.GetPublicId(),
ScopeId: prj.GetPublicId(),
Endpoint: "tcp://127.0.0.1:22",
})
sessions = append(sessions, sess)
}
sessionRepo, err := session.NewRepository(rw, rw, kmsCache)
require.NoError(err)
count, err := sessionRepo.TerminateCompletedSessions(ctx)
assert.NoError(err)
assert.Zero(count)
for _, sess := range sessions {
// call TerminateSession
_, err = sessionRepo.TerminateSession(ctx, sess.GetPublicId(), 1, session.ClosedByUser)
assert.NoError(err)
}
// 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)
assert.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)
}
func testOidcAuthToken(t *testing.T, conn *db.DB, kms *kms.Kms, wrapper wrapping.Wrapper, scopeId string) *authtoken.AuthToken {
t.Helper()
authMethod := oidc.TestAuthMethod(
t, conn, wrapper, scopeId, oidc.ActivePrivateState,
"alice-rp", "fido",
oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://www.alice.com")[0]),
oidc.WithSigningAlgs(oidc.RS256),
oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
)
acct := oidc.TestAccount(t, conn, authMethod, "test-subject")
ctx := context.Background()
rw := db.New(conn)
iamRepo, err := iam.NewRepository(rw, rw, kms)
require.NoError(t, err)
u := iam.TestUser(t, iamRepo, scopeId, iam.WithAccountIds(acct.PublicId))
repo, err := authtoken.NewRepository(rw, rw, kms)
require.NoError(t, err)
at, err := repo.CreateAuthToken(ctx, u, acct.GetPublicId())
require.NoError(t, err)
return at
}
@@ -1,183 +0,0 @@
package oss_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault"
"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/host/static"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/session"
"github.com/hashicorp/boundary/internal/target"
"github.com/hashicorp/boundary/internal/target/tcp"
"github.com/hashicorp/boundary/testing/dbtest"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMigrations_CredentialDimension(t *testing.T) {
const (
priorMigration = 15002
currentMigration = 16005
)
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)
dbType, err := db.StringToDbType(dialect)
require.NoError(err)
// okay, now we can seed the database with test data
conn, err := db.Open(dbType, u)
require.NoError(err)
rw := db.New(conn)
wrapper := db.TestWrapper(t)
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
require.NotNil(prj)
assert.NotEmpty(prj.GetPublicId())
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
tar := tcp.TestTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
var sessions []*session.Session
kmsCache := kms.TestKms(t, conn, wrapper)
databaseWrapper, err := kmsCache.GetWrapper(ctx, org.GetPublicId(), kms.KeyPurposeDatabase)
require.NoError(err)
{
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
uId := at.GetIamUserId()
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
UserId: uId,
HostId: h.GetPublicId(),
TargetId: tar.GetPublicId(),
HostSetId: hs.GetPublicId(),
AuthTokenId: at.GetPublicId(),
ScopeId: prj.GetPublicId(),
Endpoint: "tcp://127.0.0.1:22",
})
sessions = append(sessions, sess)
}
{
at := testOidcAuthToken(t, conn, kmsCache, databaseWrapper, org.GetPublicId())
uId := at.GetIamUserId()
creds := testSessionCredentialParams(t, conn, kmsCache, wrapper, tar)
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
UserId: uId,
HostId: h.GetPublicId(),
TargetId: tar.GetPublicId(),
HostSetId: hs.GetPublicId(),
AuthTokenId: at.GetPublicId(),
ScopeId: prj.GetPublicId(),
Endpoint: "tcp://127.0.0.1:22",
DynamicCredentials: creds,
})
sessions = append(sessions, sess)
}
sessionRepo, err := session.NewRepository(rw, rw, kmsCache)
require.NoError(err)
count, err := sessionRepo.TerminateCompletedSessions(ctx)
require.NoError(err)
assert.Zero(count)
for _, sess := range sessions {
// call TerminateSession
_, err = sessionRepo.TerminateSession(ctx, sess.GetPublicId(), 1, session.ClosedByUser)
assert.NoError(err)
}
// 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)
}
func testSessionCredentialParams(t *testing.T, conn *db.DB, kms *kms.Kms, wrapper wrapping.Wrapper, tar target.Target) []*session.DynamicCredential {
t.Helper()
rw := db.New(conn)
ctx := context.Background()
stores := vault.TestCredentialStores(t, conn, wrapper, tar.GetScopeId(), 1)
libIds := vault.TestCredentialLibraries(t, conn, wrapper, stores[0].GetPublicId(), 2)
libs := []*target.CredentialLibrary{
target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[0].GetPublicId(), credential.ApplicationPurpose),
target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[1].GetPublicId(), credential.ApplicationPurpose),
}
targetRepo, err := target.NewRepository(rw, rw, kms)
require.NoError(t, err)
_, _, _, err = targetRepo.AddTargetCredentialSources(ctx, tar.GetPublicId(), tar.GetVersion(), libs)
require.NoError(t, err)
creds := []*session.DynamicCredential{
session.NewDynamicCredential(libIds[0].GetPublicId(), credential.ApplicationPurpose),
session.NewDynamicCredential(libIds[1].GetPublicId(), credential.ApplicationPurpose),
}
return creds
}
@@ -1,126 +0,0 @@
package oss_test
import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/auth/password"
"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"
)
// Tests migration:
// migrations/oss/2/07_iam.up.sql
func Test_PrimaryAuthMethodChanges(t *testing.T) {
t.Parallel()
const priorMigration = 2006
const primaryAuthMethodMigration = 2007
t.Run("migrate-store", func(t *testing.T) {
// this is a sequential test which relies on:
// 1) initializing the db using a migration up to the "priorMigration"
//
// 2) seeding the database with some test scopes and auth methods
//
// 3) running the migrations that sets the primary auth methods for
// existing scopes which only have one auth method.
//
// 4) asserting some bits about the state of the db.
assert, require := assert.New(t), require.New(t)
dialect := dbtest.Postgres
ctx := context.Background()
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)
assert.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,
},
},
}
assert.Equal(want, state)
// okay, now we can seed the database with test data
dbType, err := db.StringToDbType(dialect)
require.NoError(err)
conn, err := db.Open(dbType, u)
require.NoError(err)
rootWrapper := db.TestWrapper(t)
iamRepo := iam.TestRepo(t, conn, rootWrapper)
org, _ := iam.TestScopes(t, iamRepo)
_ = password.TestAuthMethods(t, conn, org.PublicId, 3)
org2, _ := iam.TestScopes(t, iamRepo)
_ = password.TestAuthMethods(t, conn, org2.PublicId, 2)
org3, _ := iam.TestScopes(t, iamRepo)
org3AuthMethods := password.TestAuthMethods(t, conn, org3.PublicId, 1)
org4, _ := iam.TestScopes(t, iamRepo)
org4AuthMethods := password.TestAuthMethods(t, conn, org4.PublicId, 1)
// 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": primaryAuthMethodMigration}),
))
require.NoError(err)
assert.NoError(m.ApplyMigrations(ctx))
state, err = m.CurrentState(ctx)
require.NoError(err)
want = &schema.State{
Initialized: true,
Editions: []schema.EditionState{
{
Name: "oss",
BinarySchemaVersion: primaryAuthMethodMigration,
DatabaseSchemaVersion: primaryAuthMethodMigration,
DatabaseSchemaState: schema.Equal,
},
},
}
assert.Equal(want, state)
entries, err := m.GetMigrationLog(ctx)
require.NoError(err)
assert.Equalf(2, len(entries), "expected 2 scopes without a primary auth method and got: %d", len(entries))
scope1, err := iamRepo.LookupScope(ctx, org.PublicId)
require.NoError(err)
assert.Empty(scope1.PrimaryAuthMethodId)
scope2, err := iamRepo.LookupScope(ctx, org2.PublicId)
require.NoError(err)
assert.Empty(scope2.PrimaryAuthMethodId)
scope3, err := iamRepo.LookupScope(ctx, org3.PublicId)
require.NoError(err)
assert.Equal(org3AuthMethods[0].PublicId, scope3.PrimaryAuthMethodId)
scope4, err := iamRepo.LookupScope(ctx, org4.PublicId)
require.NoError(err)
assert.Equal(org4AuthMethods[0].PublicId, scope4.PrimaryAuthMethodId)
})
}
+1 -1
View File
@@ -474,7 +474,7 @@ func (r *Repository) ListScopesRecursively(ctx context.Context, rootScopeId stri
var where string
var args []interface{}
switch {
case rootScopeId == "global":
case rootScopeId == scope.Global.String():
// Nothing -- we want all scopes
case strings.HasPrefix(rootScopeId, "o_"):
// The org itself and any projects that have it as parent
+5 -4
View File
@@ -2,6 +2,7 @@ package iam
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
@@ -36,7 +37,7 @@ var (
func NewOrg(opt ...Option) (*Scope, error) {
global := AllocScope()
global.PublicId = "global"
global.PublicId = scope.Global.String()
return newScope(&global, opt...)
}
@@ -63,7 +64,7 @@ func newScope(parent *Scope, opt ...Option) (*Scope, error) {
}
var typ scope.Type
switch {
case parent.PublicId == "global":
case parent.PublicId == scope.Global.String():
typ = scope.Org
case strings.HasPrefix(parent.PublicId, scope.Org.Prefix()):
typ = scope.Project
@@ -129,8 +130,8 @@ func (s *Scope) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType,
case s.ParentId == "":
return errors.New(ctx, errors.InvalidParameter, op, "scope must have a parent")
case s.Type == scope.Org.String():
if s.ParentId != "global" {
return errors.New(ctx, errors.InvalidParameter, op, `org's parent must be "global"`)
if s.ParentId != scope.Global.String() {
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf(`org's parent must be "%s"`, scope.Global.String()))
}
case s.Type == scope.Project.String():
parentScope := AllocScope()
+82
View File
@@ -0,0 +1,82 @@
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
}
+207
View File
@@ -0,0 +1,207 @@
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))
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
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)
require.NoError(conn.Where("1=1").Delete(kms.AllocAuditKey()).Error)
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))
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
rk := kms.TestRootKey(t, conn, org.PublicId)
require.NoError(t, conn.Where("1=1").Delete(kms.AllocAuditKey()).Error)
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))
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
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))
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
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
@@ -0,0 +1,122 @@
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"
"github.com/hashicorp/go-kms-wrapping/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
@@ -0,0 +1,250 @@
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))
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
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)
require.NoError(conn.Where("1=1").Delete(kms.AllocAuditKeyVersion()).Error)
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))
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
dk := kms.TestAuditKey(t, conn, rk.PrivateId)
require.NoError(t, conn.Where("1=1").Delete(kms.AllocAuditKeyVersion()).Error)
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))
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
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))
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
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())
})
}
}
+11
View File
@@ -28,6 +28,9 @@ const (
// KeyPurposeOidc is used for encrypting oidc states included in
// authentication URLs
KeyPurposeOidc
// KeyPurposeAudit is used for audit operations
KeyPurposeAudit
)
// String returns the key purpose cast as a string, just so it can be called as
@@ -46,6 +49,8 @@ func (k KeyPurpose) String() string {
return "sessions"
case KeyPurposeOidc:
return "oidc"
case KeyPurposeAudit:
return "audit"
default:
return "unknown"
}
@@ -69,6 +74,8 @@ const (
KeyTypeSessionKeyVersion
KeyTypeOidcKey
KeyTypeOidcKeyVersion
KeyTypeAuditKey
KeyTypeAuditKeyVersion
)
// String returns the key type cast as a string, just so it can be called as
@@ -99,6 +106,10 @@ func (k KeyType) String() string {
return "oidcKey"
case KeyTypeOidcKeyVersion:
return "oidcKeyVersion"
case KeyTypeAuditKey:
return "auditKey"
case KeyTypeAuditKeyVersion:
return "auditKeyVersion"
default:
return "unknown"
+22
View File
@@ -1,6 +1,8 @@
package kms
import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
)
@@ -18,6 +20,8 @@ const (
SessionKeyVersionPrefix = "kskv"
OidcKeyPrefix = "koidck"
OidcKeyVersionPrefix = "koidckv"
AuditKeyPrefix = "kak"
AuditKeyVersionPrefix = "kakv"
)
func newRootKeyId() (string, error) {
@@ -127,3 +131,21 @@ func newOidcKeyVersionId() (string, error) {
}
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
}
+11
View File
@@ -1,6 +1,7 @@
package kms
import (
"context"
"strings"
"testing"
@@ -70,4 +71,14 @@ func Test_Ids(t *testing.T) {
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+"_"))
})
}
+167
View File
@@ -997,3 +997,170 @@ func TestOidcKeyVersion_ImmutableFields(t *testing.T) {
})
}
}
func TestAuditKey_ImmutableFields(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
ts := timestamp.Timestamp{Timestamp: &timestamppb.Timestamp{Seconds: 0, Nanos: 0}}
wrapper := db.TestWrapper(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
require.NoError(t, conn.Where("1=1").Delete(kms.AllocAuditKey()).Error)
rk := kms.TestRootKey(t, conn, org.PublicId)
new := kms.TestAuditKey(t, conn, rk.PrivateId)
tests := []struct {
name string
update *kms.AuditKey
fieldMask []string
}{
{
name: "private_id",
update: func() *kms.AuditKey {
k := new.Clone().(*kms.AuditKey)
k.PrivateId = "o_thisIsNotAValidId"
return k
}(),
fieldMask: []string{"PrivateId"},
},
{
name: "create time",
update: func() *kms.AuditKey {
k := new.Clone().(*kms.AuditKey)
k.CreateTime = &ts
return k
}(),
fieldMask: []string{"CreateTime"},
},
{
name: "root_key_id",
update: func() *kms.AuditKey {
k := new.Clone().(*kms.AuditKey)
k.RootKeyId = "o_thisIsNotAValidId"
return k
}(),
fieldMask: []string{"ScopeId"},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
orig := new.Clone()
err := rw.LookupById(context.Background(), orig)
require.NoError(err)
rowsUpdated, err := rw.Update(context.Background(), tt.update, tt.fieldMask, nil, db.WithSkipVetForWrite(true))
require.Error(err)
assert.Equal(0, rowsUpdated)
after := new.Clone()
err = rw.LookupById(context.Background(), after)
require.NoError(err)
assert.True(proto.Equal(orig.(*kms.AuditKey), after.(*kms.AuditKey)))
})
}
}
func TestAuditKeyVersion_ImmutableFields(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
rw := db.New(conn)
ts := timestamp.Timestamp{Timestamp: &timestamppb.Timestamp{Seconds: 0, Nanos: 0}}
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
rk := kms.TestRootKey(t, conn, org.PublicId)
_, rkvWrapper := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId)
conn.Debug(true)
dk := kms.TestAuditKey(t, conn, rk.PrivateId)
new := kms.TestAuditKeyVersion(t, conn, rkvWrapper, dk.PrivateId, []byte("audit key"))
tests := []struct {
name string
update *kms.AuditKeyVersion
fieldMask []string
}{
{
name: "private_id",
update: func() *kms.AuditKeyVersion {
k := new.Clone().(*kms.AuditKeyVersion)
k.PrivateId = "o_thisIsNotAValidId"
return k
}(),
fieldMask: []string{"PrivateId"},
},
{
name: "create time",
update: func() *kms.AuditKeyVersion {
k := new.Clone().(*kms.AuditKeyVersion)
k.CreateTime = &ts
return k
}(),
fieldMask: []string{"CreateTime"},
},
{
name: "audit_key_id",
update: func() *kms.AuditKeyVersion {
k := new.Clone().(*kms.AuditKeyVersion)
k.AuditKeyId = "o_thisIsNotAValidId"
return k
}(),
fieldMask: []string{"RootKeyId"},
},
{
name: "root_key_version_id",
update: func() *kms.AuditKeyVersion {
k := new.Clone().(*kms.AuditKeyVersion)
k.RootKeyVersionId = "o_thisIsNotAValidId"
return k
}(),
fieldMask: []string{"RootKeyId"},
},
{
name: "version",
update: func() *kms.AuditKeyVersion {
k := new.Clone().(*kms.AuditKeyVersion)
k.Version = uint32(22)
return k
}(),
fieldMask: []string{"Version"},
},
{
name: "key",
update: func() *kms.AuditKeyVersion {
k := new.Clone().(*kms.AuditKeyVersion)
k.Key = []byte("updated key")
return k
}(),
fieldMask: []string{"CtKey"},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
orig := new.Clone()
err := rw.LookupById(context.Background(), orig)
require.NoError(err)
err = tt.update.Encrypt(context.Background(), wrapper)
require.NoError(err)
rowsUpdated, err := rw.Update(context.Background(), tt.update, tt.fieldMask, nil, db.WithSkipVetForWrite(true))
require.Error(err)
assert.Equal(0, rowsUpdated)
after := new.Clone()
err = rw.LookupById(context.Background(), after)
require.NoError(err)
assert.True(proto.Equal(orig.(*kms.AuditKeyVersion), after.(*kms.AuditKeyVersion)))
})
}
}
+5 -1
View File
@@ -152,7 +152,7 @@ func (k *Kms) GetWrapper(ctx context.Context, scopeId string, purpose KeyPurpose
}
switch purpose {
case KeyPurposeOplog, KeyPurposeDatabase, KeyPurposeTokens, KeyPurposeSessions, KeyPurposeOidc:
case KeyPurposeOplog, KeyPurposeDatabase, KeyPurposeTokens, KeyPurposeSessions, KeyPurposeOidc, KeyPurposeAudit:
case KeyPurposeUnknown:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing key purpose")
default:
@@ -309,6 +309,8 @@ func (k *Kms) loadDek(ctx context.Context, scopeId string, purpose KeyPurpose, r
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")
}
@@ -338,6 +340,8 @@ func (k *Kms) loadDek(ctx context.Context, scopeId string, purpose KeyPurpose, r
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")
}
+10 -1
View File
@@ -7,6 +7,7 @@ import (
"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"
"github.com/hashicorp/go-kms-wrapping/wrappers/aead"
"github.com/hashicorp/go-uuid"
@@ -161,7 +162,6 @@ func CreateKeysTx(ctx context.Context, dbReader db.Reader, dbWriter db.Writer, r
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,
@@ -176,6 +176,15 @@ func CreateKeysTx(ctx context.Context, dbReader db.Reader, dbWriter db.Writer, r
KeyTypeOidcKey: oidcKey,
KeyTypeOidcKeyVersion: oidcKeyVersion,
}
if scopeId == scope.Global.String() {
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
}
+160
View File
@@ -0,0 +1,160 @@
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"
)
// 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 := rkvWrapper.KeyID()
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
}
+297
View File
@@ -0,0 +1,297 @@
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"
"github.com/hashicorp/go-kms-wrapping/wrappers/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))
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
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(map[string]string{
"key_id": "",
})
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))
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
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 {
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
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))
require.NoError(conn.Where("scope_id in(?)", []interface{}{"global", org.PublicId, proj.PublicId}).Delete(kms.AllocRootKey()).Error)
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))
})
}
}
@@ -0,0 +1,173 @@
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"
)
// 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 := rkvWrapper.KeyID()
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
}
@@ -0,0 +1,367 @@
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"
"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))
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
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))
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
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))
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
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)
require.NoError(conn.Where("1=1").Delete(kms.AllocAuditKeyVersion()).Error)
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))
require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error)
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)
require.NoError(conn.Where("1=1").Delete(kms.AllocAuditKeyVersion()).Error)
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))
})
}
}
+26
View File
@@ -10,6 +10,7 @@ import (
"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"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -111,6 +112,16 @@ func TestCreateKeysTx(t *testing.T) {
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{
@@ -257,6 +268,21 @@ func TestCreateKeysTx(t *testing.T) {
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)
}
})
}
}
+326
View File
@@ -0,0 +1,326 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// source: controller/storage/kms/store/v1/audit_key.proto
package store
import (
timestamp "github.com/hashicorp/boundary/internal/db/timestamp"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type AuditKey struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// private_id is used to access the key via an API
// @inject_tag: gorm:"primary_key"
PrivateId string `protobuf:"bytes,10,opt,name=private_id,json=privateId,proto3" json:"private_id,omitempty" gorm:"primary_key"`
// root key id for the key
// @inject_tag: `gorm:"default:null"`
RootKeyId string `protobuf:"bytes,20,opt,name=root_key_id,json=rootKeyId,proto3" json:"root_key_id,omitempty" gorm:"default:null"`
// create_time from the RDBMS
// @inject_tag: `gorm:"default:current_timestamp"`
CreateTime *timestamp.Timestamp `protobuf:"bytes,30,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
}
func (x *AuditKey) Reset() {
*x = AuditKey{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_kms_store_v1_audit_key_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AuditKey) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AuditKey) ProtoMessage() {}
func (x *AuditKey) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_kms_store_v1_audit_key_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AuditKey.ProtoReflect.Descriptor instead.
func (*AuditKey) Descriptor() ([]byte, []int) {
return file_controller_storage_kms_store_v1_audit_key_proto_rawDescGZIP(), []int{0}
}
func (x *AuditKey) GetPrivateId() string {
if x != nil {
return x.PrivateId
}
return ""
}
func (x *AuditKey) GetRootKeyId() string {
if x != nil {
return x.RootKeyId
}
return ""
}
func (x *AuditKey) GetCreateTime() *timestamp.Timestamp {
if x != nil {
return x.CreateTime
}
return nil
}
type AuditKeyVersion struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// private_id is used to access the key version via an API
// @inject_tag: gorm:"primary_key"
PrivateId string `protobuf:"bytes,10,opt,name=private_id,json=privateId,proto3" json:"private_id,omitempty" gorm:"primary_key"`
// id for the key version
// @inject_tag: `gorm:"default:null"`
AuditKeyId string `protobuf:"bytes,20,opt,name=audit_key_id,json=auditKeyId,proto3" json:"audit_key_id,omitempty" gorm:"default:null"`
// root_key_version_id of the version of the root key data.
// @inject_tag: `gorm:"default:null"`
RootKeyVersionId string `protobuf:"bytes,30,opt,name=root_key_version_id,json=rootKeyVersionId,proto3" json:"root_key_version_id,omitempty" gorm:"default:null"`
// plain-text of the key data. we are NOT storing this plain-text key
// in the db.
// @inject_tag: `gorm:"-" wrapping:"pt,key_data"`
Key []byte `protobuf:"bytes,40,opt,name=key,proto3" json:"key,omitempty" gorm:"-" wrapping:"pt,key_data"`
// ciphertext key data stored in the database
// @inject_tag: `gorm:"column:key;not_null" wrapping:"ct,key_data"`
CtKey []byte `protobuf:"bytes,50,opt,name=ct_key,json=ctKey,proto3" json:"ct_key,omitempty" gorm:"column:key;not_null" wrapping:"ct,key_data"`
// version of the key data. This is not used for optimistic locking, since
// key versions are immutable. It's just the version of the key.
// @inject_tag: `gorm:"default:null"`
Version uint32 `protobuf:"varint,60,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"`
// create_time from the RDBMS
// @inject_tag: `gorm:"default:current_timestamp"`
CreateTime *timestamp.Timestamp `protobuf:"bytes,70,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
}
func (x *AuditKeyVersion) Reset() {
*x = AuditKeyVersion{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_kms_store_v1_audit_key_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AuditKeyVersion) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AuditKeyVersion) ProtoMessage() {}
func (x *AuditKeyVersion) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_kms_store_v1_audit_key_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AuditKeyVersion.ProtoReflect.Descriptor instead.
func (*AuditKeyVersion) Descriptor() ([]byte, []int) {
return file_controller_storage_kms_store_v1_audit_key_proto_rawDescGZIP(), []int{1}
}
func (x *AuditKeyVersion) GetPrivateId() string {
if x != nil {
return x.PrivateId
}
return ""
}
func (x *AuditKeyVersion) GetAuditKeyId() string {
if x != nil {
return x.AuditKeyId
}
return ""
}
func (x *AuditKeyVersion) GetRootKeyVersionId() string {
if x != nil {
return x.RootKeyVersionId
}
return ""
}
func (x *AuditKeyVersion) GetKey() []byte {
if x != nil {
return x.Key
}
return nil
}
func (x *AuditKeyVersion) GetCtKey() []byte {
if x != nil {
return x.CtKey
}
return nil
}
func (x *AuditKeyVersion) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
func (x *AuditKeyVersion) GetCreateTime() *timestamp.Timestamp {
if x != nil {
return x.CreateTime
}
return nil
}
var File_controller_storage_kms_store_v1_audit_key_proto protoreflect.FileDescriptor
var file_controller_storage_kms_store_v1_audit_key_proto_rawDesc = []byte{
0x0a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f,
0x72, 0x61, 0x67, 0x65, 0x2f, 0x6b, 0x6d, 0x73, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76,
0x31, 0x2f, 0x61, 0x75, 0x64, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x12, 0x1f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74,
0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x6b, 0x6d, 0x73, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e,
0x76, 0x31, 0x1a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73,
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x22, 0x96, 0x01, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x74, 0x4b, 0x65, 0x79,
0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0a,
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12,
0x1e, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x14,
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x12,
0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x91, 0x02, 0x0a,
0x0f, 0x41, 0x75, 0x64, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0a,
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12,
0x20, 0x0a, 0x0c, 0x61, 0x75, 0x64, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18,
0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x75, 0x64, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x49,
0x64, 0x12, 0x2d, 0x0a, 0x13, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10,
0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64,
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b,
0x65, 0x79, 0x12, 0x15, 0x0a, 0x06, 0x63, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x32, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x05, 0x63, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73,
0x69, 0x6f, 0x6e, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69,
0x6d, 0x65, 0x18, 0x46, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65,
0x42, 0x38, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68,
0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72,
0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6b, 0x6d, 0x73, 0x2f, 0x73,
0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (
file_controller_storage_kms_store_v1_audit_key_proto_rawDescOnce sync.Once
file_controller_storage_kms_store_v1_audit_key_proto_rawDescData = file_controller_storage_kms_store_v1_audit_key_proto_rawDesc
)
func file_controller_storage_kms_store_v1_audit_key_proto_rawDescGZIP() []byte {
file_controller_storage_kms_store_v1_audit_key_proto_rawDescOnce.Do(func() {
file_controller_storage_kms_store_v1_audit_key_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_kms_store_v1_audit_key_proto_rawDescData)
})
return file_controller_storage_kms_store_v1_audit_key_proto_rawDescData
}
var file_controller_storage_kms_store_v1_audit_key_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_controller_storage_kms_store_v1_audit_key_proto_goTypes = []interface{}{
(*AuditKey)(nil), // 0: controller.storage.kms.store.v1.AuditKey
(*AuditKeyVersion)(nil), // 1: controller.storage.kms.store.v1.AuditKeyVersion
(*timestamp.Timestamp)(nil), // 2: controller.storage.timestamp.v1.Timestamp
}
var file_controller_storage_kms_store_v1_audit_key_proto_depIdxs = []int32{
2, // 0: controller.storage.kms.store.v1.AuditKey.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
2, // 1: controller.storage.kms.store.v1.AuditKeyVersion.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_controller_storage_kms_store_v1_audit_key_proto_init() }
func file_controller_storage_kms_store_v1_audit_key_proto_init() {
if File_controller_storage_kms_store_v1_audit_key_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_controller_storage_kms_store_v1_audit_key_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AuditKey); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_controller_storage_kms_store_v1_audit_key_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AuditKeyVersion); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_storage_kms_store_v1_audit_key_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_controller_storage_kms_store_v1_audit_key_proto_goTypes,
DependencyIndexes: file_controller_storage_kms_store_v1_audit_key_proto_depIdxs,
MessageInfos: file_controller_storage_kms_store_v1_audit_key_proto_msgTypes,
}.Build()
File_controller_storage_kms_store_v1_audit_key_proto = out.File
file_controller_storage_kms_store_v1_audit_key_proto_rawDesc = nil
file_controller_storage_kms_store_v1_audit_key_proto_goTypes = nil
file_controller_storage_kms_store_v1_audit_key_proto_depIdxs = nil
}
+36
View File
@@ -229,3 +229,39 @@ func TestOidcKeyVersion(t *testing.T, conn *db.DB, rootKeyVersionWrapper wrappin
require.NoError(err)
return k
}
func TestAuditKey(t *testing.T, conn *db.DB, rootKeyId string) *AuditKey {
t.Helper()
ctx := context.Background()
require := require.New(t)
rw := db.New(conn)
require.NoError(conn.Where("root_key_id = ?", rootKeyId).Delete(AllocAuditKey()).Error)
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.T, 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 := rootKeyVersionWrapper.KeyID()
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
}
+1 -1
View File
@@ -290,7 +290,7 @@ func Parse(scopeId, grantString string, opt ...Option) (Grant, error) {
scope: Scope{Id: scopeId},
}
switch {
case scopeId == "global":
case scopeId == scope.Global.String():
grant.scope.Type = scope.Global
case strings.HasPrefix(scopeId, scope.Org.Prefix()):
grant.scope.Type = scope.Org
@@ -0,0 +1,52 @@
syntax = "proto3";
package controller.storage.kms.store.v1;
option go_package = "github.com/hashicorp/boundary/internal/kms/store;store";
import "controller/storage/timestamp/v1/timestamp.proto";
message AuditKey {
// private_id is used to access the key via an API
// @inject_tag: gorm:"primary_key"
string private_id = 10;
// root key id for the key
// @inject_tag: `gorm:"default:null"`
string root_key_id = 20;
// create_time from the RDBMS
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp create_time = 30;
}
message AuditKeyVersion {
// private_id is used to access the key version via an API
// @inject_tag: gorm:"primary_key"
string private_id = 10;
// id for the key version
// @inject_tag: `gorm:"default:null"`
string audit_key_id = 20;
// root_key_version_id of the version of the root key data.
// @inject_tag: `gorm:"default:null"`
string root_key_version_id = 30;
// plain-text of the key data. we are NOT storing this plain-text key
// in the db.
// @inject_tag: `gorm:"-" wrapping:"pt,key_data"`
bytes key = 40;
// ciphertext key data stored in the database
// @inject_tag: `gorm:"column:key;not_null" wrapping:"ct,key_data"`
bytes ct_key = 50;
// version of the key data. This is not used for optimistic locking, since
// key versions are immutable. It's just the version of the key.
// @inject_tag: `gorm:"default:null"`
uint32 version = 60;
// create_time from the RDBMS
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp create_time = 70;
}
+2 -2
View File
@@ -173,7 +173,7 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
// Look up scope details to return. We can skip a lookup when using the
// global scope
switch ret.Scope.Id {
case "global":
case scope.Global.String():
ret.Scope = &scopes.ScopeInfo{
Id: scope.Global.String(),
Type: scope.Global.String(),
@@ -503,7 +503,7 @@ func (v verifier) performAuthCheck(ctx context.Context) (
// Look up scope details to return. We can skip a lookup when using the
// global scope
switch v.res.ScopeId {
case "global":
case scope.Global.String():
scopeInfo = &scopes.ScopeInfo{
Id: scope.Global.String(),
Type: scope.Global.String(),
@@ -225,7 +225,7 @@ func (s Service) GetScope(ctx context.Context, req *pbs.GetScopeRequest) (*pbs.G
}
act := IdActions
// Can't delete global so elide it
if p.GetPublicId() == "global" {
if p.GetPublicId() == scope.Global.String() {
act = act[0:3]
}
if outputFields.Has(globals.AuthorizedActionsField) {
@@ -596,7 +596,7 @@ func validateGetRequest(req *pbs.GetScopeRequest) error {
badFields := map[string]string{}
id := req.GetId()
switch {
case id == "global":
case id == scope.Global.String():
case strings.HasPrefix(id, scope.Org.Prefix()):
if !handlers.ValidId(handlers.Id(id), scope.Org.Prefix()) {
badFields["id"] = "Invalidly formatted scope id."
@@ -654,7 +654,7 @@ func validateUpdateRequest(req *pbs.UpdateScopeRequest) error {
badFields := map[string]string{}
id := req.GetId()
switch {
case id == "global":
case id == scope.Global.String():
case strings.HasPrefix(id, scope.Org.Prefix()):
if !handlers.ValidId(handlers.Id(id), scope.Org.Prefix()) {
badFields["id"] = "Invalidly formatted scope id."
@@ -711,7 +711,7 @@ func validateDeleteRequest(req *pbs.DeleteScopeRequest) error {
badFields := map[string]string{}
id := req.GetId()
switch {
case id == "global":
case id == scope.Global.String():
badFields["id"] = "Invalid to delete the global scope."
case strings.HasPrefix(id, scope.Org.Prefix()):
if !handlers.ValidId(handlers.Id(id), scope.Org.Prefix()) {