diff --git a/Makefile b/Makefile index 8fb307760..acbe47dc6 100644 --- a/Makefile +++ b/Makefile @@ -84,6 +84,7 @@ protobuild: @protoc-go-inject-tag -input=./internal/auth/store/account.pb.go @protoc-go-inject-tag -input=./internal/auth/password/store/password.pb.go @protoc-go-inject-tag -input=./internal/auth/password/store/argon2.pb.go + @protoc-go-inject-tag -input=./internal/kms/store/root_key.pb.go @rm -R ${TMP_DIR} protolint: diff --git a/api/authmethods/account_test.go b/api/authmethods/account_test.go index e07d40dc0..a29600257 100644 --- a/api/authmethods/account_test.go +++ b/api/authmethods/account_test.go @@ -26,7 +26,7 @@ func TestAccounts_List(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org := iam.TestOrg(t, tc.DbConn()) + org := iam.TestOrg(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) accountClient := authmethods.NewAccountsClient(client) @@ -88,7 +88,7 @@ func TestAccount_Crud(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org := iam.TestOrg(t, tc.DbConn()) + org := iam.TestOrg(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) accountClient := authmethods.NewAccountsClient(client) @@ -142,7 +142,7 @@ func TestAccount_CustomMethods(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org := iam.TestOrg(t, tc.DbConn()) + org := iam.TestOrg(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) accountClient := authmethods.NewAccountsClient(client) @@ -179,7 +179,7 @@ func TestAccount_Errors(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org := iam.TestOrg(t, tc.DbConn()) + org := iam.TestOrg(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) accountClient := authmethods.NewAccountsClient(client) diff --git a/api/groups/group_test.go b/api/groups/group_test.go index 8b999220e..1de95544b 100644 --- a/api/groups/group_test.go +++ b/api/groups/group_test.go @@ -26,7 +26,7 @@ func TestGroup_List(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org, proj := iam.TestScopes(t, tc.DbConn()) + org, proj := iam.TestScopes(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) projClient := client.Clone() projClient.SetScopeId(proj.GetPublicId()) @@ -96,7 +96,7 @@ func TestGroup_Crud(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org, proj := iam.TestScopes(t, tc.DbConn()) + org, proj := iam.TestScopes(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) projClient := client.Clone() projClient.SetScopeId(proj.GetPublicId()) @@ -188,7 +188,7 @@ func TestGroup_Errors(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org, proj := iam.TestScopes(t, tc.DbConn()) + org, proj := iam.TestScopes(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) projClient := client.Clone() projClient.SetScopeId(proj.GetPublicId()) diff --git a/api/hosts/host_catalog_test.go b/api/hosts/host_catalog_test.go index 10ceabb0b..52436a059 100644 --- a/api/hosts/host_catalog_test.go +++ b/api/hosts/host_catalog_test.go @@ -25,7 +25,7 @@ func TestCatalogs_Crud(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org, proj := iam.TestScopes(t, tc.DbConn()) + org, proj := iam.TestScopes(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) projClient := client.Clone() projClient.SetScopeId(proj.GetPublicId()) @@ -80,7 +80,7 @@ func TestCatalogs_Errors(t *testing.T) { defer tc.Shutdown() client := tc.Client() - _, proj := iam.TestScopes(t, tc.DbConn()) + _, proj := iam.TestScopes(t, tc.IamRepo()) client.SetScopeId(proj.GetPublicId()) pc := hosts.NewHostCatalogsClient(client) diff --git a/api/roles/role_test.go b/api/roles/role_test.go index f87364cf3..764c44816 100644 --- a/api/roles/role_test.go +++ b/api/roles/role_test.go @@ -28,7 +28,7 @@ func TestCustom(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org, proj := iam.TestScopes(t, tc.DbConn()) + org, proj := iam.TestScopes(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) projClient := client.Clone() projClient.SetScopeId(proj.GetPublicId()) @@ -141,7 +141,7 @@ func TestRole_List(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org, proj := iam.TestScopes(t, tc.DbConn()) + org, proj := iam.TestScopes(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) projClient := client.Clone() projClient.SetScopeId(proj.GetPublicId()) @@ -222,7 +222,7 @@ func TestRole_Crud(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org, proj := iam.TestScopes(t, tc.DbConn()) + org, proj := iam.TestScopes(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) projClient := client.Clone() projClient.SetScopeId(proj.GetPublicId()) @@ -295,7 +295,7 @@ func TestRole_Errors(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org, proj := iam.TestScopes(t, tc.DbConn()) + org, proj := iam.TestScopes(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) projClient := client.Clone() projClient.SetScopeId(proj.GetPublicId()) diff --git a/api/scopes/project_test.go b/api/scopes/project_test.go index c41bf88ff..8ed0102ca 100644 --- a/api/scopes/project_test.go +++ b/api/scopes/project_test.go @@ -25,7 +25,7 @@ func TestProjects_List(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org := iam.TestOrg(t, tc.DbConn()) + org := iam.TestOrg(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) scps := scopes.NewScopesClient(client) @@ -74,7 +74,7 @@ func TestProjects_Crud(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org, _ := iam.TestScopes(t, tc.DbConn()) + org, _ := iam.TestScopes(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) scps := scopes.NewScopesClient(client) @@ -127,7 +127,7 @@ func TestProject_Errors(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org, _ := iam.TestScopes(t, tc.DbConn()) + org, _ := iam.TestScopes(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) scps := scopes.NewScopesClient(client) diff --git a/api/users/user_test.go b/api/users/user_test.go index cfb4845d1..e3a628ae6 100644 --- a/api/users/user_test.go +++ b/api/users/user_test.go @@ -25,7 +25,7 @@ func TestUsers_List(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org := iam.TestOrg(t, tc.DbConn()) + org := iam.TestOrg(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) userClient := users.NewUsersClient(client) @@ -86,7 +86,7 @@ func TestUser_Crud(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org := iam.TestOrg(t, tc.DbConn()) + org := iam.TestOrg(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) userClient := users.NewUsersClient(client) @@ -139,7 +139,7 @@ func TestUser_Errors(t *testing.T) { defer tc.Shutdown() client := tc.Client() - org := iam.TestOrg(t, tc.DbConn()) + org := iam.TestOrg(t, tc.IamRepo()) client.SetScopeId(org.GetPublicId()) userClient := users.NewUsersClient(client) diff --git a/go.mod b/go.mod index 791d0acb3..e13459e30 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/hashicorp/go-alpnmux v0.0.0-20200513011953-0293f5d23c31 github.com/hashicorp/go-cleanhttp v0.5.1 github.com/hashicorp/go-hclog v0.14.1 - github.com/hashicorp/go-kms-wrapping v0.5.12 + github.com/hashicorp/go-kms-wrapping v0.5.14 github.com/hashicorp/go-multierror v1.1.0 github.com/hashicorp/go-plugin v1.2.0 // indirect github.com/hashicorp/go-retryablehttp v0.6.6 diff --git a/go.sum b/go.sum index a6d449f8b..473d04a92 100644 --- a/go.sum +++ b/go.sum @@ -158,6 +158,7 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4Yn github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bufbuild/buf v0.20.5 h1:msLCy5vBoCdFXD+PxtMNA43KMkVJml7UCBCSaFWmEkU= github.com/bufbuild/buf v0.20.5/go.mod h1:Kgpp44ayt6oQ0J0kXEzIkYRfPggDxOriiEHNswd2vEo= +github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -528,6 +529,8 @@ github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTME github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-kms-wrapping v0.5.12 h1:4zZCaLqOob5moaAmpS6ZtGZYm4yOcqvmt2lO+zNXHls= github.com/hashicorp/go-kms-wrapping v0.5.12/go.mod h1:yVIWtGOTh/cdGc++/NOlXLus0hJ19Lz4iFrpF6WsZh4= +github.com/hashicorp/go-kms-wrapping v0.5.14 h1:XtNZYE9n4ypz9CnhQc/5Vd6ovGqbHO1lQ3IVPc2TV+w= +github.com/hashicorp/go-kms-wrapping v0.5.14/go.mod h1:hKJ7tS+eMXOLxwFs7mYJtPRQtT/rGtwqE6awY3JATCw= github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= @@ -572,10 +575,13 @@ github.com/hashicorp/shared-secure-libs v0.0.2/go.mod h1:xhtA0FH6AYYFOy0sir7u0O0 github.com/hashicorp/vault/api v1.0.5-0.20200514164214-89b1987e38c2/go.mod h1:P8A7gn1a6j/7qQ4zOaWI6FC/Q9jQWEBRTOwZH33tC9o= github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f h1:PYtnlUZzFSZxPcq7mYp5oC9N+BcJ8IKYf6/EG0GHM2Y= github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f/go.mod h1:euTFbi2YJgwcju3imEt919lhJKF68nN1cQPq3aA+kBE= +github.com/hashicorp/vault/api v1.0.5-0.20200805123347-1ef507638af6 h1:ChHpobQse3kjVX7GmbpUFVqaMTf4lb1gcLrJltsUmS4= +github.com/hashicorp/vault/api v1.0.5-0.20200805123347-1ef507638af6/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk= github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= github.com/hashicorp/vault/sdk v0.1.14-0.20200514144402-4bfac290c352/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= github.com/hashicorp/vault/sdk v0.1.14-0.20200514164214-89b1987e38c2/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= +github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= github.com/hashicorp/vault/sdk v0.1.14-0.20200805123347-1ef507638af6 h1:8bzfSEdW5ztWR73O5ByXbOAx8V6lHL1qTH7PYDLRITI= github.com/hashicorp/vault/sdk v0.1.14-0.20200805123347-1ef507638af6/go.mod h1:+S2qzS1Tex9JgbHxb/Jv7CdZyKydxqg09G/qVvyVmUc= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= @@ -1027,6 +1033,8 @@ github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhe github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yandex-cloud/go-genproto v0.0.0-20200722140432-762fe965ce77/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= +github.com/yandex-cloud/go-sdk v0.0.0-20200722140627-2194e5077f13/go.mod h1:LEdAMqa1v/7KYe4b13ALLkonuDxLph57ibUb50ctvJk= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1165,6 +1173,7 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -1379,6 +1388,7 @@ google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200323114720-3f67cca34472/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go index ee40edbc1..5a3ee8d65 100644 --- a/internal/auth/auth_test.go +++ b/internal/auth/auth_test.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/boundary/internal/authtoken" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/types/action" "github.com/hashicorp/boundary/internal/types/resource" "github.com/kr/pretty" @@ -19,14 +20,12 @@ import ( func TestHandler_AuthDecoration(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrapper := db.TestWrapper(t) - iamRepo, err := iam.NewRepository(rw, rw, wrapper) - require.NoError(t, err) + iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } - org, proj := iam.TestScopes(t, conn) + org, proj := iam.TestScopes(t, iamRepo) cases := []struct { name string path string @@ -272,10 +271,10 @@ func TestAuthTokenAuthenticator(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - tokenRepo, err := authtoken.NewRepository(rw, rw, wrapper) - require.NoError(t, err) - iamRepo, err := iam.NewRepository(rw, rw, wrapper) + kms := kms.TestKms(t, conn, wrapper) + tokenRepo, err := authtoken.NewRepository(rw, rw, kms) require.NoError(t, err) + iamRepo := iam.TestRepo(t, conn, wrapper) tokenRepoFn := func() (*authtoken.Repository, error) { return tokenRepo, nil } @@ -283,8 +282,8 @@ func TestAuthTokenAuthenticator(t *testing.T) { return iamRepo, nil } - o, _ := iam.TestScopes(t, conn) - at := authtoken.TestAuthToken(t, conn, wrapper, o.GetPublicId()) + o, _ := iam.TestScopes(t, iamRepo) + at := authtoken.TestAuthToken(t, conn, kms, o.GetPublicId()) tokValue := at.GetPublicId() + "_" + at.GetToken() jsCookieVal, httpCookieVal := tokValue[:len(tokValue)/2], tokValue[len(tokValue)/2:] diff --git a/internal/auth/db_test.go b/internal/auth/db_test.go index 0d92fbbaf..16cdd45d8 100644 --- a/internal/auth/db_test.go +++ b/internal/auth/db_test.go @@ -41,6 +41,7 @@ select count(*) from test_auth_method where public_id = $1; assert, require := assert.New(t), require.New(t) conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) db := conn.DB() _, err := db.Exec(createTable) require.NoError(err) @@ -48,7 +49,7 @@ select count(*) from test_auth_method where public_id = $1; _, err = db.Exec(addTriggers) require.NoError(err) - org, _ := iam.TestScopes(t, conn) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) id := "l1Ocw0TpHn800CekIxIXlmQqRDgFDfYl" _, err = db.Query(insert, id, org.GetPublicId()) @@ -105,6 +106,8 @@ values assert, require := assert.New(t), require.New(t) conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := iam.TestRepo(t, conn, wrapper) db := conn.DB() _, err := db.Exec(createTable) require.NoError(err) @@ -112,7 +115,7 @@ values _, err = db.Exec(addTriggers) require.NoError(err) - org, _ := iam.TestScopes(t, conn) + org, _ := iam.TestScopes(t, repo) meth_id := "31Ocw0TpHn800CekIxIXlmQqRDgFDfYl" _, err = db.Query(insertAuthMethod, meth_id, org.GetPublicId()) require.NoError(err) diff --git a/internal/auth/password/account_test.go b/internal/auth/password/account_test.go index 97543231c..efa90e623 100644 --- a/internal/auth/password/account_test.go +++ b/internal/auth/password/account_test.go @@ -12,8 +12,8 @@ import ( func TestAccount_New(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - - o, _ := iam.TestScopes(t, conn) + wrapper := db.TestWrapper(t) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) auts := TestAuthMethods(t, conn, o.GetPublicId(), 1) aut := auts[0] @@ -59,7 +59,7 @@ func TestAccount_New(t *testing.T) { want: &Account{ Account: &store.Account{ AuthMethodId: aut.GetPublicId(), - LoginName: "kazmierczak1", + LoginName: "kazmierczak1", }, }, }, diff --git a/internal/auth/password/argon2.go b/internal/auth/password/argon2.go index 1947778e4..0f3a8d15c 100644 --- a/internal/auth/password/argon2.go +++ b/internal/auth/password/argon2.go @@ -173,6 +173,7 @@ func (c *Argon2Credential) encrypt(ctx context.Context, cipher wrapping.Wrapper) if err := structwrapping.WrapStruct(ctx, cipher, c.Argon2Credential, nil); err != nil { return fmt.Errorf("error encrypting argon2 credential: %w", err) } + c.KeyId = cipher.KeyID() return nil } diff --git a/internal/auth/password/argon2_test.go b/internal/auth/password/argon2_test.go index 01d09add7..3fe434cbe 100644 --- a/internal/auth/password/argon2_test.go +++ b/internal/auth/password/argon2_test.go @@ -16,7 +16,8 @@ import ( func TestArgon2Configuration_New(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) - o, _ := iam.TestScopes(t, conn) + wrapper := db.TestWrapper(t) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) authMethods := TestAuthMethods(t, conn, o.GetPublicId(), 1) authMethod := authMethods[0] authMethodId := authMethod.GetPublicId() @@ -93,7 +94,7 @@ func TestArgon2Configuration_New(t *testing.T) { func TestArgon2Configuration_Readonly(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - + wrapper := db.TestWrapper(t) rw := db.New(conn) changeIterations := func() func(*Argon2Configuration) (*Argon2Configuration, []string) { @@ -115,7 +116,7 @@ func TestArgon2Configuration_Readonly(t *testing.T) { } } - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) authMethods := TestAuthMethods(t, conn, o.GetPublicId(), 1) authMethod := authMethods[0] authMethodId := authMethod.GetPublicId() @@ -303,7 +304,7 @@ func TestArgon2Credential_New(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) auts := TestAuthMethods(t, conn, o.GetPublicId(), 1) aut := auts[0] accts := TestAccounts(t, conn, aut.PublicId, 5) diff --git a/internal/auth/password/authmethod_test.go b/internal/auth/password/authmethod_test.go index c6c06f700..8831c9dd4 100644 --- a/internal/auth/password/authmethod_test.go +++ b/internal/auth/password/authmethod_test.go @@ -13,6 +13,7 @@ import ( func TestAuthMethod_New(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) w := db.New(conn) @@ -73,7 +74,7 @@ func TestAuthMethod_New(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - org, _ := iam.TestScopes(t, conn) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) got, err := NewAuthMethod(org.GetPublicId(), tt.args.opts...) if tt.wantErr { assert.Error(err) diff --git a/internal/auth/password/repository.go b/internal/auth/password/repository.go index a4045e5aa..041222cd1 100644 --- a/internal/auth/password/repository.go +++ b/internal/auth/password/repository.go @@ -5,15 +5,15 @@ import ( "strings" "github.com/hashicorp/boundary/internal/db" - wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/hashicorp/boundary/internal/kms" ) // A Repository stores and retrieves the persistent types in the password // package. It is not safe to use a repository concurrently. type Repository struct { - reader db.Reader - writer db.Writer - wrapper wrapping.Wrapper + reader db.Reader + writer db.Writer + kms *kms.Kms // defaultLimit provides a default for limiting the number of results returned from the repo defaultLimit int } @@ -22,14 +22,14 @@ type Repository struct { // only be used for one transaction and it is not safe for concurrent go // routines to access it. WithLimit option is used as a repo wide default // limit applied to all ListX methods. -func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper, opt ...Option) (*Repository, error) { +func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repository, error) { switch { case r == nil: return nil, fmt.Errorf("db.Reader: %w", db.ErrNilParameter) case w == nil: return nil, fmt.Errorf("db.Writer: %w", db.ErrNilParameter) - case wrapper == nil: - return nil, fmt.Errorf("wrapping.Wrapper: %w", db.ErrNilParameter) + case kms == nil: + return nil, fmt.Errorf("kms: %w", db.ErrNilParameter) } opts := getOpts(opt...) @@ -41,7 +41,7 @@ func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper, opt ...Op return &Repository{ reader: r, writer: w, - wrapper: wrapper, + kms: kms, defaultLimit: opts.withLimit, }, nil } diff --git a/internal/auth/password/repository_account.go b/internal/auth/password/repository_account.go index 57e10d12a..16eafc0d6 100644 --- a/internal/auth/password/repository_account.go +++ b/internal/auth/password/repository_account.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/boundary/internal/db" dbcommon "github.com/hashicorp/boundary/internal/db/common" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" ) @@ -24,7 +25,7 @@ import ( // // Both a.Name and a.Description are optional. If a.Name is set, it must be // unique within a.AuthMethodId. -func (r *Repository) CreateAccount(ctx context.Context, a *Account, opt ...Option) (*Account, error) { +func (r *Repository) CreateAccount(ctx context.Context, scopeId string, a *Account, opt ...Option) (*Account, error) { if a == nil { return nil, fmt.Errorf("create: password account: %w", db.ErrNilParameter) } @@ -37,6 +38,9 @@ func (r *Repository) CreateAccount(ctx context.Context, a *Account, opt ...Optio if a.PublicId != "" { return nil, fmt.Errorf("create: password account: public id not empty: %w", db.ErrInvalidParameter) } + if scopeId == "" { + return nil, fmt.Errorf("create: password account: scope id empty: %w", db.ErrNilParameter) + } if !validLoginName(a.LoginName) { return nil, fmt.Errorf("create: password account: invalid user name: %w", db.ErrInvalidParameter) } @@ -69,21 +73,30 @@ func (r *Repository) CreateAccount(ctx context.Context, a *Account, opt ...Optio } } + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, fmt.Errorf("create: password account: unable to get oplog wrapper: %w", err) + } + databaseWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, fmt.Errorf("create: password account: unable to get database wrapper: %w", err) + } + var newCred *Argon2Credential var newAccount *Account _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, w db.Writer) error { newAccount = a.clone() - if err := w.Create(ctx, newAccount, db.WithOplog(r.wrapper, a.oplog(oplog.OpType_OP_TYPE_CREATE))); err != nil { + if err := w.Create(ctx, newAccount, db.WithOplog(oplogWrapper, a.oplog(oplog.OpType_OP_TYPE_CREATE))); err != nil { return err } if cred != nil { newCred = cred.clone() - if err := newCred.encrypt(ctx, r.wrapper); err != nil { + if err := newCred.encrypt(ctx, databaseWrapper); err != nil { return err } - if err := w.Create(ctx, newCred, db.WithOplog(r.wrapper, cred.oplog(oplog.OpType_OP_TYPE_CREATE))); err != nil { + if err := w.Create(ctx, newCred, db.WithOplog(oplogWrapper, cred.oplog(oplog.OpType_OP_TYPE_CREATE))); err != nil { return err } } @@ -139,22 +152,30 @@ func (r *Repository) ListAccounts(ctx context.Context, withAuthMethodId string, // DeleteAccount deletes the account for the provided id from the repository returning a count of the // number of records deleted. All options are ignored. -func (r *Repository) DeleteAccount(ctx context.Context, withPublicId string, opt ...Option) (int, error) { +func (r *Repository) DeleteAccount(ctx context.Context, scopeId, withPublicId string, opt ...Option) (int, error) { if withPublicId == "" { return db.NoRowsAffected, fmt.Errorf("delete: password account: missing public id: %w", db.ErrInvalidParameter) } + if scopeId == "" { + return db.NoRowsAffected, fmt.Errorf("delete: password account: scope id empty: %w", db.ErrNilParameter) + } ac := allocAccount() ac.PublicId = withPublicId + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, fmt.Errorf("delete: password account: unable to get oplog wrapper: %w", err) + } + var rowsDeleted int - _, err := r.writer.DoTx( + _, err = r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, w db.Writer) (err error) { metadata := ac.oplog(oplog.OpType_OP_TYPE_DELETE) dAc := ac.clone() - rowsDeleted, err = w.Delete(ctx, dAc, db.WithOplog(r.wrapper, metadata)) + rowsDeleted, err = w.Delete(ctx, dAc, db.WithOplog(oplogWrapper, metadata)) if err == nil && rowsDeleted > 1 { return db.ErrMultipleRecords } @@ -191,7 +212,7 @@ func validLoginName(u string) bool { // An attribute of a will be set to NULL in the database if the attribute // in a is the zero value and it is included in fieldMaskPaths. a.LoginName // cannot be set to NULL. -func (r *Repository) UpdateAccount(ctx context.Context, a *Account, version uint32, fieldMaskPaths []string, opt ...Option) (*Account, int, error) { +func (r *Repository) UpdateAccount(ctx context.Context, scopeId string, a *Account, version uint32, fieldMaskPaths []string, opt ...Option) (*Account, int, error) { if a == nil { return nil, db.NoRowsAffected, fmt.Errorf("update: password account: %w", db.ErrNilParameter) } @@ -204,6 +225,9 @@ func (r *Repository) UpdateAccount(ctx context.Context, a *Account, version uint if version == 0 { return nil, db.NoRowsAffected, fmt.Errorf("update: password account: no version supplied: %w", db.ErrInvalidParameter) } + if scopeId == "" { + return nil, db.NoRowsAffected, fmt.Errorf("update: password account: scope id empty: %w", db.ErrNilParameter) + } var changeLoginName bool for _, f := range fieldMaskPaths { @@ -242,17 +266,22 @@ func (r *Repository) UpdateAccount(ctx context.Context, a *Account, version uint } } + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, db.NoRowsAffected, fmt.Errorf("update: password account: unable to get oplog wrapper: %w", err) + } + a = a.clone() metadata := a.oplog(oplog.OpType_OP_TYPE_UPDATE) var rowsUpdated int var returnedAccount *Account - _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, w db.Writer) error { returnedAccount = a.clone() var err error - rowsUpdated, err = w.Update(ctx, returnedAccount, dbMask, nullFields, db.WithOplog(r.wrapper, metadata), db.WithVersion(&version)) + rowsUpdated, err = w.Update(ctx, returnedAccount, dbMask, nullFields, db.WithOplog(oplogWrapper, metadata), db.WithVersion(&version)) if err == nil && rowsUpdated > 1 { return db.ErrMultipleRecords } diff --git a/internal/auth/password/repository_account_test.go b/internal/auth/password/repository_account_test.go index 8acf7cb7d..a7dc5eca2 100644 --- a/internal/auth/password/repository_account_test.go +++ b/internal/auth/password/repository_account_test.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/boundary/internal/db" dbassert "github.com/hashicorp/boundary/internal/db/assert" "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -40,7 +41,10 @@ func TestRepository_CreateAccount(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) - org, _ := iam.TestScopes(t, conn) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + org, _ := iam.TestScopes(t, iamRepo) + authMethods := TestAuthMethods(t, conn, org.GetPublicId(), 1) authMethod := authMethods[0] @@ -213,10 +217,10 @@ func TestRepository_CreateAccount(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) - got, err := repo.CreateAccount(context.Background(), tt.in, tt.opts...) + got, err := repo.CreateAccount(context.Background(), org.GetPublicId(), tt.in, tt.opts...) if tt.wantIsErr != nil { assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err) assert.Nil(got) @@ -235,7 +239,7 @@ func TestRepository_CreateAccount(t *testing.T) { opts := getOpts(tt.opts...) if opts.withPassword { - authAcct, err := repo.Authenticate(context.Background(), tt.in.AuthMethodId, tt.in.LoginName, opts.password) + authAcct, err := repo.Authenticate(context.Background(), org.GetPublicId(), tt.in.AuthMethodId, tt.in.LoginName, opts.password) require.NoError(err) assert.NoError(db.TestVerifyOplog(t, rw, authAcct.CredentialId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))) } @@ -244,11 +248,11 @@ func TestRepository_CreateAccount(t *testing.T) { t.Run("invalid-duplicate-names", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) - org, _ := iam.TestScopes(t, conn) + org, _ := iam.TestScopes(t, iamRepo) authMethods := TestAuthMethods(t, conn, org.GetPublicId(), 1) authMethod := authMethods[0] @@ -260,7 +264,7 @@ func TestRepository_CreateAccount(t *testing.T) { }, } - got, err := repo.CreateAccount(context.Background(), in) + got, err := repo.CreateAccount(context.Background(), org.GetPublicId(), in) require.NoError(err) require.NotNil(got) assertPublicId(t, AccountPrefix, got.PublicId) @@ -269,18 +273,18 @@ func TestRepository_CreateAccount(t *testing.T) { assert.Equal(in.Description, got.Description) assert.Equal(got.CreateTime, got.UpdateTime) - got2, err := repo.CreateAccount(context.Background(), in) + got2, err := repo.CreateAccount(context.Background(), org.GetPublicId(), in) assert.Truef(errors.Is(err, db.ErrNotUnique), "want err: %v got: %v", db.ErrNotUnique, err) assert.Nil(got2) }) t.Run("valid-duplicate-names-diff-parents", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) - org, _ := iam.TestScopes(t, conn) + org, _ := iam.TestScopes(t, iamRepo) authMethods := TestAuthMethods(t, conn, org.GetPublicId(), 2) authMethoda, authMethodb := authMethods[0], authMethods[1] in := &Account{ @@ -292,7 +296,7 @@ func TestRepository_CreateAccount(t *testing.T) { in2 := in.clone() in.AuthMethodId = authMethoda.GetPublicId() - got, err := repo.CreateAccount(context.Background(), in) + got, err := repo.CreateAccount(context.Background(), org.GetPublicId(), in) require.NoError(err) require.NotNil(got) assertPublicId(t, AccountPrefix, got.PublicId) @@ -302,7 +306,7 @@ func TestRepository_CreateAccount(t *testing.T) { assert.Equal(got.CreateTime, got.UpdateTime) in2.AuthMethodId = authMethodb.GetPublicId() - got2, err := repo.CreateAccount(context.Background(), in2) + got2, err := repo.CreateAccount(context.Background(), org.GetPublicId(), in2) assert.NoError(err) require.NotNil(got2) assertPublicId(t, AccountPrefix, got2.PublicId) @@ -318,7 +322,10 @@ func TestRepository_LookupAccount(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) - org, _ := iam.TestScopes(t, conn) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + org, _ := iam.TestScopes(t, iamRepo) + authMethod := TestAuthMethods(t, conn, org.GetPublicId(), 1)[0] account := TestAccounts(t, conn, authMethod.GetPublicId(), 1)[0] @@ -349,7 +356,7 @@ func TestRepository_LookupAccount(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) got, err := repo.LookupAccount(context.Background(), tt.in) @@ -369,7 +376,10 @@ func TestRepository_DeleteAccount(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) - org, _ := iam.TestScopes(t, conn) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + org, _ := iam.TestScopes(t, iamRepo) + authMethod := TestAuthMethods(t, conn, org.GetPublicId(), 1)[0] account := TestAccounts(t, conn, authMethod.GetPublicId(), 1)[0] @@ -401,10 +411,10 @@ func TestRepository_DeleteAccount(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) - got, err := repo.DeleteAccount(context.Background(), tt.in) + got, err := repo.DeleteAccount(context.Background(), org.GetPublicId(), tt.in) if tt.wantIsErr != nil { assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err) assert.Zero(got) @@ -421,7 +431,10 @@ func TestRepository_ListAccounts(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) - org, _ := iam.TestScopes(t, conn) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + org, _ := iam.TestScopes(t, iamRepo) + authMethods := TestAuthMethods(t, conn, org.GetPublicId(), 3) accounts1 := TestAccounts(t, conn, authMethods[0].GetPublicId(), 3) accounts2 := TestAccounts(t, conn, authMethods[1].GetPublicId(), 4) @@ -454,7 +467,7 @@ func TestRepository_ListAccounts(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) got, err := repo.ListAccounts(context.Background(), tt.in, tt.opts...) @@ -474,7 +487,10 @@ func TestRepository_ListAccounts_Limits(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) - org, _ := iam.TestScopes(t, conn) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + org, _ := iam.TestScopes(t, iamRepo) + am := TestAuthMethods(t, conn, org.GetPublicId(), 1)[0] accountCount := 10 @@ -528,7 +544,7 @@ func TestRepository_ListAccounts_Limits(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper, tt.repoOpts...) + repo, err := NewRepository(rw, rw, kms, tt.repoOpts...) assert.NoError(err) require.NotNil(repo) got, err := repo.ListAccounts(context.Background(), am.GetPublicId(), tt.listOpts...) @@ -542,6 +558,8 @@ func TestRepository_UpdateAccount(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) changeLoginName := func(s string) func(*Account) *Account { return func(a *Account) *Account { @@ -853,25 +871,25 @@ func TestRepository_UpdateAccount(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) - org, _ := iam.TestScopes(t, conn) + org, _ := iam.TestScopes(t, iamRepo) am := TestAuthMethods(t, conn, org.PublicId, 1)[0] tt.orig.AuthMethodId = am.PublicId if tt.orig.LoginName == "" { tt.orig.LoginName = "kazmierczak" } - orig, err := repo.CreateAccount(context.Background(), tt.orig) + orig, err := repo.CreateAccount(context.Background(), org.GetPublicId(), tt.orig) assert.NoError(err) require.NotNil(orig) if tt.chgFn != nil { orig = tt.chgFn(orig) } - got, gotCount, err := repo.UpdateAccount(context.Background(), orig, 1, tt.masks) + got, gotCount, err := repo.UpdateAccount(context.Background(), org.GetPublicId(), orig, 1, tt.masks) if tt.wantIsErr != nil { assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err) assert.Equal(tt.wantCount, gotCount, "row count") @@ -909,12 +927,12 @@ func TestRepository_UpdateAccount(t *testing.T) { t.Run("invalid-duplicate-names", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) name := "test-dup-name" - org, _ := iam.TestScopes(t, conn) + org, _ := iam.TestScopes(t, iamRepo) am := TestAuthMethods(t, conn, org.PublicId, 1)[0] acts := TestAccounts(t, conn, am.PublicId, 2) @@ -922,7 +940,7 @@ func TestRepository_UpdateAccount(t *testing.T) { ab := acts[1] aa.Name = name - got1, gotCount1, err := repo.UpdateAccount(context.Background(), aa, 1, []string{"name"}) + got1, gotCount1, err := repo.UpdateAccount(context.Background(), org.GetPublicId(), aa, 1, []string{"name"}) assert.NoError(err) require.NotNil(got1) assert.Equal(name, got1.Name) @@ -930,7 +948,7 @@ func TestRepository_UpdateAccount(t *testing.T) { assert.NoError(db.TestVerifyOplog(t, rw, aa.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))) ab.Name = name - got2, gotCount2, err := repo.UpdateAccount(context.Background(), ab, 1, []string{"name"}) + got2, gotCount2, err := repo.UpdateAccount(context.Background(), org.GetPublicId(), ab, 1, []string{"name"}) assert.Truef(errors.Is(err, db.ErrNotUnique), "want err: %v got: %v", db.ErrNotUnique, err) assert.Nil(got2) assert.Equal(db.NoRowsAffected, gotCount2, "row count") @@ -941,11 +959,11 @@ func TestRepository_UpdateAccount(t *testing.T) { t.Run("valid-duplicate-names-diff-AuthMethods", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) - org, _ := iam.TestScopes(t, conn) + org, _ := iam.TestScopes(t, iamRepo) ams := TestAuthMethods(t, conn, org.PublicId, 2) ama := ams[0] @@ -960,7 +978,7 @@ func TestRepository_UpdateAccount(t *testing.T) { in.AuthMethodId = ama.PublicId in.LoginName = "kazmierczak" - got, err := repo.CreateAccount(context.Background(), in) + got, err := repo.CreateAccount(context.Background(), org.GetPublicId(), in) assert.NoError(err) require.NotNil(got) assertPublicId(t, AccountPrefix, got.PublicId) @@ -971,11 +989,11 @@ func TestRepository_UpdateAccount(t *testing.T) { in2.AuthMethodId = amb.PublicId in2.LoginName = "kazmierczak2" in2.Name = "first-name" - got2, err := repo.CreateAccount(context.Background(), in2) + got2, err := repo.CreateAccount(context.Background(), org.GetPublicId(), in2) assert.NoError(err) require.NotNil(got2) got2.Name = got.Name - got3, gotCount3, err := repo.UpdateAccount(context.Background(), got2, 1, []string{"name"}) + got3, gotCount3, err := repo.UpdateAccount(context.Background(), org.GetPublicId(), got2, 1, []string{"name"}) assert.NoError(err) require.NotNil(got3) assert.NotSame(got2, got3) @@ -987,12 +1005,12 @@ func TestRepository_UpdateAccount(t *testing.T) { t.Run("invalid-duplicate-loginnames", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) loginName := "kazmierczak12" - org, _ := iam.TestScopes(t, conn) + org, _ := iam.TestScopes(t, iamRepo) am := TestAuthMethods(t, conn, org.PublicId, 1)[0] acts := TestAccounts(t, conn, am.PublicId, 2) @@ -1000,7 +1018,7 @@ func TestRepository_UpdateAccount(t *testing.T) { ab := acts[1] aa.LoginName = loginName - got1, gotCount1, err := repo.UpdateAccount(context.Background(), aa, 1, []string{"LoginName"}) + got1, gotCount1, err := repo.UpdateAccount(context.Background(), org.GetPublicId(), aa, 1, []string{"LoginName"}) assert.NoError(err) require.NotNil(got1) assert.Equal(loginName, got1.LoginName) @@ -1008,7 +1026,7 @@ func TestRepository_UpdateAccount(t *testing.T) { assert.NoError(db.TestVerifyOplog(t, rw, aa.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))) ab.LoginName = loginName - got2, gotCount2, err := repo.UpdateAccount(context.Background(), ab, 1, []string{"LoginName"}) + got2, gotCount2, err := repo.UpdateAccount(context.Background(), org.GetPublicId(), ab, 1, []string{"LoginName"}) assert.Truef(errors.Is(err, db.ErrNotUnique), "want err: %v got: %v", db.ErrNotUnique, err) assert.Nil(got2) assert.Equal(db.NoRowsAffected, gotCount2, "row count") @@ -1019,11 +1037,11 @@ func TestRepository_UpdateAccount(t *testing.T) { t.Run("valid-duplicate-loginnames-diff-AuthMethods", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) - org, _ := iam.TestScopes(t, conn) + org, _ := iam.TestScopes(t, iamRepo) ams := TestAuthMethods(t, conn, org.PublicId, 2) ama := ams[0] @@ -1037,7 +1055,7 @@ func TestRepository_UpdateAccount(t *testing.T) { in2 := in.clone() in.AuthMethodId = ama.PublicId - got, err := repo.CreateAccount(context.Background(), in) + got, err := repo.CreateAccount(context.Background(), org.GetPublicId(), in) assert.NoError(err) require.NotNil(got) assertPublicId(t, AccountPrefix, got.PublicId) @@ -1046,11 +1064,11 @@ func TestRepository_UpdateAccount(t *testing.T) { in2.AuthMethodId = amb.PublicId in2.LoginName = "kazmierczak2" - got2, err := repo.CreateAccount(context.Background(), in2) + got2, err := repo.CreateAccount(context.Background(), org.GetPublicId(), in2) assert.NoError(err) require.NotNil(got2) got2.LoginName = got.LoginName - got3, gotCount3, err := repo.UpdateAccount(context.Background(), got2, 1, []string{"LoginName"}) + got3, gotCount3, err := repo.UpdateAccount(context.Background(), org.GetPublicId(), got2, 1, []string{"LoginName"}) assert.NoError(err) require.NotNil(got3) assert.NotSame(got2, got3) @@ -1061,11 +1079,11 @@ func TestRepository_UpdateAccount(t *testing.T) { t.Run("change-authmethod-id", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) - org, _ := iam.TestScopes(t, conn) + org, _ := iam.TestScopes(t, iamRepo) ams := TestAuthMethods(t, conn, org.PublicId, 2) ama := ams[0] @@ -1080,7 +1098,7 @@ func TestRepository_UpdateAccount(t *testing.T) { aa.AuthMethodId = ab.AuthMethodId assert.Equal(aa.AuthMethodId, ab.AuthMethodId) - got1, gotCount1, err := repo.UpdateAccount(context.Background(), aa, 1, []string{"name"}) + got1, gotCount1, err := repo.UpdateAccount(context.Background(), org.GetPublicId(), aa, 1, []string{"name"}) assert.NoError(err) require.NotNil(got1) diff --git a/internal/auth/password/repository_authmethod.go b/internal/auth/password/repository_authmethod.go index 4cf1e16b4..ac2bf95db 100644 --- a/internal/auth/password/repository_authmethod.go +++ b/internal/auth/password/repository_authmethod.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/boundary/internal/db" dbcommon "github.com/hashicorp/boundary/internal/db/common" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" ) @@ -66,16 +67,21 @@ func (r *Repository) CreateAuthMethod(ctx context.Context, m *AuthMethod, opt .. } m.PasswordConfId, c.PasswordMethodId = c.PrivateId, m.PublicId + oplogWrapper, err := r.kms.GetWrapper(ctx, m.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return nil, fmt.Errorf("create: password auth method: unable to get oplog wrapper: %w", err) + } + var newAuthMethod *AuthMethod var newArgon2Conf *Argon2Configuration _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, w db.Writer) error { newArgon2Conf = c.clone() - if err := w.Create(ctx, newArgon2Conf, db.WithOplog(r.wrapper, c.oplog(oplog.OpType_OP_TYPE_CREATE))); err != nil { + if err := w.Create(ctx, newArgon2Conf, db.WithOplog(oplogWrapper, c.oplog(oplog.OpType_OP_TYPE_CREATE))); err != nil { return err } newAuthMethod = m.clone() - return w.Create(ctx, newAuthMethod, db.WithOplog(r.wrapper, m.oplog(oplog.OpType_OP_TYPE_CREATE))) + return w.Create(ctx, newAuthMethod, db.WithOplog(oplogWrapper, m.oplog(oplog.OpType_OP_TYPE_CREATE))) }, ) @@ -127,22 +133,27 @@ func (r *Repository) ListAuthMethods(ctx context.Context, scopeId string, opt .. // DeleteAuthMethod deletes the auth method for the provided id from the repository returning a count of the // number of records deleted. All options are ignored. -func (r *Repository) DeleteAuthMethod(ctx context.Context, publicId string, opt ...Option) (int, error) { +func (r *Repository) DeleteAuthMethod(ctx context.Context, scopeId, publicId string, opt ...Option) (int, error) { if publicId == "" { return db.NoRowsAffected, fmt.Errorf("delete: password auth method: missing public id: %w", db.ErrInvalidParameter) } am := allocAuthMethod() am.PublicId = publicId + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, fmt.Errorf("delete: password auth method: unable to get oplog wrapper: %w", err) + } + var rowsDeleted int - _, err := r.writer.DoTx( + _, err = r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, w db.Writer) (err error) { metadata := am.oplog(oplog.OpType_OP_TYPE_DELETE) dAc := am.clone() - rowsDeleted, err = w.Delete(ctx, dAc, db.WithOplog(r.wrapper, metadata)) + rowsDeleted, err = w.Delete(ctx, dAc, db.WithOplog(oplogWrapper, metadata)) if err == nil && rowsDeleted > 1 { return db.ErrMultipleRecords } @@ -176,6 +187,9 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, authMethod *AuthMetho if authMethod.PublicId == "" { return nil, db.NoRowsAffected, fmt.Errorf("update: password auth method: missing authMethod public id: %w", db.ErrInvalidParameter) } + if authMethod.ScopeId == "" { + return nil, db.NoRowsAffected, fmt.Errorf("update: password auth method: scope id empty: %w", db.ErrNilParameter) + } for _, f := range fieldMaskPaths { switch { case strings.EqualFold("name", f): @@ -200,15 +214,20 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, authMethod *AuthMetho return nil, db.NoRowsAffected, fmt.Errorf("update: password auth method: %w", db.ErrEmptyFieldMask) } + oplogWrapper, err := r.kms.GetWrapper(ctx, authMethod.ScopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, db.NoRowsAffected, fmt.Errorf("update: password auth method: unable to get oplog wrapper: %w", err) + } + upAuthMethod := authMethod.clone() var rowsUpdated int - _, err := r.writer.DoTx( + _, err = r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, w db.Writer) error { dbOpts := []db.Option{ - db.WithOplog(r.wrapper, upAuthMethod.oplog(oplog.OpType_OP_TYPE_UPDATE)), + db.WithOplog(oplogWrapper, upAuthMethod.oplog(oplog.OpType_OP_TYPE_UPDATE)), db.WithVersion(&version), } var err error diff --git a/internal/auth/password/repository_authmethod_test.go b/internal/auth/password/repository_authmethod_test.go index ab54385ce..2d3f4f5b4 100644 --- a/internal/auth/password/repository_authmethod_test.go +++ b/internal/auth/password/repository_authmethod_test.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/boundary/internal/db" dbassert "github.com/hashicorp/boundary/internal/db/assert" "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -23,8 +24,9 @@ func TestRepository_CreateAuthMethod(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - - org, _ := iam.TestScopes(t, conn) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + org, _ := iam.TestScopes(t, iamRepo) var tests = []struct { name string @@ -132,7 +134,7 @@ func TestRepository_CreateAuthMethod(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(err) require.NotNil(repo) got, err := repo.CreateAuthMethod(context.Background(), tt.in, tt.opts...) @@ -154,11 +156,11 @@ func TestRepository_CreateAuthMethod(t *testing.T) { t.Run("invalid-duplicate-names", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(err) require.NotNil(repo) - org, _ := iam.TestScopes(t, conn) + org, _ := iam.TestScopes(t, iamRepo) in := &AuthMethod{ AuthMethod: &store.AuthMethod{ ScopeId: org.GetPublicId(), @@ -182,11 +184,11 @@ func TestRepository_CreateAuthMethod(t *testing.T) { t.Run("valid-duplicate-names-diff-scopes", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(err) require.NotNil(repo) - org1, _ := iam.TestScopes(t, conn) + org1, _ := iam.TestScopes(t, iamRepo) in := &AuthMethod{ AuthMethod: &store.AuthMethod{ Name: "test-name-repo", @@ -204,7 +206,7 @@ func TestRepository_CreateAuthMethod(t *testing.T) { assert.Equal(in.Description, got.Description) assert.Equal(got.CreateTime, got.UpdateTime) - org2, _ := iam.TestScopes(t, conn) + org2, _ := iam.TestScopes(t, iamRepo) in2.ScopeId = org2.GetPublicId() got2, err := repo.CreateAuthMethod(context.Background(), in2) require.NoError(err) @@ -218,11 +220,11 @@ func TestRepository_CreateAuthMethod(t *testing.T) { t.Run("valid-with-publicid", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(err) require.NotNil(repo) - org1, _ := iam.TestScopes(t, conn) + org1, _ := iam.TestScopes(t, iamRepo) in := allocAuthMethod() amId, err := newAuthMethodId() @@ -238,11 +240,11 @@ func TestRepository_CreateAuthMethod(t *testing.T) { t.Run("invalid-with-badpublicid", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(err) require.NotNil(repo) - org1, _ := iam.TestScopes(t, conn) + org1, _ := iam.TestScopes(t, iamRepo) in := allocAuthMethod() in.ScopeId = org1.GetPublicId() @@ -257,8 +259,9 @@ func TestRepository_LookupAuthMethod(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) authMethod := TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] amId, err := newAuthMethodId() @@ -288,7 +291,7 @@ func TestRepository_LookupAuthMethod(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) got, err := repo.LookupAuthMethod(context.Background(), tt.in) @@ -307,8 +310,9 @@ func TestRepository_DeleteAuthMethod(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) authMethod := TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] newAuthMethodId, err := newAuthMethodId() @@ -339,10 +343,10 @@ func TestRepository_DeleteAuthMethod(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) - got, err := repo.DeleteAuthMethod(context.Background(), tt.in) + got, err := repo.DeleteAuthMethod(context.Background(), o.GetPublicId(), tt.in) if tt.wantIsErr != nil { assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err) assert.Zero(got) @@ -358,9 +362,10 @@ func TestRepository_ListAuthMethods(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - - noAuthMethodOrg, _ := iam.TestScopes(t, conn) - o, _ := iam.TestScopes(t, conn) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + noAuthMethodOrg, _ := iam.TestScopes(t, iamRepo) + o, _ := iam.TestScopes(t, iamRepo) authMethods := TestAuthMethods(t, conn, o.GetPublicId(), 3) var tests = []struct { @@ -390,7 +395,7 @@ func TestRepository_ListAuthMethods(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) got, err := repo.ListAuthMethods(context.Background(), tt.in, tt.opts...) @@ -409,8 +414,9 @@ func TestRepository_ListAuthMethods_Limits(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) authMethodCount := 10 ams := TestAuthMethods(t, conn, o.GetPublicId(), authMethodCount) @@ -462,7 +468,7 @@ func TestRepository_ListAuthMethods_Limits(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper, tt.repoOpts...) + repo, err := NewRepository(rw, rw, kms, tt.repoOpts...) assert.NoError(err) require.NotNil(repo) got, err := repo.ListAuthMethods(context.Background(), ams[0].GetScopeId(), tt.listOpts...) @@ -477,8 +483,10 @@ func TestRepository_UpdateAuthMethod(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(t, err) + iamRepo := iam.TestRepo(t, conn, wrapper) ctx := context.Background() @@ -654,7 +662,7 @@ func TestRepository_UpdateAuthMethod(t *testing.T) { assert, require := assert.New(t), require.New(t) // create the initial auth method - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iamRepo) am, err := NewAuthMethod(o.GetPublicId(), WithName("default"), WithDescription("default")) require.NoError(err) origAM, err := repo.CreateAuthMethod(ctx, am) diff --git a/internal/auth/password/repository_configuration.go b/internal/auth/password/repository_configuration.go index f308fe350..6ffe37722 100644 --- a/internal/auth/password/repository_configuration.go +++ b/internal/auth/password/repository_configuration.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/boundary/internal/auth/password/store" "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" ) @@ -39,7 +40,7 @@ func (r *Repository) GetConfiguration(ctx context.Context, authMethodId string) // for c.AuthMethodId, SetConfiguration ignores c. If c contains settings // equal to a previous configuration for c.AuthMethodId, SetConfiguration // updates AuthMethod to use the previous configuration. -func (r *Repository) SetConfiguration(ctx context.Context, c Configuration) (Configuration, error) { +func (r *Repository) SetConfiguration(ctx context.Context, scopeId string, c Configuration) (Configuration, error) { if c == nil { return nil, fmt.Errorf("set password configuration: %w", db.ErrNilParameter) } @@ -52,7 +53,7 @@ func (r *Repository) SetConfiguration(ctx context.Context, c Configuration) (Con switch v := c.(type) { case *Argon2Configuration: - out, err := r.setArgon2Conf(ctx, v) + out, err := r.setArgon2Conf(ctx, scopeId, v) if err != nil { return nil, fmt.Errorf("set password configuration: %w", err) } @@ -62,7 +63,7 @@ func (r *Repository) SetConfiguration(ctx context.Context, c Configuration) (Con } } -func (r *Repository) setArgon2Conf(ctx context.Context, c *Argon2Configuration) (*Argon2Configuration, error) { +func (r *Repository) setArgon2Conf(ctx context.Context, scopeId string, c *Argon2Configuration) (*Argon2Configuration, error) { c = c.clone() id, err := newArgon2ConfigurationId() @@ -77,6 +78,11 @@ func (r *Repository) setArgon2Conf(ctx context.Context, c *Argon2Configuration) }, } + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, fmt.Errorf("update: password account: unable to get oplog wrapper: %w", err) + } + newArgon2Conf := &Argon2Configuration{Argon2Configuration: &store.Argon2Configuration{}} _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, @@ -87,13 +93,13 @@ func (r *Repository) setArgon2Conf(ctx context.Context, c *Argon2Configuration) return err } newArgon2Conf = c.clone() - if err := w.Create(ctx, newArgon2Conf, db.WithOplog(r.wrapper, c.oplog(oplog.OpType_OP_TYPE_CREATE))); err != nil { + if err := w.Create(ctx, newArgon2Conf, db.WithOplog(oplogWrapper, c.oplog(oplog.OpType_OP_TYPE_CREATE))); err != nil { return err } } a.PasswordConfId = newArgon2Conf.PrivateId - rowsUpdated, err := w.Update(ctx, a, []string{"PasswordConfId"}, nil, db.WithOplog(r.wrapper, a.oplog(oplog.OpType_OP_TYPE_UPDATE))) + rowsUpdated, err := w.Update(ctx, a, []string{"PasswordConfId"}, nil, db.WithOplog(oplogWrapper, a.oplog(oplog.OpType_OP_TYPE_UPDATE))) if err == nil && rowsUpdated > 1 { return db.ErrMultipleRecords } diff --git a/internal/auth/password/repository_configuration_test.go b/internal/auth/password/repository_configuration_test.go index 8a1c2787d..28d8f907a 100644 --- a/internal/auth/password/repository_configuration_test.go +++ b/internal/auth/password/repository_configuration_test.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/boundary/internal/auth/password/store" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,11 +19,12 @@ func TestRepository_GetSetConfiguration(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(t, err) require.NotNil(t, repo) - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) authMethods := TestAuthMethods(t, conn, o.GetPublicId(), 1) authMethod := authMethods[0] authMethodId := authMethod.GetPublicId() @@ -68,7 +70,7 @@ func TestRepository_GetSetConfiguration(t *testing.T) { newConf.PasswordMethodId = currentConf.PasswordMethodId newConf.Memory = currentConf.Memory * 2 - updated, err := repo.SetConfiguration(ctx, newConf) + updated, err := repo.SetConfiguration(ctx, o.GetPublicId(), newConf) assert.NoError(err) require.NotNil(updated) assert.NotSame(newConf, updated) @@ -97,7 +99,7 @@ func TestRepository_GetSetConfiguration(t *testing.T) { newConf.PasswordMethodId = authMethodId assert.Empty(newConf.PrivateId) - updated, err := repo.SetConfiguration(ctx, newConf) + updated, err := repo.SetConfiguration(ctx, o.GetPublicId(), newConf) assert.NoError(err) require.NotNil(updated) assert.NotSame(newConf, updated) @@ -123,11 +125,12 @@ func TestRepository_GetConfiguration(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(t, err) require.NotNil(t, repo) - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) authMethods := TestAuthMethods(t, conn, o.GetPublicId(), 1) authMethod := authMethods[0] authMethodId := authMethod.GetPublicId() @@ -194,11 +197,12 @@ func TestRepository_SetConfiguration(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(t, err) require.NotNil(t, repo) - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) authMethods := TestAuthMethods(t, conn, o.GetPublicId(), 1) authMethod := authMethods[0] authMethodId := authMethod.GetPublicId() @@ -285,7 +289,7 @@ func TestRepository_SetConfiguration(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - got, err := repo.SetConfiguration(context.Background(), tt.in) + got, err := repo.SetConfiguration(context.Background(), o.GetPublicId(), tt.in) if tt.wantIsErr != nil { assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err) assert.Nil(got, "returned configuration") diff --git a/internal/auth/password/repository_password.go b/internal/auth/password/repository_password.go index 649b401d3..219284b72 100644 --- a/internal/auth/password/repository_password.go +++ b/internal/auth/password/repository_password.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" "golang.org/x/crypto/argon2" ) @@ -29,7 +30,7 @@ type authAccount struct { // Authenticate will update the stored values for password to the current // password settings for authMethodId if authentication is successful and // the stored values are not using the current password settings. -func (r *Repository) Authenticate(ctx context.Context, authMethodId string, loginName string, password string) (*Account, error) { +func (r *Repository) Authenticate(ctx context.Context, scopeId, authMethodId, loginName, password string) (*Account, error) { if authMethodId == "" { return nil, fmt.Errorf("password authenticate: no authMethodId: %w", db.ErrInvalidParameter) } @@ -39,8 +40,16 @@ func (r *Repository) Authenticate(ctx context.Context, authMethodId string, logi if password == "" { return nil, fmt.Errorf("password authenticate: no password: %w", db.ErrInvalidParameter) } + if scopeId == "" { + return nil, fmt.Errorf("password authenticate: no scopeId: %w", db.ErrNilParameter) + } - acct, err := r.authenticate(ctx, authMethodId, loginName, password) + databaseWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, fmt.Errorf("password authenticate: unable to get database wrapper: %w", err) + } + + acct, err := r.authenticate(ctx, scopeId, authMethodId, loginName, password) if err != nil { return nil, fmt.Errorf("password authenticate: %w", err) } @@ -58,9 +67,14 @@ func (r *Repository) Authenticate(ctx context.Context, authMethodId string, logi return acct.Account, fmt.Errorf("password authenticate: update credential: %w", err) } + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return acct.Account, fmt.Errorf("password authenticate: unable to get oplog wrapper: %w", err) + } + // do not change the Credential Id cred.PrivateId = acct.CredentialId - if err := cred.encrypt(ctx, r.wrapper); err != nil { + if err := cred.encrypt(ctx, databaseWrapper); err != nil { return acct.Account, fmt.Errorf("password authenticate: update credential: encrypt: %w", err) } @@ -69,7 +83,7 @@ func (r *Repository) Authenticate(ctx context.Context, authMethodId string, logi _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, w db.Writer) error { - rowsUpdated, err := w.Update(ctx, cred, fields, nil, db.WithOplog(r.wrapper, metadata)) + rowsUpdated, err := w.Update(ctx, cred, fields, nil, db.WithOplog(oplogWrapper, metadata)) if err == nil && rowsUpdated > 1 { return db.ErrMultipleRecords } @@ -89,7 +103,7 @@ func (r *Repository) Authenticate(ctx context.Context, authMethodId string, logi // // Returns nil if old does not match the stored password for accountId. // Returns ErrPasswordsEqual if old and new are equal. -func (r *Repository) ChangePassword(ctx context.Context, accountId string, old, new string, version uint32) (*Account, error) { +func (r *Repository) ChangePassword(ctx context.Context, scopeId, accountId, old, new string, version uint32) (*Account, error) { if accountId == "" { return nil, fmt.Errorf("change password: no account id: %w", db.ErrInvalidParameter) } @@ -105,6 +119,9 @@ func (r *Repository) ChangePassword(ctx context.Context, accountId string, old, if version == 0 { return nil, fmt.Errorf("change password: no version supplied: %w", db.ErrInvalidParameter) } + if scopeId == "" { + return nil, fmt.Errorf("change password: no scopeId: %w", db.ErrNilParameter) + } authAccount, err := r.LookupAccount(ctx, accountId) if err != nil { @@ -114,7 +131,16 @@ func (r *Repository) ChangePassword(ctx context.Context, accountId string, old, return nil, fmt.Errorf("change password: lookup account: account not found: %w", db.ErrRecordNotFound) } - acct, err := r.authenticate(ctx, authAccount.GetAuthMethodId(), authAccount.GetLoginName(), old) + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, fmt.Errorf("change password: unable to get oplog wrapper: %w", err) + } + databaseWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, fmt.Errorf("change password: unable to get database wrapper: %w", err) + } + + acct, err := r.authenticate(ctx, scopeId, authAccount.GetAuthMethodId(), authAccount.GetLoginName(), old) if err != nil { return nil, fmt.Errorf("change password: %w", err) } @@ -134,7 +160,7 @@ func (r *Repository) ChangePassword(ctx context.Context, accountId string, old, return nil, fmt.Errorf("change password: %w", err) } - if err := newCred.encrypt(ctx, r.wrapper); err != nil { + if err := newCred.encrypt(ctx, databaseWrapper); err != nil { return nil, fmt.Errorf("change password: encrypt: %w", err) } @@ -146,7 +172,7 @@ func (r *Repository) ChangePassword(ctx context.Context, accountId string, old, updatedAccount = allocAccount() updatedAccount.PublicId = accountId updatedAccount.Version = version + 1 - rowsUpdated, err := w.Update(ctx, updatedAccount, []string{"Version"}, nil, db.WithOplog(r.wrapper, acct.Account.oplog(oplog.OpType_OP_TYPE_UPDATE)), db.WithVersion(&version)) + rowsUpdated, err := w.Update(ctx, updatedAccount, []string{"Version"}, nil, db.WithOplog(oplogWrapper, acct.Account.oplog(oplog.OpType_OP_TYPE_UPDATE)), db.WithVersion(&version)) if err != nil { return fmt.Errorf("change password: unable to update account version: %w", err) } @@ -154,14 +180,14 @@ func (r *Repository) ChangePassword(ctx context.Context, accountId string, old, return fmt.Errorf("change password: updated account and %d rows updated", rowsUpdated) } - rowsDeleted, err := w.Delete(ctx, oldCred, db.WithOplog(r.wrapper, oldCred.oplog(oplog.OpType_OP_TYPE_DELETE))) + rowsDeleted, err := w.Delete(ctx, oldCred, db.WithOplog(oplogWrapper, oldCred.oplog(oplog.OpType_OP_TYPE_DELETE))) if err == nil && rowsDeleted > 1 { return db.ErrMultipleRecords } if err != nil { return err } - return w.Create(ctx, newCred, db.WithOplog(r.wrapper, newCred.oplog(oplog.OpType_OP_TYPE_CREATE))) + return w.Create(ctx, newCred, db.WithOplog(oplogWrapper, newCred.oplog(oplog.OpType_OP_TYPE_CREATE))) }, ) if err != nil { @@ -173,7 +199,7 @@ func (r *Repository) ChangePassword(ctx context.Context, accountId string, old, return updatedAccount, nil } -func (r *Repository) authenticate(ctx context.Context, authMethodId string, loginName string, password string) (*authAccount, error) { +func (r *Repository) authenticate(ctx context.Context, scopeId, authMethodId, loginName, password string) (*authAccount, error) { var accts []authAccount tx, err := r.reader.DB() @@ -204,7 +230,13 @@ func (r *Repository) authenticate(ctx context.Context, authMethodId string, logi acct = accts[0] } - if err := acct.decrypt(ctx, r.wrapper); err != nil { + // We don't pass a wrapper in here because for ecryption we want to indicate the expected key ID + databaseWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase, kms.WithKeyId(acct.GetKeyId())) + if err != nil { + return nil, fmt.Errorf("unable to get database wrapper: %w", err) + } + + if err := acct.decrypt(ctx, databaseWrapper); err != nil { return nil, fmt.Errorf("cannot decrypt credential: %w", err) } @@ -218,13 +250,25 @@ func (r *Repository) authenticate(ctx context.Context, authMethodId string, logi // SetPassword sets the password for accountId to password. If password // contains an empty string, the password for accountId will be deleted. -func (r *Repository) SetPassword(ctx context.Context, accountId string, password string, version uint32) (*Account, error) { +func (r *Repository) SetPassword(ctx context.Context, scopeId, accountId, password string, version uint32) (*Account, error) { if accountId == "" { return nil, fmt.Errorf("set password: no accountId: %w", db.ErrInvalidParameter) } if version == 0 { return nil, fmt.Errorf("set password: no version supplied: %w", db.ErrInvalidParameter) } + if scopeId == "" { + return nil, fmt.Errorf("set password: no scopeId: %w", db.ErrNilParameter) + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, fmt.Errorf("set password: unable to get oplog wrapper: %w", err) + } + databaseWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, fmt.Errorf("set password: unable to get database wrapper: %w", err) + } var newCred *Argon2Credential if password != "" { @@ -242,18 +286,18 @@ func (r *Repository) SetPassword(ctx context.Context, accountId string, password if err != nil { return nil, fmt.Errorf("set password: %w", err) } - if err := newCred.encrypt(ctx, r.wrapper); err != nil { + if err := newCred.encrypt(ctx, databaseWrapper); err != nil { return nil, fmt.Errorf("set password: encrypt: %w", err) } } var acct *Account - _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(rr db.Reader, w db.Writer) error { updatedAccount := allocAccount() updatedAccount.PublicId = accountId updatedAccount.Version = version + 1 - rowsUpdated, err := w.Update(ctx, updatedAccount, []string{"Version"}, nil, db.WithOplog(r.wrapper, updatedAccount.oplog(oplog.OpType_OP_TYPE_UPDATE)), db.WithVersion(&version)) + rowsUpdated, err := w.Update(ctx, updatedAccount, []string{"Version"}, nil, db.WithOplog(oplogWrapper, updatedAccount.oplog(oplog.OpType_OP_TYPE_UPDATE)), db.WithVersion(&version)) if err != nil { return fmt.Errorf("set password: unable to update account version: %w", err) } @@ -270,7 +314,7 @@ func (r *Repository) SetPassword(ctx context.Context, accountId string, password } if oldCred.PrivateId != "" { dCred := oldCred.clone() - rowsDeleted, err := w.Delete(ctx, dCred, db.WithOplog(r.wrapper, oldCred.oplog(oplog.OpType_OP_TYPE_DELETE))) + rowsDeleted, err := w.Delete(ctx, dCred, db.WithOplog(oplogWrapper, oldCred.oplog(oplog.OpType_OP_TYPE_DELETE))) if err == nil && rowsDeleted > 1 { return db.ErrMultipleRecords } @@ -279,7 +323,7 @@ func (r *Repository) SetPassword(ctx context.Context, accountId string, password } } if newCred != nil { - return w.Create(ctx, newCred, db.WithOplog(r.wrapper, newCred.oplog(oplog.OpType_OP_TYPE_CREATE))) + return w.Create(ctx, newCred, db.WithOplog(oplogWrapper, newCred.oplog(oplog.OpType_OP_TYPE_CREATE))) } return nil }, diff --git a/internal/auth/password/repository_password_test.go b/internal/auth/password/repository_password_test.go index 568aa5e3c..fb28b8594 100644 --- a/internal/auth/password/repository_password_test.go +++ b/internal/auth/password/repository_password_test.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/boundary/internal/auth/password/store" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,8 +19,8 @@ func TestRepository_Authenticate(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - - o, _ := iam.TestScopes(t, conn) + kms := kms.TestKms(t, conn, wrapper) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) authMethods := TestAuthMethods(t, conn, o.GetPublicId(), 1) authMethod := authMethods[0] @@ -31,10 +32,10 @@ func TestRepository_Authenticate(t *testing.T) { } passwd := "12345678" - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(t, err) require.NotNil(t, repo) - outAcct, err := repo.CreateAccount(context.Background(), inAcct, WithPassword(passwd)) + outAcct, err := repo.CreateAccount(context.Background(), o.GetPublicId(), inAcct, WithPassword(passwd)) assert.NoError(t, err) require.NotNil(t, outAcct) @@ -101,7 +102,7 @@ func TestRepository_Authenticate(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - authAcct, err := repo.Authenticate(context.Background(), tt.args.authMethodId, tt.args.loginName, tt.args.password) + authAcct, err := repo.Authenticate(context.Background(), o.GetPublicId(), tt.args.authMethodId, tt.args.loginName, tt.args.password) if tt.wantIsErr != nil { assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err) assert.Nil(authAcct, "returned account") @@ -128,8 +129,8 @@ func TestRepository_AuthenticateRehash(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) assert, require := assert.New(t), require.New(t) - - o, _ := iam.TestScopes(t, conn) + kmsCache := kms.TestKms(t, conn, wrapper) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) authMethods := TestAuthMethods(t, conn, o.GetPublicId(), 1) authMethod := authMethods[0] authMethodId := authMethod.GetPublicId() @@ -137,7 +138,7 @@ func TestRepository_AuthenticateRehash(t *testing.T) { passwd := "12345678" ctx := context.Background() - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kmsCache) assert.NoError(err) require.NotNil(repo) @@ -158,7 +159,7 @@ func TestRepository_AuthenticateRehash(t *testing.T) { }, } - origAcct, err := repo.CreateAccount(ctx, inAcct, WithPassword(passwd)) + origAcct, err := repo.CreateAccount(ctx, o.GetPublicId(), inAcct, WithPassword(passwd)) require.NoError(err) require.NotNil(origAcct) assert.NotEmpty(origAcct.PublicId) @@ -173,7 +174,7 @@ func TestRepository_AuthenticateRehash(t *testing.T) { origCredId := origCred.PrivateId // Authenticate and verify the credential ID - authAcct, err := repo.Authenticate(ctx, authMethodId, loginName, passwd) + authAcct, err := repo.Authenticate(ctx, o.GetPublicId(), authMethodId, loginName, passwd) require.NoError(err) require.NotNil(authAcct, "auth account") assert.Equal(origAcct.PublicId, authAcct.PublicId) @@ -199,7 +200,7 @@ func TestRepository_AuthenticateRehash(t *testing.T) { inArgonConf := origArgonConf.clone() inArgonConf.Threads = origArgonConf.Threads + 1 - upConf, err := repo.SetConfiguration(ctx, inArgonConf) + upConf, err := repo.SetConfiguration(ctx, o.GetPublicId(), inArgonConf) require.NoError(err) require.NotNil(upConf) assert.NotSame(inArgonConf, upConf) @@ -209,7 +210,7 @@ func TestRepository_AuthenticateRehash(t *testing.T) { assert.NotEqual(origConfId, upArgonConf.PrivateId) // Authenticate and verify the credential ID has not changed - auth2Acct, err := repo.Authenticate(ctx, authMethodId, loginName, passwd) + auth2Acct, err := repo.Authenticate(ctx, o.GetPublicId(), authMethodId, loginName, passwd) require.NoError(err) require.NotNil(auth2Acct, "auth2 account") assert.Equal(origAcct.PublicId, auth2Acct.PublicId) @@ -230,7 +231,9 @@ func TestRepository_AuthenticateRehash(t *testing.T) { assert.Equal(origCred.CreateTime, auth2Cred.CreateTime, "the create time should not change") // Verify fields that should change - require.NoError(auth2Cred.decrypt(ctx, wrapper)) + decryptWrapper, err := kmsCache.GetWrapper(ctx, o.GetPublicId(), kms.KeyPurposeDatabase, kms.WithKeyId(auth2Cred.GetKeyId())) + require.NoError(err) + require.NoError(auth2Cred.decrypt(ctx, decryptWrapper)) assert.NotEqual(origCred.UpdateTime, auth2Cred.UpdateTime, "the update time should be different") assert.NotEqual(origCred.PasswordConfId, auth2Cred.PasswordConfId, "the configuration Id should be different") assert.NotEqual(origCred.Salt, auth2Cred.Salt, "a new salt value should be generated") @@ -243,12 +246,13 @@ func TestRepository_ChangePassword(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(t, err) require.NotNil(t, repo) - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) authMethod := TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] inAcct := &Account{ @@ -259,7 +263,7 @@ func TestRepository_ChangePassword(t *testing.T) { } passwd := "12345678" - acct, err := repo.CreateAccount(context.Background(), inAcct, WithPassword(passwd)) + acct, err := repo.CreateAccount(context.Background(), o.GetPublicId(), inAcct, WithPassword(passwd)) require.NoError(t, err) require.NotNil(t, acct) @@ -356,7 +360,7 @@ func TestRepository_ChangePassword(t *testing.T) { // Calls to Authenticate should always succeed in these tests authFn := func(pwd string, name string) *Account { - acct, err := repo.Authenticate(context.Background(), inAcct.AuthMethodId, inAcct.LoginName, pwd) + acct, err := repo.Authenticate(context.Background(), o.GetPublicId(), inAcct.AuthMethodId, inAcct.LoginName, pwd) require.NotNilf(acct, "%s: Authenticate should return an account", name) require.NoErrorf(err, "%s: Authenticate should succeed", name) return acct @@ -364,7 +368,7 @@ func TestRepository_ChangePassword(t *testing.T) { // authenticate with original password authAcct1 := authFn(passwd, "original account") - chgAuthAcct, err := repo.ChangePassword(context.Background(), tt.args.acctId, tt.args.old, tt.args.new, authAcct1.Version) + chgAuthAcct, err := repo.ChangePassword(context.Background(), o.GetPublicId(), tt.args.acctId, tt.args.old, tt.args.new, authAcct1.Version) if tt.wantIsErr != nil { assert.Error(err) assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err) @@ -399,12 +403,13 @@ func TestRepository_SetPassword(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(t, err) require.NotNil(t, repo) - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) authMethod := TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] origPasswd := "12345678" @@ -421,7 +426,7 @@ func TestRepository_SetPassword(t *testing.T) { if pw != "" { opts = append(opts, WithPassword(origPasswd)) } - acct, err := repo.CreateAccount(context.Background(), inAcct, opts...) + acct, err := repo.CreateAccount(context.Background(), o.GetPublicId(), inAcct, opts...) require.NoError(t, err) require.NotNil(t, acct) return acct @@ -429,7 +434,7 @@ func TestRepository_SetPassword(t *testing.T) { } wantAuthenticate := func(t *testing.T, ln, pw, msg string) string { - acct, err := repo.Authenticate(context.Background(), authMethod.PublicId, ln, pw) + acct, err := repo.Authenticate(context.Background(), o.GetPublicId(), authMethod.PublicId, ln, pw) assert.NoErrorf(t, err, "%s: authenticate should not return an error", msg) if assert.NotNilf(t, acct, "%s: authenticate should succeed", msg) { return acct.CredentialId @@ -437,7 +442,7 @@ func TestRepository_SetPassword(t *testing.T) { return "" } wantNoAuthenticate := func(t *testing.T, ln, pw, msg string) string { - acct, err := repo.Authenticate(context.Background(), authMethod.PublicId, ln, pw) + acct, err := repo.Authenticate(context.Background(), o.GetPublicId(), authMethod.PublicId, ln, pw) assert.NoErrorf(t, err, "%s: authenticate should not return an error", msg) assert.Nilf(t, acct, "%s: authenticate should not succeed", msg) return "" @@ -480,7 +485,7 @@ func TestRepository_SetPassword(t *testing.T) { } oldCredId := nextAuth(t, acct.LoginName, origPasswd, "after create account") - acct, err = repo.SetPassword(context.Background(), acct.PublicId, tt.newPw, acct.Version) + acct, err = repo.SetPassword(context.Background(), o.GetPublicId(), acct.PublicId, tt.newPw, acct.Version) require.NoError(err) if oldCredId != "" { @@ -527,7 +532,7 @@ func TestRepository_SetPassword(t *testing.T) { t.Run(tt.name, func(t *testing.T) { assert := assert.New(t) - acct, err := repo.SetPassword(context.Background(), tt.accountId, tt.pw, tt.version) + acct, err := repo.SetPassword(context.Background(), o.GetPublicId(), tt.accountId, tt.pw, tt.version) assert.Error(err) assert.Truef(errors.Is(err, tt.wantError), "want err: %q got: %q", tt.wantError, err) assert.Nil(acct) diff --git a/internal/auth/password/repository_test.go b/internal/auth/password/repository_test.go index 700cb3d4a..258f15bae 100644 --- a/internal/auth/password/repository_test.go +++ b/internal/auth/password/repository_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/hashicorp/boundary/internal/db" - wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/hashicorp/boundary/internal/kms" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -15,12 +15,13 @@ func TestRepository_New(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) type args struct { - r db.Reader - w db.Writer - wrapper wrapping.Wrapper - opts []Option + r db.Reader + w db.Writer + kms *kms.Kms + opts []Option } var tests = []struct { @@ -32,38 +33,38 @@ func TestRepository_New(t *testing.T) { { name: "valid", args: args{ - r: rw, - w: rw, - wrapper: wrapper, + r: rw, + w: rw, + kms: kmsCache, }, want: &Repository{ reader: rw, writer: rw, - wrapper: wrapper, + kms: kmsCache, defaultLimit: db.DefaultLimit, }, }, { name: "valid with limit", args: args{ - r: rw, - w: rw, - wrapper: wrapper, - opts: []Option{WithLimit(5)}, + r: rw, + w: rw, + kms: kmsCache, + opts: []Option{WithLimit(5)}, }, want: &Repository{ reader: rw, writer: rw, - wrapper: wrapper, + kms: kmsCache, defaultLimit: 5, }, }, { name: "nil-reader", args: args{ - r: nil, - w: rw, - wrapper: wrapper, + r: nil, + w: rw, + kms: kmsCache, }, want: nil, wantIsErr: db.ErrNilParameter, @@ -71,9 +72,9 @@ func TestRepository_New(t *testing.T) { { name: "nil-writer", args: args{ - r: rw, - w: nil, - wrapper: wrapper, + r: rw, + w: nil, + kms: kmsCache, }, want: nil, wantIsErr: db.ErrNilParameter, @@ -81,9 +82,9 @@ func TestRepository_New(t *testing.T) { { name: "nil-wrapper", args: args{ - r: rw, - w: rw, - wrapper: nil, + r: rw, + w: rw, + kms: nil, }, want: nil, wantIsErr: db.ErrNilParameter, @@ -91,9 +92,9 @@ func TestRepository_New(t *testing.T) { { name: "all-nils", args: args{ - r: nil, - w: nil, - wrapper: nil, + r: nil, + w: nil, + kms: nil, }, want: nil, wantIsErr: db.ErrNilParameter, @@ -103,7 +104,7 @@ func TestRepository_New(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - got, err := NewRepository(tt.args.r, tt.args.w, tt.args.wrapper, tt.args.opts...) + got, err := NewRepository(tt.args.r, tt.args.w, tt.args.kms, tt.args.opts...) if tt.wantIsErr != nil { assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err) assert.Nil(got) diff --git a/internal/auth/password/store/argon2.pb.go b/internal/auth/password/store/argon2.pb.go index 907ea694c..eec8e3762 100644 --- a/internal/auth/password/store/argon2.pb.go +++ b/internal/auth/password/store/argon2.pb.go @@ -187,6 +187,11 @@ type Argon2Credential struct { // derivation function. // @inject_tag: `gorm:"not_null"` DerivedKey []byte `protobuf:"bytes,9,opt,name=derived_key,json=derivedKey,proto3" json:"derived_key,omitempty" gorm:"not_null"` + // key_id is the key ID that was used for the encryption operation. It can be + // used to identify a specific version of the key needed to decrypt the value, + // which is useful for caching purposes. + // @inject_tag: `gorm:"not_null"` + KeyId string `protobuf:"bytes,10,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty" gorm:"not_null"` } func (x *Argon2Credential) Reset() { @@ -284,6 +289,13 @@ func (x *Argon2Credential) GetDerivedKey() []byte { return nil } +func (x *Argon2Credential) GetKeyId() string { + if x != nil { + return x.KeyId + } + return "" +} + var File_controller_storage_auth_password_store_v1_argon2_proto protoreflect.FileDescriptor var file_controller_storage_auth_password_store_v1_argon2_proto_rawDesc = []byte{ @@ -316,7 +328,7 @@ var file_controller_storage_auth_password_store_v1_argon2_proto_rawDesc = []byte 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x61, 0x6c, 0x74, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x6b, - 0x65, 0x79, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0xa1, 0x03, 0x0a, 0x10, 0x41, 0x72, 0x67, + 0x65, 0x79, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0xb8, 0x03, 0x0a, 0x10, 0x41, 0x72, 0x67, 0x6f, 0x6e, 0x32, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, @@ -342,12 +354,13 @@ var file_controller_storage_auth_password_store_v1_argon2_proto_rawDesc = []byte 0x06, 0x63, 0x74, 0x53, 0x61, 0x6c, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0a, 0x64, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x42, 0x42, 0x5a, 0x40, - 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, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x70, 0x61, 0x73, 0x73, - 0x77, 0x6f, 0x72, 0x64, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x0a, 0x64, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x15, 0x0a, 0x06, + 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, + 0x79, 0x49, 0x64, 0x42, 0x42, 0x5a, 0x40, 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, 0x61, 0x75, + 0x74, 0x68, 0x2f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x2f, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/auth/password/testing_test.go b/internal/auth/password/testing_test.go index b985048b1..b1e0e0cfe 100644 --- a/internal/auth/password/testing_test.go +++ b/internal/auth/password/testing_test.go @@ -12,7 +12,8 @@ import ( func Test_TestAuthMethods(t *testing.T) { assert, require := assert.New(t), require.New(t) conn, _ := db.TestSetup(t, "postgres") - org, _ := iam.TestScopes(t, conn) + wrapper := db.TestWrapper(t) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) require.NotNil(org) assert.NotEmpty(org.GetPublicId()) @@ -28,7 +29,8 @@ func Test_TestAccounts(t *testing.T) { t.Helper() assert, require := assert.New(t), require.New(t) conn, _ := db.TestSetup(t, "postgres") - org, _ := iam.TestScopes(t, conn) + wrapper := db.TestWrapper(t) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) require.NotNil(org) assert.NotEmpty(org.GetPublicId()) diff --git a/internal/authtoken/authtoken.go b/internal/authtoken/authtoken.go index 7b236609f..076124991 100644 --- a/internal/authtoken/authtoken.go +++ b/internal/authtoken/authtoken.go @@ -59,6 +59,7 @@ func (s *writableAuthToken) encrypt(ctx context.Context, cipher wrapping.Wrapper if err := structwrapping.WrapStruct(ctx, cipher, s.AuthToken, nil); err != nil { return fmt.Errorf("error encrypting auth token: %w", err) } + s.KeyId = cipher.KeyID() return nil } diff --git a/internal/authtoken/authtoken_test.go b/internal/authtoken/authtoken_test.go index bd53299f2..4e16bc3d5 100644 --- a/internal/authtoken/authtoken_test.go +++ b/internal/authtoken/authtoken_test.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db/timestamp" "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" @@ -23,8 +24,10 @@ import ( func TestAuthToken_DbUpdate(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) - org, _ := iam.TestScopes(t, conn) + org, _ := iam.TestScopes(t, iamRepo) am := password.TestAuthMethods(t, conn, org.GetPublicId(), 1)[0] acct := password.TestAccounts(t, conn, am.GetPublicId(), 1)[0] @@ -94,8 +97,8 @@ func TestAuthToken_DbUpdate(t *testing.T) { assert := assert.New(t) w := db.New(conn) - org, _ := iam.TestScopes(t, conn) - authTok := TestAuthToken(t, conn, wrapper, org.GetPublicId()) + org, _ := iam.TestScopes(t, iamRepo) + authTok := TestAuthToken(t, conn, kms, org.GetPublicId()) proto.Merge(authTok.AuthToken, tt.args.authTok) wAuthToken := authTok.toWritableAuthToken() @@ -116,11 +119,11 @@ func TestAuthToken_DbUpdate(t *testing.T) { func TestAuthToken_DbCreate(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) - - org, _ := iam.TestScopes(t, conn) + kms := kms.TestKms(t, conn, wrapper) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) am := password.TestAuthMethods(t, conn, org.GetPublicId(), 1)[0] acct := password.TestAccounts(t, conn, am.GetPublicId(), 1)[0] - createdAuthToken := TestAuthToken(t, conn, wrapper, org.GetPublicId()) + createdAuthToken := TestAuthToken(t, conn, kms, org.GetPublicId()) testAuthTokenId := func() string { id, err := newAuthTokenId() @@ -178,8 +181,9 @@ func TestAuthToken_DbDelete(t *testing.T) { } wrapper := db.TestWrapper(t) - org, _ := iam.TestScopes(t, conn) - existingAuthTok := TestAuthToken(t, conn, wrapper, org.GetPublicId()) + kms := kms.TestKms(t, conn, wrapper) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + existingAuthTok := TestAuthToken(t, conn, kms, org.GetPublicId()) var tests = []struct { name string diff --git a/internal/authtoken/immutable_fields_test.go b/internal/authtoken/immutable_fields_test.go index fbae6c8a6..c1c8dabca 100644 --- a/internal/authtoken/immutable_fields_test.go +++ b/internal/authtoken/immutable_fields_test.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db/timestamp" "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" @@ -20,9 +21,10 @@ func TestAuthToken_ImmutableFields(t *testing.T) { w := db.New(conn) ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}} - - org, _ := iam.TestScopes(t, conn) - new := TestAuthToken(t, conn, wrapper, org.PublicId) + kms := kms.TestKms(t, conn, wrapper) + repo := iam.TestRepo(t, conn, wrapper) + org, _ := iam.TestScopes(t, repo) + new := TestAuthToken(t, conn, kms, org.PublicId) var tests = []struct { name string diff --git a/internal/authtoken/repository.go b/internal/authtoken/repository.go index e8cf369ef..181e15d8a 100644 --- a/internal/authtoken/repository.go +++ b/internal/authtoken/repository.go @@ -10,8 +10,9 @@ import ( "github.com/hashicorp/boundary/internal/authtoken/store" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db/timestamp" + "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" ) // TODO (ICU-406): Make these fields configurable. @@ -24,23 +25,23 @@ var ( // A Repository stores and retrieves the persistent types in the authtoken // package. It is not safe to use a repository concurrently. type Repository struct { - reader db.Reader - writer db.Writer - wrapper wrapping.Wrapper + reader db.Reader + writer db.Writer + kms *kms.Kms // defaultLimit provides a default for limiting the number of results returned from the repo defaultLimit int } // NewRepository creates a new Repository. The returned repository is not safe for concurrent go // routines to access it. -func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper, opt ...Option) (*Repository, error) { +func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repository, error) { switch { case r == nil: return nil, fmt.Errorf("db.Reader: auth token: %w", db.ErrNilParameter) case w == nil: return nil, fmt.Errorf("db.Writer: auth token: %w", db.ErrNilParameter) - case wrapper == nil: - return nil, fmt.Errorf("wrapping.Wrapper: auth token: %w", db.ErrNilParameter) + case kms == nil: + return nil, fmt.Errorf("kms: auth token: %w", db.ErrNilParameter) } opts := getOpts(opt...) @@ -51,7 +52,7 @@ func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper, opt ...Op return &Repository{ reader: r, writer: w, - wrapper: wrapper, + kms: kms, defaultLimit: opts.withLimit, }, nil } @@ -59,8 +60,11 @@ func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper, opt ...Op // CreateAuthToken inserts an Auth Token into the repository and returns a new Auth Token. The returned auth token // contains the auth token value. The provided IAM User ID must be associated to the provided auth account id // or an error will be returned. All options are ignored. -func (r *Repository) CreateAuthToken(ctx context.Context, withIamUserId, withAuthAccountId string, opt ...Option) (*AuthToken, error) { - if withIamUserId == "" { +func (r *Repository) CreateAuthToken(ctx context.Context, withIamUser *iam.User, withAuthAccountId string, opt ...Option) (*AuthToken, error) { + if withIamUser == nil { + return nil, fmt.Errorf("create: auth token: no user: %w", db.ErrNilParameter) + } + if withIamUser.GetPublicId() == "" { return nil, fmt.Errorf("create: auth token: no user id: %w", db.ErrInvalidParameter) } if withAuthAccountId == "" { @@ -82,6 +86,15 @@ func (r *Repository) CreateAuthToken(ctx context.Context, withIamUserId, withAut } at.Token = token + oplogWrapper, err := r.kms.GetWrapper(ctx, withIamUser.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return nil, fmt.Errorf("create: unable to get oplog wrapper: %w", err) + } + databaseWrapper, err := r.kms.GetWrapper(ctx, withIamUser.GetScopeId(), kms.KeyPurposeDatabase) + if err != nil { + return nil, fmt.Errorf("create: unable to get database wrapper: %w", err) + } + // TODO: Allow the caller to specify something different than the default duration. // We truncate the expiration time to the nearest second to make testing in different platforms with // different time resolutions easier. @@ -102,8 +115,8 @@ func (r *Repository) CreateAuthToken(ctx context.Context, withIamUserId, withAut if err := read.LookupByPublicId(ctx, acct); err != nil { return fmt.Errorf("create: auth token: auth account lookup: %w", err) } - if acct.GetIamUserId() != withIamUserId { - return fmt.Errorf("create: auth token: auth account %q mismatch with iam user %q", withAuthAccountId, withIamUserId) + if acct.GetIamUserId() != withIamUser.GetPublicId() { + return fmt.Errorf("create: auth token: auth account %q mismatch with iam user %q", withAuthAccountId, withIamUser.GetPublicId()) } at.ScopeId = acct.GetScopeId() at.AuthMethodId = acct.GetAuthMethodId() @@ -111,10 +124,10 @@ func (r *Repository) CreateAuthToken(ctx context.Context, withIamUserId, withAut metadata := newAuthTokenMetadata(at, oplog.OpType_OP_TYPE_CREATE) newAuthToken = at.toWritableAuthToken() - if err := newAuthToken.encrypt(ctx, r.wrapper); err != nil { + if err := newAuthToken.encrypt(ctx, databaseWrapper); err != nil { return err } - if err := w.Create(ctx, newAuthToken, db.WithOplog(r.wrapper, metadata)); err != nil { + if err := w.Create(ctx, newAuthToken, db.WithOplog(oplogWrapper, metadata)); err != nil { return err } newAuthToken.CtToken = nil @@ -147,12 +160,17 @@ func (r *Repository) LookupAuthToken(ctx context.Context, id string, opt ...Opti return nil, fmt.Errorf("auth token: lookup: %w", err) } if opts.withTokenValue { - if err := at.decrypt(ctx, r.wrapper); err != nil { + databaseWrapper, err := r.kms.GetWrapper(ctx, at.GetScopeId(), kms.KeyPurposeDatabase, kms.WithKeyId(at.GetKeyId())) + if err != nil { + return nil, fmt.Errorf("lookup: unable to get database wrapper: %w", err) + } + if err := at.decrypt(ctx, databaseWrapper); err != nil { return nil, fmt.Errorf("lookup: auth token: cannot decrypt auth token value: %w", err) } } at.CtToken = nil + at.KeyId = "" return at, nil } @@ -193,6 +211,11 @@ func (r *Repository) ValidateToken(ctx context.Context, id, token string, opt .. return nil, fmt.Errorf("validate token: last accessed time : %w", err) } + oplogWrapper, err := r.kms.GetWrapper(ctx, retAT.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return nil, fmt.Errorf("validate token: unable to get oplog wrapper: %w", err) + } + now := time.Now() sinceLastAccessed := now.Sub(lastAccessed) if now.After(exp) || sinceLastAccessed > maxStaleness { @@ -204,7 +227,7 @@ func (r *Repository) ValidateToken(ctx context.Context, id, token string, opt .. func(_ db.Reader, w db.Writer) error { metadata := newAuthTokenMetadata(retAT, oplog.OpType_OP_TYPE_DELETE) delAt := retAT.toWritableAuthToken() - if _, err := w.Delete(ctx, delAt, db.WithOplog(r.wrapper, metadata)); err != nil { + if _, err := w.Delete(ctx, delAt, db.WithOplog(oplogWrapper, metadata)); err != nil { return fmt.Errorf("validate token: delete auth token: %w", err) } retAT = nil @@ -237,7 +260,7 @@ func (r *Repository) ValidateToken(ctx context.Context, id, token string, opt .. at, nil, []string{"ApproximateLastAccessTime"}, - db.WithOplog(r.wrapper, metadata), + db.WithOplog(oplogWrapper, metadata), ) if err == nil && rowsUpdated > 1 { return db.ErrMultipleRecords @@ -271,6 +294,7 @@ func (r *Repository) ListAuthTokens(ctx context.Context, withOrgId string, opt . for _, at := range authTokens { at.Token = "" at.CtToken = nil + at.KeyId = "" } return authTokens, nil } @@ -293,6 +317,11 @@ func (r *Repository) DeleteAuthToken(ctx context.Context, id string, opt ...Opti return db.NoRowsAffected, nil } + oplogWrapper, err := r.kms.GetWrapper(ctx, at.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, fmt.Errorf("delete: unable to get oplog wrapper: %w", err) + } + var rowsDeleted int _, err = r.writer.DoTx( ctx, @@ -301,7 +330,7 @@ func (r *Repository) DeleteAuthToken(ctx context.Context, id string, opt ...Opti func(_ db.Reader, w db.Writer) error { metadata := newAuthTokenMetadata(at, oplog.OpType_OP_TYPE_DELETE) deleteAT := at.toWritableAuthToken() - rowsDeleted, err = w.Delete(ctx, deleteAT, db.WithOplog(r.wrapper, metadata)) + rowsDeleted, err = w.Delete(ctx, deleteAT, db.WithOplog(oplogWrapper, metadata)) if err == nil && rowsDeleted > 1 { return db.ErrMultipleRecords } diff --git a/internal/authtoken/repository_test.go b/internal/authtoken/repository_test.go index c71dd50c7..6863e198a 100644 --- a/internal/authtoken/repository_test.go +++ b/internal/authtoken/repository_test.go @@ -10,8 +10,8 @@ import ( "github.com/golang/protobuf/ptypes" "github.com/google/go-cmp/cmp" "github.com/hashicorp/boundary/internal/auth/password" + "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/testing/protocmp" @@ -25,12 +25,13 @@ func TestRepository_New(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) type args struct { - r db.Reader - w db.Writer - wrapper wrapping.Wrapper - opts []Option + r db.Reader + w db.Writer + kms *kms.Kms + opts []Option } var tests = []struct { @@ -42,38 +43,38 @@ func TestRepository_New(t *testing.T) { { name: "valid default limit", args: args{ - r: rw, - w: rw, - wrapper: wrapper, + r: rw, + w: rw, + kms: kmsCache, }, want: &Repository{ reader: rw, writer: rw, - wrapper: wrapper, + kms: kmsCache, defaultLimit: db.DefaultLimit, }, }, { name: "valid new limit", args: args{ - r: rw, - w: rw, - wrapper: wrapper, - opts: []Option{WithLimit(5)}, + r: rw, + w: rw, + kms: kmsCache, + opts: []Option{WithLimit(5)}, }, want: &Repository{ reader: rw, writer: rw, - wrapper: wrapper, + kms: kmsCache, defaultLimit: 5, }, }, { name: "nil-reader", args: args{ - r: nil, - w: rw, - wrapper: wrapper, + r: nil, + w: rw, + kms: kmsCache, }, want: nil, wantIsErr: db.ErrNilParameter, @@ -81,19 +82,19 @@ func TestRepository_New(t *testing.T) { { name: "nil-writer", args: args{ - r: rw, - w: nil, - wrapper: wrapper, + r: rw, + w: nil, + kms: kmsCache, }, want: nil, wantIsErr: db.ErrNilParameter, }, { - name: "nil-wrapper", + name: "nil-kms", args: args{ - r: rw, - w: rw, - wrapper: nil, + r: rw, + w: rw, + kms: nil, }, want: nil, wantIsErr: db.ErrNilParameter, @@ -101,9 +102,9 @@ func TestRepository_New(t *testing.T) { { name: "all-nils", args: args{ - r: nil, - w: nil, - wrapper: nil, + r: nil, + w: nil, + kms: nil, }, want: nil, wantIsErr: db.ErrNilParameter, @@ -113,7 +114,7 @@ func TestRepository_New(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert := assert.New(t) - got, err := NewRepository(tt.args.r, tt.args.w, tt.args.wrapper, tt.args.opts...) + got, err := NewRepository(tt.args.r, tt.args.w, tt.args.kms, tt.args.opts...) if tt.wantIsErr != nil { assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err) assert.Nil(got) @@ -130,29 +131,31 @@ func TestRepository_CreateAuthToken(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + repo := iam.TestRepo(t, conn, wrapper) - org1, _ := iam.TestScopes(t, conn) + org1, _ := iam.TestScopes(t, repo) am := password.TestAuthMethods(t, conn, org1.GetPublicId(), 1)[0] aAcct := password.TestAccounts(t, conn, am.GetPublicId(), 1)[0] - iamRepo, err := iam.NewRepository(rw, rw, wrapper) + iamRepo, err := iam.NewRepository(rw, rw, kms) require.NoError(t, err) u1, err := iamRepo.LookupUserWithLogin(context.Background(), aAcct.GetPublicId(), iam.WithAutoVivify(true)) require.NoError(t, err) - org2, _ := iam.TestScopes(t, conn) - u2 := iam.TestUser(t, conn, org2.GetPublicId()) + org2, _ := iam.TestScopes(t, repo) + u2 := iam.TestUser(t, repo, org2.GetPublicId()) var tests = []struct { name string - iamUserId string + iamUser *iam.User authAcctId string want *AuthToken wantErr bool }{ { name: "valid", - iamUserId: u1.GetPublicId(), + iamUser: u1, authAcctId: aAcct.GetPublicId(), want: &AuthToken{ AuthToken: &store.AuthToken{ @@ -162,14 +165,14 @@ func TestRepository_CreateAuthToken(t *testing.T) { }, { name: "unconnected-authaccount-user", - iamUserId: u2.GetPublicId(), + iamUser: u2, authAcctId: aAcct.GetPublicId(), wantErr: true, }, { - name: "no-authacctid", - iamUserId: u1.GetPublicId(), - wantErr: true, + name: "no-authacctid", + iamUser: u1, + wantErr: true, }, { name: "no-userid", @@ -178,13 +181,13 @@ func TestRepository_CreateAuthToken(t *testing.T) { }, { name: "invalid-authacctid", - iamUserId: u1.GetPublicId(), + iamUser: u1, authAcctId: "this_is_invalid", wantErr: true, }, { name: "invalid-userid", - iamUserId: "this_is_invalid", + iamUser: func() *iam.User { u := u1.Clone().(*iam.User); u.PublicId = "this_is_invalid"; return u }(), authAcctId: aAcct.GetPublicId(), wantErr: true, }, @@ -193,16 +196,16 @@ func TestRepository_CreateAuthToken(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(err) require.NotNil(repo) - got, err := repo.CreateAuthToken(context.Background(), tt.iamUserId, tt.authAcctId) + got, err := repo.CreateAuthToken(context.Background(), tt.iamUser, tt.authAcctId) if tt.wantErr { assert.Error(err) assert.Nil(got) return } - require.NoError(err, "Got error for CreateAuthToken(ctx, %v, %v)", tt.iamUserId, tt.authAcctId) + require.NoError(err, "Got error for CreateAuthToken(ctx, %v, %v)", tt.iamUser, tt.authAcctId) assert.NotNil(got) db.AssertPublicId(t, AuthTokenPrefix, got.PublicId) assert.Equal(tt.authAcctId, got.GetAuthAccountId()) @@ -217,11 +220,13 @@ func TestRepository_LookupAuthToken(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - - org, _ := iam.TestScopes(t, conn) - at := TestAuthToken(t, conn, wrapper, org.GetPublicId()) + kms := kms.TestKms(t, conn, wrapper) + repo := iam.TestRepo(t, conn, wrapper) + org, _ := iam.TestScopes(t, repo) + at := TestAuthToken(t, conn, kms, org.GetPublicId()) at.Token = "" at.CtToken = nil + at.KeyId = "" badId, err := newAuthTokenId() require.NoError(t, err) @@ -255,7 +260,7 @@ func TestRepository_LookupAuthToken(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(err) require.NotNil(repo) @@ -292,15 +297,18 @@ func TestRepository_ValidateToken(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(t, err) require.NotNil(t, repo) - org, _ := iam.TestScopes(t, conn) - at := TestAuthToken(t, conn, wrapper, org.GetPublicId()) + org, _ := iam.TestScopes(t, iamRepo) + at := TestAuthToken(t, conn, kms, org.GetPublicId()) atToken := at.GetToken() at.Token = "" at.CtToken = nil + at.KeyId = "" atTime, err := ptypes.Timestamp(at.GetApproximateLastAccessTime().GetTimestamp()) require.NoError(t, err) require.NotNil(t, atTime) @@ -402,17 +410,21 @@ func TestRepository_ValidateToken_expired(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(t, err) require.NotNil(t, repo) - org, _ := iam.TestScopes(t, conn) - baseAT := TestAuthToken(t, conn, wrapper, org.GetPublicId()) + org, _ := iam.TestScopes(t, iamRepo) + baseAT := TestAuthToken(t, conn, kms, org.GetPublicId()) baseAT.GetAuthAccountId() aAcct := allocAuthAccount() aAcct.PublicId = baseAT.GetAuthAccountId() require.NoError(t, rw.LookupByPublicId(context.Background(), aAcct)) - iamUserId := aAcct.GetIamUserId() + iamUser, err := iamRepo.LookupUser(context.Background(), aAcct.GetIamUserId()) + require.NoError(t, err) + require.NotNil(t, iamUser) defaultStaleTime := maxStaleness defaultExpireDuration := maxTokenDuration @@ -451,7 +463,7 @@ func TestRepository_ValidateToken_expired(t *testing.T) { maxTokenDuration = tt.expirationDuration ctx := context.Background() - at, err := repo.CreateAuthToken(ctx, iamUserId, baseAT.GetAuthAccountId()) + at, err := repo.CreateAuthToken(ctx, iamUser, baseAT.GetAuthAccountId()) require.NoError(err) got, err := repo.ValidateToken(ctx, at.GetPublicId(), at.GetToken()) @@ -475,9 +487,10 @@ func TestRepository_DeleteAuthToken(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - - org, _ := iam.TestScopes(t, conn) - at := TestAuthToken(t, conn, wrapper, org.GetPublicId()) + kms := kms.TestKms(t, conn, wrapper) + repo := iam.TestRepo(t, conn, wrapper) + org, _ := iam.TestScopes(t, repo) + at := TestAuthToken(t, conn, kms, org.GetPublicId()) badId, err := newAuthTokenId() require.NoError(t, err) require.NotNil(t, badId) @@ -510,7 +523,7 @@ func TestRepository_DeleteAuthToken(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(err) require.NotNil(repo) @@ -532,16 +545,20 @@ func TestRepository_ListAuthTokens(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - - org, _ := iam.TestScopes(t, conn) - at1 := TestAuthToken(t, conn, wrapper, org.GetPublicId()) + kms := kms.TestKms(t, conn, wrapper) + repo := iam.TestRepo(t, conn, wrapper) + org, _ := iam.TestScopes(t, repo) + at1 := TestAuthToken(t, conn, kms, org.GetPublicId()) at1.Token = "" - at2 := TestAuthToken(t, conn, wrapper, org.GetPublicId()) + at1.KeyId = "" + at2 := TestAuthToken(t, conn, kms, org.GetPublicId()) at2.Token = "" - at3 := TestAuthToken(t, conn, wrapper, org.GetPublicId()) + at2.KeyId = "" + at3 := TestAuthToken(t, conn, kms, org.GetPublicId()) at3.Token = "" + at3.KeyId = "" - emptyOrg, _ := iam.TestScopes(t, conn) + emptyOrg, _ := iam.TestScopes(t, repo) var tests = []struct { name string @@ -571,7 +588,7 @@ func TestRepository_ListAuthTokens(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(err) require.NotNil(repo) diff --git a/internal/authtoken/store/authtoken.pb.go b/internal/authtoken/store/authtoken.pb.go index 73884e98e..43e9c867f 100644 --- a/internal/authtoken/store/authtoken.pb.go +++ b/internal/authtoken/store/authtoken.pb.go @@ -68,6 +68,11 @@ type AuthToken struct { // iam_user_id is not stored in the backing DB but it derived from the linked to auth account. // @inject_tag: gorm:"-" IamUserId string `protobuf:"bytes,13,opt,name=iam_user_id,json=iamUserId,proto3" json:"iam_user_id,omitempty" gorm:"-"` + // key_id is the key ID that was used for the encryption operation. It can be + // used to identify a specific version of the key needed to decrypt the value, + // which is useful for caching purposes. + // @inject_tag: `gorm:"not_null"` + KeyId string `protobuf:"bytes,14,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty" gorm:"not_null"` } func (x *AuthToken) Reset() { @@ -179,6 +184,13 @@ func (x *AuthToken) GetIamUserId() string { return "" } +func (x *AuthToken) GetKeyId() string { + if x != nil { + return x.KeyId + } + return "" +} + var File_controller_storage_authtoken_store_v1_authtoken_proto protoreflect.FileDescriptor var file_controller_storage_authtoken_store_v1_authtoken_proto_rawDesc = []byte{ @@ -191,7 +203,7 @@ var file_controller_storage_authtoken_store_v1_authtoken_proto_rawDesc = []byte{ 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, - 0xbe, 0x04, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, + 0xd5, 0x04, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, @@ -227,11 +239,12 @@ var file_controller_storage_authtoken_store_v1_authtoken_proto_rawDesc = []byte{ 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x69, 0x61, 0x6d, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x61, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x42, 0x3e, 0x5a, 0x3c, 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, 0x61, 0x75, 0x74, 0x68, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x42, 0x3e, 0x5a, 0x3c, 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, 0x61, 0x75, 0x74, 0x68, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2f, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/authtoken/testing.go b/internal/authtoken/testing.go index f0aca7e4c..a9faa24c6 100644 --- a/internal/authtoken/testing.go +++ b/internal/authtoken/testing.go @@ -7,12 +7,12 @@ import ( "github.com/hashicorp/boundary/internal/auth/password" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/iam" - wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/hashicorp/boundary/internal/kms" "github.com/jinzhu/gorm" "github.com/stretchr/testify/require" ) -func TestAuthToken(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, scopeId string) *AuthToken { +func TestAuthToken(t *testing.T, conn *gorm.DB, kms *kms.Kms, scopeId string) *AuthToken { t.Helper() authMethod := password.TestAuthMethods(t, conn, scopeId, 1)[0] // auth account is only used to join auth method to user. @@ -21,16 +21,16 @@ func TestAuthToken(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, scopeI ctx := context.Background() rw := db.New(conn) - iamRepo, err := iam.NewRepository(rw, rw, wrapper) + iamRepo, err := iam.NewRepository(rw, rw, kms) require.NoError(t, err) u, err := iamRepo.LookupUserWithLogin(ctx, acct.GetPublicId(), iam.WithAutoVivify(true)) require.NoError(t, err) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(t, err) - at, err := repo.CreateAuthToken(ctx, u.GetPublicId(), acct.GetPublicId()) + at, err := repo.CreateAuthToken(ctx, u, acct.GetPublicId()) require.NoError(t, err) return at } diff --git a/internal/cmd/base/servers.go b/internal/cmd/base/servers.go index b54b56fb9..8b5d6ae18 100644 --- a/internal/cmd/base/servers.go +++ b/internal/cmd/base/servers.go @@ -18,12 +18,14 @@ import ( "github.com/hashicorp/boundary/internal/auth/password" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/types/scope" "github.com/hashicorp/boundary/version" "github.com/hashicorp/errwrap" "github.com/hashicorp/go-hclog" wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/hashicorp/go-multierror" + "github.com/hashicorp/go-uuid" "github.com/hashicorp/shared-secure-libs/configutil" "github.com/hashicorp/shared-secure-libs/gatedwriter" "github.com/hashicorp/shared-secure-libs/reloadutil" @@ -49,8 +51,9 @@ type Server struct { CombineLogs bool LogLevel hclog.Level - ControllerKMS wrapping.Wrapper - WorkerAuthKMS wrapping.Wrapper + RootKms wrapping.Wrapper + WorkerAuthKms wrapping.Wrapper + Kms *kms.Kms SecureRandomReader io.Reader InmemSink *metrics.InmemSink @@ -347,9 +350,9 @@ func (b *Server) SetupKMSes(ui cli.Ui, config *configutil.SharedConfig, purposes } if purpose == "root" { - b.ControllerKMS = wrapper + b.RootKms = wrapper } else { - b.WorkerAuthKMS = wrapper + b.WorkerAuthKms = wrapper } // Ensure that the seal finalizer is called, even if using verify-only @@ -365,7 +368,7 @@ func (b *Server) SetupKMSes(ui cli.Ui, config *configutil.SharedConfig, purposes // prepare a secure random reader var err error - b.SecureRandomReader, err = configutil.CreateSecureRandomReaderFunc(config, b.ControllerKMS) + b.SecureRandomReader, err = configutil.CreateSecureRandomReaderFunc(config, b.RootKms) if err != nil { return err } @@ -433,7 +436,23 @@ func (b *Server) CreateDevDatabase(dialect string) error { b.Database.LogMode(true) rw := db.New(b.Database) - repo, err := iam.NewRepository(rw, rw, b.ControllerKMS) + + kmsRepo, err := kms.NewRepository(rw, rw) + if err != nil { + return fmt.Errorf("error creating kms repository: %w", err) + } + kmsCache, err := kms.NewKms(kmsRepo) + if err != nil { + return fmt.Errorf("error creating kms cache: %w", err) + } + if err := kmsCache.AddExternalWrappers( + kms.WithRootWrapper(b.RootKms), + kms.WithWorkerAuthWrapper(b.WorkerAuthKms), + ); err != nil { + return fmt.Errorf("error adding config keys to kms: %w", err) + } + + repo, err := iam.NewRepository(rw, rw, kmsCache, iam.WithRandomReader(b.SecureRandomReader)) if err != nil { return fmt.Errorf("unable to create repo for org id: %w", err) } @@ -444,8 +463,17 @@ func (b *Server) CreateDevDatabase(dialect string) error { cancel() }() + rootKey, err := uuid.GenerateRandomBytesWithReader(32, b.SecureRandomReader) + if err != nil { + return fmt.Errorf("error generating random bytes for scope root key: %w", err) + } + _, _, err = kmsRepo.CreateRootKey(ctx, b.RootKms, scope.Global.String(), rootKey) + if err != nil { + return fmt.Errorf("error saving global scope root key: %w", err) + } + // Create the dev auth method - pwRepo, err := password.NewRepository(rw, rw, b.ControllerKMS) + pwRepo, err := password.NewRepository(rw, rw, kmsCache) if err != nil { return fmt.Errorf("error creating password repo: %w", err) } @@ -482,7 +510,7 @@ func (b *Server) CreateDevDatabase(dialect string) error { if err != nil { return fmt.Errorf("error creating new in memory auth account: %w", err) } - acct, err = pwRepo.CreateAccount(ctx, acct, password.WithPassword(pw)) + acct, err = pwRepo.CreateAccount(ctx, scope.Global.String(), acct, password.WithPassword(pw)) if err != nil { return fmt.Errorf("error saving auth account to the db: %w", err) } diff --git a/internal/cmd/commands/controller/controller.go b/internal/cmd/commands/controller/controller.go index 7822cef43..016f3f66a 100644 --- a/internal/cmd/commands/controller/controller.go +++ b/internal/cmd/commands/controller/controller.go @@ -202,11 +202,11 @@ func (c *Command) Run(args []string) int { c.UI.Error(err.Error()) return 1 } - if c.ControllerKMS == nil { + if c.RootKms == nil { c.UI.Error("Controller KMS not found after parsing KMS blocks") return 1 } - if c.WorkerAuthKMS == nil { + if c.WorkerAuthKms == nil { c.UI.Error("Worker Auth KMS not found after parsing KMS blocks") return 1 } diff --git a/internal/cmd/commands/dev/dev.go b/internal/cmd/commands/dev/dev.go index dd9adddb5..0b0aeeea1 100644 --- a/internal/cmd/commands/dev/dev.go +++ b/internal/cmd/commands/dev/dev.go @@ -225,11 +225,11 @@ func (c *Command) Run(args []string) int { c.UI.Error(err.Error()) return 1 } - if c.ControllerKMS == nil { + if c.RootKms == nil { c.UI.Error("Controller KMS not found after parsing KMS blocks") return 1 } - if c.WorkerAuthKMS == nil { + if c.WorkerAuthKms == nil { c.UI.Error("Worker Auth KMS not found after parsing KMS blocks") return 1 } @@ -237,7 +237,7 @@ func (c *Command) Run(args []string) int { c.Info["[Controller] AEAD Key Bytes"] = devConfig.Controller.DevControllerKey c.InfoKeys = append(c.InfoKeys, "[Worker-Auth] AEAD Key Bytes") c.Info["[Worker-Auth] AEAD Key Bytes"] = devConfig.Controller.DevWorkerAuthKey - if c.WorkerAuthKMS == nil { + if c.WorkerAuthKms == nil { c.UI.Error("Worker Auth KMS not found after parsing KMS blocks") return 1 } diff --git a/internal/cmd/commands/worker/worker.go b/internal/cmd/commands/worker/worker.go index 9aa30c791..b52858fd4 100644 --- a/internal/cmd/commands/worker/worker.go +++ b/internal/cmd/commands/worker/worker.go @@ -151,7 +151,7 @@ func (c *Command) Run(args []string) int { c.UI.Error(err.Error()) return 1 } - if c.WorkerAuthKMS == nil { + if c.WorkerAuthKms == nil { c.UI.Error("Worker Auth KMS not found after parsing KMS blocks") return 1 } diff --git a/internal/cmd/config/config.go b/internal/cmd/config/config.go index 1a1676ed3..246e8a083 100644 --- a/internal/cmd/config/config.go +++ b/internal/cmd/config/config.go @@ -33,12 +33,14 @@ kms "aead" { purpose = "root" aead_type = "aes-gcm" key = "%s" + key_id = "global_root" } kms "aead" { purpose = "worker-auth" aead_type = "aes-gcm" key = "%s" + key_id = "global_worker-auth" } listener "tcp" { diff --git a/internal/cmd/config/config_test.go b/internal/cmd/config/config_test.go index 13b0de2f3..acd3edaa7 100644 --- a/internal/cmd/config/config_test.go +++ b/internal/cmd/config/config_test.go @@ -52,6 +52,7 @@ func TestDevController(t *testing.T) { Purpose: []string{"root"}, Config: map[string]string{ "aead_type": "aes-gcm", + "key_id": "global_root", }, }, { @@ -59,6 +60,7 @@ func TestDevController(t *testing.T) { Purpose: []string{"worker-auth"}, Config: map[string]string{ "aead_type": "aes-gcm", + "key_id": "global_worker-auth", }, }, }, diff --git a/internal/db/domains_test.go b/internal/db/domains_test.go index 8a66eb8a8..cdc3e2a77 100644 --- a/internal/db/domains_test.go +++ b/internal/db/domains_test.go @@ -285,6 +285,65 @@ returning public_id; assert.Equal(3, v) } +func TestDomain_wt_version_with_private_id(t *testing.T) { + const ( + createTable = ` +create table if not exists test_table_wt_version( + private_id bigint generated always as identity primary key, + name text, + version wt_version +); +` + addTrigger = ` +create trigger + update_version_column +after update on test_table_wt_version + for each row execute procedure update_version_column('private_id'); +` + insert = ` +insert into test_table_wt_version (name) +values ($1) +returning private_id; +` + update = `update test_table_wt_version set name = $1 where private_id = $2;` + + updateVersion = `update test_table_wt_version set version = $1 where private_id = $2;` + + search = `select version from test_table_wt_version where private_id = $1` + ) + conn, _ := TestSetup(t, "postgres") + assert, require := assert.New(t), require.New(t) + + db := conn.DB() + _, err := db.Exec(createTable) + require.NoError(err) + + _, err = db.Exec(addTrigger) + require.NoError(err) + + var id int + err = db.QueryRow(insert, "alice").Scan(&id) + require.NoError(err) + require.NotEmpty(id) + + var v int + err = db.QueryRow(search, id).Scan(&v) + require.NoError(err) + assert.Equal(1, v) + + _, err = db.Exec(update, "bob", id) + require.NoError(err) + err = db.QueryRow(search, id).Scan(&v) + require.NoError(err) + assert.Equal(2, v) + + _, err = db.Exec(updateVersion, 22, id) + require.NoError(err) + err = db.QueryRow(search, id).Scan(&v) + require.NoError(err) + assert.Equal(3, v) +} + func TestDomain_DefaultUsersExist(t *testing.T) { conn, _ := TestSetup(t, "postgres") db := conn.DB() diff --git a/internal/db/migrations/postgres.gen.go b/internal/db/migrations/postgres.gen.go index a9f894c95..608cea8f4 100644 --- a/internal/db/migrations/postgres.gen.go +++ b/internal/db/migrations/postgres.gen.go @@ -121,7 +121,8 @@ comment on domain wt_version is -- update_version_column() will increment the version column whenever row data -- is updated and should only be used in an update after trigger. This function --- will overwrite any explicit updates to the version column. +-- will overwrite any explicit updates to the version column. The function +-- accepts an optional parameter of 'private_id' for the tables primary key. create or replace function update_version_column() returns trigger @@ -129,9 +130,16 @@ as $$ begin if pg_trigger_depth() = 1 then if row(new.*) is distinct from row(old.*) then - execute format('update %I set version = $1 where public_id = $2', tg_relid::regclass) using old.version+1, new.public_id; - new.version = old.version + 1; - return new; + if tg_nargs = 0 then + execute format('update %I set version = $1 where public_id = $2', tg_relid::regclass) using old.version+1, new.public_id; + new.version = old.version + 1; + return new; + end if; + if tg_argv[0] = 'private_id' then + execute format('update %I set version = $1 where private_id = $2', tg_relid::regclass) using old.version+1, new.private_id; + new.version = old.version + 1; + return new; + end if; end if; end if; return new; @@ -1323,7 +1331,8 @@ begin; create table servers ( private_id text, type text, - name text not null unique, + name text not null unique + check(length(trim(name)) > 0), description text, address text, create_time wt_timestamp, @@ -1373,6 +1382,9 @@ begin; create table auth_token ( public_id wt_public_id primary key, token bytea not null unique, + -- TODO: Make key_id a foreign key once we have DEKs + key_id text not null + check(length(trim(key_id)) > 0), auth_account_id wt_public_id not null references auth_account(public_id) on delete cascade @@ -1888,6 +1900,9 @@ begin; check(length(salt) > 0), derived_key bytea not null check(length(derived_key) > 0), + -- TODO: Make key_id a foreign key once we have DEKs + key_id text not null + check(length(trim(key_id)) > 0), foreign key (password_method_id, password_conf_id) references auth_password_argon2_conf (password_method_id, private_id) on delete cascade @@ -2427,6 +2442,341 @@ begin; commit; +`), + }, + "migrations/30_keys.down.sql": { + name: "30_keys.down.sql", + bytes: []byte(` +begin; + +drop function kms_version_column cascade; + +commit; + +`), + }, + "migrations/30_keys.up.sql": { + name: "30_keys.up.sql", + bytes: []byte(` +begin; + +-- kms_version_column() will increment the version column whenever row data +-- is inserted and should only be used in an before insert trigger. This +-- function will overwrite any explicit values to the version column. +create or replace function + kms_version_column() + returns trigger +as $$ +declare + _key_id text; + _max bigint; +begin + execute format('SELECT $1.%I', tg_argv[0]) into _key_id using new; + execute format('select max(version) + 1 from %I where %I = $1', tg_relid::regclass, tg_argv[0]) using _key_id into _max; + if _max is null then + _max = 1; + end if; + new.version = _max; + return new; +end; +$$ language plpgsql; + +comment on function + kms_version_column() +is + 'function used in before insert triggers to properly set version columns for kms_* tables with a version column'; + + commit; +`), + }, + "migrations/31_keys.down.sql": { + name: "31_keys.down.sql", + bytes: []byte(` +begin; + +drop table kms_root_key cascade; +drop table kms_root_key_version cascade; +drop table kms_database_key cascade; +drop table kms_database_key_version cascade; +drop table kms_oplog_key cascade; +drop table kms_oplog_key_version cascade; +drop table kms_session_key cascade; +drop table kms_session_key_version cascade; + +commit; + +`), + }, + "migrations/31_keys.up.sql": { + name: "31_keys.up.sql", + bytes: []byte(` +begin; + +/* + ┌────────────────────────────────────────────────────────────────────────────────────────────┐ + ├────────────────────────────────────────────────────────────────┐ ○ + ├────────────────────────────────────┐ ○ ┼ + │ ○ ┼ ┌────────────────────────┐ + ┼ ┼ ┌────────────────────────┐ │ kms_session_key │ +┌────────────────────────┐ ┌────────────────────────┐ │ kms_oplog_key │ ├────────────────────────┤ +│ kms_root_key │ │ kms_database_key │ ├────────────────────────┤ │private_id │ +├────────────────────────┤ ├────────────────────────┤ │private_id │ │root_key_id │ +│private_id │ │private_id │ │root_key_id │ │ │ +│scope_id │ │root_key_id │ │ │ │ │ +│ │ │ │ │ │ │ │ +└────────────────────────┘ └────────────────────────┘ └────────────────────────┘ └────────────────────────┘ + ┼ ┼ ┼ ┼ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + ┼ ┼ ┼ ┼ + ╱│╲ ╱│╲ ╱│╲ ╱│╲ +┌────────────────────────┐ ┌────────────────────────┐ ┌────────────────────────┐ ┌────────────────────────┐ +│ kms_root_key_version │ │kms_database_key_version│ │ kms_oplog_key_version │ │kms_session_key_version │ +├────────────────────────┤ ├────────────────────────┤ ├────────────────────────┤ ├────────────────────────┤ +│private_id │ │private_id │ │private_id │ │private_id │ +│root_key_id │ │database_key_id │ │oplog_key_id │ │session_key_id │ +│key │ │root_key_id │ │root_key_id │ │root_key_id │ +│version │ │key │ │key │ │key │ +│ │ │version │ │version │ │version │ +└────────────────────────┘ └────────────────────────┘ │ │ │ │ + ┼ ┼ └────────────────────────┘ │ │ + │ ○ ┼ └────────────────────────┘ + ├────────────────────────────────────┘ ○ ┼ + ├────────────────────────────────────────────────────────────────┘ ○ + └────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ + +create table kms_root_key ( + private_id wt_private_id primary key, + scope_id wt_scope_id not null unique -- there can only be one root key for a scope. + references iam_scope(public_id) + on delete cascade + on update cascade, + create_time wt_timestamp +); + + -- define the immutable fields for kms_root_key (all of them) +create trigger + immutable_columns +before +update on kms_root_key + for each row execute procedure immutable_columns('private_id', 'scope_id', 'create_time'); + +create trigger + default_create_time_column +before +insert on kms_root_key + for each row execute procedure default_create_time(); + +create table kms_root_key_version ( + private_id wt_private_id primary key, + root_key_id wt_private_id not null + references kms_root_key(private_id) + on delete cascade + on update cascade, + version wt_version, + key bytea not null, + create_time wt_timestamp, + unique(root_key_id, version) +); + + -- define the immutable fields for kms_root_key_version (all of them) +create trigger + immutable_columns +before +update on kms_root_key_version + for each row execute procedure immutable_columns('private_id', 'root_key_id', 'version', 'key', 'create_time'); + +create trigger + default_create_time_column +before +insert on kms_root_key_version + for each row execute procedure default_create_time(); + + +create trigger + kms_version_column +before insert on kms_root_key_version + for each row execute procedure kms_version_column('root_key_id'); + +create table kms_database_key ( + private_id wt_private_id primary key, + root_key_id wt_private_id not null + references kms_root_key(private_id) + on delete cascade + on update cascade, + create_time wt_timestamp +); + + -- define the immutable fields for kms_database_key (all of them) +create trigger + immutable_columns +before +update on kms_database_key + for each row execute procedure immutable_columns('private_id', 'root_key_id', 'create_time'); + +create trigger + default_create_time_column +before +insert on kms_database_key + for each row execute procedure default_create_time(); + +create table kms_database_key_version ( + private_id wt_private_id primary key, + database_key_id wt_private_id not null + references kms_database_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, + unique(database_key_id, version) +); + + -- define the immutable fields for kms_database_key_version (all of them) +create trigger + immutable_columns +before +update on kms_database_key_version + for each row execute procedure immutable_columns('private_id', 'database_key_id', 'root_key_version_id', 'version', 'key', 'create_time'); + +create trigger + default_create_time_column +before +insert on kms_database_key_version + for each row execute procedure default_create_time(); + +create trigger + kms_version_column +before insert on kms_database_key_version + for each row execute procedure kms_version_column('database_key_id'); + +create table kms_oplog_key ( + private_id wt_private_id primary key, + root_key_id wt_private_id not null + references kms_root_key(private_id) + on delete cascade + on update cascade, + create_time wt_timestamp +); + + -- define the immutable fields for kms_oplog_key (all of them) +create trigger + immutable_columns +before +update on kms_oplog_key + for each row execute procedure immutable_columns('private_id', 'root_key_id', 'create_time'); + +create trigger + default_create_time_column +before +insert on kms_oplog_key + for each row execute procedure default_create_time(); + +create table kms_oplog_key_version ( + private_id wt_private_id primary key, + oplog_key_id wt_private_id not null + references kms_oplog_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, + unique(oplog_key_id, version) +); + + -- define the immutable fields for kms_oplog_key_version (all of them) +create trigger + immutable_columns +before +update on kms_oplog_key_version + for each row execute procedure immutable_columns('private_id', 'oplog_key_id', 'root_key_version_id', 'version', 'key', 'create_time'); + +create trigger + default_create_time_column +before +insert on kms_oplog_key_version + for each row execute procedure default_create_time(); + +create trigger + kms_version_column +before insert on kms_oplog_key_version + for each row execute procedure kms_version_column('oplog_key_id'); + +create table kms_session_key ( + private_id wt_private_id primary key, + root_key_id wt_private_id not null + references kms_root_key(private_id) + on delete cascade + on update cascade, + create_time wt_timestamp +); + + -- define the immutable fields for kms_oplog_key (all of them) +create trigger + immutable_columns +before +update on kms_session_key + for each row execute procedure immutable_columns('private_id', 'root_key_id', 'create_time'); + +create trigger + default_create_time_column +before +insert on kms_session_key + for each row execute procedure default_create_time(); + +create table kms_session_key_version ( + private_id wt_private_id primary key, + session_key_id wt_private_id not null + references kms_session_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, + unique(session_key_id, version) +); + + + -- define the immutable fields for kms_session_key_version (all of them) +create trigger + immutable_columns +before +update on kms_session_key_version + for each row execute procedure immutable_columns('private_id', 'session_key_id', 'root_key_version_id', 'version', 'key', 'create_time'); + +create trigger + default_create_time_column +before +insert on kms_session_key_version + for each row execute procedure default_create_time(); + +create trigger + kms_version_column +before insert on kms_session_key_version + for each row execute procedure kms_version_column('session_key_id'); + + insert into oplog_ticket + (name, version) + values + ('kms_root_key', 1), + ('kms_root_key_version', 1); + +commit; `), }, } diff --git a/internal/db/migrations/postgres/01_domain_types.up.sql b/internal/db/migrations/postgres/01_domain_types.up.sql index 45b2387a8..b96b44f5d 100644 --- a/internal/db/migrations/postgres/01_domain_types.up.sql +++ b/internal/db/migrations/postgres/01_domain_types.up.sql @@ -90,7 +90,8 @@ comment on domain wt_version is -- update_version_column() will increment the version column whenever row data -- is updated and should only be used in an update after trigger. This function --- will overwrite any explicit updates to the version column. +-- will overwrite any explicit updates to the version column. The function +-- accepts an optional parameter of 'private_id' for the tables primary key. create or replace function update_version_column() returns trigger @@ -98,9 +99,16 @@ as $$ begin if pg_trigger_depth() = 1 then if row(new.*) is distinct from row(old.*) then - execute format('update %I set version = $1 where public_id = $2', tg_relid::regclass) using old.version+1, new.public_id; - new.version = old.version + 1; - return new; + if tg_nargs = 0 then + execute format('update %I set version = $1 where public_id = $2', tg_relid::regclass) using old.version+1, new.public_id; + new.version = old.version + 1; + return new; + end if; + if tg_argv[0] = 'private_id' then + execute format('update %I set version = $1 where private_id = $2', tg_relid::regclass) using old.version+1, new.private_id; + new.version = old.version + 1; + return new; + end if; end if; end if; return new; diff --git a/internal/db/migrations/postgres/08_servers.up.sql b/internal/db/migrations/postgres/08_servers.up.sql index 64f8641fc..a86158cc4 100644 --- a/internal/db/migrations/postgres/08_servers.up.sql +++ b/internal/db/migrations/postgres/08_servers.up.sql @@ -7,7 +7,8 @@ begin; create table servers ( private_id text, type text, - name text not null unique, + name text not null unique + check(length(trim(name)) > 0), description text, address text, create_time wt_timestamp, diff --git a/internal/db/migrations/postgres/11_auth_token.up.sql b/internal/db/migrations/postgres/11_auth_token.up.sql index bf09dd04c..127b7436f 100644 --- a/internal/db/migrations/postgres/11_auth_token.up.sql +++ b/internal/db/migrations/postgres/11_auth_token.up.sql @@ -5,6 +5,9 @@ begin; create table auth_token ( public_id wt_public_id primary key, token bytea not null unique, + -- TODO: Make key_id a foreign key once we have DEKs + key_id text not null + check(length(trim(key_id)) > 0), auth_account_id wt_public_id not null references auth_account(public_id) on delete cascade diff --git a/internal/db/migrations/postgres/13_auth_password_argon.up.sql b/internal/db/migrations/postgres/13_auth_password_argon.up.sql index 743f3a708..78fb9244c 100644 --- a/internal/db/migrations/postgres/13_auth_password_argon.up.sql +++ b/internal/db/migrations/postgres/13_auth_password_argon.up.sql @@ -67,6 +67,9 @@ begin; check(length(salt) > 0), derived_key bytea not null check(length(derived_key) > 0), + -- TODO: Make key_id a foreign key once we have DEKs + key_id text not null + check(length(trim(key_id)) > 0), foreign key (password_method_id, password_conf_id) references auth_password_argon2_conf (password_method_id, private_id) on delete cascade diff --git a/internal/db/migrations/postgres/30_keys.down.sql b/internal/db/migrations/postgres/30_keys.down.sql new file mode 100644 index 000000000..b87742fe4 --- /dev/null +++ b/internal/db/migrations/postgres/30_keys.down.sql @@ -0,0 +1,5 @@ +begin; + +drop function kms_version_column cascade; + +commit; diff --git a/internal/db/migrations/postgres/30_keys.up.sql b/internal/db/migrations/postgres/30_keys.up.sql new file mode 100644 index 000000000..ea4042672 --- /dev/null +++ b/internal/db/migrations/postgres/30_keys.up.sql @@ -0,0 +1,29 @@ +begin; + +-- kms_version_column() will increment the version column whenever row data +-- is inserted and should only be used in an before insert trigger. This +-- function will overwrite any explicit values to the version column. +create or replace function + kms_version_column() + returns trigger +as $$ +declare + _key_id text; + _max bigint; +begin + execute format('SELECT $1.%I', tg_argv[0]) into _key_id using new; + execute format('select max(version) + 1 from %I where %I = $1', tg_relid::regclass, tg_argv[0]) using _key_id into _max; + if _max is null then + _max = 1; + end if; + new.version = _max; + return new; +end; +$$ language plpgsql; + +comment on function + kms_version_column() +is + 'function used in before insert triggers to properly set version columns for kms_* tables with a version column'; + + commit; \ No newline at end of file diff --git a/internal/db/migrations/postgres/31_keys.down.sql b/internal/db/migrations/postgres/31_keys.down.sql new file mode 100644 index 000000000..18ae7b485 --- /dev/null +++ b/internal/db/migrations/postgres/31_keys.down.sql @@ -0,0 +1,12 @@ +begin; + +drop table kms_root_key cascade; +drop table kms_root_key_version cascade; +drop table kms_database_key cascade; +drop table kms_database_key_version cascade; +drop table kms_oplog_key cascade; +drop table kms_oplog_key_version cascade; +drop table kms_session_key cascade; +drop table kms_session_key_version cascade; + +commit; diff --git a/internal/db/migrations/postgres/31_keys.up.sql b/internal/db/migrations/postgres/31_keys.up.sql new file mode 100644 index 000000000..5fe819aa1 --- /dev/null +++ b/internal/db/migrations/postgres/31_keys.up.sql @@ -0,0 +1,267 @@ +begin; + +/* + ┌────────────────────────────────────────────────────────────────────────────────────────────┐ + ├────────────────────────────────────────────────────────────────┐ ○ + ├────────────────────────────────────┐ ○ ┼ + │ ○ ┼ ┌────────────────────────┐ + ┼ ┼ ┌────────────────────────┐ │ kms_session_key │ +┌────────────────────────┐ ┌────────────────────────┐ │ kms_oplog_key │ ├────────────────────────┤ +│ kms_root_key │ │ kms_database_key │ ├────────────────────────┤ │private_id │ +├────────────────────────┤ ├────────────────────────┤ │private_id │ │root_key_id │ +│private_id │ │private_id │ │root_key_id │ │ │ +│scope_id │ │root_key_id │ │ │ │ │ +│ │ │ │ │ │ │ │ +└────────────────────────┘ └────────────────────────┘ └────────────────────────┘ └────────────────────────┘ + ┼ ┼ ┼ ┼ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + ┼ ┼ ┼ ┼ + ╱│╲ ╱│╲ ╱│╲ ╱│╲ +┌────────────────────────┐ ┌────────────────────────┐ ┌────────────────────────┐ ┌────────────────────────┐ +│ kms_root_key_version │ │kms_database_key_version│ │ kms_oplog_key_version │ │kms_session_key_version │ +├────────────────────────┤ ├────────────────────────┤ ├────────────────────────┤ ├────────────────────────┤ +│private_id │ │private_id │ │private_id │ │private_id │ +│root_key_id │ │database_key_id │ │oplog_key_id │ │session_key_id │ +│key │ │root_key_id │ │root_key_id │ │root_key_id │ +│version │ │key │ │key │ │key │ +│ │ │version │ │version │ │version │ +└────────────────────────┘ └────────────────────────┘ │ │ │ │ + ┼ ┼ └────────────────────────┘ │ │ + │ ○ ┼ └────────────────────────┘ + ├────────────────────────────────────┘ ○ ┼ + ├────────────────────────────────────────────────────────────────┘ ○ + └────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ + +create table kms_root_key ( + private_id wt_private_id primary key, + scope_id wt_scope_id not null unique -- there can only be one root key for a scope. + references iam_scope(public_id) + on delete cascade + on update cascade, + create_time wt_timestamp +); + + -- define the immutable fields for kms_root_key (all of them) +create trigger + immutable_columns +before +update on kms_root_key + for each row execute procedure immutable_columns('private_id', 'scope_id', 'create_time'); + +create trigger + default_create_time_column +before +insert on kms_root_key + for each row execute procedure default_create_time(); + +create table kms_root_key_version ( + private_id wt_private_id primary key, + root_key_id wt_private_id not null + references kms_root_key(private_id) + on delete cascade + on update cascade, + version wt_version, + key bytea not null, + create_time wt_timestamp, + unique(root_key_id, version) +); + + -- define the immutable fields for kms_root_key_version (all of them) +create trigger + immutable_columns +before +update on kms_root_key_version + for each row execute procedure immutable_columns('private_id', 'root_key_id', 'version', 'key', 'create_time'); + +create trigger + default_create_time_column +before +insert on kms_root_key_version + for each row execute procedure default_create_time(); + + +create trigger + kms_version_column +before insert on kms_root_key_version + for each row execute procedure kms_version_column('root_key_id'); + +create table kms_database_key ( + private_id wt_private_id primary key, + root_key_id wt_private_id not null + references kms_root_key(private_id) + on delete cascade + on update cascade, + create_time wt_timestamp +); + + -- define the immutable fields for kms_database_key (all of them) +create trigger + immutable_columns +before +update on kms_database_key + for each row execute procedure immutable_columns('private_id', 'root_key_id', 'create_time'); + +create trigger + default_create_time_column +before +insert on kms_database_key + for each row execute procedure default_create_time(); + +create table kms_database_key_version ( + private_id wt_private_id primary key, + database_key_id wt_private_id not null + references kms_database_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, + unique(database_key_id, version) +); + + -- define the immutable fields for kms_database_key_version (all of them) +create trigger + immutable_columns +before +update on kms_database_key_version + for each row execute procedure immutable_columns('private_id', 'database_key_id', 'root_key_version_id', 'version', 'key', 'create_time'); + +create trigger + default_create_time_column +before +insert on kms_database_key_version + for each row execute procedure default_create_time(); + +create trigger + kms_version_column +before insert on kms_database_key_version + for each row execute procedure kms_version_column('database_key_id'); + +create table kms_oplog_key ( + private_id wt_private_id primary key, + root_key_id wt_private_id not null + references kms_root_key(private_id) + on delete cascade + on update cascade, + create_time wt_timestamp +); + + -- define the immutable fields for kms_oplog_key (all of them) +create trigger + immutable_columns +before +update on kms_oplog_key + for each row execute procedure immutable_columns('private_id', 'root_key_id', 'create_time'); + +create trigger + default_create_time_column +before +insert on kms_oplog_key + for each row execute procedure default_create_time(); + +create table kms_oplog_key_version ( + private_id wt_private_id primary key, + oplog_key_id wt_private_id not null + references kms_oplog_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, + unique(oplog_key_id, version) +); + + -- define the immutable fields for kms_oplog_key_version (all of them) +create trigger + immutable_columns +before +update on kms_oplog_key_version + for each row execute procedure immutable_columns('private_id', 'oplog_key_id', 'root_key_version_id', 'version', 'key', 'create_time'); + +create trigger + default_create_time_column +before +insert on kms_oplog_key_version + for each row execute procedure default_create_time(); + +create trigger + kms_version_column +before insert on kms_oplog_key_version + for each row execute procedure kms_version_column('oplog_key_id'); + +create table kms_session_key ( + private_id wt_private_id primary key, + root_key_id wt_private_id not null + references kms_root_key(private_id) + on delete cascade + on update cascade, + create_time wt_timestamp +); + + -- define the immutable fields for kms_oplog_key (all of them) +create trigger + immutable_columns +before +update on kms_session_key + for each row execute procedure immutable_columns('private_id', 'root_key_id', 'create_time'); + +create trigger + default_create_time_column +before +insert on kms_session_key + for each row execute procedure default_create_time(); + +create table kms_session_key_version ( + private_id wt_private_id primary key, + session_key_id wt_private_id not null + references kms_session_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, + unique(session_key_id, version) +); + + + -- define the immutable fields for kms_session_key_version (all of them) +create trigger + immutable_columns +before +update on kms_session_key_version + for each row execute procedure immutable_columns('private_id', 'session_key_id', 'root_key_version_id', 'version', 'key', 'create_time'); + +create trigger + default_create_time_column +before +insert on kms_session_key_version + for each row execute procedure default_create_time(); + +create trigger + kms_version_column +before insert on kms_session_key_version + for each row execute procedure kms_version_column('session_key_id'); + + insert into oplog_ticket + (name, version) + values + ('kms_root_key', 1), + ('kms_root_key_version', 1); + +commit; \ No newline at end of file diff --git a/internal/db/option.go b/internal/db/option.go index fb8a547a2..b55af90c2 100644 --- a/internal/db/option.go +++ b/internal/db/option.go @@ -39,6 +39,7 @@ type Options struct { withWhereClause string withWhereClauseArgs []interface{} + withOrder string } type oplogOpts struct { @@ -146,3 +147,11 @@ func WithWhere(whereClause string, args ...interface{}) Option { o.withWhereClauseArgs = append(o.withWhereClauseArgs, args...) } } + +// WithOrder provides an option to provide an order when searching and looking +// up. +func WithOrder(withOrder string) Option { + return func(o *Options) { + o.withOrder = withOrder + } +} diff --git a/internal/db/option_test.go b/internal/db/option_test.go index c10525020..8a1abda47 100644 --- a/internal/db/option_test.go +++ b/internal/db/option_test.go @@ -162,4 +162,17 @@ func Test_getOpts(t *testing.T) { testOpts.withWhereClauseArgs = []interface{}{1234, "bar"} assert.Equal(opts, testOpts) }) + t.Run("WithOrder", func(t *testing.T) { + assert := assert.New(t) + // test default of false + opts := GetOpts() + testOpts := getDefaultOptions() + testOpts.withOrder = "" + assert.Equal(opts, testOpts) + + // try setting to false + opts = GetOpts(WithOrder("version desc")) + testOpts.withOrder = "version desc" + assert.Equal(opts, testOpts) + }) } diff --git a/internal/db/read_writer.go b/internal/db/read_writer.go index 256f2bdb9..3cd75ff8e 100644 --- a/internal/db/read_writer.go +++ b/internal/db/read_writer.go @@ -914,7 +914,7 @@ func (rw *Db) LookupById(ctx context.Context, resourceWithIder interface{}, opt return fmt.Errorf("lookup by id: underlying db nil %w", ErrNilParameter) } if reflect.ValueOf(resourceWithIder).Kind() != reflect.Ptr { - return fmt.Errorf("lookup by id: interface parameter must to be a pointer %w", ErrInvalidParameter) + return fmt.Errorf("lookup by id: interface parameter must to be a pointer: %w", ErrInvalidParameter) } primaryKey, where, err := primaryKeyWhere(resourceWithIder) if err != nil { @@ -973,7 +973,7 @@ func (rw *Db) LookupWhere(ctx context.Context, resource interface{}, where strin // SearchWhere will search for all the resources it can find using a where // clause with parameters. Supports the WithLimit option. If // WithLimit < 0, then unlimited results are returned. If WithLimit == 0, then -// default limits are used for results. +// default limits are used for results. Supports the WithOrder option. func (rw *Db) SearchWhere(ctx context.Context, resources interface{}, where string, args []interface{}, opt ...Option) error { opts := GetOpts(opt...) if rw.underlying == nil { @@ -985,11 +985,11 @@ func (rw *Db) SearchWhere(ctx context.Context, resources interface{}, where stri var err error switch { case opts.WithLimit < 0: // any negative number signals unlimited results - err = rw.underlying.Where(where, args...).Find(resources).Error + err = rw.underlying.Order(opts.withOrder).Where(where, args...).Find(resources).Error case opts.WithLimit == 0: // zero signals the default value and default limits - err = rw.underlying.Limit(DefaultLimit).Where(where, args...).Find(resources).Error + err = rw.underlying.Order(opts.withOrder).Limit(DefaultLimit).Where(where, args...).Find(resources).Error default: - err = rw.underlying.Limit(opts.WithLimit).Where(where, args...).Find(resources).Error + err = rw.underlying.Order(opts.withOrder).Limit(opts.WithLimit).Where(where, args...).Find(resources).Error } if err != nil { // searching with a slice parameter does not return a gorm.ErrRecordNotFound diff --git a/internal/db/read_writer_test.go b/internal/db/read_writer_test.go index 015ea2c46..e32f29593 100644 --- a/internal/db/read_writer_test.go +++ b/internal/db/read_writer_test.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "strconv" "strings" "testing" "time" @@ -949,7 +950,7 @@ func TestDb_LookupWhere(t *testing.T) { func TestDb_SearchWhere(t *testing.T) { t.Parallel() db, _ := TestSetup(t, "postgres") - knownUser := testUser(t, db, "", "", "") + knownUser := testUser(t, db, "zedUser", "", "") type args struct { where string @@ -957,12 +958,13 @@ func TestDb_SearchWhere(t *testing.T) { opt []Option } tests := []struct { - name string - db Db - createCnt int - args args - wantCnt int - wantErr bool + name string + db Db + createCnt int + args args + wantCnt int + wantErr bool + wantNameOrder bool }{ { name: "no-limit", @@ -970,10 +972,11 @@ func TestDb_SearchWhere(t *testing.T) { createCnt: 10, args: args{ where: "1=1", - opt: []Option{WithLimit(-1)}, + opt: []Option{WithLimit(-1), WithOrder("name asc")}, }, - wantCnt: 11, // there's an additional knownUser - wantErr: false, + wantCnt: 11, // there's an additional knownUser + wantErr: false, + wantNameOrder: true, }, { name: "custom-limit", @@ -1040,11 +1043,13 @@ func TestDb_SearchWhere(t *testing.T) { assert, require := assert.New(t), require.New(t) testUsers := []*db_test.TestUser{} for i := 0; i < tt.createCnt; i++ { - testUsers = append(testUsers, testUser(t, db, "", "", "")) + testUsers = append(testUsers, testUser(t, db, tt.name+strconv.Itoa(i), "", "")) } assert.Equal(tt.createCnt, len(testUsers)) var foundUsers []db_test.TestUser + db.LogMode(true) + defer db.LogMode(false) err := tt.db.SearchWhere(context.Background(), &foundUsers, tt.args.where, tt.args.arg, tt.args.opt...) if tt.wantErr { require.Error(err) @@ -1052,6 +1057,14 @@ func TestDb_SearchWhere(t *testing.T) { } require.NoError(err) assert.Equal(tt.wantCnt, len(foundUsers)) + if tt.wantNameOrder { + assert.Equal(tt.name+strconv.Itoa(0), foundUsers[0].Name) + for i, u := range foundUsers { + if u.Name != "zedUser" { + assert.Equal(tt.name+strconv.Itoa(i), u.Name) + } + } + } }) } } diff --git a/internal/db/testing.go b/internal/db/testing.go index 32020b253..4c362a508 100644 --- a/internal/db/testing.go +++ b/internal/db/testing.go @@ -3,6 +3,7 @@ package db import ( "context" "crypto/rand" + "encoding/base64" "strings" "testing" "time" @@ -47,6 +48,12 @@ func TestWrapper(t *testing.T) wrapping.Wrapper { t.Fatal(n) } root := aead.NewWrapper(nil) + _, err = root.SetConfig(map[string]string{ + "key_id": base64.StdEncoding.EncodeToString(rootKey), + }) + if err != nil { + t.Fatal(err) + } if err := root.SetAESGCMKeyBytes(rootKey); err != nil { t.Fatal(err) } diff --git a/internal/host/static/host_catalog_test.go b/internal/host/static/host_catalog_test.go index ec0f7211b..06932f227 100644 --- a/internal/host/static/host_catalog_test.go +++ b/internal/host/static/host_catalog_test.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/host/static/store" "github.com/hashicorp/boundary/internal/iam" + wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -14,7 +15,8 @@ import ( func TestHostCatalog_New(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - _, prj := iam.TestScopes(t, conn) + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) type args struct { scopeId string @@ -107,7 +109,7 @@ func TestHostCatalog_New(t *testing.T) { } } -func testCatalogs(t *testing.T, conn *gorm.DB, scopeId string, count int) []*HostCatalog { +func testCatalogs(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, scopeId string, count int) []*HostCatalog { t.Helper() assert := assert.New(t) var cats []*HostCatalog @@ -128,9 +130,9 @@ func testCatalogs(t *testing.T, conn *gorm.DB, scopeId string, count int) []*Hos return cats } -func testCatalog(t *testing.T, conn *gorm.DB, scopeId string) *HostCatalog { +func testCatalog(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, scopeId string) *HostCatalog { t.Helper() - cats := testCatalogs(t, conn, scopeId, 1) + cats := testCatalogs(t, conn, wrapper, scopeId, 1) return cats[0] } diff --git a/internal/host/static/host_set_member_test.go b/internal/host/static/host_set_member_test.go index 59ad0ebb3..dffab09a4 100644 --- a/internal/host/static/host_set_member_test.go +++ b/internal/host/static/host_set_member_test.go @@ -14,9 +14,10 @@ import ( func TestHostSetMember_New(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") conn.LogMode(false) + wrapper := db.TestWrapper(t) - _, prj := iam.TestScopes(t, conn) - cats := testCatalogs(t, conn, prj.PublicId, 2) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cats := testCatalogs(t, conn, wrapper, prj.PublicId, 2) blueCat := cats[0] blueSets := testSets(t, conn, blueCat.GetPublicId(), 2) diff --git a/internal/host/static/host_set_test.go b/internal/host/static/host_set_test.go index 8e0ab1769..ee3fd2080 100644 --- a/internal/host/static/host_set_test.go +++ b/internal/host/static/host_set_test.go @@ -14,8 +14,9 @@ import ( func TestHostSet_New(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - _, prj := iam.TestScopes(t, conn) - cat := testCatalog(t, conn, prj.PublicId) + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cat := testCatalog(t, conn, wrapper, prj.PublicId) conn.LogMode(false) type args struct { diff --git a/internal/host/static/host_test.go b/internal/host/static/host_test.go index af976cdff..eb1e3d91e 100644 --- a/internal/host/static/host_test.go +++ b/internal/host/static/host_test.go @@ -15,8 +15,9 @@ import ( func TestHost_New(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - _, prj := iam.TestScopes(t, conn) - cat := testCatalog(t, conn, prj.PublicId) + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cat := testCatalog(t, conn, wrapper, prj.PublicId) conn.LogMode(false) type args struct { diff --git a/internal/host/static/immutable_fields_test.go b/internal/host/static/immutable_fields_test.go index 7d182d539..a431e3a63 100644 --- a/internal/host/static/immutable_fields_test.go +++ b/internal/host/static/immutable_fields_test.go @@ -18,11 +18,10 @@ func TestHostCatalog_ImmutableFields(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") w := db.New(conn) - - _, proj := iam.TestScopes(t, conn) + wrapper := db.TestWrapper(t) ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}} - _, prj := iam.TestScopes(t, conn) - new := testCatalog(t, conn, prj.PublicId) + o, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + new := testCatalog(t, conn, wrapper, prj.PublicId) var tests = []struct { name string @@ -51,7 +50,7 @@ func TestHostCatalog_ImmutableFields(t *testing.T) { name: "scope id", update: func() *HostCatalog { c := new.clone() - c.ScopeId = proj.PublicId + c.ScopeId = o.PublicId return c }(), fieldMask: []string{"ScopeId"}, @@ -83,10 +82,11 @@ func TestStaticHost_ImmutableFields(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") w := db.New(conn) + wrapper := db.TestWrapper(t) ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}} - _, prj := iam.TestScopes(t, conn) - cat := testCatalog(t, conn, prj.PublicId) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cat := testCatalog(t, conn, wrapper, prj.PublicId) hosts := testHosts(t, conn, cat.GetPublicId(), 1) new := hosts[0] @@ -157,10 +157,11 @@ func TestStaticHostSet_ImmutableFields(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") w := db.New(conn) + wrapper := db.TestWrapper(t) ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}} - _, prj := iam.TestScopes(t, conn) - cat := testCatalog(t, conn, prj.PublicId) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cat := testCatalog(t, conn, wrapper, prj.PublicId) sets := testSets(t, conn, cat.GetPublicId(), 1) new := sets[0] @@ -231,9 +232,10 @@ func TestStaticHostSetMember_ImmutableFields(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") w := db.New(conn) + wrapper := db.TestWrapper(t) - _, prj := iam.TestScopes(t, conn) - cat := testCatalog(t, conn, prj.PublicId) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cat := testCatalog(t, conn, wrapper, prj.PublicId) sets := testSets(t, conn, cat.GetPublicId(), 1) hosts := testHosts(t, conn, cat.GetPublicId(), 1) diff --git a/internal/host/static/repository.go b/internal/host/static/repository.go index 47695c16f..5ecacc54f 100644 --- a/internal/host/static/repository.go +++ b/internal/host/static/repository.go @@ -3,17 +3,16 @@ package static import ( "fmt" - wrapping "github.com/hashicorp/go-kms-wrapping" - "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms" ) // A Repository stores and retrieves the persistent types in the static // package. It is not safe to use a repository concurrently. type Repository struct { - reader db.Reader - writer db.Writer - wrapper wrapping.Wrapper + reader db.Reader + writer db.Writer + kms *kms.Kms // defaultLimit provides a default for limiting the number of results // returned from the repo defaultLimit int @@ -23,14 +22,15 @@ type Repository struct { // only be used for one transaction and it is not safe for concurrent go // routines to access it. WithLimit option is used as a repo wide default // limit applied to all ListX methods. -func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper, opt ...Option) (*Repository, error) { +// routines to access it. +func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repository, error) { switch { case r == nil: return nil, fmt.Errorf("db.Reader: %w", db.ErrNilParameter) case w == nil: return nil, fmt.Errorf("db.Writer: %w", db.ErrNilParameter) - case wrapper == nil: - return nil, fmt.Errorf("wrapping.Wrapper: %w", db.ErrNilParameter) + case kms == nil: + return nil, fmt.Errorf("kms: %w", db.ErrNilParameter) } opts := getOpts(opt...) @@ -42,7 +42,7 @@ func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper, opt ...Op return &Repository{ reader: r, writer: w, - wrapper: wrapper, + kms: kms, defaultLimit: opts.withLimit, }, nil } diff --git a/internal/host/static/repository_host.go b/internal/host/static/repository_host.go index 79584dedb..ce2982e42 100644 --- a/internal/host/static/repository_host.go +++ b/internal/host/static/repository_host.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/boundary/internal/db" dbcommon "github.com/hashicorp/boundary/internal/db/common" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" ) @@ -20,7 +21,7 @@ import ( // // Both h.Name and h.Description are optional. If h.Name is set, it must be // unique within h.CatalogId. -func (r *Repository) CreateHost(ctx context.Context, h *Host, opt ...Option) (*Host, error) { +func (r *Repository) CreateHost(ctx context.Context, scopeId string, h *Host, opt ...Option) (*Host, error) { if h == nil { return nil, fmt.Errorf("create: static host: %w", db.ErrNilParameter) } @@ -33,6 +34,9 @@ func (r *Repository) CreateHost(ctx context.Context, h *Host, opt ...Option) (*H if h.PublicId != "" { return nil, fmt.Errorf("create: static host: public id not empty: %w", db.ErrInvalidParameter) } + if scopeId == "" { + return nil, fmt.Errorf("create: static host: no scopeId: %w", db.ErrNilParameter) + } h = h.clone() id, err := newHostId() @@ -41,11 +45,16 @@ func (r *Repository) CreateHost(ctx context.Context, h *Host, opt ...Option) (*H } h.PublicId = id + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, fmt.Errorf("create: static host: unable to get oplog wrapper: %w", err) + } + var newHost *Host _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, w db.Writer) error { newHost = h.clone() - return w.Create(ctx, newHost, db.WithOplog(r.wrapper, h.oplog(oplog.OpType_OP_TYPE_CREATE))) + return w.Create(ctx, newHost, db.WithOplog(oplogWrapper, h.oplog(oplog.OpType_OP_TYPE_CREATE))) }, ) @@ -75,7 +84,7 @@ func (r *Repository) CreateHost(ctx context.Context, h *Host, opt ...Option) (*H // // An attribute of h will be set to NULL in the database if the attribute // in h is the zero value and it is included in fieldMaskPaths. -func (r *Repository) UpdateHost(ctx context.Context, h *Host, version uint32, fieldMaskPaths []string, opt ...Option) (*Host, int, error) { +func (r *Repository) UpdateHost(ctx context.Context, scopeId string, h *Host, version uint32, fieldMaskPaths []string, opt ...Option) (*Host, int, error) { if h == nil { return nil, db.NoRowsAffected, fmt.Errorf("update: static host: %w", db.ErrNilParameter) } @@ -88,6 +97,9 @@ func (r *Repository) UpdateHost(ctx context.Context, h *Host, version uint32, fi if version == 0 { return nil, db.NoRowsAffected, fmt.Errorf("update: static host: no version supplied: %w", db.ErrInvalidParameter) } + if scopeId == "" { + return nil, db.NoRowsAffected, fmt.Errorf("update: static host: no scopeId: %w", db.ErrNilParameter) + } for _, f := range fieldMaskPaths { switch { @@ -111,14 +123,19 @@ func (r *Repository) UpdateHost(ctx context.Context, h *Host, version uint32, fi return nil, db.NoRowsAffected, fmt.Errorf("update: static host: %w", db.ErrEmptyFieldMask) } + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, db.NoRowsAffected, fmt.Errorf("update: static host: unable to get oplog wrapper: %w", err) + } + var rowsUpdated int var returnedHost *Host - _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, w db.Writer) error { returnedHost = h.clone() var err error rowsUpdated, err = w.Update(ctx, returnedHost, dbMask, nullFields, - db.WithOplog(r.wrapper, h.oplog(oplog.OpType_OP_TYPE_UPDATE)), + db.WithOplog(oplogWrapper, h.oplog(oplog.OpType_OP_TYPE_UPDATE)), db.WithVersion(&version)) if err == nil && rowsUpdated > 1 { return db.ErrMultipleRecords @@ -181,19 +198,24 @@ func (r *Repository) ListHosts(ctx context.Context, catalogId string, opt ...Opt // DeleteHost deletes the host for the provided id from the repository // returning a count of the number of records deleted. All options are // ignored. -func (r *Repository) DeleteHost(ctx context.Context, publicId string, opt ...Option) (int, error) { +func (r *Repository) DeleteHost(ctx context.Context, scopeId string, publicId string, opt ...Option) (int, error) { if publicId == "" { return db.NoRowsAffected, fmt.Errorf("delete: static host: missing public id: %w", db.ErrInvalidParameter) } h := allocHost() h.PublicId = publicId + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, fmt.Errorf("delete: static host catalog: unable to get oplog wrapper: %w", err) + } + var rowsDeleted int - _, err := r.writer.DoTx( + _, err = r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, w db.Writer) (err error) { dh := h.clone() - rowsDeleted, err = w.Delete(ctx, dh, db.WithOplog(r.wrapper, h.oplog(oplog.OpType_OP_TYPE_DELETE))) + rowsDeleted, err = w.Delete(ctx, dh, db.WithOplog(oplogWrapper, h.oplog(oplog.OpType_OP_TYPE_DELETE))) if err == nil && rowsDeleted > 1 { return db.ErrMultipleRecords } diff --git a/internal/host/static/repository_host_catalog.go b/internal/host/static/repository_host_catalog.go index 04b60be4f..3655f1109 100644 --- a/internal/host/static/repository_host_catalog.go +++ b/internal/host/static/repository_host_catalog.go @@ -2,10 +2,12 @@ package static import ( "context" + "errors" "fmt" "strings" "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" ) @@ -39,6 +41,11 @@ func (r *Repository) CreateCatalog(ctx context.Context, c *HostCatalog, opt ...O } c.PublicId = id + oplogWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, fmt.Errorf("create: static host catalog: unable to get oplog wrapper: %w", err) + } + metadata := newCatalogMetadata(c, oplog.OpType_OP_TYPE_CREATE) var newHostCatalog *HostCatalog @@ -51,7 +58,7 @@ func (r *Repository) CreateCatalog(ctx context.Context, c *HostCatalog, opt ...O return w.Create( ctx, newHostCatalog, - db.WithOplog(r.wrapper, metadata), + db.WithOplog(oplogWrapper, metadata), ) }, ) @@ -87,6 +94,9 @@ func (r *Repository) UpdateCatalog(ctx context.Context, c *HostCatalog, version if c.PublicId == "" { return nil, db.NoRowsAffected, fmt.Errorf("update: static host catalog: missing public id: %w", db.ErrInvalidParameter) } + if c.ScopeId == "" { + return nil, db.NoRowsAffected, fmt.Errorf("update: static host catalog: missing scope id: %w", db.ErrNilParameter) + } if len(fieldMask) == 0 { return nil, db.NoRowsAffected, fmt.Errorf("update: static host catalog: %w", db.ErrEmptyFieldMask) } @@ -107,13 +117,19 @@ func (r *Repository) UpdateCatalog(ctx context.Context, c *HostCatalog, version return nil, db.NoRowsAffected, fmt.Errorf("update: static host catalog: field: %s: %w", f, db.ErrInvalidFieldMask) } } + + oplogWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, db.NoRowsAffected, fmt.Errorf("update: static host catalog: unable to get oplog wrapper: %w", err) + } + c = c.clone() metadata := newCatalogMetadata(c, oplog.OpType_OP_TYPE_UPDATE) var rowsUpdated int var returnedCatalog *HostCatalog - _, err := r.writer.DoTx( + _, err = r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, @@ -125,7 +141,7 @@ func (r *Repository) UpdateCatalog(ctx context.Context, c *HostCatalog, version returnedCatalog, dbMask, nullFields, - db.WithOplog(r.wrapper, metadata), + db.WithOplog(oplogWrapper, metadata), db.WithVersion(&version), ) if err == nil && rowsUpdated > 1 { @@ -172,12 +188,25 @@ func (r *Repository) DeleteCatalog(ctx context.Context, id string, opt ...Option c := allocCatalog() c.PublicId = id + if err := r.reader.LookupByPublicId(ctx, c); err != nil { + if errors.Is(err, db.ErrRecordNotFound) { + return db.NoRowsAffected, nil + } + return db.NoRowsAffected, fmt.Errorf("delete: static host catalog: failed %w for %s", err, id) + } + if c.ScopeId == "" { + return db.NoRowsAffected, fmt.Errorf("delete: static host catalog: missing scope id: %w", db.ErrNilParameter) + } + oplogWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, fmt.Errorf("delete: static host catalog: unable to get oplog wrapper: %w", err) + } metadata := newCatalogMetadata(c, oplog.OpType_OP_TYPE_DELETE) var rowsDeleted int var deleteCatalog *HostCatalog - _, err := r.writer.DoTx( + _, err = r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, @@ -187,7 +216,7 @@ func (r *Repository) DeleteCatalog(ctx context.Context, id string, opt ...Option rowsDeleted, err = w.Delete( ctx, deleteCatalog, - db.WithOplog(r.wrapper, metadata), + db.WithOplog(oplogWrapper, metadata), ) if err == nil && rowsDeleted > 1 { return db.ErrMultipleRecords diff --git a/internal/host/static/repository_host_catalog_test.go b/internal/host/static/repository_host_catalog_test.go index 0ed4ec632..45d9dd4ae 100644 --- a/internal/host/static/repository_host_catalog_test.go +++ b/internal/host/static/repository_host_catalog_test.go @@ -10,6 +10,7 @@ import ( dbassert "github.com/hashicorp/boundary/internal/db/assert" "github.com/hashicorp/boundary/internal/host/static/store" "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -76,10 +77,11 @@ func TestRepository_CreateCatalog(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert := assert.New(t) - repo, err := NewRepository(rw, rw, wrapper) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) assert.NotNil(repo) - _, prj := iam.TestScopes(t, conn) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) if tt.in != nil && tt.in.HostCatalog != nil { tt.in.ScopeId = prj.GetPublicId() assert.Empty(tt.in.PublicId) @@ -103,11 +105,11 @@ func TestRepository_CreateCatalog(t *testing.T) { t.Run("invalid-duplicate-names", func(t *testing.T) { assert := assert.New(t) - repo, err := NewRepository(rw, rw, wrapper) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) assert.NotNil(repo) - - _, prj := iam.TestScopes(t, conn) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) in := &HostCatalog{ HostCatalog: &store.HostCatalog{ ScopeId: prj.GetPublicId(), @@ -131,11 +133,11 @@ func TestRepository_CreateCatalog(t *testing.T) { t.Run("valid-duplicate-names-diff-scopes", func(t *testing.T) { assert := assert.New(t) - repo, err := NewRepository(rw, rw, wrapper) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) assert.NotNil(repo) - - org, prj := iam.TestScopes(t, conn) + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) in := &HostCatalog{ HostCatalog: &store.HostCatalog{ Name: "test-name-repo", @@ -432,10 +434,11 @@ func TestRepository_UpdateCatalog(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert := assert.New(t) - repo, err := NewRepository(rw, rw, wrapper) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) assert.NotNil(repo) - _, prj := iam.TestScopes(t, conn) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) tt.orig.ScopeId = prj.GetPublicId() orig, err := repo.CreateCatalog(context.Background(), tt.orig) require.NoError(t, err) @@ -474,13 +477,14 @@ func TestRepository_UpdateCatalog(t *testing.T) { t.Run("invalid-duplicate-names", func(t *testing.T) { assert := assert.New(t) - repo, err := NewRepository(rw, rw, wrapper) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) assert.NotNil(repo) name := "test-dup-name" - _, prj := iam.TestScopes(t, conn) - cats := testCatalogs(t, conn, prj.PublicId, 2) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cats := testCatalogs(t, conn, wrapper, prj.PublicId, 2) c1 := cats[0] c1.Name = name got1, gotCount1, err := repo.UpdateCatalog(context.Background(), c1, 1, []string{"name"}) @@ -499,11 +503,11 @@ func TestRepository_UpdateCatalog(t *testing.T) { t.Run("valid-duplicate-names-diff-scopes", func(t *testing.T) { assert := assert.New(t) - repo, err := NewRepository(rw, rw, wrapper) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) assert.NotNil(repo) - - org, prj := iam.TestScopes(t, conn) + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) in := &HostCatalog{ HostCatalog: &store.HostCatalog{ Name: "test-name-repo", @@ -537,13 +541,15 @@ func TestRepository_UpdateCatalog(t *testing.T) { t.Run("change-scope-id", func(t *testing.T) { assert := assert.New(t) - repo, err := NewRepository(rw, rw, wrapper) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) assert.NotNil(repo) - _, prj1 := iam.TestScopes(t, conn) - _, prj2 := iam.TestScopes(t, conn) - c1, c2 := testCatalog(t, conn, prj1.PublicId), testCatalog(t, conn, prj2.PublicId) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, prj1 := iam.TestScopes(t, iamRepo) + _, prj2 := iam.TestScopes(t, iamRepo) + c1, c2 := testCatalog(t, conn, wrapper, prj1.PublicId), testCatalog(t, conn, wrapper, prj2.PublicId) assert.NotEqual(c1.ScopeId, c2.ScopeId) orig := c1.clone() @@ -561,15 +567,14 @@ func TestRepository_UpdateCatalog(t *testing.T) { func TestRepository_LookupCatalog(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - _, prj := iam.TestScopes(t, conn) - cat := testCatalog(t, conn, prj.PublicId) + rw := db.New(conn) + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cat := testCatalog(t, conn, wrapper, prj.PublicId) badId, err := newHostCatalogId() assert.NoError(t, err) assert.NotNil(t, badId) - rw := db.New(conn) - wrapper := db.TestWrapper(t) - var tests = []struct { name string id string @@ -598,7 +603,8 @@ func TestRepository_LookupCatalog(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert := assert.New(t) - repo, err := NewRepository(rw, rw, wrapper) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) assert.NotNil(repo) @@ -622,15 +628,15 @@ func TestRepository_LookupCatalog(t *testing.T) { func TestRepository_DeleteCatalog(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - _, prj := iam.TestScopes(t, conn) - cat := testCatalog(t, conn, prj.PublicId) + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cat := testCatalog(t, conn, wrapper, prj.PublicId) badId, err := newHostCatalogId() assert.NoError(t, err) assert.NotNil(t, badId) - rw := db.New(conn) - wrapper := db.TestWrapper(t) - var tests = []struct { name string id string @@ -659,7 +665,8 @@ func TestRepository_DeleteCatalog(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert := assert.New(t) - repo, err := NewRepository(rw, rw, wrapper) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) assert.NotNil(repo) diff --git a/internal/host/static/repository_host_test.go b/internal/host/static/repository_host_test.go index 08aabd277..9c01d8871 100644 --- a/internal/host/static/repository_host_test.go +++ b/internal/host/static/repository_host_test.go @@ -12,6 +12,7 @@ import ( dbassert "github.com/hashicorp/boundary/internal/db/assert" "github.com/hashicorp/boundary/internal/host/static/store" "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -22,9 +23,10 @@ func TestRepository_CreateHost(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - - _, prj := iam.TestScopes(t, conn) - catalog := testCatalogs(t, conn, prj.PublicId, 1)[0] + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, prj := iam.TestScopes(t, iamRepo) + catalog := testCatalogs(t, conn, wrapper, prj.PublicId, 1)[0] var tests = []struct { name string @@ -144,10 +146,10 @@ func TestRepository_CreateHost(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(err) require.NotNil(repo) - got, err := repo.CreateHost(context.Background(), tt.in, tt.opts...) + got, err := repo.CreateHost(context.Background(), prj.GetPublicId(), tt.in, tt.opts...) if tt.wantIsErr != nil { assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err) assert.Nil(got) @@ -167,12 +169,12 @@ func TestRepository_CreateHost(t *testing.T) { t.Run("invalid-duplicate-names", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(err) require.NotNil(repo) - _, prj := iam.TestScopes(t, conn) - catalog := testCatalogs(t, conn, prj.PublicId, 1)[0] + _, prj := iam.TestScopes(t, iamRepo) + catalog := testCatalogs(t, conn, wrapper, prj.PublicId, 1)[0] in := &Host{ Host: &store.Host{ @@ -182,7 +184,7 @@ func TestRepository_CreateHost(t *testing.T) { }, } - got, err := repo.CreateHost(context.Background(), in) + got, err := repo.CreateHost(context.Background(), prj.GetPublicId(), in) require.NoError(err) require.NotNil(got) assertPublicId(t, HostPrefix, got.PublicId) @@ -191,19 +193,19 @@ func TestRepository_CreateHost(t *testing.T) { assert.Equal(in.Description, got.Description) assert.Equal(got.CreateTime, got.UpdateTime) - got2, err := repo.CreateHost(context.Background(), in) + got2, err := repo.CreateHost(context.Background(), prj.GetPublicId(), in) assert.Truef(errors.Is(err, db.ErrNotUnique), "want err: %v got: %v", db.ErrNotUnique, err) assert.Nil(got2) }) t.Run("valid-duplicate-names-diff-catalogs", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) require.NoError(err) require.NotNil(repo) - _, prj := iam.TestScopes(t, conn) - catalogs := testCatalogs(t, conn, prj.PublicId, 2) + _, prj := iam.TestScopes(t, iamRepo) + catalogs := testCatalogs(t, conn, wrapper, prj.PublicId, 2) catalogA, catalogB := catalogs[0], catalogs[1] @@ -216,7 +218,7 @@ func TestRepository_CreateHost(t *testing.T) { in2 := in.clone() in.CatalogId = catalogA.PublicId - got, err := repo.CreateHost(context.Background(), in) + got, err := repo.CreateHost(context.Background(), prj.GetPublicId(), in) require.NoError(err) require.NotNil(got) assertPublicId(t, HostPrefix, got.PublicId) @@ -226,7 +228,7 @@ func TestRepository_CreateHost(t *testing.T) { assert.Equal(got.CreateTime, got.UpdateTime) in2.CatalogId = catalogB.PublicId - got2, err := repo.CreateHost(context.Background(), in2) + got2, err := repo.CreateHost(context.Background(), prj.GetPublicId(), in2) require.NoError(err) require.NotNil(got2) assertPublicId(t, HostPrefix, got2.PublicId) @@ -241,6 +243,8 @@ func TestRepository_UpdateHost(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) changeAddress := func(s string) func(*Host) *Host { return func(h *Host) *Host { @@ -576,22 +580,22 @@ func TestRepository_UpdateHost(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) - _, prj := iam.TestScopes(t, conn) - catalog := testCatalogs(t, conn, prj.PublicId, 1)[0] + _, prj := iam.TestScopes(t, iamRepo) + catalog := testCatalogs(t, conn, wrapper, prj.PublicId, 1)[0] tt.orig.CatalogId = catalog.PublicId - orig, err := repo.CreateHost(context.Background(), tt.orig) + orig, err := repo.CreateHost(context.Background(), prj.GetPublicId(), tt.orig) assert.NoError(err) require.NotNil(orig) if tt.chgFn != nil { orig = tt.chgFn(orig) } - got, gotCount, err := repo.UpdateHost(context.Background(), orig, 1, tt.masks) + got, gotCount, err := repo.UpdateHost(context.Background(), prj.GetPublicId(), orig, 1, tt.masks) if tt.wantIsErr != nil { assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err) assert.Equal(tt.wantCount, gotCount, "row count") @@ -624,19 +628,19 @@ func TestRepository_UpdateHost(t *testing.T) { t.Run("invalid-duplicate-names", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) name := "test-dup-name" - _, prj := iam.TestScopes(t, conn) - catalog := testCatalogs(t, conn, prj.PublicId, 1)[0] + _, prj := iam.TestScopes(t, iamRepo) + catalog := testCatalogs(t, conn, wrapper, prj.PublicId, 1)[0] hs := testHosts(t, conn, catalog.PublicId, 2) hA, hB := hs[0], hs[1] hA.Name = name - got1, gotCount1, err := repo.UpdateHost(context.Background(), hA, 1, []string{"name"}) + got1, gotCount1, err := repo.UpdateHost(context.Background(), prj.GetPublicId(), hA, 1, []string{"name"}) assert.NoError(err) require.NotNil(got1) assert.Equal(name, got1.Name) @@ -644,7 +648,7 @@ func TestRepository_UpdateHost(t *testing.T) { assert.NoError(db.TestVerifyOplog(t, rw, hA.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))) hB.Name = name - got2, gotCount2, err := repo.UpdateHost(context.Background(), hB, 1, []string{"name"}) + got2, gotCount2, err := repo.UpdateHost(context.Background(), prj.GetPublicId(), hB, 1, []string{"name"}) assert.Truef(errors.Is(err, db.ErrNotUnique), "want err: %v got: %v", db.ErrNotUnique, err) assert.Nil(got2) assert.Equal(db.NoRowsAffected, gotCount2, "row count") @@ -655,12 +659,12 @@ func TestRepository_UpdateHost(t *testing.T) { t.Run("valid-duplicate-names-diff-Catalogs", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) - _, prj := iam.TestScopes(t, conn) - catalogs := testCatalogs(t, conn, prj.PublicId, 2) + _, prj := iam.TestScopes(t, iamRepo) + catalogs := testCatalogs(t, conn, wrapper, prj.PublicId, 2) catalogA, catalogB := catalogs[0], catalogs[1] @@ -673,7 +677,7 @@ func TestRepository_UpdateHost(t *testing.T) { in2 := in.clone() in.CatalogId = catalogA.PublicId - got, err := repo.CreateHost(context.Background(), in) + got, err := repo.CreateHost(context.Background(), prj.GetPublicId(), in) assert.NoError(err) require.NotNil(got) assertPublicId(t, HostPrefix, got.PublicId) @@ -683,11 +687,11 @@ func TestRepository_UpdateHost(t *testing.T) { in2.CatalogId = catalogB.PublicId in2.Name = "first-name" - got2, err := repo.CreateHost(context.Background(), in2) + got2, err := repo.CreateHost(context.Background(), prj.GetPublicId(), in2) assert.NoError(err) require.NotNil(got2) got2.Name = got.Name - got3, gotCount3, err := repo.UpdateHost(context.Background(), got2, 1, []string{"name"}) + got3, gotCount3, err := repo.UpdateHost(context.Background(), prj.GetPublicId(), got2, 1, []string{"name"}) assert.NoError(err) require.NotNil(got3) assert.NotSame(got2, got3) @@ -699,12 +703,12 @@ func TestRepository_UpdateHost(t *testing.T) { t.Run("change-scope-id", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) - _, prj := iam.TestScopes(t, conn) - catalogs := testCatalogs(t, conn, prj.PublicId, 2) + _, prj := iam.TestScopes(t, iamRepo) + catalogs := testCatalogs(t, conn, wrapper, prj.PublicId, 2) catalogA, catalogB := catalogs[0], catalogs[1] @@ -717,7 +721,7 @@ func TestRepository_UpdateHost(t *testing.T) { hA.CatalogId = hB.CatalogId assert.Equal(hA.CatalogId, hB.CatalogId) - got1, gotCount1, err := repo.UpdateHost(context.Background(), hA, 1, []string{"name"}) + got1, gotCount1, err := repo.UpdateHost(context.Background(), prj.GetPublicId(), hA, 1, []string{"name"}) assert.NoError(err) require.NotNil(got1) @@ -731,9 +735,11 @@ func TestRepository_LookupHost(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) - _, prj := iam.TestScopes(t, conn) - catalog := testCatalogs(t, conn, prj.PublicId, 1)[0] + _, prj := iam.TestScopes(t, iamRepo) + catalog := testCatalogs(t, conn, wrapper, prj.PublicId, 1)[0] host := testHosts(t, conn, catalog.PublicId, 1)[0] hostId, err := newHostId() @@ -763,7 +769,7 @@ func TestRepository_LookupHost(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) got, err := repo.LookupHost(context.Background(), tt.in) @@ -782,9 +788,11 @@ func TestRepository_ListHosts(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) - _, prj := iam.TestScopes(t, conn) - catalogs := testCatalogs(t, conn, prj.PublicId, 2) + _, prj := iam.TestScopes(t, iamRepo) + catalogs := testCatalogs(t, conn, wrapper, prj.PublicId, 2) catalogA, catalogB := catalogs[0], catalogs[1] hosts := testHosts(t, conn, catalogA.PublicId, 3) @@ -816,7 +824,7 @@ func TestRepository_ListHosts(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) got, err := repo.ListHosts(context.Background(), tt.in, tt.opts...) @@ -839,9 +847,11 @@ func TestRepository_ListHosts_Limits(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) - _, prj := iam.TestScopes(t, conn) - catalog := testCatalogs(t, conn, prj.PublicId, 1)[0] + _, prj := iam.TestScopes(t, iamRepo) + catalog := testCatalogs(t, conn, wrapper, prj.PublicId, 1)[0] count := 10 hosts := testHosts(t, conn, catalog.PublicId, count) @@ -893,7 +903,7 @@ func TestRepository_ListHosts_Limits(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper, tt.repoOpts...) + repo, err := NewRepository(rw, rw, kms, tt.repoOpts...) assert.NoError(err) require.NotNil(repo) got, err := repo.ListHosts(context.Background(), hosts[0].CatalogId, tt.listOpts...) @@ -907,9 +917,11 @@ func TestRepository_DeleteHost(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) - _, prj := iam.TestScopes(t, conn) - catalog := testCatalogs(t, conn, prj.PublicId, 1)[0] + _, prj := iam.TestScopes(t, iamRepo) + catalog := testCatalogs(t, conn, wrapper, prj.PublicId, 1)[0] host := testHosts(t, conn, catalog.PublicId, 1)[0] newHostId, err := newHostId() @@ -940,10 +952,10 @@ func TestRepository_DeleteHost(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, wrapper) + repo, err := NewRepository(rw, rw, kms) assert.NoError(err) require.NotNil(repo) - got, err := repo.DeleteHost(context.Background(), tt.in) + got, err := repo.DeleteHost(context.Background(), catalog.ScopeId, tt.in) if tt.wantIsErr != nil { assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err) assert.Zero(got) diff --git a/internal/host/static/repository_test.go b/internal/host/static/repository_test.go index 987542bc8..659eece99 100644 --- a/internal/host/static/repository_test.go +++ b/internal/host/static/repository_test.go @@ -4,23 +4,24 @@ import ( "errors" "testing" - wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms" ) func TestRepository_New(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) type args struct { - r db.Reader - w db.Writer - wrapper wrapping.Wrapper - opts []Option + r db.Reader + w db.Writer + kms *kms.Kms + opts []Option } var tests = []struct { @@ -32,38 +33,38 @@ func TestRepository_New(t *testing.T) { { name: "valid", args: args{ - r: rw, - w: rw, - wrapper: wrapper, + r: rw, + w: rw, + kms: kmsCache, }, want: &Repository{ reader: rw, writer: rw, - wrapper: wrapper, + kms: kmsCache, defaultLimit: db.DefaultLimit, }, }, { name: "valid-with-limit", args: args{ - r: rw, - w: rw, - wrapper: wrapper, - opts: []Option{WithLimit(5)}, + r: rw, + w: rw, + kms: kmsCache, + opts: []Option{WithLimit(5)}, }, want: &Repository{ reader: rw, writer: rw, - wrapper: wrapper, + kms: kmsCache, defaultLimit: 5, }, }, { name: "nil-reader", args: args{ - r: nil, - w: rw, - wrapper: wrapper, + r: nil, + w: rw, + kms: kmsCache, }, want: nil, wantIsErr: db.ErrNilParameter, @@ -71,19 +72,19 @@ func TestRepository_New(t *testing.T) { { name: "nil-writer", args: args{ - r: rw, - w: nil, - wrapper: wrapper, + r: rw, + w: nil, + kms: kmsCache, }, want: nil, wantIsErr: db.ErrNilParameter, }, { - name: "nil-wrapper", + name: "nil-kms", args: args{ - r: rw, - w: rw, - wrapper: nil, + r: rw, + w: rw, + kms: nil, }, want: nil, wantIsErr: db.ErrNilParameter, @@ -91,9 +92,9 @@ func TestRepository_New(t *testing.T) { { name: "all-nils", args: args{ - r: nil, - w: nil, - wrapper: nil, + r: nil, + w: nil, + kms: nil, }, want: nil, wantIsErr: db.ErrNilParameter, @@ -103,7 +104,7 @@ func TestRepository_New(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - got, err := NewRepository(tt.args.r, tt.args.w, tt.args.wrapper, tt.args.opts...) + got, err := NewRepository(tt.args.r, tt.args.w, tt.args.kms, tt.args.opts...) if tt.wantIsErr != nil { assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err) assert.Nil(got) diff --git a/internal/iam/account_test.go b/internal/iam/account_test.go index 716691511..2757ea747 100644 --- a/internal/iam/account_test.go +++ b/internal/iam/account_test.go @@ -20,11 +20,13 @@ import ( func Test_AccountUpdate(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, _ := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) rw := db.New(conn) t.Run("simple-update", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) authMethodPublicId := testAuthMethod(t, conn, org.PublicId) acct := testAccount(t, conn, org.PublicId, authMethodPublicId, "") @@ -45,12 +47,14 @@ func Test_AccountUpdate(t *testing.T) { func TestAccount_GetScope(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, _ := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) t.Run("valid-org", func(t *testing.T) { assert, require := assert.New(t), require.New(t) w := db.New(conn) - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) authMethodPublicId := testAuthMethod(t, conn, org.PublicId) acct := testAccount(t, conn, org.PublicId, authMethodPublicId, u.PublicId) scope, err := acct.GetScope(context.Background(), w) @@ -62,10 +66,12 @@ func TestAccount_GetScope(t *testing.T) { func TestAccount_Clone(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, _ := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) t.Run("valid", func(t *testing.T) { assert := assert.New(t) - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) authMethodPublicId := testAuthMethod(t, conn, org.PublicId) acct := testAccount(t, conn, org.PublicId, authMethodPublicId, u.PublicId) cp := acct.Clone() @@ -74,7 +80,7 @@ func TestAccount_Clone(t *testing.T) { t.Run("not-equal", func(t *testing.T) { assert := assert.New(t) rw := db.New(conn) - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) authMethodPublicId := testAuthMethod(t, conn, org.PublicId) acct := testAccount(t, conn, org.PublicId, authMethodPublicId, u.PublicId) acct2 := testAccount(t, conn, org.PublicId, authMethodPublicId, "") diff --git a/internal/iam/group_member_test.go b/internal/iam/group_member_test.go index b9f36dd63..2bad5e64e 100644 --- a/internal/iam/group_member_test.go +++ b/internal/iam/group_member_test.go @@ -15,13 +15,15 @@ import ( func Test_NewGroupMember(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) - org2, _ := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) + org2, _ := TestScopes(t, repo) orgGroup := TestGroup(t, conn, org.PublicId) projGroup := TestGroup(t, conn, proj.PublicId) - user := TestUser(t, conn, org.PublicId) - user2 := TestUser(t, conn, org2.PublicId) + user := TestUser(t, repo, org.PublicId) + user2 := TestUser(t, repo, org2.PublicId) type args struct { groupId string @@ -112,7 +114,9 @@ func Test_NewGroupMember(t *testing.T) { func Test_GroupMemberCreate(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) type args struct { gm *GroupMemberUser } @@ -129,7 +133,7 @@ func Test_GroupMemberCreate(t *testing.T) { args: args{ gm: func() *GroupMemberUser { g := TestGroup(t, conn, org.PublicId) - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) gm, err := NewGroupMemberUser(g.PublicId, u.PublicId) require.NoError(t, err) return gm @@ -142,7 +146,7 @@ func Test_GroupMemberCreate(t *testing.T) { args: args{ gm: func() *GroupMemberUser { g := TestGroup(t, conn, proj.PublicId) - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) gm, err := NewGroupMemberUser(g.PublicId, u.PublicId) require.NoError(t, err) return gm @@ -155,7 +159,7 @@ func Test_GroupMemberCreate(t *testing.T) { args: args{ gm: func() *GroupMemberUser { id := testId(t) - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) gm, err := NewGroupMemberUser(id, u.PublicId) require.NoError(t, err) return gm @@ -182,7 +186,7 @@ func Test_GroupMemberCreate(t *testing.T) { name: "missing-group-id", args: args{ gm: func() *GroupMemberUser { - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) return &GroupMemberUser{ GroupMemberUser: &store.GroupMemberUser{ GroupId: "", @@ -215,7 +219,7 @@ func Test_GroupMemberCreate(t *testing.T) { args: args{ gm: func() *GroupMemberUser { g := TestGroup(t, conn, org.PublicId) - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) gm, err := NewGroupMemberUser(g.PublicId, u.PublicId) require.NoError(t, err) return gm @@ -259,14 +263,16 @@ func Test_GroupMemberCreate(t *testing.T) { func Test_GroupMemberUpdate(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, _ := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) rw := db.New(conn) t.Run("updates not allowed", func(t *testing.T) { assert, require := assert.New(t), require.New(t) g := TestGroup(t, conn, org.PublicId) - u := TestUser(t, conn, org.PublicId) - u2 := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) + u2 := TestUser(t, repo, org.PublicId) gm := TestGroupMember(t, conn, g.PublicId, u.PublicId) updateGrpMember := gm.Clone().(*GroupMemberUser) updateGrpMember.MemberId = u2.PublicId @@ -279,10 +285,12 @@ func Test_GroupMemberUpdate(t *testing.T) { func Test_GroupMemberDelete(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) rw := db.New(conn) id := testId(t) - org, _ := TestScopes(t, conn) - u := TestUser(t, conn, org.PublicId) + org, _ := TestScopes(t, repo) + u := TestUser(t, repo, org.PublicId) g := TestGroup(t, conn, org.PublicId) tests := []struct { @@ -333,8 +341,10 @@ func Test_GroupMemberDelete(t *testing.T) { func TestGroupMember_Clone(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) - user := TestUser(t, conn, org.PublicId) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) + user := TestUser(t, repo, org.PublicId) t.Run("valid", func(t *testing.T) { assert := assert.New(t) group := TestGroup(t, conn, org.PublicId) diff --git a/internal/iam/group_test.go b/internal/iam/group_test.go index 9d87770ed..63ab007c9 100644 --- a/internal/iam/group_test.go +++ b/internal/iam/group_test.go @@ -20,7 +20,9 @@ import ( func TestNewGroup(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) id := testId(t) type args struct { @@ -96,7 +98,9 @@ func TestNewGroup(t *testing.T) { func Test_GroupCreate(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) type args struct { group *Group } @@ -205,8 +209,10 @@ func Test_GroupCreate(t *testing.T) { func Test_GroupUpdate(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) id := testId(t) - org, proj := TestScopes(t, conn) + org, proj := TestScopes(t, repo) rw := db.New(conn) type args struct { name string @@ -375,9 +381,11 @@ func Test_GroupUpdate(t *testing.T) { func Test_GroupDelete(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) rw := db.New(conn) id := testId(t) - org, _ := TestScopes(t, conn) + org, _ := TestScopes(t, repo) tests := []struct { name string @@ -444,7 +452,9 @@ func TestGroup_ResourceType(t *testing.T) { func TestGroup_GetScope(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) t.Run("valid-org", func(t *testing.T) { assert, require := assert.New(t), require.New(t) @@ -467,7 +477,9 @@ func TestGroup_GetScope(t *testing.T) { func TestGroup_Clone(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, _ := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) t.Run("valid", func(t *testing.T) { assert := assert.New(t) diff --git a/internal/iam/immutable_fields_test.go b/internal/iam/immutable_fields_test.go index 400f61550..ee1e41a4e 100644 --- a/internal/iam/immutable_fields_test.go +++ b/internal/iam/immutable_fields_test.go @@ -16,11 +16,13 @@ import ( func TestScope_ImmutableFields(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) w := db.New(conn) ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}} - new, _ := TestScopes(t, conn) + new, _ := TestScopes(t, repo) var tests = []struct { name string update *Scope @@ -92,9 +94,11 @@ func TestConcreteScope_ImmutableFields(t *testing.T) { ) t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) db := conn.DB() - org, proj := TestScopes(t, conn) + org, proj := TestScopes(t, repo) var tests = []struct { name string @@ -142,12 +146,14 @@ func TestConcreteScope_ImmutableFields(t *testing.T) { func TestUser_ImmutableFields(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) w := db.New(conn) ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}} - org, proj := TestScopes(t, conn) - new := TestUser(t, conn, org.PublicId) + org, proj := TestScopes(t, repo) + new := TestUser(t, repo, org.PublicId) var tests = []struct { name string @@ -207,11 +213,13 @@ func TestUser_ImmutableFields(t *testing.T) { func TestRole_ImmutableFields(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) w := db.New(conn) ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}} - org, proj := TestScopes(t, conn) + org, proj := TestScopes(t, repo) new := TestRole(t, conn, org.PublicId) var tests = []struct { @@ -272,11 +280,13 @@ func TestRole_ImmutableFields(t *testing.T) { func TestGroup_ImmutableFields(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) w := db.New(conn) ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}} - org, proj := TestScopes(t, conn) + org, proj := TestScopes(t, repo) new := TestGroup(t, conn, org.PublicId) var tests = []struct { diff --git a/internal/iam/options.go b/internal/iam/options.go index d6f254345..7db2e6820 100644 --- a/internal/iam/options.go +++ b/internal/iam/options.go @@ -1,5 +1,7 @@ package iam +import "io" + // getOpts - iterate the inbound Options and return a struct func getOpts(opt ...Option) options { opts := getDefaultOptions() @@ -23,6 +25,7 @@ type options struct { withGrantScopeId string withSkipVetForWrite bool withDisassociate bool + withRandomReader io.Reader } func getDefaultOptions() options { @@ -106,3 +109,10 @@ func WithDisassociate(enable bool) Option { o.withDisassociate = enable } } + +// WithRandomReader provides and option to specify a random reader. +func WithRandomReader(reader io.Reader) Option { + return func(o *options) { + o.withRandomReader = reader + } +} diff --git a/internal/iam/principal_role_test.go b/internal/iam/principal_role_test.go index fa4c1ecd1..2b115e5bc 100644 --- a/internal/iam/principal_role_test.go +++ b/internal/iam/principal_role_test.go @@ -15,10 +15,12 @@ import ( func TestNewUserRole(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) orgRole := TestRole(t, conn, org.PublicId) projRole := TestRole(t, conn, proj.PublicId) - user := TestUser(t, conn, org.PublicId) + user := TestUser(t, repo, org.PublicId) type args struct { roleId string @@ -97,8 +99,10 @@ func TestNewUserRole(t *testing.T) { func TestUserRole_Create(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) - org2, proj2 := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) + org2, proj2 := TestScopes(t, repo) type args struct { role *UserRole } @@ -115,7 +119,7 @@ func TestUserRole_Create(t *testing.T) { args: args{ role: func() *UserRole { role := TestRole(t, conn, org.PublicId) - principal := TestUser(t, conn, org.PublicId) + principal := TestUser(t, repo, org.PublicId) principalRole, err := NewUserRole(role.PublicId, principal.PublicId) require.NoError(t, err) return principalRole @@ -128,7 +132,7 @@ func TestUserRole_Create(t *testing.T) { args: args{ role: func() *UserRole { role := TestRole(t, conn, proj.PublicId) - principal := TestUser(t, conn, org.PublicId) + principal := TestUser(t, repo, org.PublicId) principalRole, err := NewUserRole(role.PublicId, principal.PublicId) require.NoError(t, err) return principalRole @@ -141,7 +145,7 @@ func TestUserRole_Create(t *testing.T) { args: args{ role: func() *UserRole { role := TestRole(t, conn, org2.PublicId) - principal := TestUser(t, conn, org.PublicId) + principal := TestUser(t, repo, org.PublicId) principalRole, err := NewUserRole(role.PublicId, principal.PublicId) require.NoError(t, err) return principalRole @@ -153,7 +157,7 @@ func TestUserRole_Create(t *testing.T) { args: args{ role: func() *UserRole { role := TestRole(t, conn, proj2.PublicId) - principal := TestUser(t, conn, org.PublicId) + principal := TestUser(t, repo, org.PublicId) principalRole, err := NewUserRole(role.PublicId, principal.PublicId) require.NoError(t, err) return principalRole @@ -165,7 +169,7 @@ func TestUserRole_Create(t *testing.T) { args: args{ role: func() *UserRole { id := testId(t) - principal := TestUser(t, conn, org.PublicId) + principal := TestUser(t, repo, org.PublicId) principalRole, err := NewUserRole(id, principal.PublicId) require.NoError(t, err) return principalRole @@ -192,7 +196,7 @@ func TestUserRole_Create(t *testing.T) { name: "missing-role-id", args: args{ role: func() *UserRole { - principal := TestUser(t, conn, org.PublicId) + principal := TestUser(t, repo, org.PublicId) return &UserRole{ UserRole: &store.UserRole{ RoleId: "", @@ -227,7 +231,7 @@ func TestUserRole_Create(t *testing.T) { args: args{ role: func() *UserRole { role := TestRole(t, conn, org.PublicId) - principal := TestUser(t, conn, org.PublicId) + principal := TestUser(t, repo, org.PublicId) principalRole, err := NewUserRole(role.PublicId, principal.PublicId) require.NoError(t, err) return principalRole @@ -271,14 +275,16 @@ func TestUserRole_Create(t *testing.T) { func TestUserRole_Update(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, _ := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) rw := db.New(conn) t.Run("updates not allowed", func(t *testing.T) { assert, require := assert.New(t), require.New(t) r := TestRole(t, conn, org.PublicId) - u := TestUser(t, conn, org.PublicId) - u2 := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) + u2 := TestUser(t, repo, org.PublicId) userRole := TestUserRole(t, conn, r.PublicId, u.PublicId) updateRole := userRole.Clone().(*UserRole) updateRole.PrincipalId = u2.PublicId @@ -291,10 +297,12 @@ func TestUserRole_Update(t *testing.T) { func TestUserRole_Delete(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) rw := db.New(conn) id := testId(t) - org, _ := TestScopes(t, conn) - u := TestUser(t, conn, org.PublicId) + org, _ := TestScopes(t, repo) + u := TestUser(t, repo, org.PublicId) r := TestRole(t, conn, org.PublicId) tests := []struct { @@ -345,8 +353,10 @@ func TestUserRole_Delete(t *testing.T) { func TestUserRole_Clone(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) - user := TestUser(t, conn, org.PublicId) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) + user := TestUser(t, repo, org.PublicId) t.Run("valid", func(t *testing.T) { assert := assert.New(t) role := TestRole(t, conn, org.PublicId) @@ -368,7 +378,9 @@ func TestUserRole_Clone(t *testing.T) { func TestNewGroupRole(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) orgRole := TestRole(t, conn, org.PublicId) projRole := TestRole(t, conn, proj.PublicId) group := TestGroup(t, conn, org.PublicId) @@ -450,8 +462,10 @@ func TestNewGroupRole(t *testing.T) { func TestGroupRole_Create(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) - org2, proj2 := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) + org2, proj2 := TestScopes(t, repo) type args struct { role *GroupRole } @@ -639,7 +653,9 @@ func TestGroupRole_Create(t *testing.T) { func TestGroupRole_Update(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, _ := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) rw := db.New(conn) t.Run("updates not allowed", func(t *testing.T) { @@ -659,9 +675,11 @@ func TestGroupRole_Update(t *testing.T) { func TestGroupRole_Delete(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) rw := db.New(conn) id := testId(t) - org, _ := TestScopes(t, conn) + org, _ := TestScopes(t, repo) g := TestGroup(t, conn, org.PublicId) r := TestRole(t, conn, org.PublicId) @@ -718,7 +736,9 @@ func TestGroupRole_Delete(t *testing.T) { func TestGroupRole_Clone(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) t.Run("valid", func(t *testing.T) { assert := assert.New(t) grp := TestGroup(t, conn, org.PublicId) diff --git a/internal/iam/repository.go b/internal/iam/repository.go index 13b3e8889..a80b8171b 100644 --- a/internal/iam/repository.go +++ b/internal/iam/repository.go @@ -7,9 +7,9 @@ import ( "strings" "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" "github.com/hashicorp/boundary/internal/types/scope" - wrapping "github.com/hashicorp/go-kms-wrapping" ) var ( @@ -18,9 +18,9 @@ var ( // Repository is the iam database repository type Repository struct { - reader db.Reader - writer db.Writer - wrapper wrapping.Wrapper + reader db.Reader + writer db.Writer + kms *kms.Kms // defaultLimit provides a default for limiting the number of results returned from the repo defaultLimit int @@ -28,15 +28,15 @@ type Repository struct { // NewRepository creates a new iam Repository. Supports the options: WithLimit // which sets a default limit on results returned by repo operations. -func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper, opt ...Option) (*Repository, error) { +func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repository, error) { if r == nil { return nil, errors.New("error creating db repository with nil reader") } if w == nil { return nil, errors.New("error creating db repository with nil writer") } - if wrapper == nil { - return nil, errors.New("error creating db repository with nil wrapper") + if kms == nil { + return nil, errors.New("error creating db repository with nil kms") } opts := getOpts(opt...) if opts.withLimit == 0 { @@ -46,7 +46,7 @@ func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper, opt ...Op return &Repository{ reader: r, writer: w, - wrapper: wrapper, + kms: kms, defaultLimit: opts.withLimit, }, nil } @@ -78,6 +78,15 @@ func (r *Repository) create(ctx context.Context, resource Resource, opt ...Optio } metadata["op-type"] = []string{oplog.OpType_OP_TYPE_CREATE.String()} + scope, err := resource.GetScope(ctx, r.reader) + if err != nil { + return nil, fmt.Errorf("unable to get scope: %w", err) + } + oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog) + if err != nil { + return nil, fmt.Errorf("unable to get oplog wrapper: %w", err) + } + var returnedResource interface{} _, err = r.writer.DoTx( ctx, @@ -88,7 +97,7 @@ func (r *Repository) create(ctx context.Context, resource Resource, opt ...Optio return w.Create( ctx, returnedResource, - db.WithOplog(r.wrapper, metadata), + db.WithOplog(oplogWrapper, metadata), ) }, ) @@ -114,7 +123,6 @@ func (r *Repository) update(ctx context.Context, resource Resource, version uint metadata["op-type"] = []string{oplog.OpType_OP_TYPE_UPDATE.String()} dbOpts := []db.Option{ - db.WithOplog(r.wrapper, metadata), db.WithVersion(&version), } opts := getOpts(opt...) @@ -122,6 +130,22 @@ func (r *Repository) update(ctx context.Context, resource Resource, version uint dbOpts = append(dbOpts, db.WithSkipVetForWrite(true)) } + var scope *Scope + switch t := resource.(type) { + case *Scope: + scope = t + default: + scope, err = resource.GetScope(ctx, r.reader) + if err != nil { + return nil, db.NoRowsAffected, fmt.Errorf("unable to get scope: %w", err) + } + } + oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog) + if err != nil { + return nil, db.NoRowsAffected, fmt.Errorf("unable to get oplog wrapper: %w", err) + } + dbOpts = append(dbOpts, db.WithOplog(oplogWrapper, metadata)) + var rowsUpdated int var returnedResource interface{} _, err = r.writer.DoTx( @@ -130,7 +154,6 @@ func (r *Repository) update(ctx context.Context, resource Resource, version uint db.ExpBackoff{}, func(_ db.Reader, w db.Writer) error { returnedResource = resourceCloner.Clone() - var err error rowsUpdated, err = w.Update( ctx, returnedResource, @@ -163,6 +186,15 @@ func (r *Repository) delete(ctx context.Context, resource Resource, opt ...Optio } metadata["op-type"] = []string{oplog.OpType_OP_TYPE_DELETE.String()} + scope, err := resource.GetScope(ctx, r.reader) + if err != nil { + return db.NoRowsAffected, fmt.Errorf("unable to get scope: %w", err) + } + oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, fmt.Errorf("unable to get oplog wrapper: %w", err) + } + var rowsDeleted int var deleteResource interface{} _, err = r.writer.DoTx( @@ -171,11 +203,10 @@ func (r *Repository) delete(ctx context.Context, resource Resource, opt ...Optio db.ExpBackoff{}, func(_ db.Reader, w db.Writer) error { deleteResource = resourceCloner.Clone() - var err error rowsDeleted, err = w.Delete( ctx, deleteResource, - db.WithOplog(r.wrapper, metadata), + db.WithOplog(oplogWrapper, metadata), ) if err == nil && rowsDeleted > 1 { // return err, which will result in a rollback of the delete diff --git a/internal/iam/repository_group.go b/internal/iam/repository_group.go index a2072c817..7bdfbee3f 100644 --- a/internal/iam/repository_group.go +++ b/internal/iam/repository_group.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/boundary/internal/db" dbcommon "github.com/hashicorp/boundary/internal/db/common" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" ) @@ -89,7 +90,7 @@ func (r *Repository) UpdateGroup(ctx context.Context, group *Group, version uint if err != nil { return err } - repo, err := NewRepository(read, w, r.wrapper) + repo, err := NewRepository(read, w, r.kms) if err != nil { return fmt.Errorf("update group: failed creating inner repo: %w for %s", err, group.PublicId) } @@ -126,7 +127,7 @@ func (r *Repository) LookupGroup(ctx context.Context, withPublicId string, opt . if err := read.LookupByPublicId(ctx, &g); err != nil { return fmt.Errorf("lookup group: failed %w for %s", err, withPublicId) } - repo, err := NewRepository(read, w, r.wrapper) + repo, err := NewRepository(read, w, r.kms) if err != nil { return fmt.Errorf("lookup group: failed creating inner repo: %w for %s", err, withPublicId) } @@ -215,6 +216,11 @@ func (r *Repository) AddGroupMembers(ctx context.Context, groupId string, groupV newGroupMembers = append(newGroupMembers, gm) } + oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog) + if err != nil { + return nil, fmt.Errorf("add group members: unable to get oplog wrapper: %w", err) + } + var currentMembers []*GroupMember _, err = r.writer.DoTx( ctx, @@ -249,14 +255,14 @@ func (r *Repository) AddGroupMembers(ctx context.Context, groupId string, groupV "scope-type": []string{scope.Type}, "resource-public-id": []string{groupId}, } - if err := w.WriteOplogEntryWith(ctx, r.wrapper, groupTicket, metadata, msgs); err != nil { + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, groupTicket, metadata, msgs); err != nil { return fmt.Errorf("add group members: unable to write oplog: %w", err) } // we need a new repo, that's using the same reader/writer as this TxHandler txRepo := Repository{ - reader: reader, - writer: w, - wrapper: r.wrapper, + reader: reader, + writer: w, + kms: r.kms, // intentionally not setting the defaultLimit, so we'll get all // the members without a limit } @@ -302,6 +308,11 @@ func (r *Repository) DeleteGroupMembers(ctx context.Context, groupId string, gro deleteMembers = append(deleteMembers, member) } + oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, fmt.Errorf("add group members: unable to get oplog wrapper: %w", err) + } + var totalRowsDeleted int _, err = r.writer.DoTx( ctx, @@ -341,7 +352,7 @@ func (r *Repository) DeleteGroupMembers(ctx context.Context, groupId string, gro "scope-type": []string{scope.Type}, "resource-public-id": []string{groupId}, } - if err := w.WriteOplogEntryWith(ctx, r.wrapper, groupTicket, metadata, msgs); err != nil { + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, groupTicket, metadata, msgs); err != nil { return fmt.Errorf("delete group members: unable to write oplog: %w", err) } return nil @@ -414,6 +425,11 @@ func (r *Repository) SetGroupMembers(ctx context.Context, groupId string, groupV return currentMembers, db.NoRowsAffected, nil } + oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog) + if err != nil { + return nil, db.NoRowsAffected, fmt.Errorf("add group members: unable to get oplog wrapper: %w", err) + } + var totalRowsAffected int _, err = r.writer.DoTx( ctx, @@ -470,14 +486,14 @@ func (r *Repository) SetGroupMembers(ctx context.Context, groupId string, groupV } // we're done with all the membership writes, so let's write the // group's update oplog message - if err := w.WriteOplogEntryWith(ctx, r.wrapper, groupTicket, metadata, msgs); err != nil { + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, groupTicket, metadata, msgs); err != nil { return fmt.Errorf("set group members: unable to write oplog for additions: %w", err) } // we need a new repo, that's using the same reader/writer as this TxHandler txRepo := Repository{ - reader: reader, - writer: w, - wrapper: r.wrapper, + reader: reader, + writer: w, + kms: r.kms, // intentionally not setting the defaultLimit, so we'll get all // the members without a limit } diff --git a/internal/iam/repository_group_test.go b/internal/iam/repository_group_test.go index 3459d4045..d4894b215 100644 --- a/internal/iam/repository_group_test.go +++ b/internal/iam/repository_group_test.go @@ -21,11 +21,10 @@ func TestRepository_CreateGroup(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) + repo := TestRepo(t, conn, wrapper) id := testId(t) - org, proj := TestScopes(t, conn) + org, proj := TestScopes(t, repo) type args struct { group *Group @@ -177,12 +176,11 @@ func TestRepository_UpdateGroup(t *testing.T) { a := assert.New(t) rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - a.NoError(err) + repo := TestRepo(t, conn, wrapper) id, err := uuid.GenerateUUID() a.NoError(err) - org, proj := TestScopes(t, conn) + org, proj := TestScopes(t, repo) pubId := func(s string) *string { return &s } type args struct { @@ -473,9 +471,8 @@ func TestRepository_DeleteGroup(t *testing.T) { a := assert.New(t) rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - a.NoError(err) - org, _ := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) type args struct { group *Group @@ -555,11 +552,9 @@ func TestRepository_ListGroups(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") const testLimit = 10 - rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper, WithLimit(testLimit)) - require.NoError(t, err) - org, proj := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper, WithLimit(testLimit)) + org, proj := TestScopes(t, repo) type args struct { withScopeId string @@ -651,11 +646,9 @@ func TestRepository_ListMembers(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") const testLimit = 10 - rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper, WithLimit(testLimit)) - require.NoError(t, err) - org, proj := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper, WithLimit(testLimit)) + org, proj := TestScopes(t, repo) pg := TestGroup(t, conn, proj.PublicId) og := TestGroup(t, conn, org.PublicId) @@ -716,7 +709,7 @@ func TestRepository_ListMembers(t *testing.T) { require.NoError(conn.Where("1=1").Delete(allocGroupMember()).Error) gm := []*GroupMemberUser{} for i := 0; i < tt.createCnt; i++ { - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) gm = append(gm, TestGroupMember(t, conn, tt.args.withGroupId, u.PublicId)) } assert.Equal(tt.createCnt, len(gm)) @@ -745,14 +738,13 @@ func TestRepository_AddGroupMembers(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) - org, proj := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) group := TestGroup(t, conn, proj.PublicId) createUsersFn := func() []string { results := []string{} for i := 0; i < 5; i++ { - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) results = append(results, u.PublicId) } return results @@ -864,9 +856,8 @@ func TestRepository_DeleteGroupMembers(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) - org, _ := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) type args struct { group *Group @@ -959,7 +950,7 @@ func TestRepository_DeleteGroupMembers(t *testing.T) { assert, require := assert.New(t), require.New(t) userIds := make([]string, 0, tt.args.createUserCnt) for i := 0; i < tt.args.createUserCnt; i++ { - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) userIds = append(userIds, u.PublicId) } members, err := repo.AddGroupMembers(context.Background(), tt.args.group.PublicId, 1, userIds, tt.args.opt...) @@ -1001,19 +992,16 @@ func TestRepository_DeleteGroupMembers(t *testing.T) { func TestRepository_SetGroupMembers(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) - - org, proj := TestScopes(t, conn) - testUser := TestUser(t, conn, org.PublicId) + org, proj := TestScopes(t, repo) + testUser := TestUser(t, repo, org.PublicId) createUsersFn := func() []string { results := []string{} for i := 0; i < 5; i++ { - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) results = append(results, u.PublicId) } return results diff --git a/internal/iam/repository_principal_role.go b/internal/iam/repository_principal_role.go index 1fcbbfee0..64d97962a 100644 --- a/internal/iam/repository_principal_role.go +++ b/internal/iam/repository_principal_role.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" ) @@ -29,13 +30,6 @@ func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, roleV return nil, fmt.Errorf("add principal roles: missing either user or groups to add: %w", db.ErrInvalidParameter) } - role := allocRole() - role.PublicId = roleId - roleScope, err := role.GetScope(ctx, r.reader) - if err != nil { - return nil, fmt.Errorf("add principal roles: unable to get role %s scope: %w", roleId, err) - } - newUserRoles := make([]interface{}, 0, len(userIds)) for _, id := range userIds { usrRole, err := NewUserRole(roleId, id) @@ -52,6 +46,19 @@ func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, roleV } newGrpRoles = append(newGrpRoles, grpRole) } + + role := allocRole() + role.PublicId = roleId + scope, err := role.GetScope(ctx, r.reader) + if err != nil { + return nil, fmt.Errorf("add principal roles: unable to get role %s scope: %w", roleId, err) + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog) + if err != nil { + return nil, fmt.Errorf("add principal roles: unable to get oplog wrapper: %w", err) + } + var currentPrincipals []PrincipalRole _, err = r.writer.DoTx( ctx, @@ -91,18 +98,18 @@ func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, roleV } metadata := oplog.Metadata{ "op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()}, - "scope-id": []string{roleScope.PublicId}, - "scope-type": []string{roleScope.Type}, + "scope-id": []string{scope.PublicId}, + "scope-type": []string{scope.Type}, "resource-public-id": []string{roleId}, } - if err := w.WriteOplogEntryWith(ctx, r.wrapper, roleTicket, metadata, msgs); err != nil { + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil { return fmt.Errorf("add principal roles: unable to write oplog: %w", err) } // we need a new repo, that's using the same reader/writer as this TxHandler txRepo := &Repository{ - reader: reader, - writer: w, - wrapper: r.wrapper, + reader: reader, + writer: w, + kms: r.kms, // intentionally not setting the defaultLimit, so we'll get all // the principal roles without a limit } @@ -133,10 +140,7 @@ func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleV } role := allocRole() role.PublicId = roleId - scope, err := role.GetScope(ctx, r.reader) - if err != nil { - return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: unable to get role %s scope: %w", roleId, err) - } + // it's "safe" to do this lookup outside the DoTx transaction because we // have a roleVersion so the principals can’t change without the version // changing. @@ -154,6 +158,15 @@ func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleV return toSet.unchangedPrincipalRoles, db.NoRowsAffected, nil } + scope, err := role.GetScope(ctx, r.reader) + if err != nil { + return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: unable to get role %s scope: %w", roleId, err) + } + oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog) + if err != nil { + return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: unable to get oplog wrapper: %w", err) + } + var currentPrincipals []PrincipalRole var totalRowsAffected int _, err = r.writer.DoTx( @@ -234,14 +247,14 @@ func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleV msgs = append(msgs, grpOplogMsgs...) } } - if err := w.WriteOplogEntryWith(ctx, r.wrapper, roleTicket, metadata, msgs); err != nil { + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil { return fmt.Errorf("set principal roles: unable to write oplog for additions: %w", err) } // we need a new repo, that's using the same reader/writer as this TxHandler txRepo := &Repository{ - reader: reader, - writer: w, - wrapper: r.wrapper, + reader: reader, + writer: w, + kms: r.kms, // intentionally not setting the defaultLimit, so we'll get all // the principal roles without a limit } @@ -277,10 +290,7 @@ func (r *Repository) DeletePrincipalRoles(ctx context.Context, roleId string, ro } role := allocRole() role.PublicId = roleId - scope, err := role.GetScope(ctx, r.reader) - if err != nil { - return db.NoRowsAffected, fmt.Errorf("delete principal roles: unable to get role %s scope to create metadata: %w", roleId, err) - } + deleteUserRoles := make([]interface{}, 0, len(userIds)) for _, id := range userIds { usrRole, err := NewUserRole(roleId, id) @@ -298,6 +308,15 @@ func (r *Repository) DeletePrincipalRoles(ctx context.Context, roleId string, ro deleteGrpRoles = append(deleteGrpRoles, grpRole) } + scope, err := role.GetScope(ctx, r.reader) + if err != nil { + return db.NoRowsAffected, fmt.Errorf("delete principal roles: unable to get role %s scope to create metadata: %w", roleId, err) + } + oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, fmt.Errorf("delete principal roles: unable to get oplog wrapper: %w", err) + } + var totalRowsDeleted int _, err = r.writer.DoTx( ctx, @@ -351,7 +370,7 @@ func (r *Repository) DeletePrincipalRoles(ctx context.Context, roleId string, ro "scope-type": []string{scope.Type}, "resource-public-id": []string{roleId}, } - if err := w.WriteOplogEntryWith(ctx, r.wrapper, roleTicket, metadata, msgs); err != nil { + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil { return fmt.Errorf("delete principal roles: unable to write oplog: %w", err) } return nil diff --git a/internal/iam/repository_principal_role_test.go b/internal/iam/repository_principal_role_test.go index dba18fe1d..f53d1ab0c 100644 --- a/internal/iam/repository_principal_role_test.go +++ b/internal/iam/repository_principal_role_test.go @@ -18,14 +18,13 @@ func TestRepository_AddPrincipalRoles(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - staticOrg, staticProj := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper) + staticOrg, staticProj := TestScopes(t, repo) orgRole := TestRole(t, conn, staticOrg.PublicId) projRole := TestRole(t, conn, staticProj.PublicId) - require.NoError(t, err) createScopesFn := func() (orgs []string, projects []string) { for i := 0; i < 5; i++ { - org, proj := TestScopes(t, conn) + org, proj := TestScopes(t, repo) orgs = append(orgs, org.PublicId) projects = append(projects, proj.PublicId) } @@ -34,7 +33,7 @@ func TestRepository_AddPrincipalRoles(t *testing.T) { createUsersFn := func(orgs []string) []string { results := []string{} for org := 0; org < 5; org++ { - u := TestUser(t, conn, orgs[org]) + u := TestUser(t, repo, orgs[org]) results = append(results, u.PublicId) } return results @@ -128,7 +127,7 @@ func TestRepository_AddPrincipalRoles(t *testing.T) { if tt.args.wantUserIds { userIds = createUsersFn(orgs) - u := TestUser(t, conn, staticOrg.PublicId) + u := TestUser(t, repo, staticOrg.PublicId) if roleId == orgRole.PublicId { userIds = append(userIds, u.PublicId) } else { @@ -186,11 +185,9 @@ func TestRepository_ListPrincipalRoles(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") const testLimit = 10 - rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper, WithLimit(testLimit)) - require.NoError(t, err) - org, proj := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper, WithLimit(testLimit)) + org, proj := TestScopes(t, repo) type args struct { withRoleId string @@ -260,7 +257,7 @@ func TestRepository_ListPrincipalRoles(t *testing.T) { userRoles := make([]string, 0, tt.createCnt) groupRoles := make([]string, 0, tt.createCnt) for i := 0; i < tt.createCnt/2; i++ { - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) userRoles = append(userRoles, u.PublicId) g := TestGroup(t, conn, tt.createScopeId) groupRoles = append(groupRoles, g.PublicId) @@ -293,9 +290,8 @@ func TestRepository_DeletePrincipalRoles(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) - org, _ := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) type args struct { role *Role @@ -426,7 +422,7 @@ func TestRepository_DeletePrincipalRoles(t *testing.T) { } createScopesFn := func() (orgs []string, projects []string) { for i := 0; i < 5; i++ { - org, proj := TestScopes(t, conn) + org, proj := TestScopes(t, repo) orgs = append(orgs, org.PublicId) projects = append(projects, proj.PublicId) } @@ -438,10 +434,10 @@ func TestRepository_DeletePrincipalRoles(t *testing.T) { orgs, projects := createScopesFn() userIds := make([]string, 0, tt.args.createUserCnt) if tt.args.createUserCnt > 0 { - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) userIds = append(userIds, u.PublicId) for i := 0; i < tt.args.createUserCnt-1; i++ { - u := TestUser(t, conn, orgs[i]) + u := TestUser(t, repo, orgs[i]) userIds = append(userIds, u.PublicId) } } @@ -506,19 +502,17 @@ func TestRepository_DeletePrincipalRoles(t *testing.T) { func TestRepository_SetPrincipalRoles(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) + repo := TestRepo(t, conn, wrapper) - org, proj := TestScopes(t, conn) - testUser := TestUser(t, conn, org.PublicId) + org, proj := TestScopes(t, repo) + testUser := TestUser(t, repo, org.PublicId) testGrp := TestGroup(t, conn, proj.PublicId) createUsersFn := func() []string { results := []string{} for i := 0; i < 5; i++ { - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) results = append(results, u.PublicId) } results = append(results, "u_anon") @@ -677,15 +671,13 @@ func TestRepository_SetPrincipalRoles(t *testing.T) { func TestRepository_principalsToSet(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) - org, proj := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) createUsersFn := func() []string { results := []string{} for i := 0; i < 5; i++ { - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) results = append(results, u.PublicId) } return results @@ -799,7 +791,7 @@ func TestRepository_principalsToSet(t *testing.T) { wantDeleteGrps = append(wantDeleteGrps, id) } } - newUser := TestUser(t, conn, org.PublicId) + newUser := TestUser(t, repo, org.PublicId) newGrp := TestGroup(t, conn, proj.PublicId) wantSetUsers = append(wantSetUsers, newUser.PublicId) wantSetGrps = append(wantSetGrps, newGrp.PublicId) diff --git a/internal/iam/repository_role.go b/internal/iam/repository_role.go index 5fe499a5d..9a6aad4a3 100644 --- a/internal/iam/repository_role.go +++ b/internal/iam/repository_role.go @@ -92,7 +92,7 @@ func (r *Repository) UpdateRole(ctx context.Context, role *Role, version uint32, if err != nil { return err } - repo, err := NewRepository(read, w, r.wrapper) + repo, err := NewRepository(read, w, r.kms) if err != nil { return fmt.Errorf("update role: failed creating inner repo: %w for %s", err, role.PublicId) } @@ -134,7 +134,7 @@ func (r *Repository) LookupRole(ctx context.Context, withPublicId string, opt .. if err := read.LookupByPublicId(ctx, &role); err != nil { return fmt.Errorf("lookup role: failed %w for %s", err, withPublicId) } - repo, err := NewRepository(read, w, r.wrapper) + repo, err := NewRepository(read, w, r.kms) if err != nil { return fmt.Errorf("lookup role: failed creating inner repo: %w for %s", err, withPublicId) } diff --git a/internal/iam/repository_role_grant.go b/internal/iam/repository_role_grant.go index 12bc5c455..2ab53f7f6 100644 --- a/internal/iam/repository_role_grant.go +++ b/internal/iam/repository_role_grant.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" "github.com/hashicorp/boundary/internal/perms" ) @@ -34,7 +35,16 @@ func (r *Repository) AddRoleGrants(ctx context.Context, roleId string, roleVersi newRoleGrants = append(newRoleGrants, roleGrant) } - _, err := r.writer.DoTx( + scope, err := role.GetScope(ctx, r.reader) + if err != nil { + return nil, fmt.Errorf("add role grants: unable to get role %s scope: %w", roleId, err) + } + oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog) + if err != nil { + return nil, fmt.Errorf("add role grants: unable to get oplog wrapper: %w", err) + } + + _, err = r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, @@ -64,18 +74,13 @@ func (r *Repository) AddRoleGrants(ctx context.Context, roleId string, roleVersi } msgs = append(msgs, roleGrantOplogMsgs...) - s, err := role.GetScope(ctx, r.reader) - if err != nil { - return fmt.Errorf("add role grants: unable to get role %s scope: %w", roleId, err) - } - metadata := oplog.Metadata{ "op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()}, - "scope-id": []string{s.PublicId}, - "scope-type": []string{s.Type}, + "scope-id": []string{scope.PublicId}, + "scope-type": []string{scope.Type}, "resource-public-id": []string{roleId}, } - if err := w.WriteOplogEntryWith(ctx, r.wrapper, roleTicket, metadata, msgs); err != nil { + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil { return fmt.Errorf("unable to write oplog: %w", err) } @@ -109,8 +114,17 @@ func (r *Repository) DeleteRoleGrants(ctx context.Context, roleId string, roleVe role := allocRole() role.PublicId = roleId + scope, err := role.GetScope(ctx, r.reader) + if err != nil { + return db.NoRowsAffected, fmt.Errorf("delete role grants: unable to get role %s scope to create metadata: %w", roleId, err) + } + oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, fmt.Errorf("delete role grants: unable to get oplog wrapper: %w", err) + } + var totalRowsDeleted int - _, err := r.writer.DoTx( + _, err = r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, @@ -179,18 +193,13 @@ func (r *Repository) DeleteRoleGrants(ctx context.Context, roleId string, roleVe totalRowsDeleted = rowsDeleted msgs = append(msgs, roleGrantOplogMsgs...) - s, err := role.GetScope(ctx, r.reader) - if err != nil { - return fmt.Errorf("delete role grants: unable to get role %s scope: %w", roleId, err) - } - metadata := oplog.Metadata{ "op-type": []string{oplog.OpType_OP_TYPE_DELETE.String()}, - "scope-id": []string{s.PublicId}, - "scope-type": []string{s.Type}, + "scope-id": []string{scope.PublicId}, + "scope-type": []string{scope.Type}, "resource-public-id": []string{roleId}, } - if err := w.WriteOplogEntryWith(ctx, r.wrapper, roleTicket, metadata, msgs); err != nil { + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil { return fmt.Errorf("delete role grants: unable to write oplog: %w", err) } @@ -208,15 +217,15 @@ func (r *Repository) DeleteRoleGrants(ctx context.Context, roleId string, roleVe // value for the WithVersion option and will return an error. func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersion uint32, grants []string, opt ...Option) ([]*RoleGrant, int, error) { if roleId == "" { - return nil, 0, fmt.Errorf("set role grants: missing role id %w", db.ErrInvalidParameter) + return nil, db.NoRowsAffected, fmt.Errorf("set role grants: missing role id %w", db.ErrInvalidParameter) } if roleVersion == 0 { - return nil, 0, fmt.Errorf("set role grants: version cannot be zero: %w", db.ErrInvalidParameter) + return nil, db.NoRowsAffected, fmt.Errorf("set role grants: version cannot be zero: %w", db.ErrInvalidParameter) } // Explicitly set to zero clears, but treat nil as a mistake if grants == nil { - return nil, 0, fmt.Errorf("set role grants: nil grants: %w", db.ErrNilParameter) + return nil, db.NoRowsAffected, fmt.Errorf("set role grants: nil grants: %w", db.ErrNilParameter) } role := allocRole() @@ -229,7 +238,7 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi // Find existing grants roleGrants := []*RoleGrant{} if err := r.reader.SearchWhere(ctx, &roleGrants, "role_id = ?", []interface{}{roleId}); err != nil { - return nil, 0, fmt.Errorf("set role grants: unable to search for grants: %w", err) + return nil, db.NoRowsAffected, fmt.Errorf("set role grants: unable to search for grants: %w", err) } found := map[string]*RoleGrant{} for _, rg := range roleGrants { @@ -244,7 +253,7 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi // Use a fake scope, just want to get out a canonical string perm, err := perms.Parse("o_abcd1234", "", grant) if err != nil { - return nil, 0, fmt.Errorf("set role grants: error parsing grant string: %w", err) + return nil, db.NoRowsAffected, fmt.Errorf("set role grants: error parsing grant string: %w", err) } canonicalString := perm.CanonicalString() @@ -260,7 +269,7 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi // Not found, so add rg, err = NewRoleGrant(roleId, grant) if err != nil { - return nil, 0, fmt.Errorf("set role grants: unable to create in memory role grant: %w", err) + return nil, db.NoRowsAffected, fmt.Errorf("set role grants: unable to create in memory role grant: %w", err) } addRoleGrants = append(addRoleGrants, rg) currentRoleGrants = append(currentRoleGrants, rg) @@ -273,11 +282,20 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi } if len(addRoleGrants) == 0 && len(deleteRoleGrants) == 0 { - return currentRoleGrants, 0, nil + return currentRoleGrants, db.NoRowsAffected, nil + } + + scope, err := role.GetScope(ctx, r.reader) + if err != nil { + return nil, db.NoRowsAffected, fmt.Errorf("set role grants: unable to get role %s scope: %w", roleId, err) + } + oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog) + if err != nil { + return nil, db.NoRowsAffected, fmt.Errorf("set role grants: unable to get oplog wrapper: %w", err) } var totalRowsDeleted int - _, err := r.writer.DoTx( + _, err = r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, @@ -323,18 +341,13 @@ func (r *Repository) SetRoleGrants(ctx context.Context, roleId string, roleVersi msgs = append(msgs, roleGrantOplogMsgs...) } - s, err := role.GetScope(ctx, r.reader) - if err != nil { - return fmt.Errorf("set role grants: unable to get role %s scope: %w", roleId, err) - } - metadata := oplog.Metadata{ "op-type": []string{oplog.OpType_OP_TYPE_DELETE.String(), oplog.OpType_OP_TYPE_CREATE.String()}, - "scope-id": []string{s.PublicId}, - "scope-type": []string{s.Type}, + "scope-id": []string{scope.PublicId}, + "scope-type": []string{scope.Type}, "resource-public-id": []string{roleId}, } - if err := w.WriteOplogEntryWith(ctx, r.wrapper, roleTicket, metadata, msgs); err != nil { + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, roleTicket, metadata, msgs); err != nil { return fmt.Errorf("set role grants: unable to write oplog: %w", err) } diff --git a/internal/iam/repository_role_grant_test.go b/internal/iam/repository_role_grant_test.go index 9cee76f01..07dfc2594 100644 --- a/internal/iam/repository_role_grant_test.go +++ b/internal/iam/repository_role_grant_test.go @@ -19,9 +19,8 @@ func TestRepository_AddRoleGrants(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) - _, proj := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper) + _, proj := TestScopes(t, repo) role := TestRole(t, conn, proj.PublicId) createGrantsFn := func() []string { results := []string{} @@ -124,11 +123,9 @@ func TestRepository_ListRoleGrants(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") const testLimit = 10 - rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper, WithLimit(testLimit)) - require.NoError(t, err) - org, proj := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper, WithLimit(testLimit)) + org, proj := TestScopes(t, repo) type args struct { withRoleId string @@ -226,9 +223,8 @@ func TestRepository_DeleteRoleGrants(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) - org, _ := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) type args struct { role *Role @@ -410,11 +406,9 @@ func TestRepository_SetRoleGrants_Randomize(t *testing.T) { t.Parallel() require := require.New(t) conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) - org, _ := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) role := TestRole(t, conn, org.PublicId) require.NoError(conn.Where("1=1").Delete(allocRoleGrant()).Error) @@ -471,7 +465,7 @@ func TestRepository_SetRoleGrants_Randomize(t *testing.T) { } // At the end, set to explicitly empty and make sure all are cleared out - _, _, err = repo.SetRoleGrants(context.Background(), role.PublicId, uint32(totalCnt+1), []string{}) + _, _, err := repo.SetRoleGrants(context.Background(), role.PublicId, uint32(totalCnt+1), []string{}) require.NoError(err) roleGrants, err := repo.ListRoleGrants(context.Background(), role.PublicId) @@ -482,11 +476,9 @@ func TestRepository_SetRoleGrants_Randomize(t *testing.T) { func TestRepository_SetRoleGrants_Parameters(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) - org, _ := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) role := TestRole(t, conn, org.PublicId) type args struct { roleId string diff --git a/internal/iam/repository_role_test.go b/internal/iam/repository_role_test.go index 3336ce911..2d422777c 100644 --- a/internal/iam/repository_role_test.go +++ b/internal/iam/repository_role_test.go @@ -22,11 +22,10 @@ func TestRepository_CreateRole(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) + repo := TestRepo(t, conn, wrapper) id := testId(t) - org, proj := TestScopes(t, conn) + org, proj := TestScopes(t, repo) type args struct { role *Role @@ -177,13 +176,12 @@ func TestRepository_UpdateRole(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) + repo := TestRepo(t, conn, wrapper) id, err := uuid.GenerateUUID() require.NoError(t, err) - org, proj := TestScopes(t, conn) - u := TestUser(t, conn, org.GetPublicId()) + org, proj := TestScopes(t, repo) + u := TestUser(t, repo, org.GetPublicId()) pubId := func(s string) *string { return &s } @@ -470,9 +468,8 @@ func TestRepository_DeleteRole(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) - org, _ := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) type args struct { role *Role @@ -552,11 +549,9 @@ func TestRepository_ListRoles(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") const testLimit = 10 - rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper, WithLimit(testLimit)) - require.NoError(t, err) - org, proj := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper, WithLimit(testLimit)) + org, proj := TestScopes(t, repo) type args struct { withScopeId string diff --git a/internal/iam/repository_scope.go b/internal/iam/repository_scope.go index 0c9d78947..baad9091f 100644 --- a/internal/iam/repository_scope.go +++ b/internal/iam/repository_scope.go @@ -2,19 +2,24 @@ package iam import ( "context" + "crypto/rand" "errors" "fmt" "strings" "github.com/hashicorp/boundary/internal/db" dbcommon "github.com/hashicorp/boundary/internal/db/common" + "github.com/hashicorp/boundary/internal/kms" + kmsCommon "github.com/hashicorp/boundary/internal/kms/common" "github.com/hashicorp/boundary/internal/oplog" "github.com/hashicorp/boundary/internal/types/resource" "github.com/hashicorp/boundary/internal/types/scope" + wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/hashicorp/go-uuid" ) // CreateScope will create a scope in the repository and return the written -// scope. Supported options include: WithPublicId. +// scope. Supported options include: WithPublicId and WithRandomReader. func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, opt ...Option) (*Scope, error) { if s == nil { return nil, fmt.Errorf("create scope: missing scope %w", db.ErrNilParameter) @@ -25,16 +30,32 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o if s.PublicId != "" { return nil, fmt.Errorf("create scope: public id not empty: %w", db.ErrInvalidParameter) } + + var parentOplogWrapper wrapping.Wrapper + var externalWrappers *kms.ExternalWrappers + var err error switch s.Type { case scope.Unknown.String(): return nil, fmt.Errorf("create scope: unknown type: %w", db.ErrInvalidParameter) case scope.Global.String(): return nil, fmt.Errorf("create scope: invalid type: %w", db.ErrInvalidParameter) + default: + switch s.ParentId { + case "": + return nil, fmt.Errorf("create scope: missing parent id: %w", db.ErrNilParameter) + case scope.Global.String(): + parentOplogWrapper, err = r.kms.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeOplog) + default: + parentOplogWrapper, err = r.kms.GetWrapper(ctx, s.ParentId, kms.KeyPurposeOplog) + } + externalWrappers = r.kms.GetExternalWrappers() + } + if err != nil { + return nil, fmt.Errorf("create scope: unable to get oplog wrapper: %w", err) } opts := getOpts(opt...) - var err error var scopePublicId string var scopeMetadata oplog.Metadata var scopeRaw interface{} @@ -98,19 +119,45 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o } } + reader := opts.withRandomReader + if reader == nil { + reader = rand.Reader + } + rootKey, err := uuid.GenerateRandomBytesWithReader(32, reader) + if err != nil { + return nil, fmt.Errorf("create scope: error generating random bytes for scope root key: %w", err) + } + _, err = r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, - func(_ db.Reader, w db.Writer) error { + func(dbr db.Reader, w db.Writer) error { if err := w.Create( ctx, scopeRaw, - db.WithOplog(r.wrapper, scopeMetadata), + db.WithOplog(parentOplogWrapper, scopeMetadata), ); err != nil { return fmt.Errorf("error creating scope: %w", err) } + s := scopeRaw.(*Scope) + + // Create the scope's root key + _, _, err := kmsCommon.CreateRootKeyTx(ctx, w, externalWrappers.Root(), s.PublicId, rootKey) + if err != nil { + return fmt.Errorf("error creating scope root key: %w", err) + } + + kmsRepo, err := kms.NewRepository(dbr, w) + if err != nil { + return fmt.Errorf("error creating new kms repo: %w", err) + } + childOplogWrapper, err := r.kms.GetWrapper(ctx, s.PublicId, kms.KeyPurposeOplog, kms.WithRepository(kmsRepo)) + if err != nil { + return fmt.Errorf("error fetching new scope oplog wrapper: %w", err) + } + // We create a new role, then set grants and principals on it. This // turns into a bunch of stuff sadly because the role is the // aggregate. @@ -118,7 +165,7 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o if err := w.Create( ctx, roleRaw, - db.WithOplog(r.wrapper, roleMetadata), + db.WithOplog(childOplogWrapper, roleMetadata), ); err != nil { return fmt.Errorf("error creating role: %w", err) } @@ -163,15 +210,13 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o } msgs = append(msgs, roleUserOplogMsgs...) - s := scopeRaw.(*Scope) - metadata := oplog.Metadata{ "op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()}, "scope-id": []string{s.PublicId}, "scope-type": []string{s.Type}, "resource-public-id": []string{role.PublicId}, } - if err := w.WriteOplogEntryWith(ctx, r.wrapper, roleTicket, metadata, msgs); err != nil { + if err := w.WriteOplogEntryWith(ctx, childOplogWrapper, roleTicket, metadata, msgs); err != nil { return fmt.Errorf("unable to write oplog: %w", err) } } diff --git a/internal/iam/repository_scope_test.go b/internal/iam/repository_scope_test.go index 469a640fe..3abb2bdb0 100644 --- a/internal/iam/repository_scope_test.go +++ b/internal/iam/repository_scope_test.go @@ -20,14 +20,13 @@ import ( func Test_Repository_Scope_Create(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + t.Run("valid-scope", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - rw := db.New(conn) - wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) id := testId(t) - s, err := NewOrg(WithName(id)) require.NoError(err) s, err = repo.CreateScope(context.Background(), s, "") @@ -45,14 +44,11 @@ func Test_Repository_Scope_Create(t *testing.T) { }) t.Run("valid-scope-withPublicId", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - rw := db.New(conn) - wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) publicId, err := newScopeId(scope.Org) require.NoError(err) s, err := NewOrg() require.NoError(err) + s, err = repo.CreateScope(context.Background(), s, "", WithPublicId(publicId)) require.NoError(err) require.NotNil(s) @@ -66,14 +62,11 @@ func Test_Repository_Scope_Create(t *testing.T) { }) t.Run("dup-org-names", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - rw := db.New(conn) - wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) id := testId(t) s, err := NewOrg(WithName(id)) require.NoError(err) + s, err = repo.CreateScope(context.Background(), s, "") require.NoError(err) require.NotNil(s) @@ -88,10 +81,6 @@ func Test_Repository_Scope_Create(t *testing.T) { }) t.Run("dup-proj-names", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - rw := db.New(conn) - wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) id := testId(t) s, err := NewOrg(WithName(id)) @@ -119,15 +108,14 @@ func Test_Repository_Scope_Create(t *testing.T) { func Test_Repository_Scope_Update(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) t.Run("valid-scope", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - rw := db.New(conn) - wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) id := testId(t) - s := testOrg(t, conn, id, "") + s := testOrg(t, repo, id, "") assert.Equal(id, s.Name) foundScope, err := repo.LookupScope(context.Background(), s.PublicId) @@ -167,13 +155,9 @@ func Test_Repository_Scope_Update(t *testing.T) { }) t.Run("bad-parent-scope", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - rw := db.New(conn) - wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) id := testId(t) - s := testOrg(t, conn, id, "") + s := testOrg(t, repo, id, "") assert.Equal(id, s.Name) project, err := NewProject(s.PublicId) @@ -194,15 +178,13 @@ func Test_Repository_Scope_Update(t *testing.T) { func Test_Repository_Scope_Lookup(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) t.Run("found-and-not-found", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - rw := db.New(conn) - wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) id := testId(t) - s := testOrg(t, conn, id, "") + s := testOrg(t, repo, id, "") require.NotEmpty(s.GetPublicId()) assert.Equal(s.GetName(), id) @@ -222,11 +204,10 @@ func Test_Repository_Scope_Delete(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - assert.NoError(t, err) + repo := TestRepo(t, conn, wrapper) t.Run("valid-with-public-id", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - s, _ := TestScopes(t, conn) + s, _ := TestScopes(t, repo) foundScope, err := repo.LookupScope(context.Background(), s.PublicId) require.NoError(err) assert.Equal(foundScope.GetPublicId(), s.GetPublicId()) @@ -261,6 +242,7 @@ func TestRepository_UpdateScope(t *testing.T) { id := testId(t) rw := db.New(conn) wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) publicId := testPublicId(t, "o") type args struct { @@ -417,16 +399,11 @@ func TestRepository_UpdateScope(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - r := &Repository{ - reader: rw, - writer: rw, - wrapper: wrapper, - } - org := testOrg(t, conn, tt.name+"-orig-"+id, "orig-"+id) + org := testOrg(t, repo, tt.name+"-orig-"+id, "orig-"+id) if tt.args.scope != nil { tt.args.scope.PublicId = org.PublicId } - updatedScope, rowsUpdated, err := r.UpdateScope(context.Background(), tt.args.scope, 1, tt.args.fieldMaskPaths, tt.args.opt...) + updatedScope, rowsUpdated, err := repo.UpdateScope(context.Background(), tt.args.scope, 1, tt.args.fieldMaskPaths, tt.args.opt...) if tt.wantErr { require.Error(err) assert.Equal(tt.wantUpdatedRows, rowsUpdated) @@ -458,13 +435,13 @@ func TestRepository_UpdateScope(t *testing.T) { t.Run("dup-name", func(t *testing.T) { assert, require := assert.New(t), require.New(t) r := &Repository{ - reader: rw, - writer: rw, - wrapper: wrapper, + reader: rw, + writer: rw, + kms: repo.kms, } id := testId(t) - _ = testOrg(t, conn, id, id) - org2 := testOrg(t, conn, "dup-"+id, id) + _ = testOrg(t, repo, id, id) + org2 := testOrg(t, repo, "dup-"+id, id) org2.Name = id updatedScope, rowsUpdated, err := r.UpdateScope(context.Background(), org2, 1, []string{"Name"}) require.Error(err) @@ -477,11 +454,9 @@ func Test_Repository_ListProjects(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") const testLimit = 10 - rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper, WithLimit(testLimit)) - require.NoError(t, err) - org := testOrg(t, conn, "", "") + repo := TestRepo(t, conn, wrapper, WithLimit(testLimit)) + org := testOrg(t, repo, "", "") type args struct { withOrgId string @@ -539,7 +514,7 @@ func Test_Repository_ListProjects(t *testing.T) { require.NoError(conn.Where("public_id != ? and public_id != 'global'", org.PublicId).Delete(allocScope()).Error) testProjects := []*Scope{} for i := 0; i < tt.createCnt; i++ { - testProjects = append(testProjects, testProject(t, conn, org.PublicId)) + testProjects = append(testProjects, testProject(t, repo, org.PublicId)) } assert.Equal(tt.createCnt, len(testProjects)) got, err := repo.ListProjects(context.Background(), tt.args.withOrgId, tt.args.opt...) @@ -557,10 +532,8 @@ func Test_Repository_ListOrgs(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") const testLimit = 10 - rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper, WithLimit(testLimit)) - require.NoError(t, err) + repo := TestRepo(t, conn, wrapper, WithLimit(testLimit)) type args struct { opt []Option } @@ -603,7 +576,7 @@ func Test_Repository_ListOrgs(t *testing.T) { require.NoError(conn.Where("type = 'org'").Delete(allocScope()).Error) testOrgs := []*Scope{} for i := 0; i < tt.createCnt; i++ { - testOrgs = append(testOrgs, testOrg(t, conn, "", "")) + testOrgs = append(testOrgs, testOrg(t, repo, "", "")) } assert.Equal(tt.createCnt, len(testOrgs)) got, err := repo.ListOrgs(context.Background(), tt.args.opt...) diff --git a/internal/iam/repository_test.go b/internal/iam/repository_test.go index caea6c8af..21bfd241f 100644 --- a/internal/iam/repository_test.go +++ b/internal/iam/repository_test.go @@ -10,10 +10,10 @@ import ( "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db/timestamp" iam_store "github.com/hashicorp/boundary/internal/iam/store" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" "github.com/hashicorp/boundary/internal/oplog/store" "github.com/hashicorp/boundary/internal/types/scope" - wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" @@ -24,10 +24,11 @@ func TestNewRepository(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) + testKms := kms.TestKms(t, conn, wrapper) type args struct { - r db.Reader - w db.Writer - wrapper wrapping.Wrapper + r db.Reader + w db.Writer + kms *kms.Kms } tests := []struct { name string @@ -39,35 +40,35 @@ func TestNewRepository(t *testing.T) { { name: "valid", args: args{ - r: rw, - w: rw, - wrapper: wrapper, + r: rw, + w: rw, + kms: testKms, }, want: &Repository{ reader: rw, writer: rw, - wrapper: wrapper, + kms: testKms, defaultLimit: db.DefaultLimit, }, wantErr: false, }, { - name: "nil-wrapper", + name: "nil-kms", args: args{ - r: rw, - w: rw, - wrapper: nil, + r: rw, + w: rw, + kms: nil, }, want: nil, wantErr: true, - wantErrString: "error creating db repository with nil wrapper", + wantErrString: "error creating db repository with nil kms", }, { name: "nil-writer", args: args{ - r: rw, - w: nil, - wrapper: wrapper, + r: rw, + w: nil, + kms: testKms, }, want: nil, wantErr: true, @@ -76,9 +77,9 @@ func TestNewRepository(t *testing.T) { { name: "nil-reader", args: args{ - r: nil, - w: rw, - wrapper: wrapper, + r: nil, + w: rw, + kms: testKms, }, want: nil, wantErr: true, @@ -88,7 +89,7 @@ func TestNewRepository(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - got, err := NewRepository(tt.args.r, tt.args.w, tt.args.wrapper) + got, err := NewRepository(tt.args.r, tt.args.w, tt.args.kms) if tt.wantErr { require.Error(err) assert.Equal(err.Error(), tt.wantErrString) @@ -102,12 +103,10 @@ func TestNewRepository(t *testing.T) { func Test_Repository_create(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) t.Run("valid-scope", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - rw := db.New(conn) - wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) id := testId(t) s, err := NewOrg(WithName("fname-" + id)) @@ -134,10 +133,6 @@ func Test_Repository_create(t *testing.T) { }) t.Run("nil-resource", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - rw := db.New(conn) - wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) resource, err := repo.create(context.Background(), nil) require.Error(err) assert.Nil(resource) @@ -148,14 +143,13 @@ func Test_Repository_create(t *testing.T) { func Test_Repository_delete(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) t.Run("valid-org", func(t *testing.T) { assert, require := assert.New(t), require.New(t) rw := db.New(conn) - wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) - s, _ := TestScopes(t, conn) + s, _ := TestScopes(t, repo) rowsDeleted, err := repo.delete(context.Background(), s) require.NoError(err) @@ -164,14 +158,12 @@ func Test_Repository_delete(t *testing.T) { err = db.TestVerifyOplog(t, rw, s.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(5*time.Second)) require.NoError(err) }) + require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error) t.Run("nil-resource", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - rw := db.New(conn) - wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) + deletedRows, err := repo.delete(context.Background(), nil, nil) - assert.Error(err) + require.Error(err) assert.Equal(0, deletedRows) assert.Equal(err.Error(), "error deleting resource that is nil") }) @@ -181,8 +173,9 @@ func TestRepository_update(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") now := ×tamp.Timestamp{Timestamp: ptypes.TimestampNow()} id := testId(t) - rw := db.New(conn) wrapper := db.TestWrapper(t) + rw := db.New(conn) + repo := TestRepo(t, conn, wrapper) publicId := testPublicId(t, "o") type args struct { @@ -297,16 +290,11 @@ func TestRepository_update(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - r := &Repository{ - reader: rw, - writer: rw, - wrapper: wrapper, - } - org := testOrg(t, conn, tt.name+"-orig-"+id, "orig-"+id) + org := testOrg(t, repo, tt.name+"-orig-"+id, "orig-"+id) if tt.args.resource != nil { tt.args.resource.(*Scope).PublicId = org.PublicId } - updatedResource, rowsUpdated, err := r.update(context.Background(), tt.args.resource, 1, tt.args.fieldMaskPaths, tt.args.setToNullPaths, tt.args.opt...) + updatedResource, rowsUpdated, err := repo.update(context.Background(), tt.args.resource, 1, tt.args.fieldMaskPaths, tt.args.setToNullPaths, tt.args.opt...) if tt.wantErr { require.Error(err) assert.Equal(tt.wantUpdatedRows, rowsUpdated) diff --git a/internal/iam/repository_user.go b/internal/iam/repository_user.go index c44674c7b..c322fc573 100644 --- a/internal/iam/repository_user.go +++ b/internal/iam/repository_user.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/boundary/internal/db" dbcommon "github.com/hashicorp/boundary/internal/db/common" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" "github.com/hashicorp/boundary/internal/types/scope" ) @@ -159,6 +160,11 @@ func (r *Repository) LookupUserWithLogin(ctx context.Context, accountId string, "resource-type": []string{"auth-account"}, } + oplogWrapper, err := r.kms.GetWrapper(ctx, acct.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return nil, fmt.Errorf("lookup user with login: unable to get oplog wrapper: %w", err) + } + // We will create a new user and associate the user with the account // within one retryable transaction using writer.DoTx var obtainedUser *User @@ -199,7 +205,7 @@ func (r *Repository) LookupUserWithLogin(ctx context.Context, accountId string, return fmt.Errorf("account update affected %d rows", updatedRows) } msgs = append(msgs, &updateMsg) - if err := w.WriteOplogEntryWith(ctx, r.wrapper, ticket, metadata, msgs); err != nil { + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil { return err } return nil @@ -280,6 +286,11 @@ func (r *Repository) AssociateUserWithAccount(ctx context.Context, userPublicId, } } + oplogWrapper, err := r.kms.GetWrapper(ctx, acct.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return nil, fmt.Errorf("associate user with account: unable to get oplog wrapper: %w", err) + } + // validate, associated the user with the account, and then read the // user back in the same tx for consistency. _, err = r.writer.DoTx( @@ -287,7 +298,6 @@ func (r *Repository) AssociateUserWithAccount(ctx context.Context, userPublicId, db.StdRetryCnt, db.ExpBackoff{}, func(txReader db.Reader, w db.Writer) error { - metadata := oplog.Metadata{ "resource-public-id": []string{accountId}, "scope-id": []string{acct.ScopeId}, @@ -302,9 +312,9 @@ func (r *Repository) AssociateUserWithAccount(ctx context.Context, userPublicId, // transactions) switch { case opts.withDisassociate: - updatedRows, err = w.Update(ctx, updatedAcct, []string{"IamUserId"}, nil, db.WithOplog(r.wrapper, metadata), db.WithWhere("iam_user_id = ?", acct.IamUserId)) + updatedRows, err = w.Update(ctx, updatedAcct, []string{"IamUserId"}, nil, db.WithOplog(oplogWrapper, metadata), db.WithWhere("iam_user_id = ?", acct.IamUserId)) default: - updatedRows, err = w.Update(ctx, updatedAcct, []string{"IamUserId"}, nil, db.WithOplog(r.wrapper, metadata), db.WithWhere("iam_user_id is NULL")) + updatedRows, err = w.Update(ctx, updatedAcct, []string{"IamUserId"}, nil, db.WithOplog(oplogWrapper, metadata), db.WithWhere("iam_user_id is NULL")) } if err != nil { return err @@ -355,6 +365,11 @@ func (r *Repository) DissociateUserWithAccount(ctx context.Context, userPublicId return nil, fmt.Errorf("dissociate user with account: %s account is not associated with a user: %w", accountId, db.ErrInvalidParameter) } + oplogWrapper, err := r.kms.GetWrapper(ctx, acct.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return nil, fmt.Errorf("disassociate user with account: unable to get oplog wrapper: %w", err) + } + // validate, dissociate the user with the account and then read the user back in // the same tx for consistency. _, err = r.writer.DoTx( @@ -373,7 +388,7 @@ func (r *Repository) DissociateUserWithAccount(ctx context.Context, userPublicId // set the user id to null and use WithWhere to ensure that the auth // account is associated with the user (handling race conditions // with other concurrent transactions) - updatedRows, err := w.Update(ctx, updatedAcct, nil, []string{"IamUserId"}, db.WithOplog(r.wrapper, metadata), db.WithWhere("iam_user_id = ?", userPublicId)) + updatedRows, err := w.Update(ctx, updatedAcct, nil, []string{"IamUserId"}, db.WithOplog(oplogWrapper, metadata), db.WithWhere("iam_user_id = ?", userPublicId)) if err != nil { return err } diff --git a/internal/iam/repository_user_test.go b/internal/iam/repository_user_test.go index c93b50bae..0bd08e3c5 100644 --- a/internal/iam/repository_user_test.go +++ b/internal/iam/repository_user_test.go @@ -21,10 +21,9 @@ func TestRepository_CreateUser(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) + repo := TestRepo(t, conn, wrapper) id := testId(t) - org, _ := TestScopes(t, conn) + org, _ := TestScopes(t, repo) type args struct { user *User @@ -113,10 +112,9 @@ func TestRepository_UpdateUser(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) + repo := TestRepo(t, conn, wrapper) id := testId(t) - org, proj := TestScopes(t, conn) + org, proj := TestScopes(t, repo) pubId := func(s string) *string { return &s } type args struct { @@ -298,13 +296,13 @@ func TestRepository_UpdateUser(t *testing.T) { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) if tt.wantDup { - u := TestUser(t, conn, org.PublicId, tt.newUserOpts...) + u := TestUser(t, repo, org.PublicId, tt.newUserOpts...) u.Name = tt.args.name _, _, err := repo.UpdateUser(context.Background(), u, 1, tt.args.fieldMaskPaths, tt.args.opt...) require.NoError(err) } - u := TestUser(t, conn, org.PublicId, tt.newUserOpts...) + u := TestUser(t, repo, org.PublicId, tt.newUserOpts...) updateUser := allocUser() updateUser.PublicId = u.PublicId @@ -372,9 +370,8 @@ func TestRepository_DeleteUser(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) - org, _ := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) type args struct { user *User @@ -390,7 +387,7 @@ func TestRepository_DeleteUser(t *testing.T) { { name: "valid", args: args{ - user: TestUser(t, conn, org.PublicId), + user: TestUser(t, repo, org.PublicId), }, wantRowsDeleted: 1, wantErr: false, @@ -454,11 +451,9 @@ func TestRepository_ListUsers(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") const testLimit = 10 - rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper, WithLimit(testLimit)) - require.NoError(t, err) - org, _ := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper, WithLimit(testLimit)) + org, _ := TestScopes(t, repo) type args struct { withOrgId string @@ -516,7 +511,7 @@ func TestRepository_ListUsers(t *testing.T) { require.NoError(conn.Where("public_id != 'u_anon' and public_id != 'u_auth'").Delete(allocUser()).Error) testUsers := []*User{} for i := 0; i < tt.createCnt; i++ { - testUsers = append(testUsers, TestUser(t, conn, org.PublicId)) + testUsers = append(testUsers, TestUser(t, repo, org.PublicId)) } assert.Equal(tt.createCnt, len(testUsers)) got, err := repo.ListUsers(context.Background(), tt.args.withOrgId, tt.args.opt...) @@ -535,16 +530,15 @@ func TestRepository_LookupUserWithLogin(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) + repo := TestRepo(t, conn, wrapper) id := testId(t) - org, _ := TestScopes(t, conn) + org, _ := TestScopes(t, repo) authMethodId := testAuthMethod(t, conn, org.PublicId) newAuthAcct := testAccount(t, conn, org.PublicId, authMethodId, "") newAuthAcctWithoutVivify := testAccount(t, conn, org.PublicId, authMethodId, "") - user := TestUser(t, conn, org.PublicId, WithName("existing-"+id)) + user := TestUser(t, repo, org.PublicId, WithName("existing-"+id)) existingAuthAcct := testAccount(t, conn, org.PublicId, authMethodId, user.PublicId) require.Equal(t, user.PublicId, existingAuthAcct.IamUserId) @@ -676,9 +670,8 @@ func TestRepository_AssociateUserWithAccount(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) - org, _ := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) authMethodId := testAuthMethod(t, conn, org.PublicId) type Ids struct { @@ -702,7 +695,7 @@ func TestRepository_AssociateUserWithAccount(t *testing.T) { name: "simple", args: args{ Ids: func() Ids { - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) a := testAccount(t, conn, org.PublicId, authMethodId, "") return Ids{user: u.PublicId, acct: a.PublicId} }(), @@ -735,7 +728,7 @@ func TestRepository_AssociateUserWithAccount(t *testing.T) { name: "already-properly-assoc", args: args{ Ids: func() Ids { - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) a := testAccount(t, conn, org.PublicId, authMethodId, u.PublicId) return Ids{user: u.PublicId, acct: a.PublicId} }(), @@ -746,8 +739,8 @@ func TestRepository_AssociateUserWithAccount(t *testing.T) { name: "assoc-with-diff-user", args: args{ Ids: func() Ids { - u := TestUser(t, conn, org.PublicId) - diffUser := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) + diffUser := TestUser(t, repo, org.PublicId) a := testAccount(t, conn, org.PublicId, authMethodId, diffUser.PublicId) return Ids{user: u.PublicId, acct: a.PublicId} }(), @@ -759,8 +752,8 @@ func TestRepository_AssociateUserWithAccount(t *testing.T) { name: "assoc-with-diff-user-withDisassociateOption", args: args{ Ids: func() Ids { - u := TestUser(t, conn, org.PublicId) - diffUser := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) + diffUser := TestUser(t, repo, org.PublicId) a := testAccount(t, conn, org.PublicId, authMethodId, diffUser.PublicId) return Ids{user: u.PublicId, acct: a.PublicId} }(), @@ -772,7 +765,7 @@ func TestRepository_AssociateUserWithAccount(t *testing.T) { name: "bad-acct-id", args: args{ Ids: func() Ids { - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) id := testId(t) return Ids{user: u.PublicId, acct: id} }(), @@ -821,9 +814,8 @@ func TestRepository_DissociateUserWithAccount(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) - org, _ := TestScopes(t, conn) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) authMethodId := testAuthMethod(t, conn, org.PublicId) type Ids struct { @@ -846,7 +838,7 @@ func TestRepository_DissociateUserWithAccount(t *testing.T) { name: "simple", args: args{ Ids: func() Ids { - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) a := testAccount(t, conn, org.PublicId, authMethodId, u.PublicId) return Ids{user: u.PublicId, acct: a.PublicId} }(), @@ -879,7 +871,7 @@ func TestRepository_DissociateUserWithAccount(t *testing.T) { name: "already-properly-disassoc", args: args{ Ids: func() Ids { - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) a := testAccount(t, conn, org.PublicId, authMethodId, "") return Ids{user: u.PublicId, acct: a.PublicId} }(), @@ -889,8 +881,8 @@ func TestRepository_DissociateUserWithAccount(t *testing.T) { name: "assoc-with-diff-user", args: args{ Ids: func() Ids { - u := TestUser(t, conn, org.PublicId) - diffUser := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) + diffUser := TestUser(t, repo, org.PublicId) a := testAccount(t, conn, org.PublicId, authMethodId, diffUser.PublicId) return Ids{user: u.PublicId, acct: a.PublicId} }(), @@ -902,7 +894,7 @@ func TestRepository_DissociateUserWithAccount(t *testing.T) { name: "bad-acct-id", args: args{ Ids: func() Ids { - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) id := testId(t) return Ids{user: u.PublicId, acct: id} }(), diff --git a/internal/iam/resource_test.go b/internal/iam/resource_test.go index 61a9bba04..d12b1158d 100644 --- a/internal/iam/resource_test.go +++ b/internal/iam/resource_test.go @@ -13,11 +13,13 @@ import ( func Test_LookupScope(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) t.Run("valid-scope", func(t *testing.T) { assert, require := assert.New(t), require.New(t) w := db.New(conn) - org, _ := TestScopes(t, conn) - user := TestUser(t, conn, org.PublicId) + org, _ := TestScopes(t, repo) + user := TestUser(t, repo, org.PublicId) foundScope, err := LookupScope(context.Background(), w, user) require.NoError(err) @@ -32,8 +34,8 @@ func Test_LookupScope(t *testing.T) { t.Run("bad-scope", func(t *testing.T) { assert, require := assert.New(t), require.New(t) w := db.New(conn) - org, _ := TestScopes(t, conn) - user := TestUser(t, conn, org.PublicId) + org, _ := TestScopes(t, repo) + user := TestUser(t, repo, org.PublicId) s, err := LookupScope(context.Background(), nil, user) require.Error(err) diff --git a/internal/iam/role_grant_test.go b/internal/iam/role_grant_test.go index 67c5c1aab..c6816eed7 100644 --- a/internal/iam/role_grant_test.go +++ b/internal/iam/role_grant_test.go @@ -16,7 +16,9 @@ import ( func TestRoleGrant_Create(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - _, proj := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + _, proj := TestScopes(t, repo) projRole := TestRole(t, conn, proj.PublicId) type args struct { @@ -115,7 +117,9 @@ func TestRoleGrant_Create(t *testing.T) { func TestRoleGrant_Update(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, _ := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) rw := db.New(conn) t.Run("updates not allowed", func(t *testing.T) { @@ -134,8 +138,9 @@ func TestRoleGrant_Delete(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) - - _, proj := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + _, proj := TestScopes(t, repo) projRole := TestRole(t, conn, proj.PublicId) type args struct { @@ -218,9 +223,11 @@ func TestRoleGrant_Delete(t *testing.T) { func TestRoleGrant_Clone(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) t.Run("valid", func(t *testing.T) { assert := assert.New(t) - s := testOrg(t, conn, "", "") + s := testOrg(t, repo, "", "") role := TestRole(t, conn, s.PublicId) g, err := NewRoleGrant(role.PublicId, "id=*;actions=*") @@ -234,7 +241,7 @@ func TestRoleGrant_Clone(t *testing.T) { }) t.Run("not-equal", func(t *testing.T) { assert := assert.New(t) - s := testOrg(t, conn, "", "") + s := testOrg(t, repo, "", "") role := TestRole(t, conn, s.PublicId) g, err := NewRoleGrant(role.PublicId, "id=*;actions=*") diff --git a/internal/iam/role_test.go b/internal/iam/role_test.go index f066542df..852eef2c8 100644 --- a/internal/iam/role_test.go +++ b/internal/iam/role_test.go @@ -20,7 +20,9 @@ import ( func TestNewRole(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) id := testId(t) type args struct { @@ -96,7 +98,9 @@ func TestNewRole(t *testing.T) { func Test_RoleCreate(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) type args struct { role *Role } @@ -205,9 +209,11 @@ func Test_RoleCreate(t *testing.T) { func Test_RoleUpdate(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) id := testId(t) - org, proj := TestScopes(t, conn) - org2, proj2 := TestScopes(t, conn) + org, proj := TestScopes(t, repo) + org2, proj2 := TestScopes(t, repo) rw := db.New(conn) type args struct { name string @@ -474,9 +480,11 @@ func Test_RoleUpdate(t *testing.T) { func Test_RoleDelete(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) rw := db.New(conn) id := testId(t) - org, _ := TestScopes(t, conn) + org, _ := TestScopes(t, repo) tests := []struct { name string @@ -549,7 +557,9 @@ func TestRole_ResourceType(t *testing.T) { func TestRole_GetScope(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) t.Run("valid-org", func(t *testing.T) { assert, require := assert.New(t), require.New(t) @@ -572,7 +582,9 @@ func TestRole_GetScope(t *testing.T) { func TestRole_Clone(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, _ := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) t.Run("valid", func(t *testing.T) { assert := assert.New(t) role := TestRole(t, conn, org.PublicId, WithDescription("this is a test role")) diff --git a/internal/iam/scope_test.go b/internal/iam/scope_test.go index 58b2c9c1e..9472b7047 100644 --- a/internal/iam/scope_test.go +++ b/internal/iam/scope_test.go @@ -97,10 +97,12 @@ func TestScope_Create(t *testing.T) { func TestScope_Update(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) t.Run("valid", func(t *testing.T) { assert, require := assert.New(t), require.New(t) w := db.New(conn) - s, _ := TestScopes(t, conn) + s, _ := TestScopes(t, repo) s.Name = testId(t) updatedRows, err := w.Update(context.Background(), s, []string{"Name"}, nil) @@ -110,7 +112,7 @@ func TestScope_Update(t *testing.T) { t.Run("type-update-not-allowed", func(t *testing.T) { assert, require := assert.New(t), require.New(t) w := db.New(conn) - s, _ := TestScopes(t, conn) + s, _ := TestScopes(t, repo) s.Type = scope.Project.String() updatedRows, err := w.Update(context.Background(), s, []string{"Type"}, nil) @@ -121,10 +123,12 @@ func TestScope_Update(t *testing.T) { func TestScope_GetScope(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) t.Run("valid-scope", func(t *testing.T) { assert, require := assert.New(t), require.New(t) w := db.New(conn) - org, proj := TestScopes(t, conn) + org, proj := TestScopes(t, repo) global := Scope{ Scope: &store.Scope{Type: scope.Global.String(), PublicId: "global"}, } @@ -171,16 +175,18 @@ func TestScope_ResourceType(t *testing.T) { func TestScope_Clone(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) t.Run("valid", func(t *testing.T) { assert := assert.New(t) - s, _ := TestScopes(t, conn) + s, _ := TestScopes(t, repo) cp := s.Clone() assert.True(proto.Equal(cp.(*Scope).Scope, s.Scope)) }) t.Run("not-equal", func(t *testing.T) { assert := assert.New(t) - s, _ := TestScopes(t, conn) - s2, _ := TestScopes(t, conn) + s, _ := TestScopes(t, repo) + s2, _ := TestScopes(t, repo) cp := s.Clone() assert.True(!proto.Equal(cp.(*Scope).Scope, s2.Scope)) }) diff --git a/internal/iam/testing.go b/internal/iam/testing.go index f20bde222..78b4bb866 100644 --- a/internal/iam/testing.go +++ b/internal/iam/testing.go @@ -2,27 +2,55 @@ package iam import ( "context" + "crypto/rand" "testing" "github.com/hashicorp/boundary/internal/auth/store" "github.com/hashicorp/boundary/internal/db" dbassert "github.com/hashicorp/boundary/internal/db/assert" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/types/scope" + wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/hashicorp/go-uuid" "github.com/jinzhu/gorm" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -// TestScopes creates an org and project suitable for testing. -func TestScopes(t *testing.T, conn *gorm.DB) (org *Scope, prj *Scope) { - t.Helper() +// TestRepo creates a repo that can be used for various purposes. Crucially, it +// ensures that the global scope contains a valid root key. +func TestRepo(t *testing.T, conn *gorm.DB, rootWrapper wrapping.Wrapper, opt ...Option) *Repository { require := require.New(t) rw := db.New(conn) - wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) + kmsCache := kms.TestKms(t, conn, rootWrapper) + wrapper, err := kmsCache.GetWrapper(context.Background(), scope.Global.String(), kms.KeyPurposeOplog) + if err != nil { + var kmsRepo *kms.Repository + // Assume the root hasn't been created and try to insert it + kmsRepo, err = kms.NewRepository(rw, rw) + require.NoError(err) + rootKey, err := uuid.GenerateRandomBytesWithReader(32, rand.Reader) + require.NoError(err) + _, _, err = kmsRepo.CreateRootKey(context.Background(), rootWrapper, scope.Global.String(), rootKey) + require.NoError(err) + wrapper, err = kmsCache.GetWrapper(context.Background(), scope.Global.String(), kms.KeyPurposeOplog) + if err != nil { + panic(err) + } + } require.NoError(err) + require.NotNil(wrapper) - org, err = NewOrg() + repo, err := NewRepository(rw, rw, kmsCache, opt...) + require.NoError(err) + return repo +} + +// TestScopes creates an org and project suitable for testing. +func TestScopes(t *testing.T, repo *Repository) (org *Scope, prj *Scope) { + t.Helper() + require := require.New(t) + + org, err := NewOrg() require.NoError(err) org, err = repo.CreateScope(context.Background(), org, "") require.NoError(err) @@ -39,15 +67,11 @@ func TestScopes(t *testing.T, conn *gorm.DB) (org *Scope, prj *Scope) { return } -func TestOrg(t *testing.T, conn *gorm.DB) (org *Scope) { +func TestOrg(t *testing.T, repo *Repository, opt ...Option) (org *Scope) { t.Helper() require := require.New(t) - rw := db.New(conn) - wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) - org, err = NewOrg() + org, err := NewOrg(opt...) require.NoError(err) org, err = repo.CreateScope(context.Background(), org, "") require.NoError(err) @@ -57,13 +81,9 @@ func TestOrg(t *testing.T, conn *gorm.DB) (org *Scope) { return } -func testOrg(t *testing.T, conn *gorm.DB, name, description string) (org *Scope) { +func testOrg(t *testing.T, repo *Repository, name, description string) (org *Scope) { t.Helper() require := require.New(t) - rw := db.New(conn) - wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) o, err := NewOrg(WithDescription(description), WithName(name)) require.NoError(err) @@ -71,16 +91,13 @@ func testOrg(t *testing.T, conn *gorm.DB, name, description string) (org *Scope) require.NoError(err) require.NotNil(o) require.NotEmpty(o.GetPublicId()) + return o } -func testProject(t *testing.T, conn *gorm.DB, orgId string, opt ...Option) *Scope { +func testProject(t *testing.T, repo *Repository, orgId string, opt ...Option) *Scope { t.Helper() require := require.New(t) - rw := db.New(conn) - wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) p, err := NewProject(orgId, opt...) require.NoError(err) @@ -88,6 +105,7 @@ func testProject(t *testing.T, conn *gorm.DB, orgId string, opt ...Option) *Scop require.NoError(err) require.NotNil(p) require.NotEmpty(p.GetPublicId()) + return p } @@ -106,13 +124,9 @@ func testPublicId(t *testing.T, prefix string) string { } // TestUser creates a user suitable for testing. -func TestUser(t *testing.T, conn *gorm.DB, scopeId string, opt ...Option) *User { +func TestUser(t *testing.T, repo *Repository, scopeId string, opt ...Option) *User { t.Helper() - require := assert.New(t) - rw := db.New(conn) - wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) + require := require.New(t) user, err := NewUser(scopeId, opt...) require.NoError(err) diff --git a/internal/iam/testing_test.go b/internal/iam/testing_test.go index 3860e60a9..f1e499fa8 100644 --- a/internal/iam/testing_test.go +++ b/internal/iam/testing_test.go @@ -12,9 +12,11 @@ import ( func Test_testOrg(t *testing.T) { assert := assert.New(t) conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) id := testId(t) - org := testOrg(t, conn, id, id) + org := testOrg(t, repo, id, id) assert.Equal(id, org.Name) assert.Equal(id, org.Description) assert.NotEmpty(org.PublicId) @@ -24,10 +26,12 @@ func Test_testProj(t *testing.T) { assert := assert.New(t) conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) id := testId(t) - org := testOrg(t, conn, id, id) - proj := testProject(t, conn, org.PublicId, WithName(id), WithDescription(id)) + org := testOrg(t, repo, id, id) + proj := testProject(t, repo, org.PublicId, WithName(id), WithDescription(id)) assert.Equal(id, proj.Name) assert.Equal(id, proj.Description) assert.NotEmpty(proj.PublicId) @@ -47,7 +51,9 @@ func Test_testPublicId(t *testing.T) { func Test_TestScopes(t *testing.T) { assert, require := assert.New(t), require.New(t) conn, _ := db.TestSetup(t, "postgres") - org, prj := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, prj := TestScopes(t, repo) require.NotNil(org) assert.NotEmpty(org.GetPublicId()) @@ -55,16 +61,31 @@ func Test_TestScopes(t *testing.T) { require.NotNil(prj) assert.NotEmpty(prj.GetPublicId()) } + +func Test_TestRepo(t *testing.T) { + require := require.New(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + + repo := TestRepo(t, conn, wrapper) + require.NotNil(repo) + + repo = TestRepo(t, conn, wrapper) + require.NotNil(repo) +} + func Test_TestUser(t *testing.T) { t.Helper() assert, require := assert.New(t), require.New(t) conn, _ := db.TestSetup(t, "postgres") - org, _ := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) require.NotNil(org) assert.NotEmpty(org.GetPublicId()) - user := TestUser(t, conn, org.PublicId) + user := TestUser(t, repo, org.PublicId) require.NotNil(user) assert.NotEmpty(user.PublicId) } @@ -73,8 +94,10 @@ func Test_TestRole(t *testing.T) { t.Helper() assert, require := assert.New(t), require.New(t) conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) id := testId(t) - org, proj := TestScopes(t, conn) + org, proj := TestScopes(t, repo) role := TestRole(t, conn, org.PublicId, WithDescription(id), WithName(id)) require.NotNil(role) assert.Equal(id, role.Description) @@ -90,7 +113,9 @@ func Test_TestRoleGrant(t *testing.T) { t.Helper() require := require.New(t) conn, _ := db.TestSetup(t, "postgres") - _, proj := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + _, proj := TestScopes(t, repo) projRole := TestRole(t, conn, proj.PublicId) grant := TestRoleGrant(t, conn, projRole.PublicId, "actions=*;id=*") @@ -104,14 +129,16 @@ func Test_TestUserRole(t *testing.T) { t.Helper() require := require.New(t) conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) - org2, proj2 := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) + org2, proj2 := TestScopes(t, repo) orgRole := TestRole(t, conn, org.PublicId) projRole := TestRole(t, conn, proj.PublicId) org2Role := TestRole(t, conn, org2.PublicId) proj2Role := TestRole(t, conn, proj2.PublicId) - user := TestUser(t, conn, org.PublicId) + user := TestUser(t, repo, org.PublicId) userRole := TestUserRole(t, conn, orgRole.PublicId, user.PublicId) require.NotNil(userRole) @@ -138,8 +165,10 @@ func Test_TestGroupRole(t *testing.T) { t.Helper() require := require.New(t) conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) - org2, proj2 := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) + org2, proj2 := TestScopes(t, repo) orgRole := TestRole(t, conn, org.PublicId) orgGroup := TestGroup(t, conn, org.PublicId) @@ -174,10 +203,12 @@ func Test_TestGroupMember(t *testing.T) { t.Helper() require := require.New(t) conn, _ := db.TestSetup(t, "postgres") - org, proj := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, proj := TestScopes(t, repo) og := TestGroup(t, conn, org.PublicId) pg := TestGroup(t, conn, proj.PublicId) - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) gm := TestGroupMember(t, conn, og.PublicId, u.PublicId) require.NotNil(gm) diff --git a/internal/iam/user_test.go b/internal/iam/user_test.go index fa9c91115..5ae5750a4 100644 --- a/internal/iam/user_test.go +++ b/internal/iam/user_test.go @@ -17,7 +17,8 @@ import ( func TestNewUser(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, _ := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + org, _ := TestScopes(t, TestRepo(t, conn, wrapper)) id := testId(t) type args struct { @@ -92,7 +93,8 @@ func Test_UserHardcoded(t *testing.T) { func Test_UserCreate(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, _ := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + org, _ := TestScopes(t, TestRepo(t, conn, wrapper)) id := testId(t) t.Run("valid-user", func(t *testing.T) { assert, require := assert.New(t), require.New(t) @@ -131,10 +133,9 @@ func Test_UserUpdate(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) + repo := TestRepo(t, conn, wrapper) id := testId(t) - org, proj := TestScopes(t, conn) + org, proj := TestScopes(t, repo) type args struct { name string @@ -206,13 +207,13 @@ func Test_UserUpdate(t *testing.T) { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) if tt.wantDup { - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) u.Name = tt.args.name _, err := rw.Update(context.Background(), u, tt.args.fieldMaskPaths, nil) require.NoError(err) } - u := TestUser(t, conn, org.PublicId) + u := TestUser(t, repo, org.PublicId) updateUser := allocUser() updateUser.PublicId = u.PublicId @@ -243,10 +244,9 @@ func Test_UserDelete(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) - repo, err := NewRepository(rw, rw, wrapper) - require.NoError(t, err) + repo := TestRepo(t, conn, wrapper) id := testId(t) - org, _ := TestScopes(t, conn) + org, _ := TestScopes(t, repo) tests := []struct { name string @@ -257,7 +257,7 @@ func Test_UserDelete(t *testing.T) { }{ { name: "valid", - user: TestUser(t, conn, org.PublicId), + user: TestUser(t, repo, org.PublicId), wantErr: false, wantRowsDeleted: 1, }, @@ -302,11 +302,13 @@ func Test_UserDelete(t *testing.T) { func Test_UserGetScope(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, _ := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) t.Run("valid-scope", func(t *testing.T) { assert, require := assert.New(t), require.New(t) w := db.New(conn) - user := TestUser(t, conn, org.PublicId) + user := TestUser(t, repo, org.PublicId) userScope, err := user.GetScope(context.Background(), w) require.NoError(err) assert.True(proto.Equal(org, userScope)) @@ -317,11 +319,13 @@ func Test_UserGetScope(t *testing.T) { func TestUser_Clone(t *testing.T) { t.Parallel() conn, _ := db.TestSetup(t, "postgres") - org, _ := TestScopes(t, conn) + wrapper := db.TestWrapper(t) + repo := TestRepo(t, conn, wrapper) + org, _ := TestScopes(t, repo) t.Run("valid", func(t *testing.T) { assert := assert.New(t) - user := TestUser(t, conn, org.PublicId) + user := TestUser(t, repo, org.PublicId) cp := user.Clone() assert.True(proto.Equal(cp.(*User).User, user.User)) }) diff --git a/internal/kms/common/root.go b/internal/kms/common/root.go new file mode 100644 index 000000000..90ecd8c4a --- /dev/null +++ b/internal/kms/common/root.go @@ -0,0 +1,21 @@ +package common + +import ( + "context" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms" + wrapping "github.com/hashicorp/go-kms-wrapping" +) + +// CreateRootKeyTx inserts into the db (via db.Writer) and returns the new root +// key and root key version. This function encapsulates all the work required +// within a db.TxHandler and allows this capability to be shared with the iam +// repo via a common pkg without circular dependencies +// +// TODO (jimlambrt 8/2020) - refactor this... it's not needed now that we've +// moved the tests into the kms_test package. We can do this refactor once +// we've merged Jeff's sub-branch. +func CreateRootKeyTx(ctx context.Context, w db.Writer, keyWrapper wrapping.Wrapper, scopeId string, key []byte) (*kms.RootKey, *kms.RootKeyVersion, error) { + return kms.CreateRootKeyTx(ctx, w, keyWrapper, scopeId, key) +} diff --git a/internal/kms/common/root_test.go b/internal/kms/common/root_test.go new file mode 100644 index 000000000..41faf976b --- /dev/null +++ b/internal/kms/common/root_test.go @@ -0,0 +1,134 @@ +package common_test + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/kms/common" + "github.com/hashicorp/boundary/internal/kms/store" + "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_CreateRootKeyTx(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) + iamRepo := iam.TestRepo(t, conn, wrapper) + org, proj := iam.TestScopes(t, iamRepo) + + type args struct { + scopeId string + key []byte + keyWrapper wrapping.Wrapper + } + tests := []struct { + name string + args args + wantErr bool + wantIsError error + }{ + { + name: "valid-org", + args: args{ + scopeId: org.PublicId, + key: []byte("test key"), + keyWrapper: wrapper, + }, + wantErr: false, + }, + { + name: "valid-global", + args: args{ + scopeId: "global", + key: []byte("valid-global"), + keyWrapper: wrapper, + }, + wantErr: false, + }, + { + name: "valid-proj", + args: args{ + scopeId: proj.PublicId, + key: []byte("valid-proj"), + keyWrapper: wrapper, + }, + wantErr: false, + }, + { + name: "invalid-scope", + args: args{ + scopeId: "o_notAValidScopeId", + key: []byte("invalid-scope"), + keyWrapper: wrapper, + }, + wantErr: true, + }, + { + name: "empty-scope", + args: args{ + key: []byte("empty-scope"), + keyWrapper: wrapper, + }, + wantErr: true, + wantIsError: db.ErrInvalidParameter, + }, + { + name: "nil-wrapper", + args: args{ + scopeId: org.PublicId, + key: []byte("test key"), + keyWrapper: nil, + }, + wantErr: true, + wantIsError: db.ErrNilParameter, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + // need to delete root keys created by iam.TestScopes() + require.NoError(conn.Where("1=1").Delete(&kms.RootKey{RootKey: &store.RootKey{}}).Error) + rk, kv, err := common.CreateRootKeyTx(context.Background(), rw, tt.args.keyWrapper, tt.args.scopeId, tt.args.key) + if tt.wantErr { + assert.Error(err) + assert.Nil(rk) + if tt.wantIsError != nil { + assert.True(errors.Is(err, tt.wantIsError)) + } + return + } + require.NoError(err) + assert.NotNil(rk.CreateTime) + foundKey, err := repo.LookupRootKey(context.Background(), tt.args.keyWrapper, rk.PrivateId) + assert.NoError(err) + assert.True(proto.Equal(foundKey, rk)) + + // make sure there was no oplog written + err = db.TestVerifyOplog(t, rw, rk.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)) + assert.Error(err) + assert.True(errors.Is(err, db.ErrRecordNotFound)) + + assert.NotNil(kv.CreateTime) + foundKeyVersion, err := repo.LookupRootKeyVersion(context.Background(), tt.args.keyWrapper, kv.PrivateId) + assert.NoError(err) + assert.True(proto.Equal(foundKeyVersion, kv)) + + // make sure there was no oplog written + err = db.TestVerifyOplog(t, rw, kv.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)) + assert.Error(err) + assert.True(errors.Is(err, db.ErrRecordNotFound)) + }) + } +} diff --git a/internal/kms/const.go b/internal/kms/const.go new file mode 100644 index 000000000..93fd7016b --- /dev/null +++ b/internal/kms/const.go @@ -0,0 +1,31 @@ +package kms + +// KeyPurpose allows an application to specify the reason they need a key; this +// is used to select which DEK to return +type KeyPurpose uint + +const ( + // KeyPurposeUnknown is the default, and indicates that a correct purpose + // wasn't specified + KeyPurposeUnknown KeyPurpose = 0 + + // KeyPurposeDatabase is used for general encryption needs for most values + // in the database, excluding the oplog + KeyPurposeDatabase KeyPurpose = 1 + + // KeyPurposeOplog is used for oplogs + KeyPurposeOplog KeyPurpose = 2 +) + +// String returns the key purpose cast as a string, just so it can be called as +// a function instead of direct casting elsewhere, yw +func (k KeyPurpose) String() string { + switch k { + case KeyPurposeDatabase: + return "database" + case KeyPurposeOplog: + return "oplog" + default: + return "unknown" + } +} diff --git a/internal/kms/ids.go b/internal/kms/ids.go new file mode 100644 index 000000000..fbdc8715e --- /dev/null +++ b/internal/kms/ids.go @@ -0,0 +1,28 @@ +package kms + +import ( + "fmt" + + "github.com/hashicorp/boundary/internal/db" +) + +const ( + RootKeyPrefix = "krk" + RootKeyVersionPrefix = "krkv" +) + +func newRootKeyId() (string, error) { + id, err := db.NewPublicId(RootKeyPrefix) + if err != nil { + return "", fmt.Errorf("new root key id: %w", err) + } + return id, nil +} + +func newRootKeyVersionId() (string, error) { + id, err := db.NewPublicId(RootKeyVersionPrefix) + if err != nil { + return "", fmt.Errorf("new root key version id: %w", err) + } + return id, nil +} diff --git a/internal/kms/ids_test.go b/internal/kms/ids_test.go new file mode 100644 index 000000000..d6149ccbe --- /dev/null +++ b/internal/kms/ids_test.go @@ -0,0 +1,23 @@ +package kms + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Ids(t *testing.T) { + t.Parallel() + t.Run("krk", func(t *testing.T) { + id, err := newRootKeyId() + require.NoError(t, err) + assert.True(t, strings.HasPrefix(id, RootKeyPrefix+"_")) + }) + t.Run("krkv", func(t *testing.T) { + id, err := newRootKeyVersionId() + require.NoError(t, err) + assert.True(t, strings.HasPrefix(id, RootKeyVersionPrefix+"_")) + }) +} diff --git a/internal/kms/immutable_fields_test.go b/internal/kms/immutable_fields_test.go new file mode 100644 index 000000000..aefc8d29b --- /dev/null +++ b/internal/kms/immutable_fields_test.go @@ -0,0 +1,180 @@ +package kms_test + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/timestamp" + "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" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// TODO (jimlambrt 8/2020) define tests for immutable fields in the remaining +// kms tables: +// kms_database_key +// kms_database_key_version +// kms_oplog_key +// kms_oplog_key_version +// kms_session_key +// kms_session_key_version + +func TestRootKeyVersion_ImmutableFields(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + rw := db.New(conn) + + ts := timestamp.Timestamp{Timestamp: ×tamppb.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) + require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKeyVersion()).Error) + rk := kms.TestRootKey(t, conn, org.PublicId) + new := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId, []byte("test key")) + + var tests = []struct { + name string + update *kms.RootKeyVersion + fieldMask []string + }{ + { + name: "private_id", + update: func() *kms.RootKeyVersion { + k := new.Clone().(*kms.RootKeyVersion) + k.PrivateId = "o_thisIsNotAValidId" + return k + }(), + fieldMask: []string{"PrivateId"}, + }, + { + name: "create time", + update: func() *kms.RootKeyVersion { + k := new.Clone().(*kms.RootKeyVersion) + k.CreateTime = &ts + return k + }(), + fieldMask: []string{"CreateTime"}, + }, + { + name: "root_key_id", + update: func() *kms.RootKeyVersion { + k := new.Clone().(*kms.RootKeyVersion) + k.RootKeyId = "o_thisIsNotAValidId" + return k + }(), + fieldMask: []string{"RootKeyId"}, + }, + { + name: "version", + update: func() *kms.RootKeyVersion { + k := new.Clone().(*kms.RootKeyVersion) + k.Version = uint32(22) + return k + }(), + fieldMask: []string{"Version"}, + }, + { + name: "key", + update: func() *kms.RootKeyVersion { + k := new.Clone().(*kms.RootKeyVersion) + 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) + 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.RootKeyVersion), after.(*kms.RootKeyVersion))) + + }) + } +} + +func TestRootKey_ImmutableFields(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + + ts := timestamp.Timestamp{Timestamp: ×tamppb.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) + new := kms.TestRootKey(t, conn, org.PublicId) + + var tests = []struct { + name string + update *kms.RootKey + fieldMask []string + }{ + { + name: "private_id", + update: func() *kms.RootKey { + k := new.Clone().(*kms.RootKey) + k.PrivateId = "o_thisIsNotAValidId" + return k + }(), + fieldMask: []string{"PrivateId"}, + }, + { + name: "create time", + update: func() *kms.RootKey { + k := new.Clone().(*kms.RootKey) + k.CreateTime = &ts + return k + }(), + fieldMask: []string{"CreateTime"}, + }, + { + name: "scope_id", + update: func() *kms.RootKey { + k := new.Clone().(*kms.RootKey) + k.ScopeId = "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) + 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.RootKey), after.(*kms.RootKey))) + + }) + } +} diff --git a/internal/kms/kms.go b/internal/kms/kms.go new file mode 100644 index 000000000..d4f313d03 --- /dev/null +++ b/internal/kms/kms.go @@ -0,0 +1,249 @@ +package kms + +import ( + "context" + "errors" + "fmt" + "hash" + "sync" + + "github.com/hashicorp/boundary/internal/types/scope" + "github.com/hashicorp/go-hclog" + wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/hashicorp/go-kms-wrapping/wrappers/aead" + "github.com/hashicorp/go-kms-wrapping/wrappers/multiwrapper" + "golang.org/x/crypto/blake2b" +) + +// ExternalWrappers holds wrappers defined outside of Boundary, e.g. in its +// configuration file. +type ExternalWrappers struct { + m sync.RWMutex + root wrapping.Wrapper + workerAuth wrapping.Wrapper +} + +// Root returns the wrapper for root keys +func (e *ExternalWrappers) Root() wrapping.Wrapper { + e.m.RLock() + defer e.m.RUnlock() + return e.root +} + +// WorkerAuth returns the wrapper for worker authentication +func (e *ExternalWrappers) WorkerAuth() wrapping.Wrapper { + e.m.RLock() + defer e.m.RUnlock() + return e.workerAuth +} + +// Kms is a way to access wrappers for a given scope and purpose. Since keys can +// never change, only be added or (eventually) removed, it opportunistically +// caches, going to the database as needed. +type Kms struct { + logger hclog.Logger + + // scopePurposeCache holds a per-scope-purpose multiwrapper containing the + // current encrypting key and all previous key versions, for decryption + scopePurposeCache sync.Map + + externalScopeCache map[string]*ExternalWrappers + externalScopeCacheMutex sync.RWMutex + + repo *Repository +} + +// NewKms takes in a repo and returns a Kms. Supported options: WithLogger. +func NewKms(repo *Repository, opt ...Option) (*Kms, error) { + if repo == nil { + return nil, errors.New("new kms created without an underlying repo") + } + + opts := getOpts(opt...) + + return &Kms{ + logger: opts.withLogger, + externalScopeCache: make(map[string]*ExternalWrappers), + repo: repo, + }, nil +} + +// GetScopePurposeCache is used in test functions for validation. Since the +// tests need to be in a different package to avoid circular dependencies, this +// is exported. +func (k *Kms) GetScopePurposeCache() *sync.Map { + return &k.scopePurposeCache +} + +// AddExternalWrappers allows setting the external keys. +// +// TODO: If we support more than one, e.g. for encrypting against many in case +// of a key loss, there will need to be some refactoring here to have the values +// being stored in the struct be a multiwrapper, but that's for a later project. +func (k *Kms) AddExternalWrappers(opt ...Option) error { + k.externalScopeCacheMutex.Lock() + defer k.externalScopeCacheMutex.Unlock() + + ext := k.externalScopeCache[scope.Global.String()] + if ext == nil { + ext = &ExternalWrappers{} + } + ext.m.Lock() + defer ext.m.Unlock() + + opts := getOpts(opt...) + if opts.withRootWrapper != nil { + ext.root = opts.withRootWrapper + if ext.root.KeyID() == "" { + return fmt.Errorf("root wrapper has no key ID") + } + } + if opts.withWorkerAuthWrapper != nil { + ext.workerAuth = opts.withWorkerAuthWrapper + if ext.workerAuth.KeyID() == "" { + return fmt.Errorf("worker auth wrapper has no key ID") + } + } + + k.externalScopeCache[scope.Global.String()] = ext + return nil +} + +func (k *Kms) GetExternalWrappers() *ExternalWrappers { + k.externalScopeCacheMutex.RLock() + defer k.externalScopeCacheMutex.RUnlock() + + ext := k.externalScopeCache[scope.Global.String()] + ext.m.RLock() + defer ext.m.RUnlock() + + ret := &ExternalWrappers{ + root: ext.root, + workerAuth: ext.workerAuth, + } + return ret +} + +func generateKeyId(scopeId string, purpose KeyPurpose, version uint32) string { + return fmt.Sprintf("%s_%s_%d", scopeId, purpose, version) +} + +// GetWrapper returns a wrapper for the given scope and purpose. When a keyId is +// passed, it will ensure that the returning wrapper has that key ID in the +// multiwrapper. This is not necesary for encryption but should be supplied for +// decryption. +func (k *Kms) GetWrapper(ctx context.Context, scopeId string, purpose KeyPurpose, opt ...Option) (wrapping.Wrapper, error) { + switch purpose { + case KeyPurposeOplog, KeyPurposeDatabase: + case KeyPurposeUnknown: + return nil, errors.New("key purpose not specified") + default: + return nil, fmt.Errorf("unsupported purpose %q", purpose) + } + opts := getOpts(opt...) + // Fast-path: we have a valid key at the scope/purpose. Verify the key with + // that ID is in the multiwrapper; if not, fall through to reload from the + // DB. + val, ok := k.scopePurposeCache.Load(scopeId + purpose.String()) + if ok { + wrapper := val.(*multiwrapper.MultiWrapper) + if opts.withKeyId == "" || wrapper.WrapperForKeyID(opts.withKeyId) != nil { + return wrapper, nil + } + // Fall through to refresh our multiwrapper for this scope/purpose from the DB + } + + // We don't have it cached, so we'll need to read from the database. Get the + // root for the scope as we'll need it to decrypt the value coming from the + // DB. We don't cache the roots as we expect that after a few calls the + // scope-purpose cache will catch everything in steady-state. + rootWrapper, err := k.loadRoot(ctx, scopeId, opt...) + if err != nil { + return nil, fmt.Errorf("error loading root key for scope %s: %w", scopeId, err) + } + + // TODO: Look up dek in the db, then decrypt with the root wrapper. If we + // have a key ID passed in verify that we find it. For now since we don't + // have DEKs, derive a key. + baseWrapper := rootWrapper.WrapperForKeyID("__base__").(*aead.Wrapper) + derived, err := baseWrapper.NewDerivedWrapper(&aead.DerivedWrapperOptions{ + KeyID: generateKeyId(scopeId, purpose, 1), + Hash: func() hash.Hash { b, _ := blake2b.New256(nil); return b }, + Salt: []byte(scopeId), + Info: []byte(purpose.String()), + }) + if err != nil { + return nil, fmt.Errorf("error creating derived wrapper: %w", err) + } + + // Store the looked-up value into the scope cache. + multi := multiwrapper.NewMultiWrapper(derived) + k.scopePurposeCache.Store(scopeId+purpose.String(), multi) + + return multi, nil +} + +func (k *Kms) loadRoot(ctx context.Context, scopeId string, opt ...Option) (*multiwrapper.MultiWrapper, error) { + opts := getOpts(opt...) + repo := opts.withRepository + if repo == nil { + repo = k.repo + } + rootKeys, err := repo.ListRootKeys(ctx) + if err != nil { + return nil, fmt.Errorf("error listing root keys: %w", err) + } + var rootKeyId string + for _, k := range rootKeys { + if k.GetScopeId() == scopeId { + rootKeyId = k.GetPrivateId() + break + } + } + if rootKeyId == "" { + return nil, fmt.Errorf("error finding root key for scope %s", scopeId) + } + + // Now: find the external KMS that can be used to decrypt the root values + // from the DB. + k.externalScopeCacheMutex.Lock() + externalWrappers := k.externalScopeCache[scope.Global.String()] + k.externalScopeCacheMutex.Unlock() + if externalWrappers == nil { + return nil, errors.New("could not find kms information at either the needed scope or global fallback") + } + + externalWrappers.m.RLock() + defer externalWrappers.m.RUnlock() + + if externalWrappers.root == nil { + return nil, fmt.Errorf("root key wrapper for scope %s is nil", scopeId) + } + rootKeyVersions, err := repo.ListRootKeyVersions(ctx, externalWrappers.root, rootKeyId, WithOrder("version desc")) + if err != nil { + return nil, fmt.Errorf("error looking up root key versions for scope %s with key ID %s: %w", scopeId, externalWrappers.root.KeyID(), err) + } + if len(rootKeyVersions) == 0 { + return nil, fmt.Errorf("no root key versions found for scope %s", scopeId) + } + + var multi *multiwrapper.MultiWrapper + for i, key := range rootKeyVersions { + wrapper := aead.NewWrapper(nil) + if _, err := wrapper.SetConfig(map[string]string{ + "key_id": key.GetPrivateId(), + }); err != nil { + return nil, fmt.Errorf("error setting config on aead root wrapper in scope %s: %w", scopeId, err) + } + if err := wrapper.SetAESGCMKeyBytes(key.GetKey()); err != nil { + return nil, fmt.Errorf("error setting key bytes on aead root wrapper in scope %s: %w", scopeId, err) + } + if i == 0 { + multi = multiwrapper.NewMultiWrapper(wrapper) + } else { + multi.AddWrapper(wrapper) + } + } + + return multi, err +} diff --git a/internal/kms/kms_test.go b/internal/kms/kms_test.go new file mode 100644 index 000000000..bab09797d --- /dev/null +++ b/internal/kms/kms_test.go @@ -0,0 +1,127 @@ +package kms_test + +import ( + "context" + "encoding/base64" + "fmt" + "strings" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/types/scope" + "github.com/hashicorp/go-kms-wrapping/wrappers/aead" + "github.com/hashicorp/go-kms-wrapping/wrappers/multiwrapper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// NOTE: This is a sequential test that relies on the actions that have come +// before. Please see the comments for details. +func TestKms(t *testing.T) { + t.Parallel() + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + badWrapper := db.TestWrapper(t) + repo, err := kms.NewRepository(rw, rw) + require.NoError(t, err) + kmsCache := kms.TestKms(t, conn, wrapper) + org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + // Verify that the cache is empty, so we can show that by the end of the + // test sequence we did actually look up keys and store them in the cache + t.Run("verify cache empty", func(t *testing.T) { + var count int + kmsCache.GetScopePurposeCache().Range(func(key interface{}, value interface{}) bool { + count++ + return true + }) + assert.Equal(t, 0, count) + }) + // Verify that the root keys are all in the database and can be decrypted + // with the correct wrapper from the KMS object + t.Run("verify root keys", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + rootKeys, err := repo.ListRootKeys(ctx) + require.NoError(err) + wrappers := kmsCache.GetExternalWrappers() + for _, key := range rootKeys { + kvs, err := repo.ListRootKeyVersions(ctx, wrappers.Root(), key.GetPrivateId()) + require.NoError(err) + assert.Len(kvs, 1) + assert.Len(kvs[0].GetKey(), 32) + } + }) + // Verify that the wrong wrapper causes decryption to fail + t.Run("bad external keys", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + rootKeys, err := repo.ListRootKeys(ctx) + require.NoError(err) + for _, key := range rootKeys { + _, err := repo.ListRootKeyVersions(ctx, badWrapper, key.GetPrivateId()) + require.Error(err) + assert.True(strings.Contains(err.Error(), "message authentication failed"), err.Error()) + } + }) + // This next sequence is run twice to ensure that calling for the keys twice + // returns the same value each time and doesn't simply populate more keys + // into the KMS object + keyBytes := map[string]bool{} + keyIds := map[string]bool{} + scopePurposeMap := map[string]interface{}{} + for i := 1; i < 3; i++ { + // This iterates through wrappers for all three scopes and two purposes, + // ensuring that the key bytes and IDs are different for each of them, + // simulating calling the KMS object from different scopes for different + // purposes and ensuring the keys are different when that happens. + t.Run(fmt.Sprintf("verify wrappers different x %d", i), func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + for _, scopeId := range []string{scope.Global.String(), org.GetPublicId(), proj.GetPublicId()} { + for _, purpose := range []kms.KeyPurpose{kms.KeyPurposeUnknown, kms.KeyPurposeOplog, kms.KeyPurposeDatabase} { + wrapper, err := kmsCache.GetWrapper(ctx, scopeId, purpose) + if purpose == kms.KeyPurposeUnknown { + require.Error(err) + continue + } + require.NoError(err) + multi, ok := wrapper.(*multiwrapper.MultiWrapper) + require.True(ok) + aeadWrapper, ok := multi.WrapperForKeyID(multi.KeyID()).(*aead.Wrapper) + require.True(ok) + foundKeyBytes := keyBytes[base64.StdEncoding.EncodeToString(aeadWrapper.GetKeyBytes())] + foundKeyId := keyIds[aeadWrapper.KeyID()] + if i == 1 { + assert.False(foundKeyBytes) + assert.False(foundKeyId) + keyBytes[base64.StdEncoding.EncodeToString(aeadWrapper.GetKeyBytes())] = true + keyIds[aeadWrapper.KeyID()] = true + } else { + assert.True(foundKeyBytes) + assert.True(foundKeyId) + } + } + } + }) + // Verify that the cache has been populated with unique values. The + // second time we validate that the items we find when going through the + // cache a second time are the same as the first. If they were recreated + // the pointers would be different. + t.Run(fmt.Sprintf("verify cache populated x %d", i), func(t *testing.T) { + var count int + kmsCache.GetScopePurposeCache().Range(func(key interface{}, value interface{}) bool { + count++ + if i == 1 { + scopePurposeMap[key.(string)] = value + } else { + assert.Same(t, scopePurposeMap[key.(string)], value) + } + return true + }) + // two purposes x 3 scopes + assert.Equal(t, 6, count) + }) + } +} diff --git a/internal/kms/option_test.go b/internal/kms/option_test.go new file mode 100644 index 000000000..7b8bf5ed7 --- /dev/null +++ b/internal/kms/option_test.go @@ -0,0 +1,30 @@ +package kms + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// Test_GetOpts provides unit tests for GetOpts and all the options +func Test_GetOpts(t *testing.T) { + t.Parallel() + t.Run("WithLimit", func(t *testing.T) { + assert := assert.New(t) + // test default of 0 + opts := getOpts() + testOpts := getDefaultOptions() + testOpts.withLimit = 0 + assert.Equal(opts, testOpts) + + opts = getOpts(WithLimit(-1)) + testOpts = getDefaultOptions() + testOpts.withLimit = -1 + assert.Equal(opts, testOpts) + + opts = getOpts(WithLimit(1)) + testOpts = getDefaultOptions() + testOpts.withLimit = 1 + assert.Equal(opts, testOpts) + }) +} diff --git a/internal/kms/options.go b/internal/kms/options.go new file mode 100644 index 000000000..95c016f2f --- /dev/null +++ b/internal/kms/options.go @@ -0,0 +1,87 @@ +package kms + +import ( + "github.com/hashicorp/go-hclog" + wrapping "github.com/hashicorp/go-kms-wrapping" +) + +// getOpts - iterate the inbound Options and return a struct +func getOpts(opt ...Option) options { + opts := getDefaultOptions() + for _, o := range opt { + o(&opts) + } + return opts +} + +// Option - how Options are passed as arguments +type Option func(*options) + +// options = how options are represented +type options struct { + withLimit int + withLogger hclog.Logger + withRootWrapper wrapping.Wrapper + withWorkerAuthWrapper wrapping.Wrapper + withRepository *Repository + withOrder string + withKeyId string +} + +func getDefaultOptions() options { + return options{} +} + +// WithLimit provides an option to provide a limit. Intentionally allowing +// negative integers. If WithLimit < 0, then unlimited results are returned. If +// WithLimit == 0, then default limits are used for results. +func WithLimit(limit int) Option { + return func(o *options) { + o.withLimit = limit + } +} + +// WithLogger provides a logger to be used when needed +func WithLogger(l hclog.Logger) Option { + return func(o *options) { + o.withLogger = l + } +} + +// WithRootWrapper sets the external root wrapper for a given scope +func WithRootWrapper(w wrapping.Wrapper) Option { + return func(o *options) { + o.withRootWrapper = w + } +} + +// WithWorkerAuthWrapper sets the external worker authentication wrapper for a +// given scope. +func WithWorkerAuthWrapper(w wrapping.Wrapper) Option { + return func(o *options) { + o.withWorkerAuthWrapper = w + } +} + +// WithRepository sets a repository for a given wrapper lookup, useful if in the +// middle of a transaction where the reader/writer need to be specified +func WithRepository(repo *Repository) Option { + return func(o *options) { + o.withRepository = repo + } +} + +// WithOrder allows specifying an order for returned values +func WithOrder(order string) Option { + return func(o *options) { + o.withOrder = order + } +} + +// WithKeyId allows specifying a key ID that should be found in a scope's +// multiwrapper; if it is not found, keys will be refreshed +func WithKeyId(keyId string) Option { + return func(o *options) { + o.withKeyId = keyId + } +} diff --git a/internal/kms/repository.go b/internal/kms/repository.go new file mode 100644 index 000000000..c5e8e410e --- /dev/null +++ b/internal/kms/repository.go @@ -0,0 +1,59 @@ +package kms + +import ( + "context" + "errors" + + "github.com/hashicorp/boundary/internal/db" +) + +// Repository is the iam database repository +type Repository struct { + reader db.Reader + writer db.Writer + // defaultLimit provides a default for limiting the number of results returned from the repo + defaultLimit int +} + +// NewRepository creates a new kms Repository. Supports the options: WithLimit +// which sets a default limit on results returned by repo operations. +func NewRepository(r db.Reader, w db.Writer, opt ...Option) (*Repository, error) { + if r == nil { + return nil, errors.New("error creating db repository with nil reader") + } + if w == nil { + return nil, errors.New("error creating db repository with nil writer") + } + opts := getOpts(opt...) + if opts.withLimit == 0 { + // zero signals the boundary defaults should be used. + opts.withLimit = db.DefaultLimit + } + return &Repository{ + reader: r, + writer: w, + defaultLimit: opts.withLimit, + }, nil +} + +// list will return a listing of resources and honor the WithLimit option or the +// repo defaultLimit +func (r *Repository) list(ctx context.Context, resources interface{}, where string, args []interface{}, opt ...Option) error { + opts := getOpts(opt...) + limit := r.defaultLimit + var dbOpts []db.Option + if opts.withLimit != 0 { + // non-zero signals an override of the default limit for the repo. + limit = opts.withLimit + } + dbOpts = append(dbOpts, db.WithLimit(limit)) + if opts.withOrder != "" { + dbOpts = append(dbOpts, db.WithOrder(opts.withOrder)) + } + return r.reader.SearchWhere(ctx, resources, where, args, dbOpts...) +} + +// DefaultLimit returns the default limit for listing as set on the repo +func (r *Repository) DefaultLimit() int { + return r.defaultLimit +} diff --git a/internal/kms/repository_root_key.go b/internal/kms/repository_root_key.go new file mode 100644 index 000000000..1e55e9d71 --- /dev/null +++ b/internal/kms/repository_root_key.go @@ -0,0 +1,138 @@ +package kms + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/db" + wrapping "github.com/hashicorp/go-kms-wrapping" +) + +// CreateRootKey inserts into the repository and returns the new root key and +// root key version. There are no valid options at this time. +func (r *Repository) CreateRootKey(ctx context.Context, keyWrapper wrapping.Wrapper, scopeId string, key []byte, opt ...Option) (*RootKey, *RootKeyVersion, error) { + var returnedRk, returnedKv interface{} + _, err := r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + var err error + if returnedRk, returnedKv, err = CreateRootKeyTx(ctx, w, keyWrapper, scopeId, key); err != nil { + return err + } + return nil + }, + ) + if err != nil { + return nil, nil, fmt.Errorf("create root key: %w in %s", err, scopeId) + } + return returnedRk.(*RootKey), returnedKv.(*RootKeyVersion), err +} + +// CreateRootKeyTx inserts into the db (via db.Writer) and returns the new root key +// and root key version. This function encapsulates all the work required within +// a db.TxHandler and allows this capability to be shared with the iam repo via +// a common pkg without circular dependencies: kms/common.CreateRootKeyTx +func CreateRootKeyTx(ctx context.Context, w db.Writer, keyWrapper wrapping.Wrapper, scopeId string, key []byte) (*RootKey, *RootKeyVersion, error) { + if scopeId == "" { + return nil, nil, fmt.Errorf("create root key: missing scope id: %w", db.ErrInvalidParameter) + } + if keyWrapper == nil { + return nil, nil, fmt.Errorf("create root key: missing key wrapper: %w", db.ErrNilParameter) + } + if len(key) == 0 { + return nil, nil, fmt.Errorf("create root key: missing key: %w", db.ErrInvalidParameter) + } + rk := AllocRootKey() + kv := AllocRootKeyVersion() + id, err := newRootKeyId() + if err != nil { + return nil, nil, fmt.Errorf("create root key: %w", err) + } + rk.PrivateId = id + rk.ScopeId = scopeId + + id, err = newRootKeyVersionId() + if err != nil { + return nil, nil, fmt.Errorf("create root key: %w", err) + } + kv.PrivateId = id + kv.RootKeyId = rk.PrivateId + kv.Key = key + if err := kv.Encrypt(ctx, keyWrapper); err != nil { + return nil, nil, fmt.Errorf("create root key: %w", err) + } + + // no oplog entries for root keys + if err := w.Create(ctx, &rk); err != nil { + return nil, nil, fmt.Errorf("create root key: root create: %w", err) + } + // no oplog entries for root key versions + if err := w.Create(ctx, &kv); err != nil { + return nil, nil, fmt.Errorf("create root key: version create: %w", err) + } + + return &rk, &kv, err +} + +// LookupRootKey will look up a root key in the repository. If the key is not +// found, it will return nil, nil. +func (r *Repository) LookupRootKey(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, opt ...Option) (*RootKey, error) { + if privateId == "" { + return nil, fmt.Errorf("lookup root key: missing private id: %w", db.ErrNilParameter) + } + if keyWrapper == nil { + return nil, fmt.Errorf("lookup root key: missing key wrapper: %w", db.ErrNilParameter) + } + k := AllocRootKey() + k.PrivateId = privateId + if err := r.reader.LookupById(ctx, &k); err != nil { + return nil, fmt.Errorf("lookup root key: failed %w for %s", err, privateId) + } + return &k, nil +} + +// DeleteRootKey deletes the root key for the provided id from the +// repository returning a count of the number of records deleted. All options +// are ignored. +func (r *Repository) DeleteRootKey(ctx context.Context, privateId string, opt ...Option) (int, error) { + if privateId == "" { + return db.NoRowsAffected, fmt.Errorf("delete root key: missing private id: %w", db.ErrInvalidParameter) + } + k := AllocRootKey() + k.PrivateId = privateId + if err := r.reader.LookupById(ctx, &k); err != nil { + return db.NoRowsAffected, fmt.Errorf("delete root key: failed %w for %s", err, 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 && rowsDeleted > 1 { + return db.ErrMultipleRecords + } + return err + }, + ) + if err != nil { + return db.NoRowsAffected, fmt.Errorf("delete root key: %s: %w", privateId, err) + } + return rowsDeleted, nil +} + +// ListRootKeys will list the root keys. Supports the WithLimit option. +func (r *Repository) ListRootKeys(ctx context.Context, opt ...Option) ([]*RootKey, error) { + var keys []*RootKey + err := r.list(ctx, &keys, "1=1", nil, opt...) + if err != nil { + return nil, fmt.Errorf("list root keys: %w", err) + } + return keys, nil +} diff --git a/internal/kms/repository_root_key_test.go b/internal/kms/repository_root_key_test.go new file mode 100644 index 000000000..8d178083e --- /dev/null +++ b/internal/kms/repository_root_key_test.go @@ -0,0 +1,274 @@ +package kms_test + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/db" + "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_CreateRootKey(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + repo, err := kms.NewRepository(rw, rw) + require.NoError(t, err) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error) + + type args struct { + scopeId string + key []byte + keyWrapper wrapping.Wrapper + opt []kms.Option + } + tests := []struct { + name string + args args + wantErr bool + wantIsError error + }{ + { + name: "valid-org", + args: args{ + scopeId: org.PublicId, + key: []byte("test key"), + keyWrapper: wrapper, + }, + wantErr: false, + }, + { + name: "valid-global", + args: args{ + scopeId: "global", + key: []byte("valid-global"), + keyWrapper: wrapper, + }, + wantErr: false, + }, + { + name: "invalid-scope", + args: args{ + scopeId: "o_notAValidScopeId", + key: []byte("invalid-scope"), + keyWrapper: wrapper, + }, + wantErr: true, + }, + { + name: "empty-scope", + args: args{ + key: []byte("empty-scope"), + keyWrapper: wrapper, + }, + wantErr: true, + wantIsError: db.ErrInvalidParameter, + }, + { + name: "nil-wrapper", + args: args{ + scopeId: org.PublicId, + key: []byte("test key"), + keyWrapper: nil, + }, + wantErr: true, + wantIsError: db.ErrNilParameter, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + rk, kv, err := repo.CreateRootKey(context.Background(), tt.args.keyWrapper, tt.args.scopeId, tt.args.key, tt.args.opt...) + if tt.wantErr { + assert.Error(err) + assert.Nil(rk) + if tt.wantIsError != nil { + assert.True(errors.Is(err, tt.wantIsError)) + } + return + } + require.NoError(err) + assert.NotNil(rk.CreateTime) + foundKey, err := repo.LookupRootKey(context.Background(), tt.args.keyWrapper, rk.PrivateId) + assert.NoError(err) + assert.True(proto.Equal(foundKey, rk)) + + // make sure there was no oplog written + err = db.TestVerifyOplog(t, rw, rk.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)) + assert.Error(err) + assert.True(errors.Is(err, db.ErrRecordNotFound)) + + assert.NotNil(kv.CreateTime) + foundKeyVersion, err := repo.LookupRootKeyVersion(context.Background(), tt.args.keyWrapper, kv.PrivateId) + assert.NoError(err) + assert.True(proto.Equal(foundKeyVersion, kv)) + + // make sure there was no oplog written + err = db.TestVerifyOplog(t, rw, kv.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)) + assert.Error(err) + assert.True(errors.Is(err, db.ErrRecordNotFound)) + }) + } +} + +func TestRepository_DeleteRootKey(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + repo, err := kms.NewRepository(rw, rw) + require.NoError(t, err) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error) + + type args struct { + key *kms.RootKey + opt []kms.Option + } + tests := []struct { + name string + args args + wantRowsDeleted int + wantErr bool + wantIsError error + }{ + { + name: "valid", + args: args{ + key: kms.TestRootKey(t, conn, org.PublicId), + }, + wantRowsDeleted: 1, + wantErr: false, + }, + { + name: "no-private-id", + args: args{ + key: func() *kms.RootKey { + k := kms.AllocRootKey() + return &k + }(), + }, + wantRowsDeleted: 0, + wantErr: true, + wantIsError: db.ErrInvalidParameter, + }, + { + name: "not-found", + args: args{ + key: func() *kms.RootKey { + id, err := db.NewPublicId(kms.RootKeyPrefix) + require.NoError(t, err) + k := kms.AllocRootKey() + k.PrivateId = id + require.NoError(t, err) + return &k + }(), + }, + wantRowsDeleted: 0, + wantErr: true, + wantIsError: db.ErrRecordNotFound, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + deletedRows, err := repo.DeleteRootKey(context.Background(), tt.args.key.PrivateId, tt.args.opt...) + if tt.wantErr { + require.Error(err) + assert.Equal(0, deletedRows) + if tt.wantIsError != nil { + assert.True(errors.Is(err, tt.wantIsError)) + } + // 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.Is(db.ErrRecordNotFound, err)) + return + } + require.NoError(err) + assert.Equal(tt.wantRowsDeleted, deletedRows) + foundKey, err := repo.LookupRootKey(context.Background(), wrapper, tt.args.key.PrivateId) + assert.Error(err) + assert.Nil(foundKey) + assert.True(errors.Is(err, db.ErrRecordNotFound)) + + // 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.Is(db.ErrRecordNotFound, err)) + }) + } +} + +func TestRepository_ListRootKeys(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + const testLimit = 10 + rw := db.New(conn) + repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit)) + require.NoError(t, err) + wrapper := db.TestWrapper(t) + + type args struct { + opt []kms.Option + } + tests := []struct { + name string + createCnt int + args args + wantCnt int + wantErr bool + }{ + { + name: "no-limit", + createCnt: repo.DefaultLimit() + 1, + args: args{ + opt: []kms.Option{kms.WithLimit(-1)}, + }, + wantCnt: (repo.DefaultLimit()+1)*2 + 1, // org and project both have keys, plus global scope + wantErr: false, + }, + { + name: "default-limit", + createCnt: repo.DefaultLimit() + 1, + args: args{}, + wantCnt: repo.DefaultLimit(), + wantErr: false, + }, + { + name: "custom-limit", + createCnt: repo.DefaultLimit() + 1, + args: args{ + opt: []kms.Option{kms.WithLimit(3)}, + }, + wantCnt: 3, + wantErr: false, + }, + } + for _, tt := range tests { + 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++ { + _, _ = iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + } + got, err := repo.ListRootKeys(context.Background(), tt.args.opt...) + if tt.wantErr { + require.Error(err) + return + } + require.NoError(err) + assert.Equal(tt.wantCnt, len(got)) + }) + } +} diff --git a/internal/kms/repository_root_key_version.go b/internal/kms/repository_root_key_version.go new file mode 100644 index 000000000..9d33a7349 --- /dev/null +++ b/internal/kms/repository_root_key_version.go @@ -0,0 +1,151 @@ +package kms + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/db" + wrapping "github.com/hashicorp/go-kms-wrapping" +) + +// CreateRootKeyVersion inserts into the repository and returns the new root key +// version with its PrivateId. There are no valid options at this time. +func (r *Repository) CreateRootKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, rootKeyId string, key []byte, opt ...Option) (*RootKeyVersion, error) { + if rootKeyId == "" { + return nil, fmt.Errorf("create root key version: missing root key id: %w", db.ErrInvalidParameter) + } + if keyWrapper == nil { + return nil, fmt.Errorf("create root key version: missing key wrapper: %w", db.ErrNilParameter) + } + if len(key) == 0 { + return nil, fmt.Errorf("create root key version: missing key: %w", db.ErrInvalidParameter) + } + kv := AllocRootKeyVersion() + id, err := newRootKeyVersionId() + if err != nil { + return nil, fmt.Errorf("create root key version: %w", err) + } + kv.PrivateId = id + kv.RootKeyId = rootKeyId + kv.Key = key + if err := kv.Encrypt(ctx, keyWrapper); err != nil { + return nil, fmt.Errorf("create root key version: encrypt: %w", err) + } + + 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 err + } + return nil + }, + ) + if err != nil { + return nil, fmt.Errorf("create root key version: %w for %s root key id", err, kv.RootKeyId) + } + return returnedKey.(*RootKeyVersion), err +} + +// LookupRootKeyVersion will look up a root key version in the repository. If +// the key version is not found, it will return nil, nil. +func (r *Repository) LookupRootKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, opt ...Option) (*RootKeyVersion, error) { + if privateId == "" { + return nil, fmt.Errorf("lookup root key version: missing private id: %w", db.ErrNilParameter) + } + if keyWrapper == nil { + return nil, fmt.Errorf("lookup root key version: missing key wrapper: %w", db.ErrNilParameter) + } + k := AllocRootKeyVersion() + k.PrivateId = privateId + if err := r.reader.LookupById(ctx, &k); err != nil { + return nil, fmt.Errorf("lookup root key version: failed %w for %s", err, privateId) + } + if err := k.Decrypt(ctx, keyWrapper); err != nil { + return nil, fmt.Errorf("lookup root key version: decrypt: %w", err) + } + return &k, nil +} + +// DeleteRootKeyVersion deletes the root key version for the provided id from the +// repository returning a count of the number of records deleted. All options +// are ignored. +func (r *Repository) DeleteRootKeyVersion(ctx context.Context, privateId string, opt ...Option) (int, error) { + if privateId == "" { + return db.NoRowsAffected, fmt.Errorf("delete root key version: missing private id: %w", db.ErrInvalidParameter) + } + k := AllocRootKeyVersion() + k.PrivateId = privateId + if err := r.reader.LookupById(ctx, &k); err != nil { + return db.NoRowsAffected, fmt.Errorf("delete root key version: failed %w for %s", err, privateId) + } + + var rowsDeleted int + _, err := r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) (err error) { + dk := k.Clone() + // no oplog entries for root key version + rowsDeleted, err = w.Delete(ctx, dk) + if err == nil && rowsDeleted > 1 { + return db.ErrMultipleRecords + } + return err + }, + ) + if err != nil { + return db.NoRowsAffected, fmt.Errorf("delete root key version: %s: %w", privateId, err) + } + return rowsDeleted, nil +} + +// LatestRootKeyVersion searches for the root key version with the highest +// version number. When no results are found, it returns nil, +// db.ErrRecordNotFound. +func (r *Repository) LatestRootKeyVersion(ctx context.Context, keyWrapper wrapping.Wrapper, rootKeyId string, opt ...Option) (*RootKeyVersion, error) { + if rootKeyId == "" { + return nil, fmt.Errorf("latest root key version: missing root key id: %w", db.ErrNilParameter) + } + if keyWrapper == nil { + return nil, fmt.Errorf("latest root key version: missing key wrapper: %w", db.ErrNilParameter) + } + var foundKeys []RootKeyVersion + if err := r.reader.SearchWhere(ctx, &foundKeys, "root_key_id = ?", []interface{}{rootKeyId}, db.WithLimit(1), db.WithOrder("version desc")); err != nil { + return nil, fmt.Errorf("latest root key version: failed %w for %s", err, rootKeyId) + } + if len(foundKeys) == 0 { + return nil, db.ErrRecordNotFound + } + if err := foundKeys[0].Decrypt(ctx, keyWrapper); err != nil { + return nil, fmt.Errorf("latest root key version: %w", err) + } + return &foundKeys[0], nil +} + +// ListRootKeyVersions in versions of a root key. Supports the WithLimit option. +func (r *Repository) ListRootKeyVersions(ctx context.Context, keyWrapper wrapping.Wrapper, rootKeyId string, opt ...Option) ([]*RootKeyVersion, error) { + if rootKeyId == "" { + return nil, fmt.Errorf("list root key versions: missing root key id %w", db.ErrInvalidParameter) + } + if keyWrapper == nil { + return nil, fmt.Errorf("list root key versions: missing key wrapper: %w", db.ErrNilParameter) + } + var versions []*RootKeyVersion + err := r.list(ctx, &versions, "root_key_id = ?", []interface{}{rootKeyId}, opt...) + if err != nil { + return nil, fmt.Errorf("list root key versions: %w", err) + } + for i, k := range versions { + if err := k.Decrypt(ctx, keyWrapper); err != nil { + return nil, fmt.Errorf("list root key versions: error decrypting key num %d: %w", i, err) + } + } + return versions, nil +} diff --git a/internal/kms/repository_root_key_version_test.go b/internal/kms/repository_root_key_version_test.go new file mode 100644 index 000000000..e9f72da3c --- /dev/null +++ b/internal/kms/repository_root_key_version_test.go @@ -0,0 +1,364 @@ +package kms_test + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/db" + "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_CreateRootKeyVersion(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + repo, err := kms.NewRepository(rw, rw) + require.NoError(t, err) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error) + rk := kms.TestRootKey(t, conn, org.PublicId) + + type args struct { + rootKeyId string + key []byte + keyWrapper wrapping.Wrapper + opt []kms.Option + } + tests := []struct { + name string + args args + wantErr bool + wantIsError error + }{ + { + name: "valid", + args: args{ + key: []byte("test key"), + rootKeyId: rk.PrivateId, + keyWrapper: wrapper, + }, + wantErr: false, + }, + { + name: "invalid-root-key", + args: args{ + key: []byte("test key"), + rootKeyId: "krk_thisIsNotValid", + keyWrapper: wrapper, + }, + wantErr: true, + }, + { + name: "empty-key", + args: args{ + key: nil, + rootKeyId: rk.PrivateId, + keyWrapper: wrapper, + }, + wantErr: true, + wantIsError: db.ErrInvalidParameter, + }, + { + name: "nil-wrapper", + args: args{ + key: []byte("test key"), + rootKeyId: rk.PrivateId, + keyWrapper: nil, + }, + wantErr: true, + wantIsError: db.ErrNilParameter, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + k, err := repo.CreateRootKeyVersion(context.Background(), tt.args.keyWrapper, tt.args.rootKeyId, tt.args.key, tt.args.opt...) + if tt.wantErr { + assert.Error(err) + assert.Nil(k) + if tt.wantIsError != nil { + assert.True(errors.Is(err, tt.wantIsError)) + } + return + } + require.NoError(err) + assert.NotNil(k.CreateTime) + foundKey, err := repo.LookupRootKeyVersion(context.Background(), tt.args.keyWrapper, k.PrivateId) + assert.NoError(err) + assert.True(proto.Equal(foundKey, k)) + + // make sure there was no oplog written + err = db.TestVerifyOplog(t, rw, k.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)) + assert.Error(err) + assert.True(errors.Is(db.ErrRecordNotFound, err)) + }) + } +} + +func TestRepository_DeleteRootKeyVersion(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + repo, err := kms.NewRepository(rw, rw) + require.NoError(t, err) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error) + rk := kms.TestRootKey(t, conn, org.PublicId) + + type args struct { + key *kms.RootKeyVersion + opt []kms.Option + } + tests := []struct { + name string + args args + wantRowsDeleted int + wantErr bool + wantIsError error + }{ + { + name: "valid", + args: args{ + key: kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId, []byte("test key")), + }, + wantRowsDeleted: 1, + wantErr: false, + }, + { + name: "no-private-id", + args: args{ + key: func() *kms.RootKeyVersion { + k := kms.AllocRootKeyVersion() + return &k + }(), + }, + wantRowsDeleted: 0, + wantErr: true, + wantIsError: db.ErrInvalidParameter, + }, + { + name: "not-found", + args: args{ + key: func() *kms.RootKeyVersion { + id, err := db.NewPublicId(kms.RootKeyPrefix) + require.NoError(t, err) + k := kms.AllocRootKeyVersion() + k.PrivateId = id + require.NoError(t, err) + return &k + }(), + }, + wantRowsDeleted: 0, + wantErr: true, + wantIsError: db.ErrRecordNotFound, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + deletedRows, err := repo.DeleteRootKeyVersion(context.Background(), tt.args.key.PrivateId, tt.args.opt...) + if tt.wantErr { + require.Error(err) + assert.Equal(0, deletedRows) + if tt.wantIsError != nil { + assert.True(errors.Is(err, tt.wantIsError)) + } + 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.Is(db.ErrRecordNotFound, err)) + return + } + require.NoError(err) + assert.Equal(tt.wantRowsDeleted, deletedRows) + foundKey, err := repo.LookupRootKeyVersion(context.Background(), wrapper, tt.args.key.PrivateId) + assert.Error(err) + assert.Nil(foundKey) + assert.True(errors.Is(err, db.ErrRecordNotFound)) + + // 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.Is(db.ErrRecordNotFound, err)) + }) + } +} + +func TestRepository_LatestRootKeyVersion(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + repo, err := kms.NewRepository(rw, rw) + require.NoError(t, err) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error) + rk := kms.TestRootKey(t, conn, org.PublicId) + + tests := []struct { + name string + createCnt int + keyWrapper wrapping.Wrapper + wantVersion uint32 + wantErr bool + wantIsError error + }{ + { + name: "5", + createCnt: 5, + keyWrapper: wrapper, + wantVersion: 5, + wantErr: false, + }, + { + name: "1", + createCnt: 1, + keyWrapper: wrapper, + wantVersion: 1, + wantErr: false, + }, + { + name: "0", + createCnt: 0, + keyWrapper: wrapper, + wantErr: true, + wantIsError: db.ErrRecordNotFound, + }, + { + name: "nil-wrapper", + createCnt: 5, + keyWrapper: nil, + wantErr: true, + wantIsError: db.ErrNilParameter, + }, + } + 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.AllocRootKeyVersion()).Error) + testKeys := []*kms.RootKeyVersion{} + for i := 0; i < tt.createCnt; i++ { + testKeys = append(testKeys, kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId, []byte("test key"))) + } + assert.Equal(tt.createCnt, len(testKeys)) + got, err := repo.LatestRootKeyVersion(context.Background(), tt.keyWrapper, rk.PrivateId) + if tt.wantErr { + require.Error(err) + assert.Nil(got) + if tt.wantIsError != nil { + assert.True(errors.Is(err, tt.wantIsError)) + } + return + } + require.NoError(err) + require.NotNil(got) + assert.Equal(tt.wantVersion, got.Version) + }) + } +} + +func TestRepository_ListRootKeyVersions(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + const testLimit = 10 + rw := db.New(conn) + wrapper := db.TestWrapper(t) + repo, err := kms.NewRepository(rw, rw, kms.WithLimit(testLimit)) + require.NoError(t, err) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error) + rk := kms.TestRootKey(t, conn, org.PublicId) + + type args struct { + rootKeyId string + keyWrapper wrapping.Wrapper + opt []kms.Option + } + tests := []struct { + name string + createCnt int + args args + wantCnt int + wantErr bool + }{ + { + name: "no-limit", + createCnt: repo.DefaultLimit() + 1, + args: args{ + rootKeyId: rk.PrivateId, + keyWrapper: wrapper, + opt: []kms.Option{kms.WithLimit(-1)}, + }, + wantCnt: repo.DefaultLimit() + 1, + wantErr: false, + }, + { + name: "default-limit", + createCnt: repo.DefaultLimit() + 1, + args: args{ + keyWrapper: wrapper, + rootKeyId: rk.PrivateId, + }, + wantCnt: repo.DefaultLimit(), + wantErr: false, + }, + { + name: "custom-limit", + createCnt: repo.DefaultLimit() + 1, + args: args{ + keyWrapper: wrapper, + rootKeyId: rk.PrivateId, + opt: []kms.Option{kms.WithLimit(3)}, + }, + wantCnt: 3, + wantErr: false, + }, + { + name: "bad-org", + createCnt: 1, + args: args{ + keyWrapper: wrapper, + rootKeyId: "bad-id", + }, + wantCnt: 0, + wantErr: false, + }, + { + name: "nil-wrapper", + createCnt: 1, + args: args{ + keyWrapper: nil, + rootKeyId: rk.PrivateId, + }, + wantCnt: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + require.NoError(conn.Where("1=1").Delete(kms.AllocRootKeyVersion()).Error) + testRootKeyVersions := []*kms.RootKeyVersion{} + for i := 0; i < tt.createCnt; i++ { + testRootKeyVersions = append(testRootKeyVersions, kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId, []byte("test key"))) + } + assert.Equal(tt.createCnt, len(testRootKeyVersions)) + got, err := repo.ListRootKeyVersions(context.Background(), tt.args.keyWrapper, tt.args.rootKeyId, tt.args.opt...) + if tt.wantErr { + require.Error(err) + return + } + require.NoError(err) + assert.Equal(tt.wantCnt, len(got)) + }) + } +} diff --git a/internal/kms/repository_test.go b/internal/kms/repository_test.go new file mode 100644 index 000000000..9e1373582 --- /dev/null +++ b/internal/kms/repository_test.go @@ -0,0 +1,74 @@ +package kms_test + +import ( + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewRepository(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + type args struct { + r db.Reader + w db.Writer + } + tests := []struct { + name string + args args + want *kms.Repository + wantErr bool + wantErrString string + }{ + { + name: "valid", + args: args{ + r: rw, + w: rw, + }, + want: func() *kms.Repository { + ret, err := kms.NewRepository(rw, rw) + require.NoError(t, err) + return ret + }(), + wantErr: false, + }, + { + name: "nil-writer", + args: args{ + r: rw, + w: nil, + }, + want: nil, + wantErr: true, + wantErrString: "error creating db repository with nil writer", + }, + { + name: "nil-reader", + args: args{ + r: nil, + w: rw, + }, + want: nil, + wantErr: true, + wantErrString: "error creating db repository with nil reader", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + got, err := kms.NewRepository(tt.args.r, tt.args.w) + if tt.wantErr { + require.Error(err) + assert.Equal(err.Error(), tt.wantErrString) + return + } + require.NoError(err) + assert.Equal(tt.want, got) + }) + } +} diff --git a/internal/kms/root_key.go b/internal/kms/root_key.go new file mode 100644 index 000000000..db3f5b7ef --- /dev/null +++ b/internal/kms/root_key.go @@ -0,0 +1,79 @@ +package kms + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms/store" + "google.golang.org/protobuf/proto" +) + +const ( + DefaultRootKeyTableName = "kms_root_key" +) + +type RootKey struct { + *store.RootKey + tableName string `gorm:"-"` +} + +// NewRootKey creates a new in memory root key. ScopeId must be +// for a global or org scope, but the scope type validation will be deferred +// until the in memory root key is written to the database. No options +// are currently supported. +func NewRootKey(scopeId string, opt ...Option) (*RootKey, error) { + if scopeId == "" { + return nil, fmt.Errorf("new root key: missing scope id: %w", db.ErrInvalidParameter) + } + c := &RootKey{ + RootKey: &store.RootKey{ + ScopeId: scopeId, + }, + } + return c, nil +} + +// AllocRootKey will allocate a root key +func AllocRootKey() RootKey { + return RootKey{ + RootKey: &store.RootKey{}, + } +} + +// Clone creates a clone of the RootKey +func (k *RootKey) Clone() interface{} { + cp := proto.Clone(k.RootKey) + return &RootKey{ + RootKey: cp.(*store.RootKey), + } +} + +// VetForWrite implements db.VetForWrite() interface and validates the kms root +// before it's written. +func (k *RootKey) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error { + if k.PrivateId == "" { + return fmt.Errorf("root key vet for write: missing private id: %w", db.ErrInvalidParameter) + } + if opType == db.CreateOp { + if k.ScopeId == "" { + return fmt.Errorf("root key vet for write: missing scope id: %w", db.ErrInvalidParameter) + } + } + return nil +} + +// TableName returns the tablename to override the default gorm table name +func (k *RootKey) TableName() string { + if k.tableName != "" { + return k.tableName + } + return DefaultRootKeyTableName +} + +// SetTableName sets the tablename and satisfies the ReplayableMessage +// interface. If the caller attempts to set the name to "" the name will be +// reset to the default name. +func (c *RootKey) SetTableName(n string) { + c.tableName = n +} diff --git a/internal/kms/root_key_test.go b/internal/kms/root_key_test.go new file mode 100644 index 000000000..77ba5f8f9 --- /dev/null +++ b/internal/kms/root_key_test.go @@ -0,0 +1,210 @@ +package kms_test + +import ( + "context" + "errors" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +// NOTE: there are no update tests since all the RootKey attributes are +// immutable and those tests are covered by TestRootKey_ImmutableFields + +func TestRootKey_Create(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + type args struct { + scopeId string + opt []kms.Option + } + tests := []struct { + name string + args args + want *kms.RootKey + wantErr bool + wantIsErr error + create bool + wantCreateErr bool + }{ + { + name: "empty-scopeId", + args: args{}, + wantErr: true, + wantIsErr: db.ErrInvalidParameter, + }, + { + name: "valid-org-config", + args: args{ + scopeId: org.PublicId, + }, + want: func() *kms.RootKey { + k := kms.AllocRootKey() + k.ScopeId = org.PublicId + return &k + }(), + create: true, + }, + { + name: "valid-global-config", + args: args{ + scopeId: "global", + }, + want: func() *kms.RootKey { + k := kms.AllocRootKey() + k.ScopeId = "global" + return &k + }(), + create: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + require.NoError(conn.Where("1=1").Delete(kms.AllocRootKey()).Error) + got, err := kms.NewRootKey(tt.args.scopeId, tt.args.opt...) + if tt.wantErr { + require.Error(err) + assert.True(errors.Is(err, tt.wantIsErr)) + return + } + require.NoError(err) + assert.Equal(tt.want, got) + if tt.create { + id, err := db.NewPublicId(kms.RootKeyPrefix) + require.NoError(err) + got.PrivateId = id + err = db.New(conn).Create(context.Background(), got) + if tt.wantCreateErr { + assert.Error(err) + return + } else { + assert.NoError(err) + } + } + }) + } +} + +func TestRootKey_Delete(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error) + + tests := []struct { + name string + key *kms.RootKey + wantRowsDeleted int + wantErr bool + wantErrMsg string + }{ + { + name: "valid", + key: kms.TestRootKey(t, conn, org.PublicId), + wantErr: false, + wantRowsDeleted: 1, + }, + { + name: "bad-id", + key: func() *kms.RootKey { + k := kms.AllocRootKey() + id, err := db.NewPublicId(kms.RootKeyPrefix) + require.NoError(t, err) + k.PrivateId = id + k.ScopeId = org.PublicId + return &k + }(), + wantErr: false, + wantRowsDeleted: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + deleteKey := kms.AllocRootKey() + deleteKey.PrivateId = tt.key.PrivateId + deletedRows, err := rw.Delete(context.Background(), &deleteKey) + if tt.wantErr { + require.Error(err) + return + } + require.NoError(err) + if tt.wantRowsDeleted == 0 { + assert.Equal(tt.wantRowsDeleted, deletedRows) + return + } + assert.Equal(tt.wantRowsDeleted, deletedRows) + foundKey := kms.AllocRootKey() + foundKey.PrivateId = tt.key.PrivateId + err = rw.LookupById(context.Background(), &foundKey) + require.Error(err) + assert.True(errors.Is(db.ErrRecordNotFound, err)) + }) + } +} + +func TestRootKey_Clone(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + t.Run("valid", func(t *testing.T) { + assert := assert.New(t) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error) + k := kms.TestRootKey(t, conn, org.PublicId) + cp := k.Clone() + assert.True(proto.Equal(cp.(*kms.RootKey).RootKey, k.RootKey)) + }) + t.Run("not-equal", func(t *testing.T) { + assert := assert.New(t) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + org2, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error) + k := kms.TestRootKey(t, conn, org.PublicId) + k2 := kms.TestRootKey(t, conn, org2.PublicId) + + cp := k.Clone() + assert.True(!proto.Equal(cp.(*kms.RootKey).RootKey, k2.RootKey)) + }) +} + +func TestRootKey_SetTableName(t *testing.T) { + t.Parallel() + defaultTableName := kms.DefaultRootKeyTableName + tests := []struct { + name string + setNameTo string + want string + }{ + { + name: "new-name", + setNameTo: "new-name", + want: "new-name", + }, + { + name: "reset to default", + setNameTo: "", + want: defaultTableName, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + def := kms.AllocRootKey() + require.Equal(defaultTableName, def.TableName()) + s := kms.AllocRootKey() + s.SetTableName(tt.setNameTo) + assert.Equal(tt.want, s.TableName()) + }) + } +} diff --git a/internal/kms/root_key_version.go b/internal/kms/root_key_version.go new file mode 100644 index 000000000..1b0595bd8 --- /dev/null +++ b/internal/kms/root_key_version.go @@ -0,0 +1,107 @@ +package kms + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/db" + "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 ( + DefaultRootKeyVersionTableName = "kms_root_key_version" +) + +type RootKeyVersion struct { + *store.RootKeyVersion + tableName string `gorm:"-"` +} + +// NewRootKeyVersion creates a new in memory root key version. No options are +// currently supported. +func NewRootKeyVersion(rootKeyId string, key []byte, opt ...Option) (*RootKeyVersion, error) { + if rootKeyId == "" { + return nil, fmt.Errorf("new root key version: missing root key id: %w", db.ErrInvalidParameter) + } + if len(key) == 0 { + return nil, fmt.Errorf("new root key version: missing key: %w", db.ErrInvalidParameter) + } + + k := &RootKeyVersion{ + RootKeyVersion: &store.RootKeyVersion{ + RootKeyId: rootKeyId, + Key: key, + }, + } + return k, nil +} + +// AllocRootKeyVersion allocates a RootKeyVersion +func AllocRootKeyVersion() RootKeyVersion { + return RootKeyVersion{ + RootKeyVersion: &store.RootKeyVersion{}, + } +} + +// Clone creates a clone of the RootKeyVersion +func (k *RootKeyVersion) Clone() interface{} { + cp := proto.Clone(k.RootKeyVersion) + return &RootKeyVersion{ + RootKeyVersion: cp.(*store.RootKeyVersion), + } +} + +// VetForWrite implements db.VetForWrite() interface and validates the root key +// version before it's written. +func (k *RootKeyVersion) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error { + if k.PrivateId == "" { + return fmt.Errorf("root key version vet for write: missing private id: %w", db.ErrInvalidParameter) + } + if opType == db.CreateOp { + if k.CtKey == nil { + return fmt.Errorf("root key version vet for write: missing key: %w", db.ErrInvalidParameter) + } + if k.RootKeyId == "" { + return fmt.Errorf("root key version vet for write: missing root key id: %w", db.ErrInvalidParameter) + } + } + return nil +} + +// TableName returns the tablename to override the default gorm table name +func (k *RootKeyVersion) TableName() string { + if k.tableName != "" { + return k.tableName + } + return DefaultRootKeyVersionTableName +} + +// SetTableName sets the tablename and satisfies the ReplayableMessage +// interface. If the caller attempts to set the name to "" the name will be +// reset to the default name. +func (k *RootKeyVersion) SetTableName(n string) { + k.tableName = n +} + +// Encrypt will encrypt the root key version's key +func (k *RootKeyVersion) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error { + // structwrapping doesn't support embedding, so we'll pass in the + // store.RootKey directly + if err := structwrapping.WrapStruct(ctx, cipher, k.RootKeyVersion, nil); err != nil { + return fmt.Errorf("error encrypting kms root key version: %w", err) + } + return nil +} + +// Decrypt will decrypt the root key version's key +func (k *RootKeyVersion) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error { + // structwrapping doesn't support embedding, so we'll pass in the + // store.RootKeyVersion directly + if err := structwrapping.UnwrapStruct(ctx, cipher, k.RootKeyVersion, nil); err != nil { + return fmt.Errorf("error decrypting kms root key version: %w", err) + } + return nil +} diff --git a/internal/kms/root_key_version_test.go b/internal/kms/root_key_version_test.go new file mode 100644 index 000000000..72f44af70 --- /dev/null +++ b/internal/kms/root_key_version_test.go @@ -0,0 +1,219 @@ +package kms_test + +import ( + "context" + "errors" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/kms/store" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +// NOTE: there are no update tests since all the RootKeyVersion attributes are +// immutable and those tests are covered by TestRootKeyVersion_ImmutableFields + +func TestRootKeyVersion_Create(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error) + rootKey := kms.TestRootKey(t, conn, org.PublicId) + type args struct { + rootId string + key []byte + opt []kms.Option + } + tests := []struct { + name string + args args + want *kms.RootKeyVersion + wantErr bool + wantIsErr error + create bool + wantCreateErr bool + }{ + { + name: "empty-root-id", + args: args{ + key: []byte("test key"), + }, + wantErr: true, + wantIsErr: db.ErrInvalidParameter, + }, + { + name: "empty-key", + args: args{ + rootId: rootKey.PrivateId, + }, + wantErr: true, + wantIsErr: db.ErrInvalidParameter, + }, + { + name: "valid", + args: args{ + rootId: rootKey.PrivateId, + key: []byte("test key"), + }, + want: func() *kms.RootKeyVersion { + k := kms.AllocRootKeyVersion() + k.RootKeyId = rootKey.PrivateId + k.Key = []byte("test key") + return &k + }(), + create: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + require.NoError(conn.Where("1=1").Delete(kms.AllocRootKeyVersion()).Error) + got, err := kms.NewRootKeyVersion(tt.args.rootId, tt.args.key, tt.args.opt...) + if tt.wantErr { + require.Error(err) + assert.True(errors.Is(err, tt.wantIsErr)) + return + } + require.NoError(err) + assert.Equal(tt.want, got) + if tt.create { + id, err := db.NewPublicId(kms.RootKeyVersionPrefix) + require.NoError(err) + got.PrivateId = id + err = got.Encrypt(context.Background(), wrapper) + require.NoError(err) + err = db.New(conn).Create(context.Background(), got) + if tt.wantCreateErr { + assert.Error(err) + return + } else { + assert.NoError(err) + } + assert.Equal(uint32(1), got.Version) + } + }) + } +} + +func TestRootKeyVersion_Delete(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + rw := db.New(conn) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error) + rk := kms.TestRootKey(t, conn, org.PublicId) + + tests := []struct { + name string + key *kms.RootKeyVersion + wantRowsDeleted int + wantErr bool + wantErrMsg string + }{ + { + name: "valid", + key: kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId, []byte("test key")), + wantErr: false, + wantRowsDeleted: 1, + }, + { + name: "bad-id", + key: func() *kms.RootKeyVersion { + k := kms.AllocRootKeyVersion() + id, err := db.NewPublicId(kms.RootKeyVersionPrefix) + require.NoError(t, err) + k.PrivateId = id + return &k + }(), + wantErr: false, + wantRowsDeleted: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + deleteKey := kms.AllocRootKeyVersion() + deleteKey.PrivateId = tt.key.PrivateId + deletedRows, err := rw.Delete(context.Background(), &deleteKey) + if tt.wantErr { + require.Error(err) + return + } + require.NoError(err) + if tt.wantRowsDeleted == 0 { + assert.Equal(tt.wantRowsDeleted, deletedRows) + return + } + assert.Equal(tt.wantRowsDeleted, deletedRows) + foundKey := kms.AllocRootKey() + foundKey.PrivateId = tt.key.PrivateId + err = rw.LookupById(context.Background(), &foundKey) + require.Error(err) + assert.True(errors.Is(db.ErrRecordNotFound, err)) + }) + } +} + +func TestRootKeyVersion_Clone(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + t.Run("valid", func(t *testing.T) { + assert := assert.New(t) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error) + rk := kms.TestRootKey(t, conn, org.PublicId) + k := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId, []byte("test key")) + cp := k.Clone() + assert.True(proto.Equal(cp.(*kms.RootKeyVersion).RootKeyVersion, k.RootKeyVersion)) + }) + t.Run("not-equal", func(t *testing.T) { + assert := assert.New(t) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(t, conn.Where("1=1").Delete(kms.AllocRootKey()).Error) + rk := kms.TestRootKey(t, conn, org.PublicId) + k := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId, []byte("test key")) + k2 := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId, []byte("test key")) + cp := k.Clone() + assert.True(!proto.Equal(cp.(*kms.RootKeyVersion).RootKeyVersion, k2.RootKeyVersion)) + }) +} + +func TestRootKeyVersion_SetTableName(t *testing.T) { + t.Parallel() + defaultTableName := kms.DefaultRootKeyVersionTableName + tests := []struct { + name string + setNameTo string + want string + }{ + { + name: "new-name", + setNameTo: "new-name", + want: "new-name", + }, + { + name: "reset to default", + setNameTo: "", + want: defaultTableName, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + def := kms.AllocRootKeyVersion() + require.Equal(defaultTableName, def.TableName()) + s := &kms.RootKeyVersion{ + RootKeyVersion: &store.RootKeyVersion{}, + } + s.SetTableName(tt.setNameTo) + assert.Equal(tt.want, s.TableName()) + }) + } +} diff --git a/internal/kms/store/root_key.pb.go b/internal/kms/store/root_key.pb.go new file mode 100644 index 000000000..71585102b --- /dev/null +++ b/internal/kms/store/root_key.pb.go @@ -0,0 +1,321 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.12.4 +// source: controller/storage/kms/store/v1/root_key.proto + +package store + +import ( + proto "github.com/golang/protobuf/proto" + timestamp "github.com/hashicorp/boundary/internal/db/timestamp" + _ "github.com/hashicorp/boundary/internal/gen/controller/protooptions" + 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) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type RootKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // private_id is used to access the root key via an API + // @inject_tag: gorm:"primary_key" + PrivateId string `protobuf:"bytes,1,opt,name=private_id,json=privateId,proto3" json:"private_id,omitempty" gorm:"primary_key"` + // scope id for the root key + // @inject_tag: `gorm:"default:null"` + ScopeId string `protobuf:"bytes,2,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"default:null"` + // create_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` +} + +func (x *RootKey) Reset() { + *x = RootKey{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_kms_store_v1_root_key_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RootKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RootKey) ProtoMessage() {} + +func (x *RootKey) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_kms_store_v1_root_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 RootKey.ProtoReflect.Descriptor instead. +func (*RootKey) Descriptor() ([]byte, []int) { + return file_controller_storage_kms_store_v1_root_key_proto_rawDescGZIP(), []int{0} +} + +func (x *RootKey) GetPrivateId() string { + if x != nil { + return x.PrivateId + } + return "" +} + +func (x *RootKey) GetScopeId() string { + if x != nil { + return x.ScopeId + } + return "" +} + +func (x *RootKey) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +type RootKeyVersion struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // private_id is used to access the root key version via an API + // @inject_tag: gorm:"primary_key" + PrivateId string `protobuf:"bytes,1,opt,name=private_id,json=privateId,proto3" json:"private_id,omitempty" gorm:"primary_key"` + // scope id for the root key version + // @inject_tag: `gorm:"default:null"` + RootKeyId string `protobuf:"bytes,2,opt,name=root_key_id,json=rootKeyId,proto3" json:"root_key_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,3,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,4,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,5,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,7,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` +} + +func (x *RootKeyVersion) Reset() { + *x = RootKeyVersion{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_kms_store_v1_root_key_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RootKeyVersion) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RootKeyVersion) ProtoMessage() {} + +func (x *RootKeyVersion) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_kms_store_v1_root_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 RootKeyVersion.ProtoReflect.Descriptor instead. +func (*RootKeyVersion) Descriptor() ([]byte, []int) { + return file_controller_storage_kms_store_v1_root_key_proto_rawDescGZIP(), []int{1} +} + +func (x *RootKeyVersion) GetPrivateId() string { + if x != nil { + return x.PrivateId + } + return "" +} + +func (x *RootKeyVersion) GetRootKeyId() string { + if x != nil { + return x.RootKeyId + } + return "" +} + +func (x *RootKeyVersion) GetKey() []byte { + if x != nil { + return x.Key + } + return nil +} + +func (x *RootKeyVersion) GetCtKey() []byte { + if x != nil { + return x.CtKey + } + return nil +} + +func (x *RootKeyVersion) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *RootKeyVersion) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +var File_controller_storage_kms_store_v1_root_key_proto protoreflect.FileDescriptor + +var file_controller_storage_kms_store_v1_root_key_proto_rawDesc = []byte{ + 0x0a, 0x2e, 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, 0x72, 0x6f, 0x6f, 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, 0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, + 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x90, + 0x01, 0x0a, 0x07, 0x52, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, + 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, + 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, + 0x70, 0x65, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x03, 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, 0xdf, 0x01, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 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, 0x01, 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, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, + 0x79, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x15, 0x0a, 0x06, 0x63, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x63, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 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, 0x07, 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_root_key_proto_rawDescOnce sync.Once + file_controller_storage_kms_store_v1_root_key_proto_rawDescData = file_controller_storage_kms_store_v1_root_key_proto_rawDesc +) + +func file_controller_storage_kms_store_v1_root_key_proto_rawDescGZIP() []byte { + file_controller_storage_kms_store_v1_root_key_proto_rawDescOnce.Do(func() { + file_controller_storage_kms_store_v1_root_key_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_kms_store_v1_root_key_proto_rawDescData) + }) + return file_controller_storage_kms_store_v1_root_key_proto_rawDescData +} + +var file_controller_storage_kms_store_v1_root_key_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_controller_storage_kms_store_v1_root_key_proto_goTypes = []interface{}{ + (*RootKey)(nil), // 0: controller.storage.kms.store.v1.RootKey + (*RootKeyVersion)(nil), // 1: controller.storage.kms.store.v1.RootKeyVersion + (*timestamp.Timestamp)(nil), // 2: controller.storage.timestamp.v1.Timestamp +} +var file_controller_storage_kms_store_v1_root_key_proto_depIdxs = []int32{ + 2, // 0: controller.storage.kms.store.v1.RootKey.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 2, // 1: controller.storage.kms.store.v1.RootKeyVersion.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_root_key_proto_init() } +func file_controller_storage_kms_store_v1_root_key_proto_init() { + if File_controller_storage_kms_store_v1_root_key_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_storage_kms_store_v1_root_key_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RootKey); 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_root_key_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RootKeyVersion); 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_root_key_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_storage_kms_store_v1_root_key_proto_goTypes, + DependencyIndexes: file_controller_storage_kms_store_v1_root_key_proto_depIdxs, + MessageInfos: file_controller_storage_kms_store_v1_root_key_proto_msgTypes, + }.Build() + File_controller_storage_kms_store_v1_root_key_proto = out.File + file_controller_storage_kms_store_v1_root_key_proto_rawDesc = nil + file_controller_storage_kms_store_v1_root_key_proto_goTypes = nil + file_controller_storage_kms_store_v1_root_key_proto_depIdxs = nil +} diff --git a/internal/kms/testing.go b/internal/kms/testing.go new file mode 100644 index 000000000..6710b286a --- /dev/null +++ b/internal/kms/testing.go @@ -0,0 +1,55 @@ +package kms + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/jinzhu/gorm" + "github.com/stretchr/testify/require" +) + +func TestRootKey(t *testing.T, conn *gorm.DB, scopeId string) *RootKey { + t.Helper() + require := require.New(t) + rw := db.New(conn) + require.NoError(conn.Where("scope_id = ?", scopeId).Delete(AllocRootKey()).Error) + k, err := NewRootKey(scopeId) + require.NoError(err) + id, err := newRootKeyId() + require.NoError(err) + k.PrivateId = id + err = rw.Create(context.Background(), k) + require.NoError(err) + return k +} + +func TestRootKeyVersion(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, rootId string, key []byte) *RootKeyVersion { + t.Helper() + require := require.New(t) + rw := db.New(conn) + k, err := NewRootKeyVersion(rootId, key) + require.NoError(err) + id, err := newRootKeyVersionId() + require.NoError(err) + k.PrivateId = id + err = k.Encrypt(context.Background(), wrapper) + require.NoError(err) + err = rw.Create(context.Background(), k) + require.NoError(err) + return k +} + +func TestKms(t *testing.T, conn *gorm.DB, rootWrapper wrapping.Wrapper) *Kms { + t.Helper() + require := require.New(t) + rw := db.New(conn) + kmsRepo, err := NewRepository(rw, rw) + require.NoError(err) + kms, err := NewKms(kmsRepo) + require.NoError(err) + err = kms.AddExternalWrappers(WithRootWrapper(rootWrapper)) + require.NoError(err) + return kms +} diff --git a/internal/kms/testing_test.go b/internal/kms/testing_test.go new file mode 100644 index 000000000..9bdc65926 --- /dev/null +++ b/internal/kms/testing_test.go @@ -0,0 +1,36 @@ +package kms_test + +import ( + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_TestRootKey(t *testing.T) { + t.Helper() + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(conn.Where("1=1").Delete(kms.AllocRootKey()).Error) + k := kms.TestRootKey(t, conn, org.PublicId) + require.NotNil(k) + assert.NotEmpty(k.PrivateId) +} + +func Test_TestRootKeyVersion(t *testing.T) { + t.Helper() + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(conn.Where("1=1").Delete(kms.AllocRootKey()).Error) + rk := kms.TestRootKey(t, conn, org.PublicId) + k := kms.TestRootKeyVersion(t, conn, wrapper, rk.PrivateId, []byte("test key")) + require.NotNil(k) + assert.NotEmpty(k.PrivateId) +} diff --git a/internal/proto/local/controller/storage/auth/password/store/v1/argon2.proto b/internal/proto/local/controller/storage/auth/password/store/v1/argon2.proto index df396eaa1..f523ab8aa 100644 --- a/internal/proto/local/controller/storage/auth/password/store/v1/argon2.proto +++ b/internal/proto/local/controller/storage/auth/password/store/v1/argon2.proto @@ -82,4 +82,10 @@ message Argon2Credential { // derivation function. // @inject_tag: `gorm:"not_null"` bytes derived_key = 9; + + // key_id is the key ID that was used for the encryption operation. It can be + // used to identify a specific version of the key needed to decrypt the value, + // which is useful for caching purposes. + // @inject_tag: `gorm:"not_null"` + string key_id = 10; } diff --git a/internal/proto/local/controller/storage/authtoken/store/v1/authtoken.proto b/internal/proto/local/controller/storage/authtoken/store/v1/authtoken.proto index 7eff8b056..a18f4287f 100644 --- a/internal/proto/local/controller/storage/authtoken/store/v1/authtoken.proto +++ b/internal/proto/local/controller/storage/authtoken/store/v1/authtoken.proto @@ -53,4 +53,10 @@ message AuthToken { // iam_user_id is not stored in the backing DB but it derived from the linked to auth account. // @inject_tag: gorm:"-" string iam_user_id = 13; + + // key_id is the key ID that was used for the encryption operation. It can be + // used to identify a specific version of the key needed to decrypt the value, + // which is useful for caching purposes. + // @inject_tag: `gorm:"not_null"` + string key_id = 14; } \ No newline at end of file diff --git a/internal/proto/local/controller/storage/kms/store/v1/root_key.proto b/internal/proto/local/controller/storage/kms/store/v1/root_key.proto new file mode 100644 index 000000000..856b20bcc --- /dev/null +++ b/internal/proto/local/controller/storage/kms/store/v1/root_key.proto @@ -0,0 +1,49 @@ +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"; +import "controller/custom_options/v1/options.proto"; + +message RootKey { + // private_id is used to access the root key via an API + // @inject_tag: gorm:"primary_key" + string private_id = 1; + + // scope id for the root key + // @inject_tag: `gorm:"default:null"` + string scope_id = 2; + + // create_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 3; +} + +message RootKeyVersion { + // private_id is used to access the root key version via an API + // @inject_tag: gorm:"primary_key" + string private_id = 1; + + // scope id for the root key version + // @inject_tag: `gorm:"default:null"` + string root_key_id = 2; + + // 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 = 3; + + // ciphertext key data stored in the database + // @inject_tag: `gorm:"column:key;not_null" wrapping:"ct,key_data"` + bytes ct_key = 4; + + // 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 = 5; + + // create_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 7; +} \ No newline at end of file diff --git a/internal/servers/controller/controller.go b/internal/servers/controller/controller.go index 932806e70..318727e1f 100644 --- a/internal/servers/controller/controller.go +++ b/internal/servers/controller/controller.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/servers" "github.com/hashicorp/boundary/internal/servers/controller/common" "github.com/hashicorp/go-hclog" @@ -41,6 +42,8 @@ type Controller struct { ServersRepoFn common.ServersRepoFactory PasswordAuthRepoFn common.PasswordAuthRepoFactory + kms *kms.Kms + clusterAddress string } @@ -85,20 +88,34 @@ func New(conf *Config) (*Controller, error) { // Set up repo stuff dbase := db.New(c.conf.Database) + kmsRepo, err := kms.NewRepository(dbase, dbase) + if err != nil { + return nil, fmt.Errorf("error creating kms repository: %w", err) + } + c.kms, err = kms.NewKms(kmsRepo, kms.WithLogger(c.logger.Named("kms"))) + if err != nil { + return nil, fmt.Errorf("error creating kms cache: %w", err) + } + if err := c.kms.AddExternalWrappers( + kms.WithRootWrapper(c.conf.RootKms), + kms.WithWorkerAuthWrapper(c.conf.WorkerAuthKms), + ); err != nil { + return nil, fmt.Errorf("error adding config keys to kms: %w", err) + } c.IamRepoFn = func() (*iam.Repository, error) { - return iam.NewRepository(dbase, dbase, c.conf.ControllerKMS) + return iam.NewRepository(dbase, dbase, c.kms, iam.WithRandomReader(c.conf.SecureRandomReader)) } c.StaticHostRepoFn = func() (*static.Repository, error) { - return static.NewRepository(dbase, dbase, c.conf.ControllerKMS) + return static.NewRepository(dbase, dbase, c.kms) } c.AuthTokenRepoFn = func() (*authtoken.Repository, error) { - return authtoken.NewRepository(dbase, dbase, c.conf.ControllerKMS) + return authtoken.NewRepository(dbase, dbase, c.kms) } c.ServersRepoFn = func() (*servers.Repository, error) { - return servers.NewRepository(c.logger.Named("servers.repository"), dbase, dbase, c.conf.ControllerKMS) + return servers.NewRepository(c.logger.Named("servers.repository"), dbase, dbase, c.kms) } c.PasswordAuthRepoFn = func() (*password.Repository, error) { - return password.NewRepository(dbase, dbase, c.conf.ControllerKMS) + return password.NewRepository(dbase, dbase, c.kms) } c.workerAuthCache = cache.New(0, 0) diff --git a/internal/servers/controller/cors_test.go b/internal/servers/controller/cors_test.go index fe7f9cc6d..2aeb492db 100644 --- a/internal/servers/controller/cors_test.go +++ b/internal/servers/controller/cors_test.go @@ -25,13 +25,15 @@ telemetry { kms "aead" { purpose = "root" aead_type = "aes-gcm" - key = "09iqFxRJNYsl/b8CQxjnGw==" + key = "09iqFxRJNYsl/b8CQxjnGw==" + key_id = "global_root" } kms "aead" { purpose = "worker-auth" aead_type = "aes-gcm" - key = "09iqFxRJNYsl/b8CQxjnGw==" + key = "09iqFxRJNYsl/b8CQxjnGw==" + key_id = "global_worker-auth" } listener "tcp" { @@ -74,7 +76,9 @@ func TestHandler_CORS(t *testing.T) { }) defer tc.Shutdown() - org := iam.TestOrg(t, tc.DbConn()) + repo := tc.IamRepo() + require.NoError(t, err) + org := iam.TestOrg(t, repo) cases := []struct { name string diff --git a/internal/servers/controller/handler_test.go b/internal/servers/controller/handler_test.go index adf9eda3e..387d16a2a 100644 --- a/internal/servers/controller/handler_test.go +++ b/internal/servers/controller/handler_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/types/scope" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -21,9 +21,7 @@ func TestAuthenticationHandler(t *testing.T) { }) defer c.Shutdown() - org := iam.TestOrg(t, c.DbConn()) - - resp, err := http.Post(fmt.Sprintf("%s/v1/scopes/%s/auth-methods/paum_1234567890:authenticate", c.ApiAddrs()[0], org.GetPublicId()), "application/json", + resp, err := http.Post(fmt.Sprintf("%s/v1/scopes/%s/auth-methods/paum_1234567890:authenticate", c.ApiAddrs()[0], scope.Global.String()), "application/json", strings.NewReader(`{"token_type": null, "credentials": {"login_name":"admin", "password": "password123"}}`)) require.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode, "Got response: %v", resp) diff --git a/internal/servers/controller/handlers/accounts/account_service.go b/internal/servers/controller/handlers/accounts/account_service.go index 146849fb3..b139f74bc 100644 --- a/internal/servers/controller/handlers/accounts/account_service.go +++ b/internal/servers/controller/handlers/accounts/account_service.go @@ -93,7 +93,7 @@ func (s Service) CreateAccount(ctx context.Context, req *pbs.CreateAccountReques if err := validateCreateRequest(req); err != nil { return nil, err } - u, err := s.createInRepo(ctx, req.GetAuthMethodId(), req.GetItem()) + u, err := s.createInRepo(ctx, req.GetAuthMethodId(), authResults.Scope.GetId(), req.GetItem()) if err != nil { return nil, err } @@ -110,7 +110,7 @@ func (s Service) UpdateAccount(ctx context.Context, req *pbs.UpdateAccountReques if err := validateUpdateRequest(req); err != nil { return nil, err } - u, err := s.updateInRepo(ctx, req.GetAuthMethodId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem()) + u, err := s.updateInRepo(ctx, req.GetAuthMethodId(), authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem()) if err != nil { return nil, err } @@ -127,7 +127,7 @@ func (s Service) DeleteAccount(ctx context.Context, req *pbs.DeleteAccountReques if err := validateDeleteRequest(req); err != nil { return nil, err } - existed, err := s.deleteFromRepo(ctx, req.GetId()) + existed, err := s.deleteFromRepo(ctx, authResults.Scope.GetId(), req.GetId()) if err != nil { return nil, err } @@ -143,7 +143,7 @@ func (s Service) ChangePassword(ctx context.Context, req *pbs.ChangePasswordRequ if err := validateChangePasswordRequest(req); err != nil { return nil, err } - u, err := s.changePasswordInRepo(ctx, req.GetId(), req.GetVersion(), req.GetOldPassword(), req.GetNewPassword()) + u, err := s.changePasswordInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetVersion(), req.GetOldPassword(), req.GetNewPassword()) if err != nil { return nil, err } @@ -160,7 +160,7 @@ func (s Service) SetPassword(ctx context.Context, req *pbs.SetPasswordRequest) ( if err := validateSetPasswordRequest(req); err != nil { return nil, err } - u, err := s.setPasswordInRepo(ctx, req.GetId(), req.GetVersion(), req.GetPassword()) + u, err := s.setPasswordInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetVersion(), req.GetPassword()) if err != nil { return nil, err } @@ -186,7 +186,7 @@ func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Account, error return toProto(u) } -func (s Service) createInRepo(ctx context.Context, authMethodId string, item *pb.Account) (*pb.Account, error) { +func (s Service) createInRepo(ctx context.Context, authMethodId, scopeId string, item *pb.Account) (*pb.Account, error) { pwAttrs := &pb.PasswordAccountAttributes{} if err := handlers.StructToProto(item.GetAttributes(), pwAttrs); err != nil { return nil, status.Errorf(codes.InvalidArgument, "Provided attributes don't match expected format.") @@ -211,7 +211,7 @@ func (s Service) createInRepo(ctx context.Context, authMethodId string, item *pb if pwAttrs.GetPassword() != nil { createOpts = append(createOpts, password.WithPassword(pwAttrs.GetPassword().GetValue())) } - out, err := repo.CreateAccount(ctx, a, createOpts...) + out, err := repo.CreateAccount(ctx, scopeId, a, createOpts...) if err != nil { return nil, status.Errorf(codes.Internal, "Unable to create user: %v.", err) } @@ -221,7 +221,7 @@ func (s Service) createInRepo(ctx context.Context, authMethodId string, item *pb return toProto(out) } -func (s Service) updateInRepo(ctx context.Context, authMethodId, id string, mask []string, item *pb.Account) (*pb.Account, error) { +func (s Service) updateInRepo(ctx context.Context, authMethodId, scopeId, id string, mask []string, item *pb.Account) (*pb.Account, error) { var opts []password.Option if desc := item.GetDescription(); desc != nil { opts = append(opts, password.WithDescription(desc.GetValue())) @@ -252,7 +252,7 @@ func (s Service) updateInRepo(ctx context.Context, authMethodId, id string, mask if err != nil { return nil, err } - out, rowsUpdated, err := repo.UpdateAccount(ctx, u, version, dbMask) + out, rowsUpdated, err := repo.UpdateAccount(ctx, scopeId, u, version, dbMask) if err != nil { return nil, status.Errorf(codes.Internal, "Unable to update auth method: %v.", err) } @@ -262,12 +262,12 @@ func (s Service) updateInRepo(ctx context.Context, authMethodId, id string, mask return toProto(out) } -func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) { +func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) { repo, err := s.repoFn() if err != nil { return false, err } - rows, err := repo.DeleteAccount(ctx, id) + rows, err := repo.DeleteAccount(ctx, scopeId, id) if err != nil { if errors.Is(err, db.ErrRecordNotFound) { return false, nil @@ -297,12 +297,12 @@ func (s Service) listFromRepo(ctx context.Context, authMethodId string) ([]*pb.A return outUl, nil } -func (s Service) changePasswordInRepo(ctx context.Context, id string, version uint32, oldPassword, newPassword string) (*pb.Account, error) { +func (s Service) changePasswordInRepo(ctx context.Context, scopeId, id string, version uint32, oldPassword, newPassword string) (*pb.Account, error) { repo, err := s.repoFn() if err != nil { return nil, err } - out, err := repo.ChangePassword(ctx, id, oldPassword, newPassword, version) + out, err := repo.ChangePassword(ctx, scopeId, id, oldPassword, newPassword, version) if err != nil { return nil, status.Errorf(codes.Internal, "Unable to change password: %v.", err) } @@ -312,12 +312,12 @@ func (s Service) changePasswordInRepo(ctx context.Context, id string, version ui return toProto(out) } -func (s Service) setPasswordInRepo(ctx context.Context, id string, version uint32, password string) (*pb.Account, error) { +func (s Service) setPasswordInRepo(ctx context.Context, scopeId, id string, version uint32, password string) (*pb.Account, error) { repo, err := s.repoFn() if err != nil { return nil, err } - out, err := repo.SetPassword(ctx, id, password, version) + out, err := repo.SetPassword(ctx, scopeId, id, password, version) if err != nil { return nil, status.Errorf(codes.Internal, "Unable to set password: %v.", err) } diff --git a/internal/servers/controller/handlers/accounts/account_service_test.go b/internal/servers/controller/handlers/accounts/account_service_test.go index 7e4cf38f1..09d5d6376 100644 --- a/internal/servers/controller/handlers/accounts/account_service_test.go +++ b/internal/servers/controller/handlers/accounts/account_service_test.go @@ -15,6 +15,7 @@ import ( scopepb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/servers/controller/handlers" "github.com/hashicorp/boundary/internal/servers/controller/handlers/accounts" "github.com/hashicorp/boundary/internal/types/scope" @@ -32,14 +33,15 @@ func TestGet(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrap := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrap) repoFn := func() (*password.Repository, error) { - return password.NewRepository(rw, rw, wrap) + return password.NewRepository(rw, rw, kms) } s, err := accounts.NewService(repoFn) require.NoError(t, err, "Couldn't create new auth token service.") - org, _ := iam.TestScopes(t, conn) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) am := password.TestAuthMethods(t, conn, org.GetPublicId(), 1)[0] aa := password.TestAccounts(t, conn, am.GetPublicId(), 1)[0] @@ -99,11 +101,12 @@ func TestList(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrap := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrap) repoFn := func() (*password.Repository, error) { - return password.NewRepository(rw, rw, wrap) + return password.NewRepository(rw, rw, kms) } - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) ams := password.TestAuthMethods(t, conn, o.GetPublicId(), 3) amNoAccounts, amSomeAccounts, amOtherAccounts := ams[0], ams[1], ams[2] @@ -183,11 +186,12 @@ func TestDelete(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrap := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrap) repoFn := func() (*password.Repository, error) { - return password.NewRepository(rw, rw, wrap) + return password.NewRepository(rw, rw, kms) } - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) ams := password.TestAuthMethods(t, conn, o.GetPublicId(), 2) am1, wrongAm := ams[0], ams[1] @@ -262,13 +266,14 @@ func TestDelete(t *testing.T) { func TestDelete_twice(t *testing.T) { assert := assert.New(t) conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + rw := db.New(conn) + kms := kms.TestKms(t, conn, wrap) repoFn := func() (*password.Repository, error) { - return password.NewRepository(rw, rw, wrap) + return password.NewRepository(rw, rw, kms) } - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] ac := password.TestAccounts(t, conn, am.GetPublicId(), 1)[0] @@ -290,14 +295,15 @@ func TestCreate(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrap := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrap) repoFn := func() (*password.Repository, error) { - return password.NewRepository(rw, rw, wrap) + return password.NewRepository(rw, rw, kms) } s, err := accounts.NewService(repoFn) require.NoError(t, err, "Error when getting new account service.") - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] defaultAccount := password.TestAccounts(t, conn, am.GetPublicId(), 1)[0] defaultCreated, err := ptypes.Timestamp(defaultAccount.GetCreateTime().GetTimestamp()) @@ -494,11 +500,12 @@ func TestUpdate(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrap := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrap) repoFn := func() (*password.Repository, error) { - return password.NewRepository(rw, rw, wrap) + return password.NewRepository(rw, rw, kms) } - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] tested, err := accounts.NewService(repoFn) require.NoError(t, err, "Error when getting new auth_method service.") @@ -849,11 +856,12 @@ func TestSetPassword(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrap := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrap) repoFn := func() (*password.Repository, error) { - return password.NewRepository(rw, rw, wrap) + return password.NewRepository(rw, rw, kms) } - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) tested, err := accounts.NewService(repoFn) require.NoError(t, err, "Error when getting new auth_method service.") @@ -973,11 +981,12 @@ func TestChangePassword(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrap := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrap) repoFn := func() (*password.Repository, error) { - return password.NewRepository(rw, rw, wrap) + return password.NewRepository(rw, rw, kms) } - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) tested, err := accounts.NewService(repoFn) require.NoError(t, err, "Error when getting new auth_method service.") diff --git a/internal/servers/controller/handlers/authenticate/authenticate_service.go b/internal/servers/controller/handlers/authenticate/authenticate_service.go index 8af2d86ca..cdacb5893 100644 --- a/internal/servers/controller/handlers/authenticate/authenticate_service.go +++ b/internal/servers/controller/handlers/authenticate/authenticate_service.go @@ -61,7 +61,7 @@ func (s Service) Authenticate(ctx context.Context, req *pbs.AuthenticateRequest) return nil, err } creds := req.GetCredentials().GetFields() - tok, err := s.authenticateWithRepo(ctx, req.GetAuthMethodId(), creds[loginNameKey].GetStringValue(), creds[pwKey].GetStringValue()) + tok, err := s.authenticateWithRepo(ctx, authResults.Scope.GetId(), req.GetAuthMethodId(), creds[loginNameKey].GetStringValue(), creds[pwKey].GetStringValue()) if err != nil { return nil, err } @@ -73,7 +73,7 @@ func (s Service) Deauthenticate(ctx context.Context, req *pbs.DeauthenticateRequ return nil, status.Error(codes.Unimplemented, "Requested method is unimplemented.") } -func (s Service) authenticateWithRepo(ctx context.Context, authMethodId, loginName, pw string) (*pba.AuthToken, error) { +func (s Service) authenticateWithRepo(ctx context.Context, scopeId, authMethodId, loginName, pw string) (*pba.AuthToken, error) { iamRepo, err := s.iamRepo() if err != nil { return nil, err @@ -87,7 +87,7 @@ func (s Service) authenticateWithRepo(ctx context.Context, authMethodId, loginNa return nil, err } - acct, err := pwRepo.Authenticate(ctx, authMethodId, loginName, pw) + acct, err := pwRepo.Authenticate(ctx, scopeId, authMethodId, loginName, pw) if err != nil { return nil, err } @@ -99,7 +99,7 @@ func (s Service) authenticateWithRepo(ctx context.Context, authMethodId, loginNa if err != nil { return nil, err } - tok, err := atRepo.CreateAuthToken(ctx, u.GetPublicId(), acct.GetPublicId()) + tok, err := atRepo.CreateAuthToken(ctx, u, acct.GetPublicId()) if err != nil { return nil, err } diff --git a/internal/servers/controller/handlers/authenticate/authenticate_service_test.go b/internal/servers/controller/handlers/authenticate/authenticate_service_test.go index 290da7a79..532930695 100644 --- a/internal/servers/controller/handlers/authenticate/authenticate_service_test.go +++ b/internal/servers/controller/handlers/authenticate/authenticate_service_test.go @@ -13,6 +13,7 @@ import ( pba "github.com/hashicorp/boundary/internal/gen/controller/api/resources/authtokens" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" "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/grpc/codes" @@ -28,11 +29,12 @@ func TestAuthenticate(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - o, _ := iam.TestScopes(t, conn) + kms := kms.TestKms(t, conn, wrapper) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) - authTokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(rw, rw, wrapper) } - iamRepoFn := func() (*iam.Repository, error) { return iam.NewRepository(rw, rw, wrapper) } - passwordRepoFn := func() (*password.Repository, error) { return password.NewRepository(rw, rw, wrapper) } + authTokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(rw, rw, kms) } + iamRepoFn := func() (*iam.Repository, error) { return iam.NewRepository(rw, rw, kms) } + passwordRepoFn := func() (*password.Repository, error) { return password.NewRepository(rw, rw, kms) } am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] acct, err := password.NewAccount(am.GetPublicId(), password.WithLoginName(testLoginName)) @@ -40,7 +42,7 @@ func TestAuthenticate(t *testing.T) { pwRepo, err := passwordRepoFn() require.NoError(t, err) - acct, err = pwRepo.CreateAccount(context.Background(), acct, password.WithPassword(testPassword)) + acct, err = pwRepo.CreateAccount(context.Background(), o.GetPublicId(), acct, password.WithPassword(testPassword)) require.NoError(t, err) cases := []struct { @@ -171,11 +173,12 @@ func TestAuthenticate_AuthAccountConnectedToIamUser(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - o, _ := iam.TestScopes(t, conn) + kms := kms.TestKms(t, conn, wrapper) + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) - passwordRepoFn := func() (*password.Repository, error) { return password.NewRepository(rw, rw, wrapper) } - authTokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(rw, rw, wrapper) } - iamRepoFn := func() (*iam.Repository, error) { return iam.NewRepository(rw, rw, wrapper) } + passwordRepoFn := func() (*password.Repository, error) { return password.NewRepository(rw, rw, kms) } + authTokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(rw, rw, kms) } + iamRepoFn := func() (*iam.Repository, error) { return iam.NewRepository(rw, rw, kms) } am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] acct, err := password.NewAccount(am.GetPublicId(), password.WithLoginName(testLoginName)) @@ -183,7 +186,7 @@ func TestAuthenticate_AuthAccountConnectedToIamUser(t *testing.T) { pwRepo, err := passwordRepoFn() require.NoError(err) - acct, err = pwRepo.CreateAccount(context.Background(), acct, password.WithPassword(testPassword)) + acct, err = pwRepo.CreateAccount(context.Background(), o.GetPublicId(), acct, password.WithPassword(testPassword)) require.NoError(err) // connected to an account. diff --git a/internal/servers/controller/handlers/authmethods/authmethod_service.go b/internal/servers/controller/handlers/authmethods/authmethod_service.go index 4e7f81c4a..aa587c3e6 100644 --- a/internal/servers/controller/handlers/authmethods/authmethod_service.go +++ b/internal/servers/controller/handlers/authmethods/authmethod_service.go @@ -125,7 +125,7 @@ func (s Service) DeleteAuthMethod(ctx context.Context, req *pbs.DeleteAuthMethod if err := validateDeleteRequest(req); err != nil { return nil, err } - existed, err := s.deleteFromRepo(ctx, req.GetId()) + existed, err := s.deleteFromRepo(ctx, authResults.Scope.GetId(), req.GetId()) if err != nil { return nil, err } @@ -240,12 +240,12 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st return toProto(out) } -func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) { +func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) { repo, err := s.repoFn() if err != nil { return false, err } - rows, err := repo.DeleteAuthMethod(ctx, id) + rows, err := repo.DeleteAuthMethod(ctx, scopeId, id) if err != nil { if errors.Is(err, db.ErrRecordNotFound) { return false, nil diff --git a/internal/servers/controller/handlers/authmethods/authmethod_service_test.go b/internal/servers/controller/handlers/authmethods/authmethod_service_test.go index ffb367065..5cae88144 100644 --- a/internal/servers/controller/handlers/authmethods/authmethod_service_test.go +++ b/internal/servers/controller/handlers/authmethods/authmethod_service_test.go @@ -14,6 +14,7 @@ import ( scopepb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/servers/controller/handlers/authmethods" "github.com/hashicorp/boundary/internal/types/scope" "google.golang.org/genproto/protobuf/field_mask" @@ -30,12 +31,14 @@ import ( func TestGet(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) - wrap := db.TestWrapper(t) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) repoFn := func() (*password.Repository, error) { - return password.NewRepository(rw, rw, wrap) + return password.NewRepository(rw, rw, kms) } + iamRepo := iam.TestRepo(t, conn, wrapper) - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iamRepo) am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] wantU := &pb.AuthMethod{ @@ -107,14 +110,16 @@ func TestGet(t *testing.T) { func TestList(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) - wrap := db.TestWrapper(t) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) repoFn := func() (*password.Repository, error) { - return password.NewRepository(rw, rw, wrap) + return password.NewRepository(rw, rw, kms) } + iamRepo := iam.TestRepo(t, conn, wrapper) - oNoAuthMethods, _ := iam.TestScopes(t, conn) - oWithAuthMethods, _ := iam.TestScopes(t, conn) - oWithOtherAuthMethods, _ := iam.TestScopes(t, conn) + oNoAuthMethods, _ := iam.TestScopes(t, iamRepo) + oWithAuthMethods, _ := iam.TestScopes(t, iamRepo) + oWithOtherAuthMethods, _ := iam.TestScopes(t, iamRepo) var wantSomeAuthMethods []*pb.AuthMethod for _, am := range password.TestAuthMethods(t, conn, oWithAuthMethods.GetPublicId(), 3) { @@ -196,12 +201,14 @@ func TestList(t *testing.T) { func TestDelete(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) - wrap := db.TestWrapper(t) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) repoFn := func() (*password.Repository, error) { - return password.NewRepository(rw, rw, wrap) + return password.NewRepository(rw, rw, kms) } + iamRepo := iam.TestRepo(t, conn, wrapper) - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iamRepo) am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] s, err := authmethods.NewService(repoFn) @@ -256,12 +263,14 @@ func TestDelete_twice(t *testing.T) { assert, require := assert.New(t), require.New(t) conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) - wrap := db.TestWrapper(t) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) repoFn := func() (*password.Repository, error) { - return password.NewRepository(rw, rw, wrap) + return password.NewRepository(rw, rw, kms) } + iamRepo := iam.TestRepo(t, conn, wrapper) - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iamRepo) am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] s, err := authmethods.NewService(repoFn) @@ -281,12 +290,14 @@ func TestDelete_twice(t *testing.T) { func TestCreate(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) - wrap := db.TestWrapper(t) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) repoFn := func() (*password.Repository, error) { - return password.NewRepository(rw, rw, wrap) + return password.NewRepository(rw, rw, kms) } + iamRepo := iam.TestRepo(t, conn, wrapper) - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iamRepo) defaultAm := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] defaultCreated, err := ptypes.Timestamp(defaultAm.GetCreateTime().GetTimestamp()) require.NoError(t, err, "Error converting proto to timestamp.") @@ -418,12 +429,14 @@ func TestCreate(t *testing.T) { func TestUpdate(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) - wrap := db.TestWrapper(t) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) repoFn := func() (*password.Repository, error) { - return password.NewRepository(rw, rw, wrap) + return password.NewRepository(rw, rw, kms) } + iamRepo := iam.TestRepo(t, conn, wrapper) - o, _ := iam.TestScopes(t, conn) + o, _ := iam.TestScopes(t, iamRepo) tested, err := authmethods.NewService(repoFn) require.NoError(t, err, "Error when getting new auth_method service.") diff --git a/internal/servers/controller/handlers/authtokens/authtoken_service_test.go b/internal/servers/controller/handlers/authtokens/authtoken_service_test.go index 6fc37b799..ee4251cf3 100644 --- a/internal/servers/controller/handlers/authtokens/authtoken_service_test.go +++ b/internal/servers/controller/handlers/authtokens/authtoken_service_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/servers/controller/handlers/authtokens" "github.com/hashicorp/boundary/internal/types/scope" "google.golang.org/grpc/codes" @@ -25,15 +26,16 @@ func TestGet(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrap := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrap) repoFn := func() (*authtoken.Repository, error) { - return authtoken.NewRepository(rw, rw, wrap) + return authtoken.NewRepository(rw, rw, kms) } s, err := authtokens.NewService(repoFn) require.NoError(t, err, "Couldn't create new auth token service.") - org, _ := iam.TestScopes(t, conn) - at := authtoken.TestAuthToken(t, conn, wrap, org.GetPublicId()) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) + at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) wireAuthToken := pb.AuthToken{ Id: at.GetPublicId(), @@ -91,16 +93,18 @@ func TestList(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrap := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrap) repoFn := func() (*authtoken.Repository, error) { - return authtoken.NewRepository(rw, rw, wrap) + return authtoken.NewRepository(rw, rw, kms) } + iamRepo := iam.TestRepo(t, conn, wrap) - orgNoTokens, _ := iam.TestScopes(t, conn) + orgNoTokens, _ := iam.TestScopes(t, iamRepo) - orgWithSomeTokens, _ := iam.TestScopes(t, conn) + orgWithSomeTokens, _ := iam.TestScopes(t, iamRepo) var wantSomeTokens []*pb.AuthToken for i := 0; i < 3; i++ { - at := authtoken.TestAuthToken(t, conn, wrap, orgWithSomeTokens.GetPublicId()) + at := authtoken.TestAuthToken(t, conn, kms, orgWithSomeTokens.GetPublicId()) wantSomeTokens = append(wantSomeTokens, &pb.AuthToken{ Id: at.GetPublicId(), UserId: at.GetIamUserId(), @@ -113,10 +117,10 @@ func TestList(t *testing.T) { }) } - orgWithOtherTokens, _ := iam.TestScopes(t, conn) + orgWithOtherTokens, _ := iam.TestScopes(t, iamRepo) var wantOtherTokens []*pb.AuthToken for i := 0; i < 3; i++ { - at := authtoken.TestAuthToken(t, conn, wrap, orgWithOtherTokens.GetPublicId()) + at := authtoken.TestAuthToken(t, conn, kms, orgWithOtherTokens.GetPublicId()) wantOtherTokens = append(wantOtherTokens, &pb.AuthToken{ Id: at.GetPublicId(), UserId: at.GetIamUserId(), @@ -177,14 +181,16 @@ func TestDelete(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrap := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrap) repoFn := func() (*authtoken.Repository, error) { - return authtoken.NewRepository(rw, rw, wrap) + return authtoken.NewRepository(rw, rw, kms) } + iamRepo := iam.TestRepo(t, conn, wrap) - wrongOrg, _ := iam.TestScopes(t, conn) - org, _ := iam.TestScopes(t, conn) - at := authtoken.TestAuthToken(t, conn, wrap, org.GetPublicId()) - atForWrongOrg := authtoken.TestAuthToken(t, conn, wrap, org.GetPublicId()) + wrongOrg, _ := iam.TestScopes(t, iamRepo) + org, _ := iam.TestScopes(t, iamRepo) + at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) + atForWrongOrg := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) s, err := authtokens.NewService(repoFn) require.NoError(t, err, "Error when getting new user service.") @@ -256,12 +262,14 @@ func TestDelete_twice(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrap := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrap) repoFn := func() (*authtoken.Repository, error) { - return authtoken.NewRepository(rw, rw, wrap) + return authtoken.NewRepository(rw, rw, kms) } + iamRepo := iam.TestRepo(t, conn, wrap) - org, _ := iam.TestScopes(t, conn) - at := authtoken.TestAuthToken(t, conn, wrap, org.GetPublicId()) + org, _ := iam.TestScopes(t, iamRepo) + at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) s, err := authtokens.NewService(repoFn) require.NoError(t, err, "Error when getting new user service") diff --git a/internal/servers/controller/handlers/groups/group_service_test.go b/internal/servers/controller/handlers/groups/group_service_test.go index 16d6e7fc2..700e69840 100644 --- a/internal/servers/controller/handlers/groups/group_service_test.go +++ b/internal/servers/controller/handlers/groups/group_service_test.go @@ -31,13 +31,13 @@ import ( func createDefaultGroupsAndRepo(t *testing.T) (*iam.Group, *iam.Group, func() (*iam.Repository, error)) { t.Helper() conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } - o, p := iam.TestScopes(t, conn) + o, p := iam.TestScopes(t, iamRepo) og := iam.TestGroup(t, conn, o.GetPublicId(), iam.WithDescription("default"), iam.WithName("default")) pg := iam.TestGroup(t, conn, p.GetPublicId(), iam.WithDescription("default"), iam.WithName("default")) return og, pg, repoFn @@ -69,14 +69,14 @@ func equalMembers(g *pb.Group, members []string) bool { func TestGet(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } - o, p := iam.TestScopes(t, conn) - u := iam.TestUser(t, conn, o.GetPublicId()) + o, p := iam.TestScopes(t, iamRepo) + u := iam.TestUser(t, iamRepo, o.GetPublicId()) og := iam.TestGroup(t, conn, o.GetPublicId(), iam.WithDescription("default"), iam.WithName("default")) _ = iam.TestGroupMember(t, conn, og.GetPublicId(), u.GetPublicId()) @@ -201,13 +201,13 @@ func TestGet(t *testing.T) { func TestList(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } - oNoGroups, pWithGroups := iam.TestScopes(t, conn) - oWithGroups, pNoGroups := iam.TestScopes(t, conn) + oNoGroups, pWithGroups := iam.TestScopes(t, iamRepo) + oWithGroups, pNoGroups := iam.TestScopes(t, iamRepo) var wantOrgGroups []*pb.Group var wantProjGroups []*pb.Group for i := 0; i < 10; i++ { @@ -517,14 +517,14 @@ func TestCreate(t *testing.T) { func TestUpdate(t *testing.T) { require := require.New(t) conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } - o, p := iam.TestScopes(t, conn) - u := iam.TestUser(t, conn, o.GetPublicId()) + o, p := iam.TestScopes(t, iamRepo) + u := iam.TestUser(t, iamRepo, o.GetPublicId()) og := iam.TestGroup(t, conn, o.GetPublicId(), iam.WithDescription("default"), iam.WithName("default")) _ = iam.TestGroupMember(t, conn, og.GetPublicId(), u.GetPublicId()) @@ -915,19 +915,19 @@ func TestUpdate(t *testing.T) { func TestAddMember(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } s, err := groups.NewService(repoFn) require.NoError(t, err, "Error when getting new group service.") - o, p := iam.TestScopes(t, conn) + o, p := iam.TestScopes(t, iamRepo) users := []*iam.User{ - iam.TestUser(t, conn, o.GetPublicId()), - iam.TestUser(t, conn, o.GetPublicId()), - iam.TestUser(t, conn, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), } addCases := []struct { @@ -1015,19 +1015,19 @@ func TestAddMember(t *testing.T) { func TestSetMember(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } s, err := groups.NewService(repoFn) require.NoError(t, err, "Error when getting new group service.") - o, p := iam.TestScopes(t, conn) + o, p := iam.TestScopes(t, iamRepo) users := []*iam.User{ - iam.TestUser(t, conn, o.GetPublicId()), - iam.TestUser(t, conn, o.GetPublicId()), - iam.TestUser(t, conn, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), } setCases := []struct { @@ -1116,19 +1116,19 @@ func TestSetMember(t *testing.T) { func TestRemoveMember(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } s, err := groups.NewService(repoFn) require.NoError(t, err, "Error when getting new grp service.") - o, p := iam.TestScopes(t, conn) + o, p := iam.TestScopes(t, iamRepo) users := []*iam.User{ - iam.TestUser(t, conn, o.GetPublicId()), - iam.TestUser(t, conn, o.GetPublicId()), - iam.TestUser(t, conn, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), } addCases := []struct { diff --git a/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go b/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go index 667aefc76..6505707c7 100644 --- a/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go +++ b/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go @@ -16,6 +16,7 @@ import ( pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/servers/controller/handlers/host_catalogs" "github.com/hashicorp/boundary/internal/types/scope" "google.golang.org/genproto/protobuf/field_mask" @@ -32,12 +33,14 @@ func createDefaultHostCatalogAndRepo(t *testing.T) (*static.HostCatalog, *iam.Sc t.Helper() require := require.New(t) conn, _ := db.TestSetup(t, "postgres") - _, pRes := iam.TestScopes(t, conn) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + _, pRes := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) - wrap := db.TestWrapper(t) rw := db.New(conn) repoFn := func() (*static.Repository, error) { - return static.NewRepository(rw, rw, wrap) + return static.NewRepository(rw, rw, kms) } hc, err := static.NewHostCatalog(pRes.GetPublicId(), static.WithName("default"), static.WithDescription("default")) diff --git a/internal/servers/controller/handlers/roles/role_service_test.go b/internal/servers/controller/handlers/roles/role_service_test.go index 7c1c3056a..19507a902 100644 --- a/internal/servers/controller/handlers/roles/role_service_test.go +++ b/internal/servers/controller/handlers/roles/role_service_test.go @@ -31,13 +31,13 @@ import ( func createDefaultRolesAndRepo(t *testing.T) (*iam.Role, *iam.Role, func() (*iam.Repository, error)) { t.Helper() conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } - o, p := iam.TestScopes(t, conn) + o, p := iam.TestScopes(t, iamRepo) or := iam.TestRole(t, conn, o.GetPublicId(), iam.WithDescription("default"), iam.WithName("default"), iam.WithGrantScopeId(p.GetPublicId())) pr := iam.TestRole(t, conn, p.GetPublicId(), iam.WithDescription("default"), iam.WithName("default")) return or, pr, repoFn @@ -176,13 +176,13 @@ func TestGet(t *testing.T) { func TestList(t *testing.T) { assert, require := assert.New(t), require.New(t) conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } - oNoRoles, pWithRoles := iam.TestScopes(t, conn) - oWithRoles, pNoRoles := iam.TestScopes(t, conn) + oNoRoles, pWithRoles := iam.TestScopes(t, iamRepo) + oWithRoles, pNoRoles := iam.TestScopes(t, iamRepo) var wantOrgRoles []*pb.Role var wantProjRoles []*pb.Role for i := 0; i < 10; i++ { @@ -507,14 +507,14 @@ func TestUpdate(t *testing.T) { }, } conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } - o, p := iam.TestScopes(t, conn) - u := iam.TestUser(t, conn, o.GetPublicId()) + o, p := iam.TestScopes(t, iamRepo) + u := iam.TestUser(t, iamRepo, o.GetPublicId()) or := iam.TestRole(t, conn, o.GetPublicId(), iam.WithDescription("default"), iam.WithName("default"), iam.WithGrantScopeId(p.GetPublicId())) _ = iam.TestRoleGrant(t, conn, or.GetPublicId(), grantString) @@ -927,19 +927,19 @@ func TestUpdate(t *testing.T) { func TestAddPrincipal(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } s, err := roles.NewService(repoFn) require.NoError(t, err, "Error when getting new role service.") - o, p := iam.TestScopes(t, conn) + o, p := iam.TestScopes(t, iamRepo) users := []*iam.User{ - iam.TestUser(t, conn, o.GetPublicId()), - iam.TestUser(t, conn, o.GetPublicId()), - iam.TestUser(t, conn, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), } groups := []*iam.Group{ iam.TestGroup(t, conn, o.GetPublicId()), @@ -1046,19 +1046,19 @@ func TestAddPrincipal(t *testing.T) { func TestSetPrincipal(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } s, err := roles.NewService(repoFn) require.NoError(t, err, "Error when getting new role service.") - o, p := iam.TestScopes(t, conn) + o, p := iam.TestScopes(t, iamRepo) users := []*iam.User{ - iam.TestUser(t, conn, o.GetPublicId()), - iam.TestUser(t, conn, o.GetPublicId()), - iam.TestUser(t, conn, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), } groups := []*iam.Group{ iam.TestGroup(t, conn, o.GetPublicId()), @@ -1166,19 +1166,19 @@ func TestSetPrincipal(t *testing.T) { func TestRemovePrincipal(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } s, err := roles.NewService(repoFn) require.NoError(t, err, "Error when getting new role service.") - o, p := iam.TestScopes(t, conn) + o, p := iam.TestScopes(t, iamRepo) users := []*iam.User{ - iam.TestUser(t, conn, o.GetPublicId()), - iam.TestUser(t, conn, o.GetPublicId()), - iam.TestUser(t, conn, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), + iam.TestUser(t, iamRepo, o.GetPublicId()), } groups := []*iam.Group{ iam.TestGroup(t, conn, o.GetPublicId()), @@ -1323,10 +1323,10 @@ func checkEqualGrants(t *testing.T, expected []string, got *pb.Role) { func TestAddGrants(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } s, err := roles.NewService(repoFn) require.NoError(t, err, "Error when getting new role service.") @@ -1358,7 +1358,7 @@ func TestAddGrants(t *testing.T) { } for _, tc := range addCases { - o, p := iam.TestScopes(t, conn) + o, p := iam.TestScopes(t, iamRepo) for _, scope := range []*iam.Scope{o, p} { t.Run(tc.name+"_"+scope.Type, func(t *testing.T) { assert := assert.New(t) @@ -1386,7 +1386,7 @@ func TestAddGrants(t *testing.T) { } } - _, p := iam.TestScopes(t, conn) + _, p := iam.TestScopes(t, iamRepo) role := iam.TestRole(t, conn, p.GetPublicId()) failCases := []struct { name string @@ -1414,10 +1414,10 @@ func TestAddGrants(t *testing.T) { func TestSetGrants(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } s, err := roles.NewService(repoFn) @@ -1456,7 +1456,7 @@ func TestSetGrants(t *testing.T) { } for _, tc := range setCases { - o, p := iam.TestScopes(t, conn) + o, p := iam.TestScopes(t, iamRepo) for _, scope := range []*iam.Scope{o, p} { t.Run(tc.name+"_"+scope.Type, func(t *testing.T) { assert := assert.New(t) @@ -1485,7 +1485,7 @@ func TestSetGrants(t *testing.T) { } } - _, p := iam.TestScopes(t, conn) + _, p := iam.TestScopes(t, iamRepo) role := iam.TestRole(t, conn, p.GetPublicId()) failCases := []struct { @@ -1514,10 +1514,10 @@ func TestSetGrants(t *testing.T) { func TestRemoveGrants(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } s, err := roles.NewService(repoFn) require.NoError(t, err, "Error when getting new role service.") @@ -1555,7 +1555,7 @@ func TestRemoveGrants(t *testing.T) { } for _, tc := range removeCases { - o, p := iam.TestScopes(t, conn) + o, p := iam.TestScopes(t, iamRepo) for _, scope := range []*iam.Scope{o, p} { t.Run(tc.name+"_"+scope.Type, func(t *testing.T) { assert := assert.New(t) @@ -1585,7 +1585,7 @@ func TestRemoveGrants(t *testing.T) { } } - _, p := iam.TestScopes(t, conn) + _, p := iam.TestScopes(t, iamRepo) role := iam.TestRole(t, conn, p.GetPublicId()) failCases := []struct { name string diff --git a/internal/servers/controller/handlers/scopes/scope_service_test.go b/internal/servers/controller/handlers/scopes/scope_service_test.go index ac4274652..189ae5143 100644 --- a/internal/servers/controller/handlers/scopes/scope_service_test.go +++ b/internal/servers/controller/handlers/scopes/scope_service_test.go @@ -30,13 +30,13 @@ import ( func createDefaultScopesAndRepo(t *testing.T) (*iam.Scope, *iam.Scope, func() (*iam.Repository, error)) { t.Helper() conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } - oRes, pRes := iam.TestScopes(t, conn) + oRes, pRes := iam.TestScopes(t, iamRepo) oRes.Name = "defaultProj" oRes.Description = "defaultProj" @@ -150,18 +150,18 @@ func TestGet(t *testing.T) { func TestList(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } repo, err := repoFn() require.NoError(t, err) - oNoProjects, p1 := iam.TestScopes(t, conn) + oNoProjects, p1 := iam.TestScopes(t, repo) _, err = repo.DeleteScope(context.Background(), p1.GetPublicId()) require.NoError(t, err) - oWithProjects, p2 := iam.TestScopes(t, conn) + oWithProjects, p2 := iam.TestScopes(t, repo) _, err = repo.DeleteScope(context.Background(), p2.GetPublicId()) require.NoError(t, err) diff --git a/internal/servers/controller/handlers/users/user_service_test.go b/internal/servers/controller/handlers/users/user_service_test.go index d66ffda0f..3a5fda8d3 100644 --- a/internal/servers/controller/handlers/users/user_service_test.go +++ b/internal/servers/controller/handlers/users/user_service_test.go @@ -30,21 +30,20 @@ import ( func createDefaultUserAndRepo(t *testing.T) (*iam.User, func() (*iam.Repository, error)) { t.Helper() conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + repo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return repo, nil } - - o, _ := iam.TestScopes(t, conn) - u := iam.TestUser(t, conn, o.GetPublicId(), iam.WithDescription("default"), iam.WithName("default")) + o, _ := iam.TestScopes(t, repo) + u := iam.TestUser(t, repo, o.GetPublicId(), iam.WithDescription("default"), iam.WithName("default")) return u, repoFn } func TestGet(t *testing.T) { assert := assert.New(t) require := require.New(t) - u, repo := createDefaultUserAndRepo(t) + u, repoFn := createDefaultUserAndRepo(t) toMerge := &pbs.GetUserRequest{ Id: u.GetPublicId(), } @@ -95,7 +94,7 @@ func TestGet(t *testing.T) { req := proto.Clone(toMerge).(*pbs.GetUserRequest) proto.Merge(req, tc.req) - s, err := users.NewService(repo) + s, err := users.NewService(repoFn) require.NoError(err, "Couldn't create new user service.") got, gErr := s.GetUser(auth.DisabledAuthTestContext(auth.WithScopeId(u.GetScopeId())), req) @@ -108,16 +107,16 @@ func TestGet(t *testing.T) { func TestList(t *testing.T) { assert, require := assert.New(t), require.New(t) conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) repoFn := func() (*iam.Repository, error) { - return iam.NewRepository(rw, rw, wrap) + return iamRepo, nil } repo, err := repoFn() require.NoError(err) - oNoUsers, _ := iam.TestScopes(t, conn) - oWithUsers, _ := iam.TestScopes(t, conn) + oNoUsers, _ := iam.TestScopes(t, repo) + oWithUsers, _ := iam.TestScopes(t, repo) var wantUsers []*pb.User for i := 0; i < 10; i++ { diff --git a/internal/servers/controller/testing.go b/internal/servers/controller/testing.go index 0ecececda..4ebc1d850 100644 --- a/internal/servers/controller/testing.go +++ b/internal/servers/controller/testing.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/boundary/api" "github.com/hashicorp/boundary/internal/cmd/base" "github.com/hashicorp/boundary/internal/cmd/config" + "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/go-hclog" wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/hashicorp/vault/sdk/helper/base62" @@ -47,6 +48,14 @@ func (tc *TestController) Context() context.Context { return tc.ctx } +func (tc *TestController) IamRepo() *iam.Repository { + repo, err := tc.c.IamRepoFn() + if err != nil { + tc.t.Fatal(err) + } + return repo +} + func (tc *TestController) Cancel() { tc.cancel() } @@ -179,10 +188,10 @@ type TestControllerOpts struct { DisableAuthorizationFailures bool // The controller KMS to use, or one will be created - ControllerKMS wrapping.Wrapper + RootKms wrapping.Wrapper // The worker auth KMS to use, or one will be created - WorkerAuthKMS wrapping.Wrapper + WorkerAuthKms wrapping.Wrapper // The name to use for the controller, otherwise one will be randomly // generated, unless provided in a non-nil Config @@ -253,10 +262,10 @@ func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController { // Set up KMSes switch { - case opts.ControllerKMS != nil && opts.WorkerAuthKMS != nil: - tc.b.ControllerKMS = opts.ControllerKMS - tc.b.WorkerAuthKMS = opts.WorkerAuthKMS - case opts.ControllerKMS == nil && opts.WorkerAuthKMS == nil: + case opts.RootKms != nil && opts.WorkerAuthKms != nil: + tc.b.RootKms = opts.RootKms + tc.b.WorkerAuthKms = opts.WorkerAuthKms + case opts.RootKms == nil && opts.WorkerAuthKms == nil: if err := tc.b.SetupKMSes(nil, opts.Config.SharedConfig, []string{"root", "worker-auth"}); err != nil { t.Fatal(err) } @@ -314,8 +323,8 @@ func (tc *TestController) AddClusterControllerMember(t *testing.T, opts *TestCon nextOpts := &TestControllerOpts{ DatabaseUrl: tc.c.conf.DatabaseUrl, DefaultAuthMethodId: tc.c.conf.DevAuthMethodId, - ControllerKMS: tc.c.conf.ControllerKMS, - WorkerAuthKMS: tc.c.conf.WorkerAuthKMS, + RootKms: tc.c.conf.RootKms, + WorkerAuthKms: tc.c.conf.WorkerAuthKms, Name: opts.Name, Logger: tc.c.conf.Logger, } diff --git a/internal/servers/controller/worker_tls_config.go b/internal/servers/controller/worker_tls_config.go index 9c7ed836f..2c65aac28 100644 --- a/internal/servers/controller/worker_tls_config.go +++ b/internal/servers/controller/worker_tls_config.go @@ -60,7 +60,7 @@ func (c Controller) v1WorkerAuthConfig(protos []string) (*tls.Config, *base.Work if err := proto.Unmarshal(marshaledEncInfo, encInfo); err != nil { return nil, nil, err } - marshaledInfo, err := c.conf.WorkerAuthKMS.Decrypt(context.Background(), encInfo, nil) + marshaledInfo, err := c.conf.WorkerAuthKms.Decrypt(context.Background(), encInfo, nil) if err != nil { return nil, nil, err } diff --git a/internal/servers/repository.go b/internal/servers/repository.go index 4ad2f9c08..129d9d516 100644 --- a/internal/servers/repository.go +++ b/internal/servers/repository.go @@ -8,9 +8,9 @@ import ( "github.com/hashicorp/boundary/internal/db" timestamp "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/types/resource" "github.com/hashicorp/go-hclog" - wrapping "github.com/hashicorp/go-kms-wrapping" ) const ( @@ -19,29 +19,29 @@ const ( // Repository is the jobs database repository type Repository struct { - logger hclog.Logger - reader db.Reader - writer db.Writer - wrapper wrapping.Wrapper + logger hclog.Logger + reader db.Reader + writer db.Writer + kms *kms.Kms } // NewRepository creates a new jobs Repository. Supports the options: WithLimit // which sets a default limit on results returned by repo operations. -func NewRepository(logger hclog.Logger, r db.Reader, w db.Writer, wrapper wrapping.Wrapper) (*Repository, error) { +func NewRepository(logger hclog.Logger, r db.Reader, w db.Writer, kms *kms.Kms) (*Repository, error) { if r == nil { - return nil, errors.New("error creating db repository with nil reader") + return nil, errors.New("error creating server repository with nil reader") } if w == nil { - return nil, errors.New("error creating db repository with nil writer") + return nil, errors.New("error creating server repository with nil writer") } - if wrapper == nil { - return nil, errors.New("error creating db repository with nil wrapper") + if kms == nil { + return nil, errors.New("error creating server repository with nil kms") } return &Repository{ - logger: logger, - reader: r, - writer: w, - wrapper: wrapper, + logger: logger, + reader: r, + writer: w, + kms: kms, }, nil } diff --git a/internal/servers/worker/controller_connection.go b/internal/servers/worker/controller_connection.go index 618450c84..8f815edfc 100644 --- a/internal/servers/worker/controller_connection.go +++ b/internal/servers/worker/controller_connection.go @@ -223,7 +223,7 @@ func (w Worker) workerAuthTLSConfig() (*tls.Config, *base.WorkerAuthInfo, error) if err != nil { return nil, nil, err } - encInfo, err := w.conf.WorkerAuthKMS.Encrypt(context.Background(), marshaledInfo, nil) + encInfo, err := w.conf.WorkerAuthKms.Encrypt(context.Background(), marshaledInfo, nil) if err != nil { return nil, nil, err } diff --git a/internal/servers/worker/testing.go b/internal/servers/worker/testing.go index 68fc7309d..2b1f272ca 100644 --- a/internal/servers/worker/testing.go +++ b/internal/servers/worker/testing.go @@ -113,7 +113,7 @@ type TestWorkerOpts struct { DisableAutoStart bool // The worker auth KMS to use, or one will be created - WorkerAuthKMS wrapping.Wrapper + WorkerAuthKms wrapping.Wrapper // The name to use for the worker, otherwise one will be randomly // generated, unless provided in a non-nil Config @@ -178,8 +178,8 @@ func NewTestWorker(t *testing.T, opts *TestWorkerOpts) *TestWorker { // Set up KMSes switch { - case opts.WorkerAuthKMS != nil: - tw.b.WorkerAuthKMS = opts.WorkerAuthKMS + case opts.WorkerAuthKms != nil: + tw.b.WorkerAuthKms = opts.WorkerAuthKms default: if err := tw.b.SetupKMSes(nil, opts.Config.SharedConfig, []string{"worker-auth"}); err != nil { t.Fatal(err) @@ -220,7 +220,7 @@ func (tw *TestWorker) AddClusterWorkerMember(t *testing.T, opts *TestWorkerOpts) opts = new(TestWorkerOpts) } nextOpts := &TestWorkerOpts{ - WorkerAuthKMS: tw.w.conf.WorkerAuthKMS, + WorkerAuthKms: tw.w.conf.WorkerAuthKms, Name: opts.Name, InitialControllers: tw.ControllerAddrs(), Logger: tw.w.conf.Logger, diff --git a/internal/tests/cluster/multi_controller_worker_test.go b/internal/tests/cluster/multi_controller_worker_test.go index 7e9ac1c01..7aaaee58d 100644 --- a/internal/tests/cluster/multi_controller_worker_test.go +++ b/internal/tests/cluster/multi_controller_worker_test.go @@ -58,7 +58,7 @@ func TestMultiControllerMultiWorkerConnections(t *testing.T) { expectWorkers(c2) w1 := worker.NewTestWorker(t, &worker.TestWorkerOpts{ - WorkerAuthKMS: c1.Config().WorkerAuthKMS, + WorkerAuthKms: c1.Config().WorkerAuthKms, InitialControllers: c1.ClusterAddrs(), Logger: logger.Named("w1"), })