* Key Rotation/Destruction (#2477) * fix: Correct kms.CreateKeys comment This method does not return anything. * feat(kms): Add new ListKeys method The new ListKeys method wraps the underlying KMS wrapper, returning all the keys in the scope specified. * feat(scopes): Add new scope actions for key management Adds list-keys, rotate-keys and revoke-keys actions for scopes. * feat(scopes): Add ListKeys API endpoint This new endpoint lists all keys in a scope * feat(api): Add ListKeys to Scopes client This has to be a custom function because it is a custom action and doesn't map well to any of the existing templates, or generalises well in a way that would make it reasonable to create a new template. * feat(cli): Add new scopes list-keys command * add RotateKeys function to the kms repository * Add Rotate Keys Endpoint to Scopes Api (#2360) * add rotate keys endpoint * add tests for RotateKeys endpoint * remove that one comment I forgot about * addressing more PR comments * Add Rotate Keys CLI Command (#2395) * add cli command and client * add bats tests * fix some info text and pr comments * write a new rotate keys description * Add Missing DEK Foreign Keys (#2408) * add missing fks * mark key as nullable in gorm * update tests to use a new wrapper with a real key_id * fix formatting in test and add keys for auth_token test * replace key values * revert postgres_40_01_test.go * style: reformat sql for readability * refactor: change type of key_id columns to kms_private_id * Store key_id and scope_id with oplog entries, re-type columns referencing data key version (#2431) * fix(db): Correct types of data key version referencers * fix(oplog): Fix missing error handling * feat(oplog): Store key_id and scope_id with oplog entries This migration truncates all existing oplog entries, as migrating existing data would have been complex and likely unnecessary. * Update view references * Always delete oplog entries when dropping keys * Fix rebase issues * Fix sql tests * Add schema for new kms destruction jobs (#2479) * feat: Add schema for new kms destruction jobs The schema lets us manage destruction jobs per-table while providing a guarantee that only one run is running at a time. * Apply suggestions from code review Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com> * Fix table reference and tests Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com> * update domain, api, and cli to all include key versions in outputs (#2472) * update domain, api, and cli to all include key versions in outputs * fix go sum * add migration to fix immutable error * fix final test issue as well as make gen issue * rebase and fix * address johan pr comments * make gen, didn't realize comments were included * update migrations based on new migration 55 file 03 * Move migrations to 56 * feat: Add key version rewrapping function registry (#2478) The new RegisterTableRewrapFn can be used by a domain to register a callback to use when rewrapping data in a specific table name. * feat(kms): Add scopeId to RewrapFn (#2539) This makes it much easier for rewrap functions to look up the correct wrapper and limit the number of rows searched. * Update Root Cert Proto Definition (#2542) * update root cert proto definition to properly identify public_key as the table primary key * add the test fix as well * Rewrap Functions for Encrypted Tables (#2532) * add oidc rewrap * add argon2 config rewrap * add auth token rewrapping * add worker auth cert rewrapping * check the err * add username password credential rewrapping * add ssh private key credential rewrapping * add vault client certificate rewrap function * add host catalog secret rewrap function * cleanup a little * add host vault token rewrap function * add session credential rewrap function * add session rewrap function * add worker auth rewrap function * update test to include full assert gamut * add session rewrap function TEST * bring all tests up to current standards, make more readable, and include full assert gamut * rework all rewrap functions to utilize scope id * update comments, queries, sql refs, etc, in response to PR comments * forgot a comment * remove hmac updates and fix a couple comments again * Rewrapping Registered Tables Test (#2555) * add the registered table test as well as the two missing rewrap functions * address pr comments, add db r/w conversions, cmp.diff, sql comment, remove (now) faulty immutable test * Add ability to list key version destruction jobs, recurring jobs and destroying key versions (#2498) * feat(kms): Add ability to list data key version destruction jobs * feat(scopes): Add ListKeyVersionDestructionJobs * feat(cli): Add list-key-version-destruction-jobs * feat(kms): Add recurring job that performs table rewrapping The new recurring job runs for each job table with a registered rewrapping callback. It attempts to become the running run and start its rewrapping, gracefully handling sudden interruptions by resuming it's work if it finds evidence of sudden interruption. * feat(kms): Add recurring job for destroying key versions The new job monitors the database for data key version destruction jobs that have finished rewrapping all its data, performing the final data key version revocation. * feat(kms): Add DestroyKeyVersion DestroyKeyVersion immediately destroys a key version if it is a root key version or a data key version which encrypts no data. If the data key version encrypts data which needs to be rewrapped, it queues an asynchronous job to complete the rewrapping operation. * feat(scopes): Add DestroyKeyVersion API endpoint * feat(cli): Add scopes destroy-key-version CLI command * Ensure all secret updates include key ID (#2556) * feat(all): Ensure all secret updates include key ID When updating a secret, it is paramount that the key ID is also updated, as it is the only means we have of ensuring that we only destroy keys once there is no more data associated with the key. * Remove fix to session repository for now This breaks the current private key derivation method * add new encrypt/decrypt funcs and made necessary proto changes (#2557) * add new encrypt/decrypt funcs and made necessary proto changes * address all PR comments * fix test panic * add param checking to rewraps * fix(schema): Correctly organize migrations again * Review comments part 1 * Review comments part 2 * Review comments part 3 * Store cert private key, encrypt tofu token (#2583) * fix(session): store cert private key, encrypt tofu token Previously, we would derive a session certificate private key from the session key used in each project, and not store the key. We would also encrypt the tofu token with the database key but not store a reference to this key in the database. This change fixes this by replacing our key derivation with a key generation step, and instead store the generated key, encrypted, in the database. We also ensure that any new tofu tokens are encrypted with the same key. To handle existing sessions, we lazily rewrite the sessions whenever a user lists them. There is a minor risk that a user could end up destroying the database key that encrypts the tofu token before that session has been rewrapped, but this is going to be rare and temporary. * feat(session): Allow the random source for secrets to be configured * Review comments * Minor fixes * More minor fixes * More small fixes Co-authored-by: Danielle Miu <dani.miu@hashicorp.com> Co-authored-by: Danielle <29378233+DanielleMiu@users.noreply.github.com> Co-authored-by: Michael Gaffney <mike@gaffney.cc> Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com> * fix(session): Unset session key ID when scope is deleted Unsetting the key ID allows the session to live beyond the lifetime of the scope, while allowing the scope to cascade delete its keys. * fix(migrations): Rewrite migration 56 tests These tests can no longer use the normal test helpers as they try to use the new oplog table structure, so we have to manually write all the interactions. * fix(migrations): Rename migration 58 file name This was the name used in the release branch, update it to the new migration number. * fix(e2e): Add some comments and debug to kms tests * fix(session): Handle empty project and user IDs in read When a project or user is deleted, the session is automatically canceled and the respective field on the session is unset. LookupSession did not handle this case gracefully, and would error on decryption in this state. Skip decrypting sessions that do not have either a user or project to avoid this error. The decrypted values are not used for a canceled session. * Fix worker test * Increase test timeout Co-authored-by: Danielle Miu <dani.miu@hashicorp.com> Co-authored-by: Danielle <29378233+DanielleMiu@users.noreply.github.com> Co-authored-by: Michael Gaffney <mike@gaffney.cc> Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com>
This commit is contained in:
158 files changed
+12026
-991
No files matched your search
@@ -151,6 +151,10 @@ protobuild:
|
||||
@protoc-go-inject-tag -input=./internal/kms/store/token_key.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/kms/store/session_key.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/kms/store/oidc_key.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/kms/store/data_key_version_destruction_job.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/kms/store/data_key_version_destruction_job_run.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/kms/store/data_key_version_destruction_job_progress.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/kms/store/data_key_version_destruction_job_run_allowed_table_name.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/server/store/controller.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/server/store/worker.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/server/store/root_certificate.pb.go
|
||||
@@ -169,6 +173,7 @@ protobuild:
|
||||
@protoc-go-inject-tag -input=./internal/gen/controller/api/services/auth_method_service.pb.go
|
||||
@protoc-go-inject-tag -input=./sdk/pbs/controller/api/resources/authmethods/auth_method.pb.go
|
||||
@protoc-go-inject-tag -input=./sdk/pbs/controller/api/resources/scopes/scope.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/gen/controller/api/services/scope_service.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/gen/controller/servers/services/session_service.pb.go
|
||||
@protoc-go-inject-tag -input=./sdk/pbs/controller/api/resources/targets/target.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/gen/controller/api/services/target_service.pb.go
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
package scopes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
)
|
||||
|
||||
type KeyListResult struct {
|
||||
Items []*Key
|
||||
response *api.Response
|
||||
}
|
||||
|
||||
func (n KeyListResult) GetItems() []*Key {
|
||||
return n.Items
|
||||
}
|
||||
|
||||
func (n KeyListResult) GetResponse() *api.Response {
|
||||
return n.response
|
||||
}
|
||||
|
||||
type KeysRotateResult struct {
|
||||
response *api.Response
|
||||
}
|
||||
|
||||
func (n KeysRotateResult) GetResponse() *api.Response {
|
||||
return n.response
|
||||
}
|
||||
|
||||
type KeyVersionDestructionJobListResult struct {
|
||||
Items []*KeyVersionDestructionJob
|
||||
response *api.Response
|
||||
}
|
||||
|
||||
func (n KeyVersionDestructionJobListResult) GetItems() []*KeyVersionDestructionJob {
|
||||
return n.Items
|
||||
}
|
||||
|
||||
func (n KeyVersionDestructionJobListResult) GetResponse() *api.Response {
|
||||
return n.response
|
||||
}
|
||||
|
||||
type KeyVersionDestructionResult struct {
|
||||
State string
|
||||
response *api.Response
|
||||
}
|
||||
|
||||
func (n KeyVersionDestructionResult) GetResponse() *api.Response {
|
||||
return n.response
|
||||
}
|
||||
|
||||
func (c *Client) ListKeys(ctx context.Context, scopeId string, opt ...Option) (*KeyListResult, error) {
|
||||
if scopeId == "" {
|
||||
return nil, fmt.Errorf("empty scopeId value passed into ListKeys request")
|
||||
}
|
||||
if c.client == nil {
|
||||
return nil, fmt.Errorf("nil client")
|
||||
}
|
||||
|
||||
opts, apiOpts := getOpts(opt...)
|
||||
|
||||
req, err := c.client.NewRequest(ctx, "GET", "scopes/"+url.PathEscape(scopeId)+":list-keys", nil, apiOpts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating ListKeys request: %w", err)
|
||||
}
|
||||
|
||||
if len(opts.queryMap) > 0 {
|
||||
q := url.Values{}
|
||||
for k, v := range opts.queryMap {
|
||||
q.Add(k, v)
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error performing client request during ListKeys call: %w", err)
|
||||
}
|
||||
|
||||
target := new(KeyListResult)
|
||||
apiErr, err := resp.Decode(target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding ListKeys response: %w", err)
|
||||
}
|
||||
if apiErr != nil {
|
||||
return nil, apiErr
|
||||
}
|
||||
target.response = resp
|
||||
return target, nil
|
||||
}
|
||||
|
||||
func (c *Client) RotateKeys(ctx context.Context, scopeId string, rewrapKeys bool, opt ...Option) (*KeysRotateResult, error) {
|
||||
if scopeId == "" {
|
||||
return nil, fmt.Errorf("empty scopeId value passed into RotateKeys request")
|
||||
}
|
||||
if c.client == nil {
|
||||
return nil, fmt.Errorf("nil client")
|
||||
}
|
||||
|
||||
opts, apiOpts := getOpts(opt...)
|
||||
|
||||
opts.postMap["scope_id"] = scopeId
|
||||
opts.postMap["rewrap"] = rewrapKeys
|
||||
|
||||
req, err := c.client.NewRequest(ctx, "POST", "scopes:rotate-keys", opts.postMap, apiOpts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating RotateKeys request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error performing client request during RotateKeys call: %w", err)
|
||||
}
|
||||
|
||||
apiErr, err := resp.Decode(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding RotateKeys response: %w", err)
|
||||
}
|
||||
if apiErr != nil {
|
||||
return nil, apiErr
|
||||
}
|
||||
|
||||
target := &KeysRotateResult{
|
||||
response: resp,
|
||||
}
|
||||
return target, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListKeyVersionDestructionJobs(ctx context.Context, scopeId string, opt ...Option) (*KeyVersionDestructionJobListResult, error) {
|
||||
if scopeId == "" {
|
||||
return nil, fmt.Errorf("empty scopeId value passed into ListKeyVersionDestructionJobs request")
|
||||
}
|
||||
if c.client == nil {
|
||||
return nil, fmt.Errorf("nil client")
|
||||
}
|
||||
|
||||
opts, apiOpts := getOpts(opt...)
|
||||
|
||||
req, err := c.client.NewRequest(ctx, "GET", "scopes/"+url.PathEscape(scopeId)+":list-key-version-destruction-jobs", nil, apiOpts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating ListKeyVersionDestructionJobs request: %w", err)
|
||||
}
|
||||
|
||||
if len(opts.queryMap) > 0 {
|
||||
q := url.Values{}
|
||||
for k, v := range opts.queryMap {
|
||||
q.Add(k, v)
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error performing client request during ListKeyVersionDestructionJobs call: %w", err)
|
||||
}
|
||||
|
||||
target := new(KeyVersionDestructionJobListResult)
|
||||
apiErr, err := resp.Decode(target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding ListKeyVersionDestructionJobs response: %w", err)
|
||||
}
|
||||
if apiErr != nil {
|
||||
return nil, apiErr
|
||||
}
|
||||
target.response = resp
|
||||
return target, nil
|
||||
}
|
||||
|
||||
func (c *Client) DestroyKeyVersion(ctx context.Context, scopeId string, keyVersionId string, opt ...Option) (*KeyVersionDestructionResult, error) {
|
||||
if scopeId == "" {
|
||||
return nil, fmt.Errorf("empty scopeId value passed into DestroyKeyVersion request")
|
||||
}
|
||||
if keyVersionId == "" {
|
||||
return nil, fmt.Errorf("empty keyVersionId value passed into DestroyKeyVersion request")
|
||||
}
|
||||
if c.client == nil {
|
||||
return nil, fmt.Errorf("nil client")
|
||||
}
|
||||
|
||||
opts, apiOpts := getOpts(opt...)
|
||||
opts.postMap["scope_id"] = scopeId
|
||||
opts.postMap["key_version_id"] = keyVersionId
|
||||
|
||||
req, err := c.client.NewRequest(ctx, "POST", "scopes:destroy-key-version", opts.postMap, apiOpts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating RotateKeys request: %w", err)
|
||||
}
|
||||
|
||||
if len(opts.queryMap) > 0 {
|
||||
q := url.Values{}
|
||||
for k, v := range opts.queryMap {
|
||||
q.Add(k, v)
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error performing client request during DestroyKeyVersion call: %w", err)
|
||||
}
|
||||
|
||||
target := new(KeyVersionDestructionResult)
|
||||
apiErr, err := resp.Decode(target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding DestroyKeyVersion response: %w", err)
|
||||
}
|
||||
if apiErr != nil {
|
||||
return nil, apiErr
|
||||
}
|
||||
target.response = resp
|
||||
return target, nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Code generated by "make api"; DO NOT EDIT.
|
||||
package scopes
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Key struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Scope *ScopeInfo `json:"scope,omitempty"`
|
||||
Purpose string `json:"purpose,omitempty"`
|
||||
CreatedTime time.Time `json:"created_time,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Versions []*KeyVersion `json:"versions,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Code generated by "make api"; DO NOT EDIT.
|
||||
package scopes
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type KeyVersion struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Version uint32 `json:"version,omitempty"`
|
||||
CreatedTime time.Time `json:"created_time,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Code generated by "make api"; DO NOT EDIT.
|
||||
package scopes
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type KeyVersionDestructionJob struct {
|
||||
KeyVersionId string `json:"key_version_id,omitempty"`
|
||||
Scope *ScopeInfo `json:"scope,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
CreatedTime time.Time `json:"created_time,omitempty"`
|
||||
CompletedCount int64 `json:"completed_count,string,omitempty"`
|
||||
TotalCount int64 `json:"total_count,string,omitempty"`
|
||||
}
|
||||
@@ -86,4 +86,9 @@ const (
|
||||
ActiveConnectionCountField = "active_connection_count"
|
||||
ControllerGeneratedActivationToken = "controller_generated_activation_token"
|
||||
ReleaseVersionField = "release_version"
|
||||
KeyPurposeField = "purpose"
|
||||
KeyVersionsField = "key_versions"
|
||||
KeyVersionIdField = "key_version_id"
|
||||
CompletedCountField = "completed_count"
|
||||
TotalCountField = "total_count"
|
||||
)
|
||||
@@ -73,10 +73,10 @@ require (
|
||||
github.com/zalando/go-keyring v0.2.1
|
||||
go.uber.org/atomic v1.9.0
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.org/x/tools v0.1.10
|
||||
golang.org/x/tools v0.1.12
|
||||
google.golang.org/genproto v0.0.0-20220805133916-01dd62135a58
|
||||
google.golang.org/grpc v1.48.0
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
|
||||
@@ -87,15 +87,16 @@ require (
|
||||
nhooyr.io/websocket v1.8.7
|
||||
)
|
||||
|
||||
require github.com/hashicorp/go-dbw v0.0.0-20220725170111-b7cb3aa3d628
|
||||
require github.com/hashicorp/go-dbw v0.0.0-20220910135738-ed4505749995
|
||||
|
||||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||
github.com/cenkalti/backoff/v4 v4.1.0
|
||||
github.com/hashicorp/go-kms-wrapping/extras/kms/v2 v2.0.0-20220711120347-32232bae6803
|
||||
github.com/hashicorp/go-kms-wrapping/extras/kms/v2 v2.0.0-20220914160710-1c6d04de2431
|
||||
github.com/hashicorp/nodeenrollment v0.1.18
|
||||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e
|
||||
golang.org/x/exp v0.0.0-20220921164117-439092de6870
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -184,11 +185,10 @@ require (
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xo/dburl v0.11.0 // indirect
|
||||
go.uber.org/goleak v1.1.10 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
||||
@@ -665,8 +665,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-dbw v0.0.0-20220725170111-b7cb3aa3d628 h1:+K6QNSsNVaiSWoNKqs1omUzhIWiKLxGuK6sRK3Ch+84=
|
||||
github.com/hashicorp/go-dbw v0.0.0-20220725170111-b7cb3aa3d628/go.mod h1:kRCoWMcOV6AHTOYq9ZILAD7xPzd5fU2M1QKJnhmOH6g=
|
||||
github.com/hashicorp/go-dbw v0.0.0-20220910135738-ed4505749995 h1:+cSg/gVZ1RMFeZbZoQTbNu8pnb30bDQ6jysmKBKDSvg=
|
||||
github.com/hashicorp/go-dbw v0.0.0-20220910135738-ed4505749995/go.mod h1:+GVM0D4y16TzXQWrVZKv3cjNHF2M7cHEBPaDAON3V6I=
|
||||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
@@ -679,8 +679,8 @@ github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjh
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g=
|
||||
github.com/hashicorp/go-kms-wrapping/extras/kms/v2 v2.0.0-20220711120347-32232bae6803 h1:PdSXkY+aAn/19A+lorRNXkfu5zQ9eZTBMNyLdHpeW0c=
|
||||
github.com/hashicorp/go-kms-wrapping/extras/kms/v2 v2.0.0-20220711120347-32232bae6803/go.mod h1:tTUGkZXcXW2y6A2Z8o2CVxHKSPNgDfUHLBhHCJb0Lts=
|
||||
github.com/hashicorp/go-kms-wrapping/extras/kms/v2 v2.0.0-20220914160710-1c6d04de2431 h1:2fmBl291s44mKzvBjF1TfqJgw1GHk97jxkGbalp4TEM=
|
||||
github.com/hashicorp/go-kms-wrapping/extras/kms/v2 v2.0.0-20220914160710-1c6d04de2431/go.mod h1:qbLdiZhFd182vRSOlhBV/OarUcJVTOF2WklPwHs8mYQ=
|
||||
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.2 h1:it114Kjpk79JVh6AfxFMP5oi1gb+RPzcSLiG3zF/J0w=
|
||||
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.2/go.mod h1:b9mq0bG5xJ10ZcTEQJLZ22O9y71hIt2MGTLqROA/SAM=
|
||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.6-0.20220722192355-a843f53fa48d h1:mOtPXWIp4cWKNt9S55IuYAdyUgNtCfUAEVIjcXDx59E=
|
||||
@@ -1383,6 +1383,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20220921164117-439092de6870 h1:j8b6j9gzSigH28O5SjSpQSSh9lFd6f5D/q0aHjNTulc=
|
||||
golang.org/x/exp v0.0.0-20220921164117-439092de6870/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
@@ -1416,8 +1418,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1478,8 +1480,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211013171255-e13a2654a71e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -1508,8 +1510,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1618,8 +1621,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d h1:Zu/JngovGLVi6t2J3nmAf3AoTDwuzw85YZ3b9o4yU7s=
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
@@ -1715,14 +1718,13 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
|
||||
@@ -157,6 +157,28 @@ var inputStructs = []*structInfo{
|
||||
outFile: "plugins/plugin_info.gen.go",
|
||||
skipOptions: true,
|
||||
},
|
||||
{
|
||||
inProto: &scopes.Key{},
|
||||
outFile: "scopes/key.gen.go",
|
||||
skipOptions: true,
|
||||
},
|
||||
{
|
||||
inProto: &scopes.KeyVersion{},
|
||||
outFile: "scopes/key_version.gen.go",
|
||||
skipOptions: true,
|
||||
},
|
||||
{
|
||||
inProto: &scopes.KeyVersionDestructionJob{},
|
||||
outFile: "scopes/key_version_destruction_job.gen.go",
|
||||
skipOptions: true,
|
||||
fieldOverrides: []fieldInfo{
|
||||
// int64 fields get marshalled by protobuf as strings, so we have
|
||||
// to tell the json parser that their json representation is a
|
||||
// string but they go into Go int64 types.
|
||||
{Name: "CompletedCount", JsonTags: []string{"string"}},
|
||||
{Name: "TotalCount", JsonTags: []string{"string"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
inProto: &scopes.Scope{},
|
||||
outFile: "scopes/scope.gen.go",
|
||||
|
||||
@@ -36,6 +36,7 @@ const (
|
||||
AccountClaimMapsField = "AccountClaimMaps"
|
||||
TokenClaimsField = "TokenClaims"
|
||||
UserinfoClaimsField = "UserinfoClaims"
|
||||
KeyIdField = "KeyId"
|
||||
)
|
||||
|
||||
// UpdateAuthMethod will retrieve the auth method from the repository,
|
||||
@@ -226,10 +227,10 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, am *AuthMethod, versi
|
||||
// ClientSecret is a bit odd, because it uses the Struct wrapping, we need
|
||||
// to add the encrypted fields to the dbMask or nullFields
|
||||
if strutil.StrListContains(filteredDbMask, ClientSecretField) {
|
||||
filteredDbMask = append(filteredDbMask, CtClientSecretField, ClientSecretHmacField)
|
||||
filteredDbMask = append(filteredDbMask, CtClientSecretField, ClientSecretHmacField, KeyIdField)
|
||||
}
|
||||
if strutil.StrListContains(filteredNullFields, ClientSecretField) {
|
||||
filteredNullFields = append(filteredNullFields, CtClientSecretField, ClientSecretHmacField)
|
||||
filteredNullFields = append(filteredNullFields, CtClientSecretField, ClientSecretHmacField, KeyIdField)
|
||||
}
|
||||
|
||||
databaseWrapper, err := r.kms.GetWrapper(ctx, origAm.ScopeId, kms.KeyPurposeDatabase)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
kms.RegisterTableRewrapFn(defaultAuthMethodTableName, authMethodRewrapFn)
|
||||
}
|
||||
|
||||
func authMethodRewrapFn(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kmsRepo *kms.Kms) error {
|
||||
const op = "oidc.authMethodRewrapFn"
|
||||
if dataKeyVersionId == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing data key version id")
|
||||
}
|
||||
if scopeId == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
if util.IsNil(reader) {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing database reader")
|
||||
}
|
||||
if util.IsNil(writer) {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing database writer")
|
||||
}
|
||||
if kmsRepo == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing kms repository")
|
||||
}
|
||||
var authMethods []*AuthMethod
|
||||
// There are indexes on (scope id, <other>), so we can query on scope and refine via key id.
|
||||
// This is the fastest query we can use without creating a new index on key_id.
|
||||
if err := reader.SearchWhere(ctx, &authMethods, "scope_id=? and key_id=?", []any{scopeId, dataKeyVersionId}, db.WithLimit(-1)); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query sql for rows that need rewrapping"))
|
||||
}
|
||||
wrapper, err := kmsRepo.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to fetch kms wrapper for rewrapping"))
|
||||
}
|
||||
for _, am := range authMethods {
|
||||
if err := am.decrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to decrypt auth method"))
|
||||
}
|
||||
if err := am.encrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to re-encrypt auth method"))
|
||||
}
|
||||
if _, err := writer.Update(ctx, am, []string{CtClientSecretField, KeyIdField}, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to update auth method row with rewrapped fields"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/go-kms-wrapping/extras/kms/v2/migrations"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRewrap_authMethodRewrapFn(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("errors-on-query-error", func(t *testing.T) {
|
||||
conn, mock := db.TestSetupWithMock(t)
|
||||
wrapper := db.TestWrapper(t)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "kms_schema_version" WHERE 1=1 ORDER BY "kms_schema_version"\."version" LIMIT 1`,
|
||||
).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now()))
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "auth_oidc_method" WHERE scope_id=\$1 and key_id=\$2`,
|
||||
).WillReturnError(errors.New("Query error"))
|
||||
err := authMethodRewrapFn(ctx, "some_id", "some_scope", rw, rw, kmsCache)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("success", func(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
|
||||
convertAlg := func(alg ...Alg) []string {
|
||||
s := make([]string, 0, len(alg))
|
||||
for _, a := range alg {
|
||||
s = append(s, string(a))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// fetch the current key version
|
||||
kmsWrapper, err := kmsCache.GetWrapper(ctx, org.Scope.GetPublicId(), kms.KeyPurposeDatabase)
|
||||
assert.NoError(t, err)
|
||||
|
||||
currentKeyVersion, err := kmsWrapper.KeyId(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// perform the setup for the authmethod
|
||||
algs := []Alg{RS256, ES256}
|
||||
cbs := TestConvertToUrls(t, "https://www.alice.com/callback")[0]
|
||||
auds := []string{"alice-rp", "bob-rp"}
|
||||
cert1, pem1 := testGenerateCA(t, "localhost")
|
||||
cert2, pem2 := testGenerateCA(t, "localhost")
|
||||
certs := []*x509.Certificate{cert1, cert2}
|
||||
pems := []string{pem1, pem2}
|
||||
am, err := NewAuthMethod(
|
||||
ctx,
|
||||
org.PublicId,
|
||||
"alice-rp",
|
||||
"alice-secret", WithAudClaims("alice-rp"),
|
||||
WithAudClaims(auds...),
|
||||
WithIssuer(TestConvertToUrls(t, "https://www.alice.com")[0]),
|
||||
WithApiUrl(cbs),
|
||||
WithSigningAlgs(algs...),
|
||||
WithCertificates(certs...),
|
||||
WithName("alice's restaurant"),
|
||||
WithDescription("it's a good place to eat"),
|
||||
WithClaimsScopes("email", "profile"),
|
||||
WithAccountClaimMap(map[string]AccountToClaim{"display_name": ToNameClaim, "oid": ToSubClaim}),
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, am.SigningAlgs, convertAlg(algs...))
|
||||
assert.Equal(t, am.ApiUrl, cbs.String())
|
||||
assert.Equal(t, "https://www.alice.com", am.Issuer)
|
||||
assert.Equal(t, am.AudClaims, auds)
|
||||
assert.Equal(t, am.Certificates, pems)
|
||||
assert.Equal(t, am.OperationalState, string(InactiveState))
|
||||
|
||||
// CreateAuthMethod to store it, both from oidc repo
|
||||
repo, err := NewRepository(ctx, rw, rw, kmsCache)
|
||||
assert.NoError(t, err)
|
||||
|
||||
authMethod, err := repo.CreateAuthMethod(ctx, am)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, authMethod)
|
||||
assert.Equal(t, authMethod.KeyId, currentKeyVersion)
|
||||
|
||||
// now things are stored in the db, we can rotate and rewrap
|
||||
assert.NoError(t, kmsCache.RotateKeys(ctx, org.Scope.GetPublicId()))
|
||||
assert.NoError(t, authMethodRewrapFn(ctx, authMethod.KeyId, org.Scope.GetPublicId(), rw, rw, kmsCache))
|
||||
|
||||
// fetch the new key version
|
||||
kmsWrapper, err = kmsCache.GetWrapper(ctx, org.Scope.GetPublicId(), kms.KeyPurposeDatabase)
|
||||
assert.NoError(t, err)
|
||||
newKeyVersion, err := kmsWrapper.KeyId(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// fetching the latest auth method itself
|
||||
ams, err := repo.getAuthMethods(ctx, authMethod.GetPublicId(), []string{})
|
||||
assert.NoError(t, err)
|
||||
got := ams[0]
|
||||
|
||||
// since getAuthMethods automatically decrypts the secret, all we need to do is make sure
|
||||
// that it's correct and that it uses the newest key version id
|
||||
assert.NotEmpty(t, got.KeyId)
|
||||
assert.Equal(t, newKeyVersion, got.KeyId)
|
||||
assert.Equal(t, "alice-secret", got.ClientSecret)
|
||||
assert.NotEqual(t, authMethod.CtClientSecret, got.CtClientSecret)
|
||||
assert.NotEmpty(t, got.ClientSecretHmac)
|
||||
assert.Equal(t, authMethod.ClientSecretHmac, got.ClientSecretHmac)
|
||||
})
|
||||
}
|
||||
@@ -406,7 +406,7 @@ func Test_Callback(t *testing.T) {
|
||||
foundAcct, foundOidcAcct := false, false
|
||||
for _, e := range entries {
|
||||
cnt += 1
|
||||
e.Cipherer = oplogWrapper
|
||||
e.Wrapper = oplogWrapper
|
||||
err := e.DecryptData(ctx)
|
||||
require.NoError(err)
|
||||
msgs, err := e.UnmarshalData(ctx, types)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -317,14 +318,18 @@ func TestArgon2Credential_New(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
rootWrapper := db.TestWrapper(t)
|
||||
|
||||
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, rootWrapper))
|
||||
auts := TestAuthMethods(t, conn, o.GetPublicId(), 1)
|
||||
aut := auts[0]
|
||||
accts := TestMultipleAccounts(t, conn, aut.PublicId, 5)
|
||||
confs := testArgon2Confs(t, conn, accts[0].AuthMethodId, 1)
|
||||
|
||||
kmsCache := kms.TestKms(t, conn, rootWrapper)
|
||||
wrapper, err := kmsCache.GetWrapper(context.Background(), o.GetPublicId(), 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
type args struct {
|
||||
accountId string
|
||||
password string
|
||||
|
||||
@@ -81,7 +81,7 @@ func (r *Repository) Authenticate(ctx context.Context, scopeId, authMethodId, lo
|
||||
return acct.Account, errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("update credential"))
|
||||
}
|
||||
|
||||
fields := []string{"CtSalt", "DerivedKey", "PasswordConfId"}
|
||||
fields := []string{"CtSalt", "DerivedKey", "PasswordConfId", "KeyId"}
|
||||
metadata := cred.oplog(oplog.OpType_OP_TYPE_UPDATE)
|
||||
|
||||
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
|
||||
@@ -213,6 +213,11 @@ func TestRepository_AuthenticateRehash(t *testing.T) {
|
||||
require.True(ok, "want *Argon2Configuration")
|
||||
assert.NotEqual(origConfId, upArgonConf.PrivateId)
|
||||
|
||||
// Change the key used to encrypt the password too, to test
|
||||
// that it gets updated appropriately
|
||||
err = kmsCache.RotateKeys(ctx, o.GetPublicId())
|
||||
require.NoError(err)
|
||||
|
||||
// Authenticate and verify the credential ID has not changed
|
||||
auth2Acct, err := repo.Authenticate(ctx, o.GetPublicId(), authMethodId, loginName, passwd)
|
||||
require.NoError(err)
|
||||
@@ -242,6 +247,7 @@ func TestRepository_AuthenticateRehash(t *testing.T) {
|
||||
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")
|
||||
assert.NotEqual(origCred.DerivedKey, auth2Cred.DerivedKey, "the derived key should be different")
|
||||
assert.NotEqual(origCred.KeyId, auth2Cred.KeyId)
|
||||
|
||||
assert.NoError(db.TestVerifyOplog(t, rw, auth2Cred.PrivateId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package password
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
kms.RegisterTableRewrapFn("auth_password_argon2_cred", argon2ConfigRewrapFn)
|
||||
}
|
||||
|
||||
func argon2ConfigRewrapFn(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kmsRepo *kms.Kms) error {
|
||||
const op = "password.argon2ConfigRewrapFn"
|
||||
if dataKeyVersionId == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing data key version id")
|
||||
}
|
||||
if scopeId == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
if util.IsNil(reader) {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing database reader")
|
||||
}
|
||||
if util.IsNil(writer) {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing database writer")
|
||||
}
|
||||
if kmsRepo == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing kms repository")
|
||||
}
|
||||
var credentials []*Argon2Credential
|
||||
// The only index on this table is on private id and there are no references to private id.
|
||||
// This is the fastest query we can use without creating a new index on key_id.
|
||||
if err := reader.SearchWhere(ctx, &credentials, "key_id=?", []any{dataKeyVersionId}, db.WithLimit(-1)); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query sql for rows that need rewrapping"))
|
||||
}
|
||||
wrapper, err := kmsRepo.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to fetch kms wrapper for rewrapping"))
|
||||
}
|
||||
for _, cred := range credentials {
|
||||
if err := cred.decrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to decrypt argon2 config"))
|
||||
}
|
||||
if err := cred.encrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to re-encrypt argon2 config"))
|
||||
}
|
||||
if _, err := writer.Update(ctx, cred, []string{"CtSalt", "KeyId"}, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to update argon2 config row with rewrapped fields"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package password
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"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/go-kms-wrapping/extras/kms/v2/migrations"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRewrap_argon2ConfigRewrapFn(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("errors-on-query-error", func(t *testing.T) {
|
||||
conn, mock := db.TestSetupWithMock(t)
|
||||
wrapper := db.TestWrapper(t)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "kms_schema_version" WHERE 1=1 ORDER BY "kms_schema_version"\."version" LIMIT 1`,
|
||||
).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now()))
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "auth_password_argon2_cred" WHERE key_id=\$1`,
|
||||
).WillReturnError(errors.New("Query error"))
|
||||
err := argon2ConfigRewrapFn(ctx, "some_id", "some_scope", rw, rw, kmsCache)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("success", func(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
auts := TestAuthMethods(t, conn, org.GetPublicId(), 1)
|
||||
aut := auts[0]
|
||||
acct := TestAccount(t, conn, aut.PublicId, "name")
|
||||
authMethodId := acct.AuthMethodId
|
||||
conf := testArgon2Confs(t, conn, authMethodId, 1)[0]
|
||||
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
wrapper, _ = kmsCache.GetWrapper(context.Background(), org.GetPublicId(), 1)
|
||||
|
||||
// actually store it
|
||||
cred, err := newArgon2Credential(acct.PublicId, "this is a password", conf)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, cred.encrypt(context.Background(), wrapper))
|
||||
assert.NoError(t, rw.Create(context.Background(), cred))
|
||||
|
||||
// now things are stored in the db, we can rotate and rewrap
|
||||
assert.NoError(t, kmsCache.RotateKeys(ctx, org.Scope.GetPublicId()))
|
||||
assert.NoError(t, argon2ConfigRewrapFn(ctx, cred.KeyId, org.Scope.GetPublicId(), rw, rw, kmsCache))
|
||||
|
||||
// now we pull the config back from the db, decrypt it with the new key, and ensure things match
|
||||
got := &Argon2Credential{
|
||||
Argon2Credential: &store.Argon2Credential{
|
||||
PrivateId: cred.PrivateId,
|
||||
},
|
||||
}
|
||||
assert.NoError(t, rw.LookupById(ctx, got))
|
||||
|
||||
// fetch the new key version
|
||||
kmsWrapper, err := kmsCache.GetWrapper(ctx, org.Scope.GetPublicId(), kms.KeyPurposeDatabase, kms.WithKeyId(got.GetKeyId()))
|
||||
assert.NoError(t, err)
|
||||
newKeyVersion, err := kmsWrapper.KeyId(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// decrypt with the new key version and check to make sure things match
|
||||
assert.NoError(t, got.decrypt(ctx, kmsWrapper))
|
||||
assert.NotEmpty(t, got.GetKeyId())
|
||||
assert.NotEqual(t, cred.GetKeyId(), got.GetKeyId())
|
||||
assert.Equal(t, newKeyVersion, got.GetKeyId())
|
||||
assert.Equal(t, cred.GetSalt(), got.GetSalt())
|
||||
assert.NotEqual(t, cred.GetCtSalt(), got.GetCtSalt())
|
||||
})
|
||||
}
|
||||
@@ -125,9 +125,11 @@ func TestAuthToken_DbUpdate(t *testing.T) {
|
||||
|
||||
func TestAuthToken_DbCreate(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
rootWrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, rootWrapper)
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, rootWrapper))
|
||||
wrapper, err := kms.GetWrapper(context.Background(), org.GetPublicId(), 1)
|
||||
require.NoError(t, err)
|
||||
am := password.TestAuthMethods(t, conn, org.GetPublicId(), 1)[0]
|
||||
acct := password.TestAccount(t, conn, am.GetPublicId(), "name1")
|
||||
createdAuthToken := TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package authtoken
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
kms.RegisterTableRewrapFn(defaultAuthTokenTableName, authTokenRewrapFn)
|
||||
}
|
||||
|
||||
func authTokenRewrapFn(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kmsRepo *kms.Kms) error {
|
||||
const op = "authtoken.authTokenRewrapFn"
|
||||
if dataKeyVersionId == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing data key version id")
|
||||
}
|
||||
if scopeId == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
if util.IsNil(reader) {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing database reader")
|
||||
}
|
||||
if util.IsNil(writer) {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing database writer")
|
||||
}
|
||||
if kmsRepo == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing kms repository")
|
||||
}
|
||||
var credentials []*AuthToken
|
||||
// Indexes exist on public id and token, and the only reference to public id is session, which may not exist for all rows we need.
|
||||
// This is the fastest query we can use without creating a new index on key_id.
|
||||
if err := reader.SearchWhere(ctx, &credentials, "key_id=?", []any{dataKeyVersionId}, db.WithLimit(-1)); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query sql for rows that need rewrapping"))
|
||||
}
|
||||
wrapper, err := kmsRepo.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to fetch kms wrapper for rewrapping"))
|
||||
}
|
||||
for _, cred := range credentials {
|
||||
if err := cred.decrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to decrypt auth token"))
|
||||
}
|
||||
if err := cred.encrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to re-encrypt auth token"))
|
||||
}
|
||||
if _, err := writer.Update(ctx, cred, []string{"CtToken", "KeyId"}, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to update auth token row with rewrapped fields"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package authtoken
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/go-kms-wrapping/extras/kms/v2/migrations"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRewrap_authTokenRewrapFn(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("errors-on-query-error", func(t *testing.T) {
|
||||
conn, mock := db.TestSetupWithMock(t)
|
||||
wrapper := db.TestWrapper(t)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "kms_schema_version" WHERE 1=1 ORDER BY "kms_schema_version"\."version" LIMIT 1`,
|
||||
).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now()))
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "auth_token" WHERE key_id=\$1`,
|
||||
).WillReturnError(errors.New("Query error"))
|
||||
err := authTokenRewrapFn(ctx, "some_id", "some_scope", rw, rw, kmsCache)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("success", func(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
|
||||
// TestAuthToken will create and store the token for us
|
||||
at := TestAuthToken(t, conn, kmsCache, org.GetPublicId())
|
||||
|
||||
// now things are stored in the db, we can rotate and rewrap
|
||||
assert.NoError(t, kmsCache.RotateKeys(ctx, org.Scope.GetPublicId()))
|
||||
assert.NoError(t, authTokenRewrapFn(ctx, at.GetKeyId(), org.Scope.GetPublicId(), rw, rw, kmsCache))
|
||||
|
||||
// now we pull the authToken back from the db, decrypt it with the new key, and ensure things match
|
||||
got := allocAuthToken()
|
||||
got.PublicId = at.GetPublicId()
|
||||
assert.NoError(t, rw.LookupById(ctx, got))
|
||||
|
||||
// fetch the new key version
|
||||
kmsWrapper, err := kmsCache.GetWrapper(ctx, org.Scope.GetPublicId(), kms.KeyPurposeDatabase, kms.WithKeyId(got.GetKeyId()))
|
||||
assert.NoError(t, err)
|
||||
newKeyVersionId, err := kmsWrapper.KeyId(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// decrypt with the new key version and check to make sure things match
|
||||
assert.NoError(t, got.decrypt(ctx, kmsWrapper))
|
||||
assert.NotEmpty(t, got.GetKeyId())
|
||||
assert.NotEqual(t, at.GetKeyId(), got.GetKeyId())
|
||||
assert.Equal(t, newKeyVersionId, got.GetKeyId())
|
||||
assert.Equal(t, at.GetToken(), got.GetToken())
|
||||
assert.NotEqual(t, at.GetCtToken(), got.GetCtToken())
|
||||
})
|
||||
}
|
||||
@@ -910,6 +910,26 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||
Func: "list",
|
||||
}, nil
|
||||
},
|
||||
"scopes list-keys": func() (cli.Command, error) {
|
||||
return &scopescmd.ListKeysCommand{
|
||||
Command: base.NewCommand(ui),
|
||||
}, nil
|
||||
},
|
||||
"scopes rotate-keys": func() (cli.Command, error) {
|
||||
return &scopescmd.RotateKeysCommand{
|
||||
Command: base.NewCommand(ui),
|
||||
}, nil
|
||||
},
|
||||
"scopes list-key-version-destruction-jobs": func() (cli.Command, error) {
|
||||
return &scopescmd.ListKeyVersionDestructionJobsCommand{
|
||||
Command: base.NewCommand(ui),
|
||||
}, nil
|
||||
},
|
||||
"scopes destroy-key-version": func() (cli.Command, error) {
|
||||
return &scopescmd.DestroyKeyVersionCommand{
|
||||
Command: base.NewCommand(ui),
|
||||
}, nil
|
||||
},
|
||||
|
||||
"sessions": func() (cli.Command, error) {
|
||||
return &sessionscmd.Command{
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package scopescmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/scopes"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/mitchellh/go-wordwrap"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var (
|
||||
_ cli.Command = (*DestroyKeyVersionCommand)(nil)
|
||||
_ cli.CommandAutocomplete = (*DestroyKeyVersionCommand)(nil)
|
||||
)
|
||||
|
||||
type DestroyKeyVersionCommand struct {
|
||||
*base.Command
|
||||
FlagKeyVersionId string
|
||||
}
|
||||
|
||||
func (c *DestroyKeyVersionCommand) Synopsis() string {
|
||||
return wordwrap.WrapString("Destroy a key version within a scope", base.TermWidth)
|
||||
}
|
||||
|
||||
func (c *DestroyKeyVersionCommand) Help() string {
|
||||
return base.WrapForHelpText([]string{
|
||||
"Usage: boundary scopes destroy-key-version [args]",
|
||||
"",
|
||||
" Destroys a key version in the scope. The key version must not be the currently active key version.",
|
||||
" This may start an asynchronous job to re-encrypt existing data encrypted with the key version.",
|
||||
" Use `boundary scopes list-key-version-destruction-jobs` to monitor the progress of this job. Example:",
|
||||
"",
|
||||
` $ boundary scopes destroy-key-version -scope-id global -key-version-id krkv_123456789`,
|
||||
"",
|
||||
"",
|
||||
}) + c.Flags().Help()
|
||||
}
|
||||
|
||||
func (c *DestroyKeyVersionCommand) Flags() *base.FlagSets {
|
||||
set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat)
|
||||
f := set.NewFlagSet("Command Options")
|
||||
|
||||
f.StringVar(&base.StringVar{
|
||||
Name: "scope-id",
|
||||
Target: &c.FlagScopeId,
|
||||
Usage: "The id of the scope in which the key version exists",
|
||||
})
|
||||
|
||||
f.StringVar(&base.StringVar{
|
||||
Name: "key-version-id",
|
||||
Target: &c.FlagKeyVersionId,
|
||||
Usage: "The id of the key version which should be destroyed",
|
||||
})
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *DestroyKeyVersionCommand) AutocompleteArgs() complete.Predictor {
|
||||
return complete.PredictAnything
|
||||
}
|
||||
|
||||
func (c *DestroyKeyVersionCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *DestroyKeyVersionCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.PrintCliError(err)
|
||||
return base.CommandUserError
|
||||
}
|
||||
|
||||
switch {
|
||||
case c.FlagScopeId == "":
|
||||
c.PrintCliError(errors.New("Scope ID must be provided via -scope-id"))
|
||||
return base.CommandUserError
|
||||
case c.FlagKeyVersionId == "":
|
||||
c.PrintCliError(errors.New("Key Version ID must be provided via -key-version-id"))
|
||||
return base.CommandUserError
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if c.WrapperCleanupFunc != nil {
|
||||
defer func() {
|
||||
if err := c.WrapperCleanupFunc(); err != nil {
|
||||
c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
if err != nil {
|
||||
c.PrintCliError(fmt.Errorf("Error creating API client: %w", err))
|
||||
return base.CommandCliError
|
||||
}
|
||||
|
||||
sClient := scopes.NewClient(client)
|
||||
result, err := sClient.DestroyKeyVersion(c.Context, c.FlagScopeId, c.FlagKeyVersionId)
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil {
|
||||
c.PrintApiError(apiErr, "Error from controller when destroying key version")
|
||||
return base.CommandApiError
|
||||
}
|
||||
c.PrintCliError(fmt.Errorf("Error trying to destroy key version: %w", err))
|
||||
return base.CommandCliError
|
||||
}
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result.GetResponse()); !ok {
|
||||
return base.CommandCliError
|
||||
}
|
||||
default:
|
||||
switch result.State {
|
||||
case "completed":
|
||||
c.UI.Output("The key version was successfully destroyed.")
|
||||
case "pending":
|
||||
c.UI.Output("A key version destruction job has been created, which will re-encrypt existing data in the background. Use `boundary scopes list-key-version-destruction-jobs` to monitor this job. Once it has completed the key version will have been destroyed.")
|
||||
default:
|
||||
c.PrintCliError(fmt.Errorf("unexpected state: %q", result.State))
|
||||
return base.CommandApiError
|
||||
}
|
||||
}
|
||||
|
||||
return base.CommandSuccess
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package scopescmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/scopes"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/mitchellh/go-wordwrap"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var (
|
||||
_ cli.Command = (*ListKeyVersionDestructionJobsCommand)(nil)
|
||||
_ cli.CommandAutocomplete = (*ListKeyVersionDestructionJobsCommand)(nil)
|
||||
)
|
||||
|
||||
type ListKeyVersionDestructionJobsCommand struct {
|
||||
*base.Command
|
||||
}
|
||||
|
||||
func (c *ListKeyVersionDestructionJobsCommand) Synopsis() string {
|
||||
return wordwrap.WrapString("List all pending key version destruction jobs within a scope", base.TermWidth)
|
||||
}
|
||||
|
||||
func (c *ListKeyVersionDestructionJobsCommand) Help() string {
|
||||
return base.WrapForHelpText([]string{
|
||||
"Usage: boundary scopes list-key-version-destruction-jobs [args]",
|
||||
"",
|
||||
" List all pending key version destruction jobs within a scope. A key version",
|
||||
" destruction job asynchronously re-encrypts any existing data with the latest",
|
||||
" key version before destroying the key version. Example:",
|
||||
"",
|
||||
` $ boundary scopes list-key-version-destruction-jobs -scope-id global`,
|
||||
"",
|
||||
"",
|
||||
}) + c.Flags().Help()
|
||||
}
|
||||
|
||||
func (c *ListKeyVersionDestructionJobsCommand) Flags() *base.FlagSets {
|
||||
set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat)
|
||||
f := set.NewFlagSet("Command Options")
|
||||
|
||||
f.StringVar(&base.StringVar{
|
||||
Name: "scope-id",
|
||||
Target: &c.FlagScopeId,
|
||||
Usage: "The id of the scope in which to list key version destruction jobs",
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *ListKeyVersionDestructionJobsCommand) AutocompleteArgs() complete.Predictor {
|
||||
return complete.PredictAnything
|
||||
}
|
||||
|
||||
func (c *ListKeyVersionDestructionJobsCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *ListKeyVersionDestructionJobsCommand) printListTable(items []*scopes.KeyVersionDestructionJob) string {
|
||||
if len(items) == 0 {
|
||||
return "No keys version destruction jobs found"
|
||||
}
|
||||
output := []string{
|
||||
"",
|
||||
"Key version destruction job information:",
|
||||
}
|
||||
for i, item := range items {
|
||||
if i > 0 {
|
||||
output = append(output, "")
|
||||
}
|
||||
if true {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Key version ID: %s", item.KeyVersionId),
|
||||
fmt.Sprintf(" Scope ID: %s", item.Scope.Id),
|
||||
)
|
||||
}
|
||||
if !item.CreatedTime.IsZero() {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Started: %s", item.CreatedTime.Local().Format(time.RFC1123)),
|
||||
)
|
||||
}
|
||||
if item.Status != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Status: %s", item.Status),
|
||||
)
|
||||
}
|
||||
if true {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Rows completed: %d", item.CompletedCount),
|
||||
)
|
||||
}
|
||||
if true {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Total rows: %d", item.TotalCount),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return base.WrapForHelpText(output)
|
||||
}
|
||||
|
||||
func (c *ListKeyVersionDestructionJobsCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.PrintCliError(err)
|
||||
return base.CommandUserError
|
||||
}
|
||||
|
||||
switch {
|
||||
case c.FlagScopeId == "":
|
||||
c.PrintCliError(errors.New("Scope ID must be provided via -scope-id"))
|
||||
return base.CommandUserError
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if c.WrapperCleanupFunc != nil {
|
||||
defer func() {
|
||||
if err := c.WrapperCleanupFunc(); err != nil {
|
||||
c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
if err != nil {
|
||||
c.PrintCliError(fmt.Errorf("Error creating API client: %w", err))
|
||||
return base.CommandCliError
|
||||
}
|
||||
|
||||
sClient := scopes.NewClient(client)
|
||||
result, err := sClient.ListKeyVersionDestructionJobs(c.Context, c.FlagScopeId)
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil {
|
||||
c.PrintApiError(apiErr, "Error from controller when listing key version destruction jobs")
|
||||
return base.CommandApiError
|
||||
}
|
||||
c.PrintCliError(fmt.Errorf("Error trying to list key version destruction jobs: %w", err))
|
||||
return base.CommandCliError
|
||||
}
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "json":
|
||||
if ok := c.PrintJsonItems(result.GetResponse()); !ok {
|
||||
return base.CommandCliError
|
||||
}
|
||||
|
||||
default:
|
||||
c.UI.Output(c.printListTable(result.GetItems()))
|
||||
}
|
||||
|
||||
return base.CommandSuccess
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package scopescmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/scopes"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/mitchellh/go-wordwrap"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var (
|
||||
_ cli.Command = (*ListKeysCommand)(nil)
|
||||
_ cli.CommandAutocomplete = (*ListKeysCommand)(nil)
|
||||
)
|
||||
|
||||
type ListKeysCommand struct {
|
||||
*base.Command
|
||||
}
|
||||
|
||||
func (c *ListKeysCommand) Synopsis() string {
|
||||
return wordwrap.WrapString("List keys within a scope", base.TermWidth)
|
||||
}
|
||||
|
||||
func (c *ListKeysCommand) Help() string {
|
||||
return base.WrapForHelpText([]string{
|
||||
"Usage: boundary scopes list-keys [args]",
|
||||
"",
|
||||
" List keys within a scope. Example:",
|
||||
"",
|
||||
` $ boundary scopes list-keys -scope-id global`,
|
||||
"",
|
||||
"",
|
||||
}) + c.Flags().Help()
|
||||
}
|
||||
|
||||
func (c *ListKeysCommand) Flags() *base.FlagSets {
|
||||
set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat)
|
||||
f := set.NewFlagSet("Command Options")
|
||||
|
||||
f.StringVar(&base.StringVar{
|
||||
Name: "scope-id",
|
||||
Target: &c.FlagScopeId,
|
||||
Usage: "The id of the scope in which to list the keys",
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *ListKeysCommand) AutocompleteArgs() complete.Predictor {
|
||||
return complete.PredictAnything
|
||||
}
|
||||
|
||||
func (c *ListKeysCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *ListKeysCommand) printListTable(items []*scopes.Key) string {
|
||||
if len(items) == 0 {
|
||||
return "No keys found"
|
||||
}
|
||||
var output []string
|
||||
output = []string{
|
||||
"",
|
||||
"Key information:",
|
||||
}
|
||||
for i, item := range items {
|
||||
if i > 0 {
|
||||
output = append(output, "")
|
||||
}
|
||||
if true {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", item.Id),
|
||||
fmt.Sprintf(" Scope ID: %s", item.Scope.Id),
|
||||
)
|
||||
}
|
||||
if item.Type != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Type: %s", item.Type),
|
||||
)
|
||||
}
|
||||
if item.Purpose != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Purpose: %s", item.Purpose),
|
||||
)
|
||||
}
|
||||
output = append(output, " Key Versions:")
|
||||
for _, keyVersion := range item.Versions {
|
||||
if true {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Version: %d", keyVersion.Version),
|
||||
)
|
||||
}
|
||||
if keyVersion.Id != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" ID: %s", keyVersion.Id),
|
||||
)
|
||||
}
|
||||
if !keyVersion.CreatedTime.IsZero() {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Created: %s", keyVersion.CreatedTime.Local().Format(time.RFC1123)),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.WrapForHelpText(output)
|
||||
}
|
||||
|
||||
func (c *ListKeysCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.PrintCliError(err)
|
||||
return base.CommandUserError
|
||||
}
|
||||
|
||||
switch {
|
||||
case c.FlagScopeId == "":
|
||||
c.PrintCliError(errors.New("Scope ID must be provided via -scope-id"))
|
||||
return base.CommandUserError
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if c.WrapperCleanupFunc != nil {
|
||||
defer func() {
|
||||
if err := c.WrapperCleanupFunc(); err != nil {
|
||||
c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
if err != nil {
|
||||
c.PrintCliError(fmt.Errorf("Error creating API client: %w", err))
|
||||
return base.CommandCliError
|
||||
}
|
||||
|
||||
sClient := scopes.NewClient(client)
|
||||
result, err := sClient.ListKeys(c.Context, c.FlagScopeId)
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil {
|
||||
c.PrintApiError(apiErr, "Error from controller when listing scope keys")
|
||||
return base.CommandApiError
|
||||
}
|
||||
c.PrintCliError(fmt.Errorf("Error trying to list scope keys: %w", err))
|
||||
return base.CommandCliError
|
||||
}
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "json":
|
||||
if ok := c.PrintJsonItems(result.GetResponse()); !ok {
|
||||
return base.CommandCliError
|
||||
}
|
||||
|
||||
default:
|
||||
c.UI.Output(c.printListTable(result.GetItems()))
|
||||
}
|
||||
|
||||
return base.CommandSuccess
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package scopescmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/scopes"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/mitchellh/go-wordwrap"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var (
|
||||
_ cli.Command = (*RotateKeysCommand)(nil)
|
||||
_ cli.CommandAutocomplete = (*RotateKeysCommand)(nil)
|
||||
)
|
||||
|
||||
type RotateKeysCommand struct {
|
||||
*base.Command
|
||||
FlagRewrap bool
|
||||
}
|
||||
|
||||
func (c *RotateKeysCommand) Synopsis() string {
|
||||
return wordwrap.WrapString("Rotate the keys within a scope", base.TermWidth)
|
||||
}
|
||||
|
||||
func (c *RotateKeysCommand) Help() string {
|
||||
return base.WrapForHelpText([]string{
|
||||
"Usage: boundary scopes rotate-keys [args]",
|
||||
"",
|
||||
" Deactivates a given scope's current KEK and DEKs and activates a new set of keys for the scope. Example:",
|
||||
"",
|
||||
` $ boundary scopes rotate-keys -scope-id global`,
|
||||
"",
|
||||
"",
|
||||
}) + c.Flags().Help()
|
||||
}
|
||||
|
||||
func (c *RotateKeysCommand) Flags() *base.FlagSets {
|
||||
set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat)
|
||||
f := set.NewFlagSet("Command Options")
|
||||
|
||||
f.StringVar(&base.StringVar{
|
||||
Name: "scope-id",
|
||||
Target: &c.FlagScopeId,
|
||||
Usage: "The id of the scope in which to rotate keys",
|
||||
})
|
||||
|
||||
f.BoolVar((&base.BoolVar{
|
||||
Name: "rewrap",
|
||||
Target: &c.FlagRewrap,
|
||||
Usage: "Whether or not to re-encrypt DEKs with the new KEK",
|
||||
}))
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *RotateKeysCommand) AutocompleteArgs() complete.Predictor {
|
||||
return complete.PredictAnything
|
||||
}
|
||||
|
||||
func (c *RotateKeysCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *RotateKeysCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.PrintCliError(err)
|
||||
return base.CommandUserError
|
||||
}
|
||||
|
||||
switch {
|
||||
case c.FlagScopeId == "":
|
||||
c.PrintCliError(errors.New("Scope ID must be provided via -scope-id"))
|
||||
return base.CommandUserError
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if c.WrapperCleanupFunc != nil {
|
||||
defer func() {
|
||||
if err := c.WrapperCleanupFunc(); err != nil {
|
||||
c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
if err != nil {
|
||||
c.PrintCliError(fmt.Errorf("Error creating API client: %w", err))
|
||||
return base.CommandCliError
|
||||
}
|
||||
|
||||
sClient := scopes.NewClient(client)
|
||||
result, err := sClient.RotateKeys(c.Context, c.FlagScopeId, c.FlagRewrap)
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil {
|
||||
c.PrintApiError(apiErr, "Error from controller when rotating scope keys")
|
||||
return base.CommandApiError
|
||||
}
|
||||
c.PrintCliError(fmt.Errorf("Error trying to rotate scope keys: %w", err))
|
||||
return base.CommandCliError
|
||||
}
|
||||
|
||||
switch base.Format(c.UI) {
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result.GetResponse()); !ok {
|
||||
return base.CommandCliError
|
||||
}
|
||||
|
||||
default:
|
||||
c.UI.Output("The rotate operation completed successfully.")
|
||||
}
|
||||
|
||||
return base.CommandSuccess
|
||||
}
|
||||
@@ -232,9 +232,9 @@ func printItemTable(item *sessions.Session, resp *api.Response) string {
|
||||
}
|
||||
}
|
||||
|
||||
var connectionsMaps []map[string]interface{}
|
||||
var connectionsMaps []map[string]any
|
||||
for _, sc := range item.Connections {
|
||||
cm := map[string]interface{}{
|
||||
cm := map[string]any{
|
||||
"Client Address": net.JoinHostPort(sc.ClientTcpAddress, strconv.FormatUint(uint64(sc.ClientTcpPort), 10)),
|
||||
"Endpoint Address": net.JoinHostPort(sc.EndpointTcpAddress, strconv.FormatUint(uint64(sc.EndpointTcpPort), 10)),
|
||||
"Bytes Up": sc.BytesUp,
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package static
|
||||
|
||||
const (
|
||||
credStaticUsernamePasswordRewrapQuery = `
|
||||
select distinct
|
||||
userpass.public_id,
|
||||
userpass.password_encrypted,
|
||||
userpass.key_id
|
||||
from credential_static_username_password_credential userpass
|
||||
inner join credential_static_store store
|
||||
on store.public_id = userpass.store_id
|
||||
where store.project_id = ?
|
||||
and userpass.key_id = ?;
|
||||
`
|
||||
|
||||
credStaticSshPrivKeyRewrapQuery = `
|
||||
select distinct
|
||||
ssh.public_id,
|
||||
ssh.private_key_encrypted,
|
||||
ssh.private_key_passphrase_encrypted,
|
||||
ssh.key_id
|
||||
from credential_static_ssh_private_key_credential ssh
|
||||
inner join credential_static_store store
|
||||
on store.public_id = ssh.store_id
|
||||
where store.project_id = ?
|
||||
and ssh.key_id = ?;
|
||||
`
|
||||
|
||||
credStaticJsonRewrapQuery = `
|
||||
select distinct
|
||||
json.public_id,
|
||||
json.object_encrypted,
|
||||
json.key_id
|
||||
from credential_static_json_credential json
|
||||
inner join credential_static_store store
|
||||
on store.public_id = json.store_id
|
||||
where store.project_id = ?
|
||||
and json.key_id = ?;
|
||||
`
|
||||
)
|
||||
@@ -410,7 +410,7 @@ func (r *Repository) UpdateUsernamePasswordCredential(ctx context.Context,
|
||||
}
|
||||
|
||||
// Set PasswordHmac and CtPassword masks for update.
|
||||
dbMask = append(dbMask, "PasswordHmac", "CtPassword")
|
||||
dbMask = append(dbMask, "PasswordHmac", "CtPassword", "KeyId")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,6 +531,7 @@ func (r *Repository) UpdateSshPrivateKeyCredential(ctx context.Context,
|
||||
}
|
||||
}
|
||||
|
||||
dbMask = append(dbMask, "KeyId")
|
||||
if strings.EqualFold(privateKeyField, f) {
|
||||
// Set PrivateKeyHmac and PrivateKeyEncrypted masks for update.
|
||||
dbMask = append(dbMask, "PrivateKeyHmac", "PrivateKeyEncrypted")
|
||||
@@ -672,7 +673,7 @@ func (r *Repository) UpdateJsonCredential(ctx context.Context,
|
||||
}
|
||||
|
||||
// Set ObjectHmac and ObjectEncrypted masks for update.
|
||||
dbMask = append(dbMask, "ObjectHmac", "ObjectEncrypted")
|
||||
dbMask = append(dbMask, "ObjectHmac", "ObjectEncrypted", "KeyId")
|
||||
}
|
||||
|
||||
oplogWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeOplog)
|
||||
|
||||
@@ -1342,6 +1342,47 @@ func TestRepository_UpdateUsernamePasswordCredential(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_UpdatePasswordCredentialKeyUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kkms := kms.TestKms(t, conn, wrapper)
|
||||
repo, err := NewRepository(ctx, rw, rw, kkms)
|
||||
require.NoError(err)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
credStore := TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
|
||||
orig, err := repo.CreateUsernamePasswordCredential(ctx, prj.GetPublicId(), &UsernamePasswordCredential{
|
||||
UsernamePasswordCredential: &store.UsernamePasswordCredential{
|
||||
Username: "user",
|
||||
Password: []byte("pass"),
|
||||
StoreId: credStore.PublicId,
|
||||
},
|
||||
})
|
||||
require.NoError(err)
|
||||
|
||||
err = kkms.RotateKeys(ctx, prj.GetPublicId())
|
||||
require.NoError(err)
|
||||
|
||||
orig.Password = []byte("pass1") // Company policy to change password every 3 months
|
||||
|
||||
got, _, err := repo.UpdateUsernamePasswordCredential(ctx, prj.GetPublicId(), orig, orig.GetVersion(), []string{"Password"})
|
||||
require.NoError(err)
|
||||
|
||||
// Validate that the KeyId has changed
|
||||
assert.NotEqual(orig.KeyId, got.KeyId)
|
||||
|
||||
// Validate hmac
|
||||
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase, kms.WithKeyId(got.KeyId))
|
||||
require.NoError(err)
|
||||
hm, err := crypto.HmacSha256(ctx, orig.Password, databaseWrapper, []byte(credStore.GetPublicId()), nil, crypto.WithEd25519())
|
||||
require.NoError(err)
|
||||
assert.Equal([]byte(hm), got.PasswordHmac)
|
||||
}
|
||||
|
||||
func TestRepository_UpdateSshPrivateKeyCredential(t *testing.T) {
|
||||
const testSecondarySshPrivateKeyPem = `
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
kms.RegisterTableRewrapFn("credential_static_username_password_credential", credStaticUsernamePasswordRewrapFn)
|
||||
kms.RegisterTableRewrapFn("credential_static_ssh_private_key_credential", credStaticSshPrivKeyRewrapFn)
|
||||
kms.RegisterTableRewrapFn("credential_static_json_credential", credStaticJsonRewrapFn)
|
||||
}
|
||||
|
||||
func rewrapParameterChecks(ctx context.Context, dataKeyVersionId string, scopeId string, reader db.Reader, writer db.Writer, kmsRepo *kms.Kms) string {
|
||||
if dataKeyVersionId == "" {
|
||||
return "missing data key version id"
|
||||
}
|
||||
if scopeId == "" {
|
||||
return "missing scope id"
|
||||
}
|
||||
if util.IsNil(reader) {
|
||||
return "missing database reader"
|
||||
}
|
||||
if util.IsNil(writer) {
|
||||
return "missing database writer"
|
||||
}
|
||||
if kmsRepo == nil {
|
||||
return "missing kms repository"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func credStaticUsernamePasswordRewrapFn(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kmsRepo *kms.Kms) error {
|
||||
const op = "static.credStaticUsernamePasswordRewrapFn"
|
||||
if errStr := rewrapParameterChecks(ctx, dataKeyVersionId, scopeId, reader, writer, kmsRepo); errStr != "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, errStr)
|
||||
}
|
||||
var creds []*UsernamePasswordCredential
|
||||
// Indexes exist on (store_id, etc), so we can query static stores via scope and refine with key id.
|
||||
// This is the fastest query we can use without creating a new index on key_id.
|
||||
rows, err := reader.Query(ctx, credStaticUsernamePasswordRewrapQuery, []any{scopeId, dataKeyVersionId})
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query sql for rows that need rewrapping"))
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
cred := allocUsernamePasswordCredential()
|
||||
if err := rows.Scan(
|
||||
&cred.PublicId,
|
||||
&cred.CtPassword,
|
||||
&cred.KeyId,
|
||||
); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to failed to scan row"))
|
||||
}
|
||||
creds = append(creds, cred)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to iterate over retrieved rows"))
|
||||
}
|
||||
wrapper, err := kmsRepo.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to fetch kms wrapper for rewrapping"))
|
||||
}
|
||||
for _, cred := range creds {
|
||||
if err := cred.decrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to decrypt username/password credential"))
|
||||
}
|
||||
if err := cred.encrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to re-encrypt username/password credential"))
|
||||
}
|
||||
if _, err := writer.Update(ctx, cred, []string{"CtPassword", "KeyId"}, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to update username/password credential row with rewrapped fields"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func credStaticSshPrivKeyRewrapFn(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kmsRepo *kms.Kms) error {
|
||||
const op = "static.credStaticSshPrivKeyRewrapFn"
|
||||
if errStr := rewrapParameterChecks(ctx, dataKeyVersionId, scopeId, reader, writer, kmsRepo); errStr != "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, errStr)
|
||||
}
|
||||
var creds []*SshPrivateKeyCredential
|
||||
// Indexes exist on (store_id, etc), so we can query static stores via scope and refine with key id.
|
||||
// This is the fastest query we can use without creating a new index on key_id.
|
||||
rows, err := reader.Query(ctx, credStaticSshPrivKeyRewrapQuery, []any{scopeId, dataKeyVersionId})
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query sql for rows that need rewrapping"))
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
cred := allocSshPrivateKeyCredential()
|
||||
if err := rows.Scan(
|
||||
&cred.PublicId,
|
||||
&cred.PrivateKeyEncrypted,
|
||||
&cred.PrivateKeyPassphraseEncrypted,
|
||||
&cred.KeyId,
|
||||
); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to failed to scan row"))
|
||||
}
|
||||
creds = append(creds, cred)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to iterate over retrieved rows"))
|
||||
}
|
||||
wrapper, err := kmsRepo.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to fetch kms wrapper for rewrapping"))
|
||||
}
|
||||
for _, cred := range creds {
|
||||
if err := cred.decrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to decrypt ssh private key"))
|
||||
}
|
||||
if err := cred.encrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to re-encrypt ssh private key"))
|
||||
}
|
||||
if _, err := writer.Update(ctx, cred, []string{"PrivateKeyEncrypted", "PrivateKeyPassphraseEncrypted", "KeyId"}, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to update ssh private key row with rewrapped fields"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func credStaticJsonRewrapFn(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kmsRepo *kms.Kms) error {
|
||||
const op = "static.credStaticJsonRewrapFn"
|
||||
if errStr := rewrapParameterChecks(ctx, dataKeyVersionId, scopeId, reader, writer, kmsRepo); errStr != "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, errStr)
|
||||
}
|
||||
var creds []*JsonCredential
|
||||
// Indexes exist on (store_id, etc), so we can query static stores via scope and refine with key id.
|
||||
// This is the fastest query we can use without creating a new index on key_id.
|
||||
rows, err := reader.Query(ctx, credStaticJsonRewrapQuery, []any{scopeId, dataKeyVersionId})
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query sql for rows that need rewrapping"))
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
cred := allocJsonCredential()
|
||||
if err := rows.Scan(
|
||||
&cred.PublicId,
|
||||
&cred.ObjectEncrypted,
|
||||
&cred.KeyId,
|
||||
); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to failed to scan row"))
|
||||
}
|
||||
creds = append(creds, cred)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to iterate over retrieved rows"))
|
||||
}
|
||||
wrapper, err := kmsRepo.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to fetch kms wrapper for rewrapping"))
|
||||
}
|
||||
for _, cred := range creds {
|
||||
if err := cred.decrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to decrypt json credential"))
|
||||
}
|
||||
if err := cred.encrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to re-encrypt json credential"))
|
||||
}
|
||||
if _, err := writer.Update(ctx, cred, []string{"ObjectEncrypted", "KeyId"}, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to update json credential row with rewrapped fields"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/hashicorp/boundary/internal/credential"
|
||||
"github.com/hashicorp/boundary/internal/credential/static/store"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/go-kms-wrapping/extras/kms/v2/migrations"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRewrap_credStaticUsernamePasswordRewrapFn(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("errors-on-query-error", func(t *testing.T) {
|
||||
conn, mock := db.TestSetupWithMock(t)
|
||||
wrapper := db.TestWrapper(t)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "kms_schema_version" WHERE 1=1 ORDER BY "kms_schema_version"\."version" LIMIT 1`,
|
||||
).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now()))
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
mock.ExpectQuery(
|
||||
`select distinct userpass\.public_id, userpass\.password_encrypted, userpass\.key_id from credential_static_username_password_credential userpass inner join credential_static_store store on store\.public_id = userpass\.store_id where store\.project_id = \$1 and userpass\.key_id = \$2;`,
|
||||
).WillReturnError(errors.New("Query error"))
|
||||
err := credStaticUsernamePasswordRewrapFn(ctx, "some_id", "some_scope", rw, rw, kmsCache)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("success", func(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
cs := TestCredentialStore(t, conn, wrapper, prj.PublicId)
|
||||
cred, err := NewUsernamePasswordCredential(cs.GetPublicId(), "username", "password")
|
||||
assert.NoError(t, err)
|
||||
|
||||
cred.PublicId, err = credential.NewUsernamePasswordCredentialId(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
kmsWrapper, err := kmsCache.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, cred.encrypt(ctx, kmsWrapper))
|
||||
assert.NoError(t, rw.Create(context.Background(), cred))
|
||||
|
||||
// now things are stored in the db, we can rotate and rewrap
|
||||
assert.NoError(t, kmsCache.RotateKeys(ctx, prj.PublicId))
|
||||
assert.NoError(t, credStaticUsernamePasswordRewrapFn(ctx, cred.GetKeyId(), prj.PublicId, rw, rw, kmsCache))
|
||||
|
||||
// now we pull the credential back from the db, decrypt it with the new key, and ensure things match
|
||||
got := allocUsernamePasswordCredential()
|
||||
got.PublicId = cred.PublicId
|
||||
assert.NoError(t, rw.LookupById(ctx, got))
|
||||
|
||||
kmsWrapper2, err := kmsCache.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase, kms.WithKeyId(got.GetKeyId()))
|
||||
assert.NoError(t, err)
|
||||
newKeyVersionId, err := kmsWrapper2.KeyId(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// decrypt with the new key version and check to make sure things match
|
||||
assert.NoError(t, got.decrypt(ctx, kmsWrapper2))
|
||||
assert.NotEmpty(t, got.GetKeyId())
|
||||
assert.NotEqual(t, cred.GetKeyId(), got.GetKeyId())
|
||||
assert.Equal(t, newKeyVersionId, got.GetKeyId())
|
||||
assert.Equal(t, "password", string(got.GetPassword()))
|
||||
assert.NotEmpty(t, got.GetPasswordHmac())
|
||||
assert.Equal(t, cred.GetPasswordHmac(), got.GetPasswordHmac())
|
||||
})
|
||||
}
|
||||
|
||||
func TestRewrap_credStaticSshPrivKeyRewrapFn(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("errors-on-query-error", func(t *testing.T) {
|
||||
conn, mock := db.TestSetupWithMock(t)
|
||||
wrapper := db.TestWrapper(t)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "kms_schema_version" WHERE 1=1 ORDER BY "kms_schema_version"\."version" LIMIT 1`,
|
||||
).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now()))
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
mock.ExpectQuery(
|
||||
`select distinct ssh\.public_id, ssh\.private_key_encrypted, ssh\.private_key_passphrase_encrypted, ssh\.key_id from credential_static_ssh_private_key_credential ssh inner join credential_static_store store on store\.public_id = ssh\.store_id where store\.project_id = \$1 and ssh\.key_id = \$2;`,
|
||||
).WillReturnError(errors.New("Query error"))
|
||||
err := credStaticSshPrivKeyRewrapFn(ctx, "some_id", "some_scope", rw, rw, kmsCache)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("success", func(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
|
||||
// since there are two possible versions (with or without passphrase) we need to make 2 copies of everything, but rewrap only once
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
cs := TestCredentialStore(t, conn, wrapper, prj.PublicId)
|
||||
cs2 := TestCredentialStore(t, conn, wrapper, prj.PublicId)
|
||||
|
||||
cred, err := NewSshPrivateKeyCredential(ctx, cs.GetPublicId(), "username", credential.PrivateKey(TestSshPrivateKeyPem))
|
||||
assert.NoError(t, err)
|
||||
|
||||
// we need to assign this one explicitly since the new function (correctly) has some checks on the passphrase actually being correct
|
||||
cred2 := &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
StoreId: cs2.GetPublicId(),
|
||||
Username: "username",
|
||||
PrivateKey: credential.PrivateKey(TestSshPrivateKeyPem),
|
||||
PrivateKeyPassphrase: []byte("passphrase"),
|
||||
},
|
||||
}
|
||||
|
||||
cred.PublicId, err = credential.NewSshPrivateKeyCredentialId(ctx)
|
||||
assert.NoError(t, err)
|
||||
cred2.PublicId, err = credential.NewSshPrivateKeyCredentialId(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
kmsWrapper, err := kmsCache.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, cred.encrypt(ctx, kmsWrapper))
|
||||
assert.NoError(t, cred2.encrypt(ctx, kmsWrapper))
|
||||
|
||||
// create them in the db
|
||||
assert.NoError(t, rw.Create(context.Background(), cred))
|
||||
assert.NoError(t, rw.Create(context.Background(), cred2))
|
||||
|
||||
// now things are stored in the db, we can rotate and rewrap
|
||||
assert.NoError(t, kmsCache.RotateKeys(ctx, prj.PublicId))
|
||||
assert.NoError(t, credStaticSshPrivKeyRewrapFn(ctx, cred.GetKeyId(), prj.PublicId, rw, rw, kmsCache))
|
||||
|
||||
// now we pull both credential2 back from the db, decrypt them with the new key, and ensure things match
|
||||
got := allocSshPrivateKeyCredential()
|
||||
got.PublicId = cred.PublicId
|
||||
assert.NoError(t, rw.LookupById(ctx, got))
|
||||
|
||||
got2 := allocSshPrivateKeyCredential()
|
||||
got2.PublicId = cred2.PublicId
|
||||
assert.NoError(t, rw.LookupById(ctx, got2))
|
||||
|
||||
kmsWrapper2, err := kmsCache.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase, kms.WithKeyId(got.GetKeyId()))
|
||||
assert.NoError(t, err)
|
||||
|
||||
newKeyVersionId, err := kmsWrapper2.KeyId(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// decrypt with the new key version and check to make sure things match
|
||||
assert.NoError(t, got.decrypt(ctx, kmsWrapper2))
|
||||
assert.NotEmpty(t, got.GetKeyId())
|
||||
assert.NotEqual(t, cred.GetKeyId(), got.GetKeyId())
|
||||
assert.Equal(t, newKeyVersionId, got.GetKeyId())
|
||||
assert.Equal(t, TestSshPrivateKeyPem, string(got.PrivateKey))
|
||||
assert.NotEqual(t, cred.GetPrivateKeyEncrypted(), got.GetPrivateKeyEncrypted())
|
||||
assert.NotEmpty(t, got.GetPrivateKeyHmac())
|
||||
assert.Equal(t, cred.GetPrivateKeyHmac(), got.GetPrivateKeyHmac())
|
||||
// we didn't set this, so they should be empty before AND after rewrapping
|
||||
assert.Empty(t, got.GetPrivateKeyPassphrase())
|
||||
assert.Empty(t, got.GetPrivateKeyPassphraseHmac())
|
||||
assert.Empty(t, got.GetPrivateKeyPassphraseEncrypted())
|
||||
|
||||
// perform all the same checks again on #2, but also check passphrase
|
||||
assert.NoError(t, got2.decrypt(ctx, kmsWrapper2))
|
||||
assert.NotEmpty(t, got2.GetKeyId())
|
||||
assert.NotEqual(t, cred2.GetKeyId(), got2.GetKeyId())
|
||||
assert.Equal(t, newKeyVersionId, got2.GetKeyId())
|
||||
assert.Equal(t, TestSshPrivateKeyPem, string(got2.PrivateKey))
|
||||
assert.NotEqual(t, cred.GetPrivateKeyEncrypted(), got.GetPrivateKeyEncrypted())
|
||||
assert.NotEmpty(t, got2.GetPrivateKeyHmac())
|
||||
assert.Equal(t, cred2.GetPrivateKeyHmac(), got2.GetPrivateKeyHmac())
|
||||
// this time, we did set this, so they should be available
|
||||
assert.NotEmpty(t, got2.GetPrivateKeyPassphraseEncrypted())
|
||||
assert.NotEmpty(t, got2.GetPrivateKeyPassphraseHmac())
|
||||
assert.Equal(t, []byte("passphrase"), got2.GetPrivateKeyPassphrase())
|
||||
assert.Equal(t, cred2.GetPrivateKeyPassphraseHmac(), got2.GetPrivateKeyPassphraseHmac())
|
||||
})
|
||||
}
|
||||
|
||||
func TestRewrap_credStaticJsonRewrapFn(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("errors-on-query-error", func(t *testing.T) {
|
||||
conn, mock := db.TestSetupWithMock(t)
|
||||
wrapper := db.TestWrapper(t)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "kms_schema_version" WHERE 1=1 ORDER BY "kms_schema_version"\."version" LIMIT 1`,
|
||||
).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now()))
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
mock.ExpectQuery(
|
||||
`select distinct json\.public_id, json\.object_encrypted, json\.key_id from credential_static_json_credential json inner join credential_static_store store on store\.public_id = json\.store_id where store\.project_id = \$1 and json\.key_id = \$2;`,
|
||||
).WillReturnError(errors.New("Query error"))
|
||||
err := credStaticJsonRewrapFn(ctx, "some_id", "some_scope", rw, rw, kmsCache)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("success", func(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
cs := TestCredentialStore(t, conn, wrapper, prj.PublicId)
|
||||
obj, objBytes, err := TestJsonObject()
|
||||
assert.NoError(t, err)
|
||||
cred, err := NewJsonCredential(ctx, cs.GetPublicId(), obj)
|
||||
assert.NoError(t, err)
|
||||
|
||||
cred.PublicId, err = credential.NewJsonCredentialId(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
kmsWrapper, err := kmsCache.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, cred.encrypt(ctx, kmsWrapper))
|
||||
assert.NoError(t, rw.Create(context.Background(), cred))
|
||||
|
||||
// now things are stored in the db, we can rotate and rewrap
|
||||
assert.NoError(t, kmsCache.RotateKeys(ctx, prj.PublicId))
|
||||
assert.NoError(t, credStaticJsonRewrapFn(ctx, cred.GetKeyId(), prj.PublicId, rw, rw, kmsCache))
|
||||
|
||||
// now we pull the credential back from the db, decrypt it with the new key, and ensure things match
|
||||
got := allocJsonCredential()
|
||||
got.PublicId = cred.PublicId
|
||||
assert.NoError(t, rw.LookupById(ctx, got))
|
||||
|
||||
kmsWrapper2, err := kmsCache.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase, kms.WithKeyId(got.GetKeyId()))
|
||||
assert.NoError(t, err)
|
||||
newKeyVersionId, err := kmsWrapper2.KeyId(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// decrypt with the new key version and check to make sure things match
|
||||
assert.NoError(t, got.decrypt(ctx, kmsWrapper2))
|
||||
assert.NotEmpty(t, got.GetKeyId())
|
||||
assert.NotEqual(t, cred.GetKeyId(), got.GetKeyId())
|
||||
assert.Equal(t, newKeyVersionId, got.GetKeyId())
|
||||
assert.Equal(t, objBytes, got.GetObject())
|
||||
assert.NotEmpty(t, got.GetObjectHmac())
|
||||
assert.Equal(t, cred.GetObjectHmac(), got.GetObjectHmac())
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
kms.RegisterTableRewrapFn("credential_vault_client_certificate", credVaultClientCertificateRewrapFn)
|
||||
kms.RegisterTableRewrapFn("credential_vault_token", credVaultTokenRewrapFn)
|
||||
}
|
||||
|
||||
func rewrapParameterChecks(ctx context.Context, dataKeyVersionId string, scopeId string, reader db.Reader, writer db.Writer, kmsRepo *kms.Kms) string {
|
||||
if dataKeyVersionId == "" {
|
||||
return "missing data key version id"
|
||||
}
|
||||
if scopeId == "" {
|
||||
return "missing scope id"
|
||||
}
|
||||
if util.IsNil(reader) {
|
||||
return "missing database reader"
|
||||
}
|
||||
if util.IsNil(writer) {
|
||||
return "missing database writer"
|
||||
}
|
||||
if kmsRepo == nil {
|
||||
return "missing kms repository"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func credVaultClientCertificateRewrapFn(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kmsRepo *kms.Kms) error {
|
||||
const op = "vault.credVaultClientCertificateRewrapFn"
|
||||
if errStr := rewrapParameterChecks(ctx, dataKeyVersionId, scopeId, reader, writer, kmsRepo); errStr != "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, errStr)
|
||||
}
|
||||
var certs []*ClientCertificate
|
||||
// only index is store id, and store isn't queryable via scope.
|
||||
// This is the fastest query we can use without creating a new index on key_id.
|
||||
if err := reader.SearchWhere(ctx, &certs, "key_id=?", []any{dataKeyVersionId}, db.WithLimit(-1)); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query sql for rows that need rewrapping"))
|
||||
}
|
||||
wrapper, err := kmsRepo.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to fetch kms wrapper for rewrapping"))
|
||||
}
|
||||
for _, cert := range certs {
|
||||
if err := cert.decrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to decrypt vault client certificate"))
|
||||
}
|
||||
if err := cert.encrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to re-encrypt vault client certificate"))
|
||||
}
|
||||
if _, err := writer.Update(ctx, cert, []string{"CtCertificateKey", "KeyId"}, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to update vault client certificate row with rewrapped fields"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func credVaultTokenRewrapFn(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kmsRepo *kms.Kms) error {
|
||||
const op = "vault.credVaultTokenRewrapFn"
|
||||
if errStr := rewrapParameterChecks(ctx, dataKeyVersionId, scopeId, reader, writer, kmsRepo); errStr != "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, errStr)
|
||||
}
|
||||
var tokens []*Token
|
||||
// Indexes exist on token hmac, store id, expiration time. none of which are queryable via scope or key.
|
||||
// This is the fastest query we can use without creating a new index on key_id.
|
||||
if err := reader.SearchWhere(ctx, &tokens, "key_id=?", []any{dataKeyVersionId}, db.WithLimit(-1)); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query sql for rows that need rewrapping"))
|
||||
}
|
||||
wrapper, err := kmsRepo.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to fetch kms wrapper for rewrapping"))
|
||||
}
|
||||
for _, token := range tokens {
|
||||
if err := token.decrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to decrypt vault token"))
|
||||
}
|
||||
if err := token.encrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to re-encrypt vault token"))
|
||||
}
|
||||
if _, err := writer.Update(ctx, token, []string{"CtToken", "KeyId"}, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to update vault token row with rewrapped fields"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/go-kms-wrapping/extras/kms/v2/migrations"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRewrap_credVaultClientCertificateRewrapFn(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("errors-on-query-error", func(t *testing.T) {
|
||||
conn, mock := db.TestSetupWithMock(t)
|
||||
wrapper := db.TestWrapper(t)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "kms_schema_version" WHERE 1=1 ORDER BY "kms_schema_version"\."version" LIMIT 1`,
|
||||
).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now()))
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "credential_vault_client_certificate" WHERE key_id=\$1`,
|
||||
).WillReturnError(errors.New("Query error"))
|
||||
err := credVaultClientCertificateRewrapFn(ctx, "some_id", "some_scope", rw, rw, kmsCache)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("success", func(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
cs := TestCredentialStore(t, conn, wrapper, prj.PublicId, "https://vault.consul.service", "token", "accessor")
|
||||
cert, err := NewClientCertificate([]byte(certPem), []byte(keyPem))
|
||||
assert.NoError(t, err)
|
||||
|
||||
cert.StoreId = cs.PublicId
|
||||
|
||||
kmsWrapper, err := kmsCache.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, cert.encrypt(ctx, kmsWrapper))
|
||||
assert.NoError(t, rw.Create(context.Background(), cert))
|
||||
|
||||
// now things are stored in the db, we can rotate and rewrap
|
||||
assert.NoError(t, kmsCache.RotateKeys(ctx, prj.PublicId))
|
||||
assert.NoError(t, credVaultClientCertificateRewrapFn(ctx, cert.GetKeyId(), prj.PublicId, rw, rw, kmsCache))
|
||||
|
||||
// now we pull the credential back from the db, decrypt it with the new key, and ensure things match
|
||||
got := allocClientCertificate()
|
||||
got.StoreId = cs.PublicId
|
||||
assert.NoError(t, rw.LookupById(ctx, got))
|
||||
|
||||
kmsWrapper2, err := kmsCache.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase, kms.WithKeyId(got.GetKeyId()))
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, got.decrypt(ctx, kmsWrapper2))
|
||||
|
||||
newKeyVersionId, err := kmsWrapper2.KeyId(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// decrypt with the new key version and check to make sure things match
|
||||
assert.NotEmpty(t, got.GetKeyId())
|
||||
assert.NotEqual(t, cert.GetKeyId(), got.GetKeyId())
|
||||
assert.Equal(t, newKeyVersionId, got.GetKeyId())
|
||||
assert.Equal(t, keyPem, string(got.GetCertificateKey()))
|
||||
assert.NotEmpty(t, got.GetCertificateKeyHmac())
|
||||
assert.Equal(t, cert.GetCertificateKeyHmac(), got.GetCertificateKeyHmac())
|
||||
})
|
||||
}
|
||||
|
||||
func TestRewrap_credVaultTokenRewrapFn(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("errors-on-query-error", func(t *testing.T) {
|
||||
conn, mock := db.TestSetupWithMock(t)
|
||||
wrapper := db.TestWrapper(t)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "kms_schema_version" WHERE 1=1 ORDER BY "kms_schema_version"\."version" LIMIT 1`,
|
||||
).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now()))
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "credential_vault_token" WHERE key_id=\$1`,
|
||||
).WillReturnError(errors.New("Query error"))
|
||||
err := credVaultTokenRewrapFn(ctx, "some_id", "some_scope", rw, rw, kmsCache)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("success", func(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
cs := TestCredentialStore(t, conn, wrapper, prj.PublicId, "https://vault.consul.service", "token", "accessor")
|
||||
token := cs.outputToken
|
||||
|
||||
// now things are stored in the db, we can rotate and rewrap
|
||||
assert.NoError(t, kmsCache.RotateKeys(ctx, prj.PublicId))
|
||||
assert.NoError(t, credVaultTokenRewrapFn(ctx, token.GetKeyId(), prj.PublicId, rw, rw, kmsCache))
|
||||
|
||||
// now we pull the token back from the db, decrypt it with the new key, and ensure things match
|
||||
got := allocToken()
|
||||
assert.NoError(t, rw.LookupWhere(ctx, &got, "store_id = ?", []any{cs.PublicId}))
|
||||
|
||||
kmsWrapper2, err := kmsCache.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase, kms.WithKeyId(got.GetKeyId()))
|
||||
assert.NoError(t, err)
|
||||
newKeyVersionId, err := kmsWrapper2.KeyId(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// decrypt with the new key version and check to make sure things match
|
||||
assert.NoError(t, got.decrypt(ctx, kmsWrapper2))
|
||||
assert.NotEmpty(t, got.GetKeyId())
|
||||
assert.NotEqual(t, token.GetKeyId(), got.GetKeyId())
|
||||
assert.Equal(t, newKeyVersionId, got.GetKeyId())
|
||||
assert.Equal(t, "token", string(got.GetToken()))
|
||||
assert.NotEmpty(t, got.GetTokenHmac())
|
||||
// vault token hmacs are calculated differently and should remain the same
|
||||
assert.Equal(t, token.GetTokenHmac(), got.GetTokenHmac())
|
||||
})
|
||||
}
|
||||
@@ -356,6 +356,7 @@ func (ws *workerServiceServer) LookupSession(ctx context.Context, req *pbs.Looku
|
||||
Authorization: &targets.SessionAuthorizationData{
|
||||
SessionId: sessionInfo.GetPublicId(),
|
||||
Certificate: sessionInfo.Certificate,
|
||||
PrivateKey: sessionInfo.CertificatePrivateKey,
|
||||
},
|
||||
Status: sessionInfo.States[0].Status.ProtoVal(),
|
||||
Version: sessionInfo.Version,
|
||||
@@ -374,18 +375,6 @@ func (ws *workerServiceServer) LookupSession(ctx context.Context, req *pbs.Looku
|
||||
resp.ConnectionsLeft -= int32(authzSummary.CurrentConnectionCount)
|
||||
}
|
||||
|
||||
wrapper, err := ws.kms.GetWrapper(ctx, sessionInfo.ProjectId, kms.KeyPurposeSessions, kms.WithKeyId(sessionInfo.KeyId))
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Error getting sessions wrapper: %v", err)
|
||||
}
|
||||
|
||||
// Derive the private key, which should match. Deriving on both ends allows
|
||||
// us to not store it in the DB.
|
||||
_, resp.Authorization.PrivateKey, err = session.DeriveED25519Key(ctx, wrapper, sessionInfo.UserId, sessionInfo.GetPublicId())
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Error deriving session key: %v", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/hashicorp/boundary/internal/authtoken"
|
||||
credstatic "github.com/hashicorp/boundary/internal/credential/static"
|
||||
"github.com/hashicorp/boundary/internal/daemon/cluster/handlers"
|
||||
@@ -23,6 +22,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/target"
|
||||
"github.com/hashicorp/boundary/internal/target/tcp"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/targets"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/ssh/testdata"
|
||||
@@ -180,6 +180,11 @@ func TestLookupSession(t *testing.T) {
|
||||
SessionId: sess.PublicId,
|
||||
},
|
||||
want: &pbs.LookupSessionResponse{
|
||||
Authorization: &targets.SessionAuthorizationData{
|
||||
SessionId: sess.PublicId,
|
||||
Certificate: sess.Certificate,
|
||||
PrivateKey: sess.CertificatePrivateKey,
|
||||
},
|
||||
ConnectionLimit: 1,
|
||||
ConnectionsLeft: 1,
|
||||
Version: 1,
|
||||
@@ -198,6 +203,11 @@ func TestLookupSession(t *testing.T) {
|
||||
WorkerId: worker1.GetPublicId(),
|
||||
},
|
||||
want: &pbs.LookupSessionResponse{
|
||||
Authorization: &targets.SessionAuthorizationData{
|
||||
SessionId: sess.PublicId,
|
||||
Certificate: sess.Certificate,
|
||||
PrivateKey: sess.CertificatePrivateKey,
|
||||
},
|
||||
ConnectionLimit: 1,
|
||||
ConnectionsLeft: 1,
|
||||
Version: 1,
|
||||
@@ -215,6 +225,11 @@ func TestLookupSession(t *testing.T) {
|
||||
SessionId: sessWithCreds.PublicId,
|
||||
},
|
||||
want: &pbs.LookupSessionResponse{
|
||||
Authorization: &targets.SessionAuthorizationData{
|
||||
SessionId: sessWithCreds.PublicId,
|
||||
Certificate: sessWithCreds.Certificate,
|
||||
PrivateKey: sessWithCreds.CertificatePrivateKey,
|
||||
},
|
||||
ConnectionLimit: 1,
|
||||
ConnectionsLeft: 1,
|
||||
Version: 1,
|
||||
@@ -244,9 +259,8 @@ func TestLookupSession(t *testing.T) {
|
||||
cmp.Diff(
|
||||
tc.want,
|
||||
got,
|
||||
cmpopts.IgnoreUnexported(pbs.LookupSessionResponse{},
|
||||
pbs.Credential{}, pbs.UsernamePassword{}, pbs.SshPrivateKey{}),
|
||||
cmpopts.IgnoreFields(pbs.LookupSessionResponse{}, "Expiration", "Authorization"),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.LookupSessionResponse{}, "expiration"),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/host/static"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
kmsjob "github.com/hashicorp/boundary/internal/kms/job"
|
||||
"github.com/hashicorp/boundary/internal/observability/event"
|
||||
"github.com/hashicorp/boundary/internal/plugin/host"
|
||||
hostplugin "github.com/hashicorp/boundary/internal/plugin/host"
|
||||
@@ -373,6 +374,9 @@ func New(ctx context.Context, conf *Config) (*Controller, error) {
|
||||
return target.NewRepository(ctx, dbase, dbase, c.kms, o...)
|
||||
}
|
||||
c.SessionRepoFn = func(opt ...session.Option) (*session.Repository, error) {
|
||||
// Always add a secure random reader to the new session repository.
|
||||
// Add it as the first option so that it can be overridden by users.
|
||||
opt = append([]session.Option{session.WithRandomReader(c.conf.SecureRandomReader)}, opt...)
|
||||
return session.NewRepository(ctx, dbase, dbase, c.kms, opt...)
|
||||
}
|
||||
c.ConnectionRepoFn = func() (*session.ConnectionRepository, error) {
|
||||
@@ -517,6 +521,9 @@ func (c *Controller) registerJobs() error {
|
||||
if err := serversjob.RegisterJobs(c.baseContext, c.scheduler, rw, rw, c.kms); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := kmsjob.RegisterJobs(c.baseContext, c.scheduler, c.kms); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -275,3 +275,44 @@ func TestController_NewPluginsConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ControllerStart(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
tc := &TestController{
|
||||
t: t,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
opts: nil,
|
||||
shutdownDoneCh: make(chan struct{}),
|
||||
shutdownOnce: new(sync.Once),
|
||||
}
|
||||
conf := TestControllerConfig(t, ctx, tc, nil)
|
||||
|
||||
c, err := New(ctx, conf)
|
||||
require.NoError(t, err)
|
||||
err = c.Start()
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
assert.NoError(t, c.Shutdown())
|
||||
})
|
||||
|
||||
sqlDb, err := tc.DbConn().SqlDB(ctx)
|
||||
require.NoError(t, err)
|
||||
rows, err := sqlDb.QueryContext(ctx, "select name from job")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
assert.NoError(t, rows.Close())
|
||||
})
|
||||
var jobNames []string
|
||||
for rows.Next() {
|
||||
var jobName string
|
||||
err := rows.Scan(&jobName)
|
||||
require.NoError(t, err)
|
||||
jobNames = append(jobNames, jobName)
|
||||
}
|
||||
require.NoError(t, rows.Err())
|
||||
// Check that the monitor job and at least one of the rewrapping jobs has been registered
|
||||
assert.Contains(t, jobNames, "data-key-version-destruction-monitor-job")
|
||||
assert.Contains(t, jobNames, "session-rewrapping-job")
|
||||
}
|
||||
@@ -153,7 +153,7 @@ func (c *Controller) registerGrpcServices(s *grpc.Server) error {
|
||||
services.RegisterAuthTokenServiceServer(s, authtoks)
|
||||
}
|
||||
if _, ok := currentServices[services.ScopeService_ServiceDesc.ServiceName]; !ok {
|
||||
os, err := scopes.NewService(c.IamRepoFn)
|
||||
os, err := scopes.NewService(c.baseContext, c.IamRepoFn, c.kms)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create scope handler service: %w", err)
|
||||
}
|
||||
|
||||
@@ -27,13 +27,17 @@ import (
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/iam/store"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/hashicorp/boundary/internal/util"
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
|
||||
wrappingKms "github.com/hashicorp/go-kms-wrapping/extras/kms/v2"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
)
|
||||
|
||||
@@ -54,6 +58,10 @@ var (
|
||||
CollectionActions = action.ActionSet{
|
||||
action.Create,
|
||||
action.List,
|
||||
action.ListScopeKeys,
|
||||
action.RotateScopeKeys,
|
||||
action.ListScopeKeyVersionDestructionJobs,
|
||||
action.DestroyScopeKeyVersion,
|
||||
}
|
||||
|
||||
scopeCollectionTypeMapMap = map[string]map[resource.Type]action.ActionSet{
|
||||
@@ -81,6 +89,7 @@ var (
|
||||
resource.Group: groups.CollectionActions,
|
||||
resource.HostCatalog: host_catalogs.CollectionActions,
|
||||
resource.Role: roles.CollectionActions,
|
||||
resource.Scope: CollectionActions[2:], // Only Scope key actions are allowed on the project level
|
||||
resource.Session: sessions.CollectionActions,
|
||||
resource.Target: targets.CollectionActions,
|
||||
},
|
||||
@@ -98,18 +107,22 @@ func init() {
|
||||
type Service struct {
|
||||
pbs.UnsafeScopeServiceServer
|
||||
|
||||
repoFn common.IamRepoFactory
|
||||
repoFn common.IamRepoFactory
|
||||
kmsRepo *kms.Kms
|
||||
}
|
||||
|
||||
var _ pbs.ScopeServiceServer = (*Service)(nil)
|
||||
|
||||
// NewService returns a project service which handles project related requests to boundary.
|
||||
func NewService(repo common.IamRepoFactory) (Service, error) {
|
||||
func NewService(ctx context.Context, repo common.IamRepoFactory, kmsRepo *kms.Kms) (Service, error) {
|
||||
const op = "scopes.(Service).NewService"
|
||||
if repo == nil {
|
||||
return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing iam repository")
|
||||
if util.IsNil(repo) {
|
||||
return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing iam repository")
|
||||
}
|
||||
return Service{repoFn: repo}, nil
|
||||
if kmsRepo == nil {
|
||||
return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing kms")
|
||||
}
|
||||
return Service{repoFn: repo, kmsRepo: kmsRepo}, nil
|
||||
}
|
||||
|
||||
// ListScopes implements the interface pbs.ScopeServiceServer.
|
||||
@@ -355,6 +368,142 @@ func (s Service) DeleteScope(ctx context.Context, req *pbs.DeleteScopeRequest) (
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ListKeys implements the interface pbs.ScopeServiceServer.
|
||||
func (s Service) ListKeys(ctx context.Context, req *pbs.ListKeysRequest) (*pbs.ListKeysResponse, error) {
|
||||
if req.GetId() == "" {
|
||||
req.Id = scope.Global.String()
|
||||
}
|
||||
if err := validateListKeysRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authResults := s.authResult(ctx, req.GetId(), action.ListScopeKeys)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
|
||||
items, err := s.kmsRepo.ListKeys(ctx, req.GetId())
|
||||
if err != nil {
|
||||
if errors.IsNotFoundError(err) {
|
||||
return nil, handlers.NotFoundErrorf("unknown scope_id %q", req.Id)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
finalItems := make([]*pb.Key, 0, len(items))
|
||||
res := perms.Resource{
|
||||
Type: resource.Scope,
|
||||
}
|
||||
for _, item := range items {
|
||||
res.Id = item.Id
|
||||
res.ScopeId = item.Scope
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.ListScopeKeys).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
|
||||
protoItem, err := keyToProto(ctx, item, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems = append(finalItems, protoItem)
|
||||
}
|
||||
sortKeys(finalItems)
|
||||
return &pbs.ListKeysResponse{Items: finalItems}, nil
|
||||
}
|
||||
|
||||
// RotateKeys implements the interface pbs.ScopeServiceServer.
|
||||
func (s Service) RotateKeys(ctx context.Context, req *pbs.RotateKeysRequest) (*pbs.RotateKeysResponse, error) {
|
||||
if req.GetScopeId() == "" {
|
||||
req.ScopeId = scope.Global.String()
|
||||
}
|
||||
if err := validateRotateKeysRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authResults := s.authResult(ctx, req.GetScopeId(), action.RotateScopeKeys)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
|
||||
if err := s.kmsRepo.RotateKeys(ctx, req.GetScopeId(), kms.WithRewrap(req.GetRewrap())); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ListKeyVersionDestructionJobs implements the interface pbs.ScopeServiceServer.
|
||||
func (s Service) ListKeyVersionDestructionJobs(ctx context.Context, req *pbs.ListKeyVersionDestructionJobsRequest) (*pbs.ListKeyVersionDestructionJobsResponse, error) {
|
||||
if req.GetScopeId() == "" {
|
||||
req.ScopeId = scope.Global.String()
|
||||
}
|
||||
if err := validateListKeyVersionDestructionJobsRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authResults := s.authResult(ctx, req.GetScopeId(), action.ListScopeKeyVersionDestructionJobs)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
jobs, err := s.kmsRepo.ListDataKeyVersionDestructionJobs(ctx, req.GetScopeId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := perms.Resource{
|
||||
Type: resource.Scope,
|
||||
}
|
||||
finalJobs := make([]*pb.KeyVersionDestructionJob, 0, len(jobs))
|
||||
for _, job := range jobs {
|
||||
res.Id = job.ScopeId
|
||||
res.ScopeId = job.ScopeId
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.ListScopeKeyVersionDestructionJobs).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
|
||||
protoJob, err := destructionJobToProto(ctx, job, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalJobs = append(finalJobs, protoJob)
|
||||
}
|
||||
return &pbs.ListKeyVersionDestructionJobsResponse{
|
||||
Items: finalJobs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DestroyKeyVersion implements the interface pbs.ScopeServiceServer.
|
||||
func (s Service) DestroyKeyVersion(ctx context.Context, req *pbs.DestroyKeyVersionRequest) (*pbs.DestroyKeyVersionResponse, error) {
|
||||
if req.GetScopeId() == "" {
|
||||
req.ScopeId = scope.Global.String()
|
||||
}
|
||||
if err := validateDestroyKeyVersionRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authResults := s.authResult(ctx, req.GetScopeId(), action.DestroyScopeKeyVersion)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
destroyed, err := s.kmsRepo.DestroyKeyVersion(ctx, req.GetScopeId(), req.GetKeyVersionId())
|
||||
if err != nil {
|
||||
if errors.Match(errors.T(errors.KeyNotFound), err) {
|
||||
return nil, handlers.NotFoundErrorf("unknown key_version_id %q", req.KeyVersionId)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
state := "completed"
|
||||
if !destroyed {
|
||||
state = "pending"
|
||||
}
|
||||
return &pbs.DestroyKeyVersionResponse{
|
||||
State: state,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s Service) getFromRepo(ctx context.Context, id string) (*iam.Scope, error) {
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
@@ -490,6 +639,21 @@ func SortScopes(scps []*pb.Scope) {
|
||||
})
|
||||
}
|
||||
|
||||
func sortKeys(keys []*pb.Key) {
|
||||
// We stable sort here even though the database may not return things in
|
||||
// sorted order, still nice to have them as consistent as possible.
|
||||
sort.SliceStable(keys, func(i, j int) bool {
|
||||
return keys[i].GetId() < keys[j].GetId()
|
||||
})
|
||||
// we also want to sort the key versions by version id so they are in
|
||||
// descending order (newest first)
|
||||
for _, key := range keys {
|
||||
sort.Slice(key.Versions, func(i, j int) bool {
|
||||
return key.Versions[i].Version > key.Versions[j].Version
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*iam.Scope, error) {
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
@@ -513,7 +677,7 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
|
||||
var parentId string
|
||||
opts := []auth.Option{auth.WithType(resource.Scope), auth.WithAction(a)}
|
||||
switch a {
|
||||
case action.List, action.Create:
|
||||
case action.List, action.Create, action.ListScopeKeys, action.ListScopeKeyVersionDestructionJobs, action.DestroyScopeKeyVersion:
|
||||
parentId = id
|
||||
s, err := repo.LookupScope(ctx, parentId)
|
||||
if err != nil {
|
||||
@@ -589,6 +753,71 @@ func ToProto(ctx context.Context, in *iam.Scope, opt ...handlers.Option) (*pb.Sc
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func keyToProto(ctx context.Context, in wrappingKms.Key, opt ...handlers.Option) (*pb.Key, error) {
|
||||
opts := handlers.GetOpts(opt...)
|
||||
if opts.WithOutputFields == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building key proto")
|
||||
}
|
||||
outputFields := *opts.WithOutputFields
|
||||
|
||||
out := pb.Key{}
|
||||
if outputFields.Has(globals.IdField) {
|
||||
out.Id = in.Id
|
||||
}
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
out.Scope = opts.WithScope
|
||||
}
|
||||
if outputFields.Has(globals.KeyPurposeField) {
|
||||
out.Purpose = string(in.Purpose)
|
||||
}
|
||||
if outputFields.Has(globals.CreatedTimeField) {
|
||||
out.CreatedTime = timestamppb.New(in.CreateTime)
|
||||
}
|
||||
if outputFields.Has(globals.TypeField) {
|
||||
out.Type = string(in.Type)
|
||||
}
|
||||
if outputFields.Has(globals.KeyVersionsField) {
|
||||
for _, keyVersion := range in.Versions {
|
||||
out.Versions = append(out.Versions, &pb.KeyVersion{
|
||||
Id: keyVersion.Id,
|
||||
Version: uint32(keyVersion.Version),
|
||||
CreatedTime: timestamppb.New(keyVersion.CreateTime),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func destructionJobToProto(ctx context.Context, in *kms.DataKeyVersionDestructionJobProgress, opt ...handlers.Option) (*pb.KeyVersionDestructionJob, error) {
|
||||
opts := handlers.GetOpts(opt...)
|
||||
if opts.WithOutputFields == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building data key version destruction job proto")
|
||||
}
|
||||
outputFields := *opts.WithOutputFields
|
||||
|
||||
out := pb.KeyVersionDestructionJob{}
|
||||
if outputFields.Has(globals.KeyVersionIdField) {
|
||||
out.KeyVersionId = in.KeyId
|
||||
}
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
out.Scope = opts.WithScope
|
||||
}
|
||||
if outputFields.Has(globals.CreatedTimeField) {
|
||||
out.CreatedTime = in.CreateTime.Timestamp
|
||||
}
|
||||
if outputFields.Has(globals.CompletedCountField) {
|
||||
out.CompletedCount = in.CompletedCount
|
||||
}
|
||||
if outputFields.Has(globals.TotalCountField) {
|
||||
out.TotalCount = in.TotalCount
|
||||
}
|
||||
if outputFields.Has(globals.StatusField) {
|
||||
out.Status = in.Status
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
|
||||
// requirements on the structure of the request. They verify that:
|
||||
// - The path passed in is correctly formatted
|
||||
@@ -745,3 +974,51 @@ func validateListRequest(req *pbs.ListScopesRequest) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateListKeysRequest(req *pbs.ListKeysRequest) error {
|
||||
badFields := map[string]string{}
|
||||
if req.GetId() != scope.Global.String() && !handlers.ValidId(handlers.Id(req.GetId()), scope.Org.Prefix()) && !handlers.ValidId(handlers.Id(req.GetId()), scope.Project.Prefix()) {
|
||||
badFields["id"] = "Must be 'global', a valid org scope id or a valid project scope id when listing keys."
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Error in provided request.", badFields)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateRotateKeysRequest(req *pbs.RotateKeysRequest) error {
|
||||
badFields := map[string]string{}
|
||||
if req.GetScopeId() != scope.Global.String() && !handlers.ValidId(handlers.Id(req.GetScopeId()), scope.Org.Prefix()) && !handlers.ValidId(handlers.Id(req.GetScopeId()), scope.Project.Prefix()) {
|
||||
badFields["id"] = "Must be 'global', a valid org scope id or a valid project scope id when listing keys."
|
||||
}
|
||||
// other field is just a bool so can't be validated
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Error in provided request.", badFields)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateListKeyVersionDestructionJobsRequest(req *pbs.ListKeyVersionDestructionJobsRequest) error {
|
||||
badFields := map[string]string{}
|
||||
if req.GetScopeId() != scope.Global.String() && !handlers.ValidId(handlers.Id(req.GetScopeId()), scope.Org.Prefix()) && !handlers.ValidId(handlers.Id(req.GetScopeId()), scope.Project.Prefix()) {
|
||||
badFields["scope_id"] = "Must be 'global', a valid org scope id or a valid project scope id when listing key version destruction jobs."
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Error in provided request.", badFields)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDestroyKeyVersionRequest(req *pbs.DestroyKeyVersionRequest) error {
|
||||
badFields := map[string]string{}
|
||||
if req.GetScopeId() != scope.Global.String() && !handlers.ValidId(handlers.Id(req.GetScopeId()), scope.Org.Prefix()) && !handlers.ValidId(handlers.Id(req.GetScopeId()), scope.Project.Prefix()) {
|
||||
badFields["scope_id"] = "Must be 'global', a valid org scope id or a valid project scope id when destroying a key version."
|
||||
}
|
||||
if !handlers.ValidId(handlers.Id(req.GetKeyVersionId()), "kdkv", "krkv") {
|
||||
badFields["key_version_id"] = "Must be a valid KEK or DEK version ID."
|
||||
}
|
||||
if len(badFields) > 0 {
|
||||
return handlers.InvalidArgumentErrorf("Error in provided request.", badFields)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -70,7 +70,7 @@ func (s Service) GetSession(ctx context.Context, req *pbs.GetSessionRequest) (*p
|
||||
if err := validateGetRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authResults := s.authResult(ctx, req.GetId(), action.ReadSelf)
|
||||
authResults := s.authResult(ctx, req.GetId(), action.ReadSelf, false)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
@@ -125,7 +125,7 @@ func (s Service) ListSessions(ctx context.Context, req *pbs.ListSessionsRequest)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authResults := s.authResult(ctx, req.GetScopeId(), action.List)
|
||||
authResults := s.authResult(ctx, req.GetScopeId(), action.List, false)
|
||||
if authResults.Error != nil {
|
||||
// If it's forbidden, and it's a recursive request, and they're
|
||||
// successfully authenticated but just not authorized, keep going as we
|
||||
@@ -212,17 +212,29 @@ func (s Service) CancelSession(ctx context.Context, req *pbs.CancelSessionReques
|
||||
if err := validateCancelRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authResults := s.authResult(ctx, req.GetId(), action.CancelSelf)
|
||||
// Ignore decryption failures to ensure the user can always cancel a session.
|
||||
authResults := s.authResult(ctx, req.GetId(), action.CancelSelf, true)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
|
||||
// We'll verify it's not already canceled, but after checking auth so as not
|
||||
// to leak that information.
|
||||
ses, err := s.getFromRepo(ctx, req.GetId())
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Ignore decryption failures to ensure the user can always cancel a session.
|
||||
ses, _, err := repo.LookupSession(ctx, req.GetId(), session.WithIgnoreDecryptionFailures(true))
|
||||
if err != nil {
|
||||
if errors.IsNotFoundError(err) {
|
||||
return nil, handlers.NotFoundErrorf("Session %q doesn't exist.", req.GetId())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if ses == nil {
|
||||
return nil, handlers.NotFoundErrorf("Session %q doesn't exist.", req.GetId())
|
||||
}
|
||||
|
||||
var outputFields perms.OutputFieldsMap
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, ses.GetPublicId(), IdActions)
|
||||
@@ -254,9 +266,10 @@ func (s Service) CancelSession(ctx context.Context, req *pbs.CancelSessionReques
|
||||
}
|
||||
|
||||
if !skipCancel {
|
||||
ses, err = s.cancelInRepo(ctx, req.GetId(), req.GetVersion())
|
||||
// Ignore decryption failures to ensure the user can always cancel a session.
|
||||
ses, err = repo.CancelSession(ctx, req.GetId(), req.GetVersion(), session.WithIgnoreDecryptionFailures(true))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update session"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,20 +307,7 @@ func (s Service) getFromRepo(ctx context.Context, id string) (*session.Session,
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
func (s Service) cancelInRepo(ctx context.Context, id string, version uint32) (*session.Session, error) {
|
||||
const op = "sessions.(Service).cancelInRepo"
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := repo.CancelSession(ctx, id, version)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update session"))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults {
|
||||
func (s Service) authResult(ctx context.Context, id string, a action.Type, ignoreSessionDecryptionFailure bool) auth.VerifyResults {
|
||||
res := auth.VerifyResults{}
|
||||
|
||||
var parentId string
|
||||
@@ -335,7 +335,7 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
|
||||
res.Error = err
|
||||
return res
|
||||
}
|
||||
t, _, err := repo.LookupSession(ctx, id)
|
||||
t, _, err := repo.LookupSession(ctx, id, session.WithIgnoreDecryptionFailures(ignoreSessionDecryptionFailure))
|
||||
if err != nil {
|
||||
res.Error = err
|
||||
return res
|
||||
|
||||
@@ -846,7 +846,7 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sess, privKey, err := sessionRepo.CreateSession(ctx, wrapper, sess, workerList(selectedWorkers).addresses())
|
||||
sess, err = sessionRepo.CreateSession(ctx, wrapper, sess, workerList(selectedWorkers).addresses())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -942,7 +942,7 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession
|
||||
CreatedTime: sess.CreateTime.GetTimestamp(),
|
||||
Type: t.GetType().String(),
|
||||
Certificate: sess.Certificate,
|
||||
PrivateKey: privKey,
|
||||
PrivateKey: sess.CertificatePrivateKey,
|
||||
HostId: chosenEndpoint.HostId,
|
||||
Endpoint: endpointUrl.String(),
|
||||
WorkerInfo: workerList(selectedWorkers).workerInfos(),
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_AllRewrapTablesRegistered(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
extWrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, extWrapper)
|
||||
|
||||
tables, err := kmsCache.ListDataKeyVersionReferencers(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var filteredTables []string
|
||||
// there are some tables we know won't be rewrapped
|
||||
for _, table := range tables {
|
||||
// oplog entries cannot be rewrapped at this time
|
||||
if table == "oplog_entry" {
|
||||
continue
|
||||
}
|
||||
// kms_data_key_version_destruction_job does not contain encrypted data, only a reference to the key versions
|
||||
if table == "kms_data_key_version_destruction_job" {
|
||||
continue
|
||||
}
|
||||
|
||||
// all other tables should be registered
|
||||
filteredTables = append(filteredTables, table)
|
||||
}
|
||||
|
||||
assert.Empty(t, cmp.Diff(filteredTables, kms.ListTablesSupportingRewrap(), cmpopts.SortSlices(func(i, j string) bool { return i < j })), "At least one table referencing a data key does not have a rewrapping function registered")
|
||||
}
|
||||
@@ -46,6 +46,10 @@ func (r *changeSafeDbwReader) SearchWhere(ctx context.Context, resources any, wh
|
||||
return dbw.New(r.db.underlying.wrapped.Load()).SearchWhere(ctx, resources, where, args, opt...)
|
||||
}
|
||||
|
||||
func (r *changeSafeDbwReader) Dialect() (_ dbw.DbType, rawName string, _ error) {
|
||||
return r.db.underlying.wrapped.Load().DbType()
|
||||
}
|
||||
|
||||
// changeSafeDbwWriter is a type that wraps a *db.Db as a dbw.Writer and ensures
|
||||
// that uses of the underlying database follows changes to the connection.
|
||||
type changeSafeDbwWriter struct {
|
||||
@@ -103,3 +107,7 @@ func (w *changeSafeDbwWriter) ScanRows(rows *sql.Rows, result any) error {
|
||||
func (w *changeSafeDbwWriter) Update(ctx context.Context, i any, fieldMaskPaths []string, setToNullPaths []string, opt ...dbw.Option) (int, error) {
|
||||
return dbw.New(w.db.underlying.wrapped.Load()).Update(ctx, i, fieldMaskPaths, setToNullPaths, opt...)
|
||||
}
|
||||
|
||||
func (w *changeSafeDbwWriter) Dialect() (_ dbw.DbType, rawName string, _ error) {
|
||||
return w.db.underlying.wrapped.Load().DbType()
|
||||
}
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
|
||||
func TestDb_Create_OnConflict(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
wrapper := db.TestWrapper(t)
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestDBWrapper(t, conn, "oplog")
|
||||
rw := db.New(conn)
|
||||
db.TestCreateTables(t, conn)
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ func TestDb_Update(t *testing.T) {
|
||||
publicId, err := NewPublicId("testuser")
|
||||
require.NoError(t, err)
|
||||
id := testId(t)
|
||||
wrapper := TestDBWrapper(t, conn, "oplog")
|
||||
|
||||
badVersion := uint32(22)
|
||||
versionOne := uint32(1)
|
||||
@@ -395,10 +396,10 @@ func TestDb_Update(t *testing.T) {
|
||||
user := testUser(t, conn, "foo-"+id, id, id)
|
||||
|
||||
user.Name = "friendly-" + id
|
||||
rowsUpdated, err := w.Update(context.Background(), user, []string{"Name"}, nil,
|
||||
rowsUpdated, err := w.Update(testCtx, user, []string{"Name"}, nil,
|
||||
// write oplogs for this update
|
||||
WithOplog(
|
||||
TestWrapper(t),
|
||||
wrapper,
|
||||
oplog.Metadata{
|
||||
"key-only": nil,
|
||||
"deployment": []string{"amex"},
|
||||
@@ -445,7 +446,7 @@ func TestDb_Update(t *testing.T) {
|
||||
user,
|
||||
[]string{"Name"}, nil,
|
||||
NewOplogMsg(&updateMsg),
|
||||
WithOplog(TestWrapper(t), oplog.Metadata{"alice": []string{"bob"}}),
|
||||
WithOplog(wrapper, oplog.Metadata{"alice": []string{"bob"}}),
|
||||
)
|
||||
require.Error(err)
|
||||
assert.Equal(0, rowsUpdated)
|
||||
@@ -488,7 +489,7 @@ func TestDb_Update(t *testing.T) {
|
||||
"resource-public-id": []string{user.PublicId},
|
||||
// "op-type": []string{oplog.OpType_OP_TYPE_UPDATE.String()},
|
||||
}
|
||||
err = w.WriteOplogEntryWith(context.Background(), TestWrapper(t), ticket, metadata, []*oplog.Message{&createMsg, &updateMsg})
|
||||
err = w.WriteOplogEntryWith(context.Background(), wrapper, ticket, metadata, []*oplog.Message{&createMsg, &updateMsg})
|
||||
require.NoError(err)
|
||||
|
||||
err = TestVerifyOplog(t, w, user.PublicId, WithOperation(oplog.OpType_OP_TYPE_UNSPECIFIED), WithCreateNotBefore(10*time.Second))
|
||||
@@ -568,7 +569,7 @@ func TestDb_Update(t *testing.T) {
|
||||
user.Name = "friendly-" + id
|
||||
rowsUpdated, err := w.Update(context.Background(), user, []string{"Name"}, nil,
|
||||
WithOplog(
|
||||
TestWrapper(t),
|
||||
wrapper,
|
||||
nil,
|
||||
),
|
||||
)
|
||||
@@ -632,6 +633,7 @@ func TestDb_Create(t *testing.T) {
|
||||
// intentionally not run with t.Parallel so we don't need to use DoTx for the Create tests
|
||||
conn, _ := TestSetup(t, "postgres")
|
||||
TestCreateTables(t, conn)
|
||||
wrapper := TestDBWrapper(t, conn, "oplog")
|
||||
t.Run("simple", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
w := New(conn)
|
||||
@@ -669,7 +671,7 @@ func TestDb_Create(t *testing.T) {
|
||||
context.Background(),
|
||||
user,
|
||||
WithOplog(
|
||||
TestWrapper(t),
|
||||
wrapper,
|
||||
oplog.Metadata{
|
||||
"key-only": nil,
|
||||
"deployment": []string{"amex"},
|
||||
@@ -700,7 +702,7 @@ func TestDb_Create(t *testing.T) {
|
||||
context.Background(),
|
||||
user,
|
||||
NewOplogMsg(&createMsg),
|
||||
WithOplog(TestWrapper(t), oplog.Metadata{"alice": []string{"bob"}}),
|
||||
WithOplog(wrapper, oplog.Metadata{"alice": []string{"bob"}}),
|
||||
)
|
||||
require.Error(err)
|
||||
assert.True(errors.Match(errors.T(errors.InvalidParameter), err))
|
||||
@@ -743,7 +745,7 @@ func TestDb_Create(t *testing.T) {
|
||||
"resource-public-id": []string{user.PublicId},
|
||||
}
|
||||
|
||||
err = w.WriteOplogEntryWith(context.Background(), TestWrapper(t), ticket, metadata, []*oplog.Message{&createMsg, &updateMsg})
|
||||
err = w.WriteOplogEntryWith(context.Background(), wrapper, ticket, metadata, []*oplog.Message{&createMsg, &updateMsg})
|
||||
require.NoError(err)
|
||||
|
||||
err = TestVerifyOplog(t, w, user.PublicId, WithOperation(oplog.OpType_OP_TYPE_UNSPECIFIED), WithCreateNotBefore(10*time.Second))
|
||||
@@ -784,7 +786,7 @@ func TestDb_Create(t *testing.T) {
|
||||
context.Background(),
|
||||
user,
|
||||
WithOplog(
|
||||
TestWrapper(t),
|
||||
wrapper,
|
||||
nil,
|
||||
),
|
||||
)
|
||||
@@ -1326,6 +1328,7 @@ func TestDb_DoTx(t *testing.T) {
|
||||
func TestDb_Delete(t *testing.T) {
|
||||
conn, _ := TestSetup(t, "postgres")
|
||||
TestCreateTables(t, conn)
|
||||
wrapper := TestDBWrapper(t, conn, "oplog")
|
||||
newUser := func() *db_test.TestUser {
|
||||
w := New(conn)
|
||||
u, err := db_test.NewTestUser()
|
||||
@@ -1373,7 +1376,7 @@ func TestDb_Delete(t *testing.T) {
|
||||
{
|
||||
name: "simple-no-oplog",
|
||||
underlying: conn,
|
||||
wrapper: TestWrapper(t),
|
||||
wrapper: wrapper,
|
||||
args: args{
|
||||
i: newUser(),
|
||||
},
|
||||
@@ -1383,7 +1386,7 @@ func TestDb_Delete(t *testing.T) {
|
||||
{
|
||||
name: "with-where-no-delete",
|
||||
underlying: conn,
|
||||
wrapper: TestWrapper(t),
|
||||
wrapper: wrapper,
|
||||
args: args{
|
||||
i: newUser(),
|
||||
opt: []Option{WithWhere("1 = ?", 2)},
|
||||
@@ -1395,7 +1398,7 @@ func TestDb_Delete(t *testing.T) {
|
||||
{
|
||||
name: "with-where-and-delete",
|
||||
underlying: conn,
|
||||
wrapper: TestWrapper(t),
|
||||
wrapper: wrapper,
|
||||
args: args{
|
||||
i: newUser(),
|
||||
opt: []Option{WithWhere("1 = ?", 1)},
|
||||
@@ -1406,7 +1409,7 @@ func TestDb_Delete(t *testing.T) {
|
||||
{
|
||||
name: "valid-with-oplog",
|
||||
underlying: conn,
|
||||
wrapper: TestWrapper(t),
|
||||
wrapper: wrapper,
|
||||
args: args{
|
||||
i: newUser(),
|
||||
metadata: newMetadata,
|
||||
@@ -1444,7 +1447,7 @@ func TestDb_Delete(t *testing.T) {
|
||||
{
|
||||
name: "nil-underlying",
|
||||
underlying: nil,
|
||||
wrapper: TestWrapper(t),
|
||||
wrapper: wrapper,
|
||||
args: args{
|
||||
i: newUser(),
|
||||
},
|
||||
@@ -1455,7 +1458,7 @@ func TestDb_Delete(t *testing.T) {
|
||||
{
|
||||
name: "not-found",
|
||||
underlying: conn,
|
||||
wrapper: TestWrapper(t),
|
||||
wrapper: wrapper,
|
||||
args: args{
|
||||
i: notFoundUser(),
|
||||
},
|
||||
@@ -1521,7 +1524,7 @@ func TestDb_Delete(t *testing.T) {
|
||||
require.NoError(err)
|
||||
|
||||
deleteMsg := oplog.Message{}
|
||||
rowsDeleted, err := w.Delete(context.Background(), user, NewOplogMsg(&deleteMsg), WithOplog(TestWrapper(t), oplog.Metadata{"alice": []string{"bob"}}))
|
||||
rowsDeleted, err := w.Delete(context.Background(), user, NewOplogMsg(&deleteMsg), WithOplog(wrapper, oplog.Metadata{"alice": []string{"bob"}}))
|
||||
require.Error(err)
|
||||
assert.Equal(0, rowsDeleted)
|
||||
assert.True(errors.Match(errors.T(errors.InvalidParameter), err))
|
||||
@@ -1562,7 +1565,7 @@ func TestDb_Delete(t *testing.T) {
|
||||
metadata := oplog.Metadata{
|
||||
"resource-public-id": []string{user.PublicId},
|
||||
}
|
||||
err = w.WriteOplogEntryWith(context.Background(), TestWrapper(t), ticket, metadata, []*oplog.Message{&createMsg, &deleteMsg})
|
||||
err = w.WriteOplogEntryWith(context.Background(), wrapper, ticket, metadata, []*oplog.Message{&createMsg, &deleteMsg})
|
||||
require.NoError(err)
|
||||
|
||||
err = TestVerifyOplog(t, w, user.PublicId, WithOperation(oplog.OpType_OP_TYPE_UNSPECIFIED), WithCreateNotBefore(10*time.Second))
|
||||
@@ -1642,6 +1645,7 @@ func TestDb_Query(t *testing.T) {
|
||||
func TestDb_CreateItems(t *testing.T) {
|
||||
db, _ := TestSetup(t, "postgres")
|
||||
TestCreateTables(t, db)
|
||||
wrapper := TestDBWrapper(t, db, "oplog")
|
||||
testOplogResourceId := testId(t)
|
||||
|
||||
createFn := func() []any {
|
||||
@@ -1694,7 +1698,7 @@ func TestDb_CreateItems(t *testing.T) {
|
||||
createItems: createFn(),
|
||||
opt: []Option{
|
||||
WithOplog(
|
||||
TestWrapper(t),
|
||||
wrapper,
|
||||
oplog.Metadata{
|
||||
"resource-public-id": []string{testOplogResourceId},
|
||||
"op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()},
|
||||
@@ -1725,7 +1729,7 @@ func TestDb_CreateItems(t *testing.T) {
|
||||
opt: []Option{
|
||||
NewOplogMsgs(&[]*oplog.Message{}),
|
||||
WithOplog(
|
||||
TestWrapper(t),
|
||||
wrapper,
|
||||
oplog.Metadata{
|
||||
"resource-public-id": []string{testOplogResourceId},
|
||||
"op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()},
|
||||
@@ -1752,7 +1756,7 @@ func TestDb_CreateItems(t *testing.T) {
|
||||
createItems: createFn(),
|
||||
opt: []Option{
|
||||
WithOplog(
|
||||
TestWrapper(t),
|
||||
wrapper,
|
||||
nil,
|
||||
),
|
||||
},
|
||||
@@ -1853,6 +1857,7 @@ func TestDb_CreateItems(t *testing.T) {
|
||||
func TestDb_DeleteItems(t *testing.T) {
|
||||
db, _ := TestSetup(t, "postgres")
|
||||
TestCreateTables(t, db)
|
||||
wrapper := TestDBWrapper(t, db, "oplog")
|
||||
testOplogResourceId := testId(t)
|
||||
|
||||
createFn := func() []any {
|
||||
@@ -1909,7 +1914,7 @@ func TestDb_DeleteItems(t *testing.T) {
|
||||
opt: []Option{
|
||||
NewOplogMsgs(&[]*oplog.Message{}),
|
||||
WithOplog(
|
||||
TestWrapper(t),
|
||||
wrapper,
|
||||
oplog.Metadata{
|
||||
"resource-public-id": []string{testOplogResourceId},
|
||||
"op-type": []string{oplog.OpType_OP_TYPE_DELETE.String()},
|
||||
@@ -1927,7 +1932,7 @@ func TestDb_DeleteItems(t *testing.T) {
|
||||
deleteItems: createFn(),
|
||||
opt: []Option{
|
||||
WithOplog(
|
||||
TestWrapper(t),
|
||||
wrapper,
|
||||
oplog.Metadata{
|
||||
"resource-public-id": []string{testOplogResourceId},
|
||||
"op-type": []string{oplog.OpType_OP_TYPE_DELETE.String()},
|
||||
@@ -1946,7 +1951,7 @@ func TestDb_DeleteItems(t *testing.T) {
|
||||
deleteItems: createFn(),
|
||||
opt: []Option{
|
||||
WithOplog(
|
||||
TestWrapper(t),
|
||||
wrapper,
|
||||
nil,
|
||||
),
|
||||
},
|
||||
@@ -2341,6 +2346,7 @@ func TestDb_GetTicket(t *testing.T) {
|
||||
func TestDb_WriteOplogEntryWith(t *testing.T) {
|
||||
conn, _ := TestSetup(t, "postgres")
|
||||
TestCreateTables(t, conn)
|
||||
wrapper := TestDBWrapper(t, conn, "oplog")
|
||||
w := New(conn)
|
||||
testCtx := context.Background()
|
||||
|
||||
@@ -2382,7 +2388,7 @@ func TestDb_WriteOplogEntryWith(t *testing.T) {
|
||||
name: "valid",
|
||||
underlying: conn,
|
||||
args: args{
|
||||
wrapper: TestWrapper(t),
|
||||
wrapper: wrapper,
|
||||
ticket: ticket,
|
||||
metadata: metadata,
|
||||
msgs: []*oplog.Message{&createMsg},
|
||||
@@ -2393,7 +2399,7 @@ func TestDb_WriteOplogEntryWith(t *testing.T) {
|
||||
name: "valid-multiple",
|
||||
underlying: conn,
|
||||
args: args{
|
||||
wrapper: TestWrapper(t),
|
||||
wrapper: wrapper,
|
||||
ticket: ticket,
|
||||
metadata: metadata,
|
||||
msgs: []*oplog.Message{&createMsg, &createMsg},
|
||||
@@ -2404,7 +2410,7 @@ func TestDb_WriteOplogEntryWith(t *testing.T) {
|
||||
name: "missing-ticket",
|
||||
underlying: conn,
|
||||
args: args{
|
||||
wrapper: TestWrapper(t),
|
||||
wrapper: wrapper,
|
||||
ticket: nil,
|
||||
metadata: metadata,
|
||||
msgs: []*oplog.Message{&createMsg},
|
||||
@@ -2416,7 +2422,7 @@ func TestDb_WriteOplogEntryWith(t *testing.T) {
|
||||
name: "missing-db",
|
||||
underlying: nil,
|
||||
args: args{
|
||||
wrapper: TestWrapper(t),
|
||||
wrapper: wrapper,
|
||||
ticket: ticket,
|
||||
metadata: metadata,
|
||||
msgs: []*oplog.Message{&createMsg},
|
||||
@@ -2440,7 +2446,7 @@ func TestDb_WriteOplogEntryWith(t *testing.T) {
|
||||
name: "nil-metadata",
|
||||
underlying: conn,
|
||||
args: args{
|
||||
wrapper: TestWrapper(t),
|
||||
wrapper: wrapper,
|
||||
ticket: ticket,
|
||||
metadata: nil,
|
||||
msgs: []*oplog.Message{&createMsg},
|
||||
@@ -2452,7 +2458,7 @@ func TestDb_WriteOplogEntryWith(t *testing.T) {
|
||||
name: "empty-metadata",
|
||||
underlying: conn,
|
||||
args: args{
|
||||
wrapper: TestWrapper(t),
|
||||
wrapper: wrapper,
|
||||
ticket: ticket,
|
||||
metadata: oplog.Metadata{},
|
||||
msgs: []*oplog.Message{&createMsg},
|
||||
|
||||
@@ -57,6 +57,7 @@ begin;
|
||||
comment on function update_last_access_time() is
|
||||
'function used in before update triggers to properly set last_access_time columns';
|
||||
|
||||
-- this trigger is deleted in 58/05_mutable_ciphertext_columns.up.sql
|
||||
create or replace function immutable_auth_token_columns() returns trigger
|
||||
as $$
|
||||
begin
|
||||
|
||||
@@ -190,6 +190,7 @@ begin;
|
||||
create trigger default_create_time_column before insert on credential_vault_token
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
-- this trigger is updated in 58/05_mutable_ciphertext_columns.up.sql
|
||||
create trigger immutable_columns before update on credential_vault_token
|
||||
for each row execute procedure immutable_columns('token_hmac', 'token', 'store_id','create_time');
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ begin;
|
||||
'session_credential is a table where each row contains a credential to be used by '
|
||||
'by a worker when a connection is established for the session_id.';
|
||||
|
||||
-- Replaced in 43/01_session_credentials.up.sql
|
||||
-- this trigger is updated in 58/05_mutable_ciphertext_columns.up.sql
|
||||
create trigger immutable_columns before update on session_credential
|
||||
for each row execute procedure immutable_columns('session_id', 'credential', 'key_id');
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ create table worker_auth_ca_certificate(
|
||||
comment on table worker_auth_ca_certificate is
|
||||
'worker_auth_ca_certificate is a table where each row represents a root certificate for used for worker authentication.';
|
||||
|
||||
-- this trigger is updated in 58/05_mutable_ciphertext_columns.up.sql
|
||||
create trigger immutable_columns before update on worker_auth_ca_certificate
|
||||
for each row execute procedure immutable_columns('serial_number', 'certificate', 'not_valid_before', 'not_valid_after', 'public_key', 'private_key', 'key_id', 'state', 'issuing_ca');
|
||||
|
||||
@@ -101,7 +102,7 @@ create table worker_auth_authorized(
|
||||
comment on table worker_auth_authorized is
|
||||
'worker_auth_authorized is a table where each row represents key and cert data associated with an authorized worker.';
|
||||
|
||||
-- Trigger updated in 55/01_worker_auth_create_time.up.sql
|
||||
-- this trigger is updated in 58/05_mutable_ciphertext_columns.up.sql
|
||||
create trigger immutable_columns before update on worker_auth_authorized
|
||||
for each row execute procedure immutable_columns('worker_key_identifier', 'worker_id', 'worker_signing_pub_key', 'worker_encryption_pub_key', 'controller_encryption_priv_key', 'key_id', 'nonce');
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ begin;
|
||||
add constraint session_credential_session_id_credential_sha256_uq
|
||||
unique(session_id, credential_sha256);
|
||||
|
||||
-- Replace the immutable columns trigger from 23/01_session_credential.up.sql
|
||||
-- this trigger is updated in 58/05_mutable_ciphertext_columns.up.sql
|
||||
drop trigger immutable_columns on session_credential;
|
||||
create trigger immutable_columns before update on session_credential
|
||||
for each row execute procedure immutable_columns('session_id', 'credential', 'key_id', 'credential_sha256');
|
||||
|
||||
@@ -212,6 +212,7 @@ begin;
|
||||
'No encrypted data is returned. This view can be used to retrieve data which will be returned external to boundary.';
|
||||
|
||||
-- Replaces view from 41/01_worker_filter_vault_cred_store.up.sql
|
||||
-- Recreated in 58/02_add_data_key_foreign_key_references.up.sql
|
||||
create view credential_vault_credential_private as
|
||||
select credential.public_id as public_id,
|
||||
credential.library_id as library_id,
|
||||
|
||||
@@ -70,8 +70,8 @@ begin;
|
||||
$$ language plpgsql;
|
||||
|
||||
-- update views
|
||||
-- Replace view from 20/10_plugin_host_secrets_hmac.up.sql
|
||||
drop view host_plugin_catalog_with_secret;
|
||||
-- Recreated in 58/02_add_data_key_foreign_key_references.up.sql
|
||||
create view host_plugin_catalog_with_secret as
|
||||
select
|
||||
hc.public_id,
|
||||
|
||||
@@ -12,6 +12,7 @@ begin;
|
||||
;
|
||||
|
||||
-- Replaces trigger from 01/50_session.up.sql
|
||||
-- Replaced in 58/02_add_data_key_foreign_key_references
|
||||
create or replace function cancel_session_with_null_fk() returns trigger
|
||||
as $$
|
||||
begin
|
||||
@@ -59,6 +60,7 @@ begin;
|
||||
$$ language plpgsql;
|
||||
|
||||
-- Replaces view from 34/04_views.up.sql
|
||||
-- Replaced in 58/06_add_session_private_key_column
|
||||
drop view session_list;
|
||||
create view session_list as
|
||||
select
|
||||
|
||||
@@ -17,6 +17,7 @@ create trigger kms_immutable_columns before update on kms_root_key_version
|
||||
-- rewrapping the data key version.
|
||||
drop trigger kms_immutable_columns on kms_data_key_version;
|
||||
|
||||
-- trigger updated again in migration 56 file 04
|
||||
create trigger kms_immutable_columns before update on kms_data_key_version
|
||||
for each row execute procedure kms_immutable_columns('private_id', 'data_key_id', 'root_key_version_id', 'create_time');
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ create table worker_auth_server_led_activation_token(
|
||||
comment on table worker_auth_server_led_activation_token is
|
||||
'worker_auth_server_led_activation_token is a table where each row represents an activation token for a worker. Only one activation token is allowed per worker.';
|
||||
|
||||
-- this trigger is updated in 58/05_mutable_ciphertext_columns.up.sql
|
||||
create trigger immutable_columns before update on worker_auth_server_led_activation_token
|
||||
for each row execute procedure immutable_columns('worker_id', 'token_id', 'creation_time_encrypted');
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ begin;
|
||||
drop view credential_vault_library_private;
|
||||
drop view credential_vault_store_private;
|
||||
|
||||
-- Recreated in 58/02_add_data_key_foreign_key_references.up.sql
|
||||
create view credential_vault_token_renewal_revocation as
|
||||
with
|
||||
tokens as (
|
||||
@@ -77,6 +78,7 @@ begin;
|
||||
'If the Vault token has expired this view will return an empty token_hmac and a token_status of ''expired'' '
|
||||
'No encrypted data is returned. This view can be used to retrieve data which will be returned external to boundary.';
|
||||
|
||||
-- Recreated in 58/02_add_data_key_foreign_key_references.up.sql
|
||||
create view credential_vault_store_client as
|
||||
select store.public_id as public_id,
|
||||
store.project_id as project_id,
|
||||
@@ -105,6 +107,7 @@ begin;
|
||||
'The view returns the current token for the store, if the Vault token has expired this view will return an empty token_hmac and a token_status of ''expired'' '
|
||||
'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.';
|
||||
|
||||
-- Recreated in 58/02_add_data_key_foreign_key_references.up.sql
|
||||
create view credential_vault_library_issue_credentials as
|
||||
with
|
||||
password_override (library_id, username_attribute, password_attribute) as (
|
||||
|
||||
@@ -93,7 +93,7 @@ alter table worker_auth_authorized
|
||||
;
|
||||
|
||||
drop trigger immutable_columns on worker_auth_authorized;
|
||||
-- Trigger updated from 34/03_worker_authentication.up.sql
|
||||
-- this trigger is updated in 58/05_mutable_ciphertext_columns.up.sql
|
||||
create trigger immutable_columns before update on worker_auth_authorized
|
||||
for each row execute function immutable_columns('worker_key_identifier', 'worker_id', 'worker_signing_pub_key',
|
||||
'worker_encryption_pub_key', 'controller_encryption_priv_key', 'key_id', 'nonce', 'create_time');
|
||||
@@ -135,4 +135,4 @@ comment on function insert_worker_auth_authorized is
|
||||
create trigger insert_worker_auth_authorized before insert on worker_auth_authorized
|
||||
for each row execute function insert_worker_auth_authorized();
|
||||
|
||||
commit;
|
||||
commit;
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
begin;
|
||||
|
||||
truncate table oplog_entry, oplog_metadata;
|
||||
|
||||
alter table oplog_entry
|
||||
add column key_id kms_private_id not null
|
||||
constraint kms_data_key_version_fkey
|
||||
references kms_data_key_version (private_id)
|
||||
on delete restrict
|
||||
on update cascade,
|
||||
add column scope_id wt_scope_id not null
|
||||
constraint iam_scope_fkey
|
||||
references iam_scope (public_id)
|
||||
on delete cascade
|
||||
on update cascade;
|
||||
|
||||
create function insert_oplog_entry() returns trigger
|
||||
as $$
|
||||
begin
|
||||
select scope_id into new.scope_id
|
||||
from kms_root_key
|
||||
inner join kms_data_key on kms_root_key.private_id = kms_data_key.root_key_id
|
||||
inner join kms_data_key_version on kms_data_key.private_id = kms_data_key_version.data_key_id
|
||||
where kms_data_key_version.private_id = new.key_id;
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
comment on function insert_oplog_entry() is
|
||||
'insert_oplog_entry sets the scope_id based on the key_id';
|
||||
|
||||
create trigger insert_oplog_entry before insert on oplog_entry
|
||||
for each row execute procedure insert_oplog_entry();
|
||||
|
||||
commit;
|
||||
+325
@@ -0,0 +1,325 @@
|
||||
begin;
|
||||
|
||||
alter table auth_token
|
||||
alter column key_id type kms_private_id,
|
||||
add constraint kms_data_key_version_fkey
|
||||
foreign key (key_id)
|
||||
references kms_data_key_version (private_id)
|
||||
on delete restrict
|
||||
on update cascade;
|
||||
|
||||
alter table auth_password_argon2_cred
|
||||
alter column key_id type kms_private_id,
|
||||
add constraint kms_data_key_version_fkey
|
||||
foreign key (key_id)
|
||||
references kms_data_key_version (private_id)
|
||||
on delete restrict
|
||||
on update cascade;
|
||||
|
||||
-- Some existing sessions may exist with an empty key_id
|
||||
update session
|
||||
set key_id = null
|
||||
where key_id = '';
|
||||
|
||||
alter table session
|
||||
-- cannot set key_id type to kms_private_id because the kms_private_id type
|
||||
-- has a 'not null' restriction and the key_id can be null in the session
|
||||
-- table.
|
||||
add constraint kms_data_key_version_fkey
|
||||
foreign key (key_id)
|
||||
references kms_data_key_version (private_id)
|
||||
on delete restrict
|
||||
on update cascade;
|
||||
|
||||
-- Replaces trigger from 44/04_session.up.sql
|
||||
-- Adding a foreign key reference to the kms key means we have
|
||||
-- to set the key_id to null when the scope_id is set to null,
|
||||
-- otherwise we can't delete scopes with sessions, since deleting
|
||||
-- the scope cascade deletes the key referenced by key_id.
|
||||
create or replace function cancel_session_with_null_fk() returns trigger
|
||||
as $$
|
||||
begin
|
||||
case
|
||||
when new.user_id is null then
|
||||
perform cancel_session(new.public_id);
|
||||
when new.host_id is null then
|
||||
perform cancel_session(new.public_id);
|
||||
when new.target_id is null then
|
||||
perform cancel_session(new.public_id);
|
||||
when new.host_set_id is null then
|
||||
perform cancel_session(new.public_id);
|
||||
when new.auth_token_id is null then
|
||||
perform cancel_session(new.public_id);
|
||||
when new.project_id is null then
|
||||
-- Setting the key_id to null will allow the scope
|
||||
-- to cascade delete its keys.
|
||||
new.key_id = null;
|
||||
perform cancel_session(new.public_id);
|
||||
end case;
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
|
||||
-- we drop some views, so we can recreate them after changing the type of the referencing columns.
|
||||
drop view
|
||||
oidc_auth_method_with_value_obj,
|
||||
host_plugin_catalog_with_secret,
|
||||
credential_vault_store_client,
|
||||
credential_vault_library_issue_credentials,
|
||||
credential_vault_token_renewal_revocation,
|
||||
credential_vault_credential_private;
|
||||
|
||||
alter table auth_oidc_method
|
||||
alter column key_id type kms_private_id;
|
||||
|
||||
alter table host_plugin_catalog_secret
|
||||
alter column key_id type kms_private_id;
|
||||
|
||||
alter table credential_vault_token
|
||||
alter column key_id type kms_private_id;
|
||||
|
||||
alter table credential_vault_client_certificate
|
||||
alter column key_id type kms_private_id;
|
||||
|
||||
alter table session_credential
|
||||
alter column key_id type kms_private_id;
|
||||
|
||||
alter table credential_static_username_password_credential
|
||||
alter column key_id type kms_private_id;
|
||||
|
||||
alter table credential_static_ssh_private_key_credential
|
||||
alter column key_id type kms_private_id;
|
||||
|
||||
create view oidc_auth_method_with_value_obj as
|
||||
select
|
||||
case when s.primary_auth_method_id is not null then
|
||||
true
|
||||
else false end
|
||||
as is_primary_auth_method,
|
||||
am.public_id,
|
||||
am.scope_id,
|
||||
am.name,
|
||||
am.description,
|
||||
am.create_time,
|
||||
am.update_time,
|
||||
am.version,
|
||||
am.state,
|
||||
am.api_url,
|
||||
am.disable_discovered_config_validation,
|
||||
am.issuer,
|
||||
am.client_id,
|
||||
am.client_secret,
|
||||
am.client_secret_hmac,
|
||||
am.key_id,
|
||||
am.max_age,
|
||||
-- the string_agg(..) column will be null if there are no associated value objects
|
||||
string_agg(distinct alg.signing_alg_name, '|') as algs,
|
||||
string_agg(distinct aud.aud_claim, '|') as auds,
|
||||
string_agg(distinct cert.certificate, '|') as certs,
|
||||
string_agg(distinct cs.scope, '|') as claims_scopes,
|
||||
string_agg(distinct concat_ws('=', acm.from_claim, acm.to_claim), '|') as account_claim_maps
|
||||
from
|
||||
auth_oidc_method am
|
||||
left outer join iam_scope s on am.public_id = s.primary_auth_method_id
|
||||
left outer join auth_oidc_signing_alg alg on am.public_id = alg.oidc_method_id
|
||||
left outer join auth_oidc_aud_claim aud on am.public_id = aud.oidc_method_id
|
||||
left outer join auth_oidc_certificate cert on am.public_id = cert.oidc_method_id
|
||||
left outer join auth_oidc_scope cs on am.public_id = cs.oidc_method_id
|
||||
left outer join auth_oidc_account_claim_map acm on am.public_id = acm.oidc_method_id
|
||||
group by am.public_id, is_primary_auth_method; -- there can be only one public_id + is_primary_auth_method, so group by isn't a problem.
|
||||
comment on view oidc_auth_method_with_value_obj is
|
||||
'oidc auth method with its associated value objects (algs, auds, certs, scopes) as columns with | delimited values';
|
||||
|
||||
create view host_plugin_catalog_with_secret as
|
||||
select
|
||||
hc.public_id,
|
||||
hc.project_id,
|
||||
hc.plugin_id,
|
||||
hc.name,
|
||||
hc.description,
|
||||
hc.create_time,
|
||||
hc.update_time,
|
||||
hc.version,
|
||||
hc.secrets_hmac,
|
||||
hc.attributes,
|
||||
hcs.secret,
|
||||
hcs.key_id,
|
||||
hcs.create_time as persisted_create_time,
|
||||
hcs.update_time as persisted_update_time
|
||||
from
|
||||
host_plugin_catalog hc
|
||||
left outer join host_plugin_catalog_secret hcs on hc.public_id = hcs.catalog_id;
|
||||
comment on view host_plugin_catalog_with_secret is
|
||||
'host plugin catalog with its associated persisted data';
|
||||
|
||||
create view credential_vault_store_client as
|
||||
select store.public_id as public_id,
|
||||
store.project_id as project_id,
|
||||
store.vault_address as vault_address,
|
||||
store.namespace as namespace,
|
||||
store.ca_cert as ca_cert,
|
||||
store.tls_server_name as tls_server_name,
|
||||
store.tls_skip_verify as tls_skip_verify,
|
||||
store.worker_filter as worker_filter,
|
||||
token.token as ct_token, -- encrypted
|
||||
token.token_hmac as token_hmac,
|
||||
coalesce(token.status, 'expired') as token_status,
|
||||
token.key_id as token_key_id,
|
||||
cert.certificate as client_cert,
|
||||
cert.certificate_key as ct_client_key, -- encrypted
|
||||
cert.key_id as client_key_id
|
||||
from credential_vault_store store
|
||||
left join credential_vault_token token
|
||||
on store.public_id = token.store_id
|
||||
and token.status = 'current'
|
||||
left join credential_vault_client_certificate cert
|
||||
on store.public_id = cert.store_id
|
||||
where store.delete_time is null;
|
||||
comment on view credential_vault_store_client is
|
||||
'credential_vault_store_client is a view where each row contains a credential store and the credential store''s data needed to connect to Vault. '
|
||||
'The view returns the current token for the store, if the Vault token has expired this view will return an empty token_hmac and a token_status of ''expired'' '
|
||||
'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.';
|
||||
|
||||
create view credential_vault_library_issue_credentials as
|
||||
with
|
||||
password_override (library_id, username_attribute, password_attribute) as (
|
||||
select library_id,
|
||||
nullif(username_attribute, wt_to_sentinel('no override')),
|
||||
nullif(password_attribute, wt_to_sentinel('no override'))
|
||||
from credential_vault_library_username_password_mapping_override
|
||||
),
|
||||
ssh_private_key_override (library_id, username_attribute, private_key_attribute, private_key_passphrase_attribute) as (
|
||||
select library_id,
|
||||
nullif(username_attribute, wt_to_sentinel('no override')),
|
||||
nullif(private_key_attribute, wt_to_sentinel('no override')),
|
||||
nullif(private_key_passphrase_attribute, wt_to_sentinel('no override'))
|
||||
from credential_vault_library_ssh_private_key_mapping_override
|
||||
)
|
||||
select library.public_id as public_id,
|
||||
library.store_id as store_id,
|
||||
library.name as name,
|
||||
library.description as description,
|
||||
library.create_time as create_time,
|
||||
library.update_time as update_time,
|
||||
library.version as version,
|
||||
library.vault_path as vault_path,
|
||||
library.http_method as http_method,
|
||||
library.http_request_body as http_request_body,
|
||||
library.credential_type as credential_type,
|
||||
store.project_id as project_id,
|
||||
store.vault_address as vault_address,
|
||||
store.namespace as namespace,
|
||||
store.ca_cert as ca_cert,
|
||||
store.tls_server_name as tls_server_name,
|
||||
store.tls_skip_verify as tls_skip_verify,
|
||||
store.worker_filter as worker_filter,
|
||||
store.ct_token as ct_token, -- encrypted
|
||||
store.token_hmac as token_hmac,
|
||||
store.token_status as token_status,
|
||||
store.token_key_id as token_key_id,
|
||||
store.client_cert as client_cert,
|
||||
store.ct_client_key as ct_client_key, -- encrypted
|
||||
store.client_key_id as client_key_id,
|
||||
coalesce(upasso.username_attribute,sshpk.username_attribute)
|
||||
as username_attribute,
|
||||
upasso.password_attribute as password_attribute,
|
||||
sshpk.private_key_attribute as private_key_attribute,
|
||||
sshpk.private_key_passphrase_attribute as private_key_passphrase_attribute
|
||||
from credential_vault_library library
|
||||
join credential_vault_store_client store
|
||||
on library.store_id = store.public_id
|
||||
left join password_override upasso
|
||||
on library.public_id = upasso.library_id
|
||||
left join ssh_private_key_override sshpk
|
||||
on library.public_id = sshpk.library_id;
|
||||
comment on view credential_vault_library_issue_credentials is
|
||||
'credential_vault_library_issue_credentials is a view where each row contains a credential library and the credential library''s data needed to connect to Vault. '
|
||||
'This view should only be used when issuing credentials from a Vault credential library. Each row may contain encrypted data. '
|
||||
'This view should not be used to retrieve data which will be returned external to boundary.';
|
||||
|
||||
create view credential_vault_token_renewal_revocation as
|
||||
with
|
||||
tokens as (
|
||||
select token, -- encrypted
|
||||
token_hmac,
|
||||
store_id,
|
||||
-- renewal time is the midpoint between the last renewal time and the expiration time
|
||||
last_renewal_time + (expiration_time - last_renewal_time) / 2 as renewal_time,
|
||||
key_id,
|
||||
status
|
||||
from credential_vault_token
|
||||
where status in ('current', 'maintaining', 'revoke')
|
||||
)
|
||||
select store.public_id as public_id,
|
||||
store.project_id as project_id,
|
||||
store.vault_address as vault_address,
|
||||
store.namespace as namespace,
|
||||
store.ca_cert as ca_cert,
|
||||
store.tls_server_name as tls_server_name,
|
||||
store.tls_skip_verify as tls_skip_verify,
|
||||
store.worker_filter as worker_filter,
|
||||
store.delete_time as delete_time,
|
||||
token.token as ct_token, -- encrypted
|
||||
token.token_hmac as token_hmac,
|
||||
token.renewal_time as token_renewal_time,
|
||||
token.key_id as token_key_id,
|
||||
token.status as token_status,
|
||||
cert.certificate as client_cert,
|
||||
cert.certificate_key as ct_client_key, -- encrypted
|
||||
cert.key_id as client_key_id
|
||||
from credential_vault_store store
|
||||
join tokens token
|
||||
on store.public_id = token.store_id
|
||||
left join credential_vault_client_certificate cert
|
||||
on store.public_id = cert.store_id;
|
||||
comment on view credential_vault_token_renewal_revocation is
|
||||
'credential_vault_token_renewal_revocation is a view where each row contains a credential store and the credential store''s data needed to connect to Vault. '
|
||||
'The view returns a separate row for each active token in Vault (current, maintaining and revoke tokens); this view should only be used for token renewal and revocation. '
|
||||
'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.';
|
||||
|
||||
create view credential_vault_credential_private as
|
||||
select credential.public_id as public_id,
|
||||
credential.library_id as library_id,
|
||||
credential.session_id as session_id,
|
||||
credential.create_time as create_time,
|
||||
credential.update_time as update_time,
|
||||
credential.version as version,
|
||||
credential.external_id as external_id,
|
||||
credential.last_renewal_time as last_renewal_time,
|
||||
credential.expiration_time as expiration_time,
|
||||
credential.is_renewable as is_renewable,
|
||||
credential.status as status,
|
||||
credential.last_renewal_time + (credential.expiration_time - credential.last_renewal_time) / 2 as renewal_time,
|
||||
token.token_hmac as token_hmac,
|
||||
token.token as ct_token, -- encrypted
|
||||
token.create_time as token_create_time,
|
||||
token.update_time as token_update_time,
|
||||
token.last_renewal_time as token_last_renewal_time,
|
||||
token.expiration_time as token_expiration_time,
|
||||
token.key_id as token_key_id,
|
||||
token.status as token_status,
|
||||
store.project_id as project_id,
|
||||
store.vault_address as vault_address,
|
||||
store.namespace as namespace,
|
||||
store.ca_cert as ca_cert,
|
||||
store.tls_server_name as tls_server_name,
|
||||
store.tls_skip_verify as tls_skip_verify,
|
||||
cert.certificate as client_cert,
|
||||
cert.certificate_key as ct_client_key, -- encrypted
|
||||
cert.certificate_key_hmac as client_cert_key_hmac,
|
||||
cert.key_id as client_key_id
|
||||
from credential_vault_credential credential
|
||||
join credential_vault_token token
|
||||
on credential.token_hmac = token.token_hmac
|
||||
join credential_vault_store store
|
||||
on token.store_id = store.public_id
|
||||
left join credential_vault_client_certificate cert
|
||||
on store.public_id = cert.store_id
|
||||
where credential.expiration_time != 'infinity'::date;
|
||||
comment on view credential_vault_credential_private is
|
||||
'credential_vault_credential_private is a view where each row contains a credential, '
|
||||
'the vault token used to issue the credential, and the credential store data needed to connect to Vault. '
|
||||
'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.';
|
||||
|
||||
commit;
|
||||
@@ -0,0 +1,110 @@
|
||||
begin;
|
||||
|
||||
create table kms_data_key_version_destruction_job (
|
||||
key_id kms_private_id primary key -- one job per key version
|
||||
references kms_data_key_version (private_id)
|
||||
on delete cascade -- Note: instead of restrict, we cascade the delete to this job when the key version is deleted
|
||||
on update cascade,
|
||||
create_time wt_timestamp not null
|
||||
);
|
||||
comment on table kms_data_key_version_destruction_job is
|
||||
'Table holding running and pending data key version destruction jobs';
|
||||
|
||||
create trigger immutable_columns before update on kms_data_key_version_destruction_job
|
||||
for each row execute procedure immutable_columns('key_id', 'create_time');
|
||||
|
||||
|
||||
create table kms_data_key_version_destruction_job_run (
|
||||
key_id kms_private_id not null
|
||||
references kms_data_key_version_destruction_job (key_id)
|
||||
on delete cascade
|
||||
on update cascade
|
||||
deferrable initially deferred,
|
||||
table_name text not null,
|
||||
total_count bigint not null
|
||||
constraint total_count_cannot_be_negative
|
||||
check (total_count > 0), -- Must not be 0
|
||||
completed_count bigint not null default 0
|
||||
constraint completed_count_cannot_be_negative
|
||||
check (completed_count >= 0),
|
||||
is_running boolean not null default false,
|
||||
|
||||
primary key (key_id, table_name),
|
||||
constraint completed_count_less_than_equal_to_total_count
|
||||
check (completed_count <= total_count),
|
||||
constraint is_running_only_when_not_completed
|
||||
check ((completed_count < total_count) or (completed_count = total_count and not is_running))
|
||||
);
|
||||
comment on table kms_data_key_version_destruction_job_run is
|
||||
'Table holding per-table runs of data key version destruction jobs';
|
||||
|
||||
create trigger immutable_columns before update on kms_data_key_version_destruction_job_run
|
||||
for each row execute procedure immutable_columns('key_id', 'table_name', 'total_count');
|
||||
|
||||
create unique index kms_data_key_version_destruction_job_run_is_running_uq
|
||||
on kms_data_key_version_destruction_job_run (is_running) where is_running = true; -- allow only one running run
|
||||
|
||||
-- A view that holds all the names of tables that reference the
|
||||
-- data_key_version.private_id column as a foreign key.
|
||||
-- Excludes tables that should not be used in a job run.
|
||||
create view kms_data_key_version_destruction_job_run_allowed_table_name as
|
||||
select distinct
|
||||
r.table_name
|
||||
from
|
||||
information_schema.constraint_column_usage u
|
||||
inner join information_schema.referential_constraints fk
|
||||
on u.constraint_catalog = fk.unique_constraint_catalog and
|
||||
u.constraint_schema = fk.unique_constraint_schema and
|
||||
u.constraint_name = fk.unique_constraint_name
|
||||
inner join information_schema.key_column_usage r
|
||||
on r.constraint_catalog = fk.constraint_catalog and
|
||||
r.constraint_schema = fk.constraint_schema and
|
||||
r.constraint_name = fk.constraint_name
|
||||
where
|
||||
u.column_name = 'private_id' and
|
||||
u.table_name = 'kms_data_key_version' and
|
||||
-- These tables reference the right column, but are not allowed as a destruction
|
||||
-- job run table name.
|
||||
r.table_name not in ('oplog_entry', 'kms_data_key_version_destruction_job');
|
||||
|
||||
create function kms_data_key_table_name_valid() returns trigger
|
||||
as $$
|
||||
begin
|
||||
if new.table_name not in (select table_name from kms_data_key_version_destruction_job_run_allowed_table_name) then
|
||||
raise exception 'invalid table name % (must be table that references kms_data_key_version.private_id)', new.table_name;
|
||||
end if;
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
comment on function kms_data_key_table_name_valid is
|
||||
'Function used to determine whether a table name is valid for a destruction job run';
|
||||
|
||||
create trigger kms_data_key_table_name_valid before insert on kms_data_key_version_destruction_job_run
|
||||
for each row execute procedure kms_data_key_table_name_valid();
|
||||
|
||||
|
||||
-- Used to list progress of all destruction jobs
|
||||
create view kms_data_key_version_destruction_job_progress as
|
||||
select
|
||||
j.key_id,
|
||||
rk.scope_id,
|
||||
j.create_time,
|
||||
case
|
||||
when bool_or(r.is_running) then 'running'
|
||||
when sum(r.total_count) = sum(r.completed_count) then 'completed'
|
||||
else 'pending'
|
||||
end status,
|
||||
sum(r.completed_count) as completed_count,
|
||||
sum(r.total_count) as total_count
|
||||
from kms_data_key_version_destruction_job j
|
||||
inner join kms_data_key_version_destruction_job_run r
|
||||
on j.key_id = r.key_id
|
||||
inner join kms_data_key_version dkv
|
||||
on j.key_id = dkv.private_id
|
||||
inner join kms_data_key dk
|
||||
on dkv.data_key_id = dk.private_id
|
||||
inner join kms_root_key rk
|
||||
on dk.root_key_id = rk.private_id
|
||||
group by (j.key_id, rk.scope_id);
|
||||
|
||||
commit;
|
||||
@@ -0,0 +1,13 @@
|
||||
begin;
|
||||
|
||||
-- this migration is from:
|
||||
-- https://github.com/hashicorp/go-kms-wrapping/blob/main/extras/kms/migrations/postgres/07_mutable_root_key_version.up.sql
|
||||
|
||||
-- we need to make the root_key_version_id mutable in order to support
|
||||
-- rewrapping the data key version.
|
||||
drop trigger kms_immutable_columns on kms_data_key_version;
|
||||
|
||||
create trigger kms_immutable_columns before update on kms_data_key_version
|
||||
for each row execute procedure kms_immutable_columns('private_id', 'data_key_id', 'create_time');
|
||||
|
||||
commit;
|
||||
@@ -0,0 +1,41 @@
|
||||
begin;
|
||||
|
||||
-- We need to make private_key and key_id mutable, so that we can rewrap them.
|
||||
drop trigger immutable_columns on worker_auth_ca_certificate;
|
||||
|
||||
create trigger immutable_columns before update on worker_auth_ca_certificate
|
||||
for each row execute procedure immutable_columns('serial_number', 'certificate', 'not_valid_before', 'not_valid_after', 'public_key', 'state', 'issuing_ca');
|
||||
|
||||
|
||||
-- We need to make token mutable so that we can rewrap it.
|
||||
drop trigger immutable_auth_token_columns on auth_token;
|
||||
drop function immutable_auth_token_columns;
|
||||
|
||||
|
||||
-- We need to make token and token_hmac mutable, so that we can rewrap them.
|
||||
drop trigger immutable_columns on credential_vault_token;
|
||||
|
||||
create trigger immutable_columns before update on credential_vault_token
|
||||
for each row execute procedure immutable_columns('store_id','create_time');
|
||||
|
||||
|
||||
-- And again on credential and key_id (and the hash of credential).
|
||||
drop trigger immutable_columns on session_credential;
|
||||
|
||||
create trigger immutable_columns before update on session_credential
|
||||
for each row execute procedure immutable_columns('session_id');
|
||||
|
||||
|
||||
-- And one final time on worker auth for controller priv key and key id.
|
||||
drop trigger immutable_columns on worker_auth_authorized;
|
||||
|
||||
create trigger immutable_columns before update on worker_auth_authorized
|
||||
for each row execute function immutable_columns('worker_key_identifier', 'worker_id', 'worker_signing_pub_key', 'worker_encryption_pub_key', 'nonce', 'create_time');
|
||||
|
||||
|
||||
drop trigger immutable_columns on worker_auth_server_led_activation_token;
|
||||
|
||||
create trigger immutable_columns before update on worker_auth_server_led_activation_token
|
||||
for each row execute procedure immutable_columns('worker_id', 'token_id');
|
||||
|
||||
commit;
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
begin;
|
||||
|
||||
-- nullable because it will not be set for sessions that are active
|
||||
-- during the upgrade to this version.
|
||||
alter table session add column certificate_private_key bytea null;
|
||||
|
||||
-- Replaces the view created in 44/05_session_list_no_connections
|
||||
drop view session_list;
|
||||
create view session_list as
|
||||
select
|
||||
s.public_id,
|
||||
s.user_id,
|
||||
s.host_id,
|
||||
s.target_id,
|
||||
s.host_set_id,
|
||||
s.auth_token_id,
|
||||
s.project_id,
|
||||
s.certificate,
|
||||
s.certificate_private_key,
|
||||
s.expiration_time,
|
||||
s.connection_limit,
|
||||
s.tofu_token,
|
||||
s.key_id,
|
||||
s.termination_reason,
|
||||
s.version,
|
||||
s.create_time,
|
||||
s.update_time,
|
||||
s.endpoint,
|
||||
s.worker_filter,
|
||||
ss.state,
|
||||
ss.previous_end_time,
|
||||
ss.start_time,
|
||||
ss.end_time,
|
||||
sc.public_id as connection_id,
|
||||
sc.client_tcp_address,
|
||||
sc.client_tcp_port,
|
||||
sc.endpoint_tcp_address,
|
||||
sc.endpoint_tcp_port,
|
||||
sc.bytes_up,
|
||||
sc.bytes_down,
|
||||
sc.closed_reason
|
||||
from session s
|
||||
join session_state ss on
|
||||
s.public_id = ss.session_id
|
||||
left join session_connection sc on
|
||||
s.public_id = sc.session_id;
|
||||
|
||||
commit;
|
||||
@@ -42,6 +42,7 @@ drop view oidc_auth_method_with_value_obj;
|
||||
-- value is part of the primary key and unique. This view will make things like
|
||||
-- recursive listing of oidc auth methods fairly straightforward to implement
|
||||
-- for the oidc repo. The view also includes an is_primary_auth_method bool
|
||||
-- Recreated in 58/02_add_data_key_foreign_key_references.up.sql
|
||||
create view oidc_auth_method_with_value_obj as
|
||||
select
|
||||
case when s.primary_auth_method_id is not null then
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/db/common"
|
||||
"github.com/hashicorp/boundary/internal/db/schema"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/iam/store"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/session"
|
||||
"github.com/hashicorp/boundary/internal/target"
|
||||
@@ -69,23 +70,103 @@ func TestMigrations_Credential_Purpose_Refactor(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
rw := db.New(conn)
|
||||
|
||||
// Create project
|
||||
wrapper := db.TestWrapper(t)
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
org, proj := iam.TestScopes(t, iamRepo)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
authRepo, err := authtoken.NewRepository(rw, rw, kmsCache)
|
||||
require.NoError(t, err)
|
||||
|
||||
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
|
||||
uId := at.GetIamUserId()
|
||||
uId := "u_1234567890"
|
||||
oId := "o_1234567890"
|
||||
pId := "p_1234567890"
|
||||
accId := "acct_1234567890"
|
||||
|
||||
num, err := rw.Exec(ctx, `
|
||||
insert into iam_scope
|
||||
(public_id, type, parent_id)
|
||||
values
|
||||
(?, ?, ?)
|
||||
`, []any{oId, "org", "global"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
require.NoError(t, kmsCache.CreateKeys(ctx, oId))
|
||||
|
||||
num, err = rw.Exec(ctx, `
|
||||
insert into iam_scope
|
||||
(public_id, type, parent_id)
|
||||
values
|
||||
(?, ?, ?)
|
||||
`, []any{pId, "project", oId})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
require.NoError(t, kmsCache.CreateKeys(ctx, pId))
|
||||
|
||||
_, err = rw.DoTx(ctx, 0, db.ExpBackoff{}, func(r db.Reader, w db.Writer) error {
|
||||
_, err := w.Exec(ctx, `
|
||||
insert into auth_password_argon2_conf
|
||||
(private_id, password_method_id)
|
||||
values
|
||||
(?, ?)
|
||||
`, []any{"arg2conf_kWA0RG11DL", "ampw_1234567890"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Exec(ctx, `
|
||||
insert into auth_password_method
|
||||
(public_id, password_conf_id, scope_id)
|
||||
values
|
||||
(?, ?, ?)
|
||||
`, []any{"ampw_1234567890", "arg2conf_kWA0RG11DL", oId})
|
||||
return err
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
num, err = rw.Exec(ctx, `
|
||||
insert into auth_password_account
|
||||
(public_id, auth_method_id, scope_id, login_name)
|
||||
values
|
||||
(?, ?, ?, ?)
|
||||
`, []any{"acctpw_1234567890", "ampw_1234567890", oId, "name1"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
num, err = rw.Exec(ctx, `
|
||||
insert into iam_user
|
||||
(public_id, scope_id)
|
||||
values
|
||||
(?, ?)
|
||||
`, []any{uId, oId})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
num, err = rw.Exec(ctx, `
|
||||
insert into auth_account
|
||||
(public_id, scope_id, auth_method_id, iam_user_id, iam_user_scope_id)
|
||||
values
|
||||
(?, ?, ?, ?, ?)
|
||||
`, []any{accId, oId, "ampw_1234567890", uId, oId})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
user := &iam.User{
|
||||
User: &store.User{
|
||||
PublicId: uId,
|
||||
},
|
||||
}
|
||||
require.NoError(t, rw.LookupById(ctx, user))
|
||||
|
||||
at, err := authRepo.CreateAuthToken(ctx, user, accId)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create host catalog
|
||||
hostCatalogId := "hcst_s1P81LMusN"
|
||||
num, err := rw.Exec(ctx, `
|
||||
num, err = rw.Exec(ctx, `
|
||||
insert into static_host_catalog
|
||||
(scope_id, public_id, name)
|
||||
values
|
||||
(?, ?, ?)
|
||||
`, []any{proj.GetPublicId(), hostCatalogId, "my-host-catalog"})
|
||||
`, []any{pId, hostCatalogId, "my-host-catalog"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
@@ -128,7 +209,7 @@ insert into target_tcp
|
||||
(public_id, scope_id, name, session_max_seconds, session_connection_limit)
|
||||
values
|
||||
(?, ?, ?, ?, ?);
|
||||
`, []any{targetId, proj.GetPublicId(), "my-credential-sources", 28800, -1})
|
||||
`, []any{targetId, pId, "my-credential-sources", 28800, -1})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
@@ -139,7 +220,7 @@ insert into credential_vault_store
|
||||
(public_id, scope_id, vault_address)
|
||||
values
|
||||
(?, ?, ?);
|
||||
`, []any{vaultStoreId, proj.GetPublicId(), "http://vault"})
|
||||
`, []any{vaultStoreId, pId, "http://vault"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
@@ -154,11 +235,11 @@ insert into credential_static_store
|
||||
(public_id, scope_id, name)
|
||||
values
|
||||
(?, ?, ?)
|
||||
`, []any{staticStoreId, proj.GetPublicId(), "my-static-credential-store"})
|
||||
`, []any{staticStoreId, pId, "my-static-credential-store"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
credsStatic := static.TestUsernamePasswordCredentials(t, conn, wrapper, "u", "p", staticStoreId, proj.GetPublicId(), 2)
|
||||
credsStatic := static.TestUsernamePasswordCredentials(t, conn, wrapper, "u", "p", staticStoreId, pId, 2)
|
||||
cred1 := credsStatic[0]
|
||||
cred2 := credsStatic[1]
|
||||
|
||||
@@ -199,14 +280,14 @@ values
|
||||
sessionId := "s_AgLzPhDINE"
|
||||
future := time.Now().Add(time.Hour)
|
||||
expirationTime := fmt.Sprintf("%v-%d-%v %v:%v:%v.000", future.Year(), future.Month(), future.Day(), future.Hour(), future.Minute(), future.Second())
|
||||
_, cert, err := session.TestCert(wrapper, uId, sessionId)
|
||||
_, cert, err := session.TestCert(sessionId)
|
||||
require.NoError(t, err)
|
||||
num, err = rw.Exec(ctx, `
|
||||
insert into session
|
||||
(public_id, user_id, host_id, target_id, host_set_id, auth_token_id, scope_id, certificate, expiration_time, endpoint)
|
||||
values
|
||||
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, []any{sessionId, uId, hostId, targetId, hostSetId, at.GetPublicId(), proj.GetPublicId(), cert, expirationTime, "tcp://127.0.0.1:22"})
|
||||
`, []any{sessionId, uId, hostId, targetId, hostSetId, at.GetPublicId(), pId, cert, expirationTime, "tcp://127.0.0.1:22"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/db/schema"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/testing/dbtest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -59,13 +60,33 @@ func TestMigrations_DeprecatedGrants(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
conn, err := db.Open(ctx, dbType, u)
|
||||
require.NoError(t, err)
|
||||
rw := db.New(conn)
|
||||
|
||||
// Create project
|
||||
wrapper := db.TestWrapper(t)
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
_, proj := iam.TestScopes(t, iamRepo)
|
||||
|
||||
role := iam.TestRole(t, conn, proj.GetPublicId())
|
||||
oId := "o_1234567890"
|
||||
pId := "p_1234567890"
|
||||
num, err := rw.Exec(ctx, `
|
||||
insert into iam_scope
|
||||
(public_id, type, parent_id)
|
||||
values
|
||||
(?, ?, ?)
|
||||
`, []any{oId, "org", "global"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
num, err = rw.Exec(ctx, `
|
||||
insert into iam_scope
|
||||
(public_id, type, parent_id)
|
||||
values
|
||||
(?, ?, ?)
|
||||
`, []any{pId, "project", oId})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
role := iam.TestRole(t, conn, pId)
|
||||
|
||||
initialGrants := map[string]bool{
|
||||
"id=*;type=target;actions=read,authorize-session,add-host-sets,add-host-sets": true,
|
||||
|
||||
@@ -75,53 +75,143 @@ func TestMigrations_DeleteOrphanedAccounts(t *testing.T) {
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
usr, err := iam.NewUser(scope.Global.String())
|
||||
require.NoError(t, err)
|
||||
usr, err = iamRepo.CreateUser(ctx, usr)
|
||||
usr.PublicId = "u_1234567890"
|
||||
num, err := rw.Exec(ctx, `
|
||||
insert into iam_user
|
||||
(public_id, scope_id)
|
||||
values
|
||||
(?, ?)
|
||||
`, []any{usr.PublicId, scope.Global.String()})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
// Create the accounts we will delete and assert their behavior
|
||||
pwRepo, err := password.NewRepository(rw, rw, kmsCache)
|
||||
require.NoError(t, err)
|
||||
|
||||
pwAm1, err := password.NewAuthMethod(scope.Global.String())
|
||||
require.NoError(t, err)
|
||||
pwAm1, err = pwRepo.CreateAuthMethod(ctx, pwAm1)
|
||||
pwAm1.PublicId = "ampw_1234567890"
|
||||
_, err = rw.DoTx(ctx, 0, db.ExpBackoff{}, func(r db.Reader, w db.Writer) error {
|
||||
_, err := w.Exec(ctx, `
|
||||
insert into auth_password_argon2_conf
|
||||
(private_id, password_method_id)
|
||||
values
|
||||
(?, ?)
|
||||
`, []any{"arg2conf_1234567890", pwAm1.PublicId})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Exec(ctx, `
|
||||
insert into auth_password_method
|
||||
(public_id, password_conf_id, scope_id)
|
||||
values
|
||||
(?, ?, ?)
|
||||
`, []any{pwAm1.PublicId, "arg2conf_1234567890", pwAm1.ScopeId})
|
||||
return err
|
||||
})
|
||||
require.NoError(t, err)
|
||||
pwAcct1, err := password.NewAccount(pwAm1.GetPublicId(), password.WithLoginName("account1"), password.WithPassword("password"))
|
||||
require.NoError(t, err)
|
||||
pwAcct1, err = pwRepo.CreateAccount(ctx, scope.Global.String(), pwAcct1)
|
||||
pwAcct1.PublicId = "acctpw_1234567890"
|
||||
num, err = rw.Exec(ctx, `
|
||||
insert into auth_password_account
|
||||
(public_id, auth_method_id, scope_id, login_name)
|
||||
values
|
||||
(?, ?, ?, ?)
|
||||
`, []any{pwAcct1.PublicId, pwAm1.PublicId, pwAm1.ScopeId, pwAcct1.LoginName})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
pwAm2, err := password.NewAuthMethod(scope.Global.String())
|
||||
require.NoError(t, err)
|
||||
pwAm2, err = pwRepo.CreateAuthMethod(ctx, pwAm2)
|
||||
require.NoError(t, err)
|
||||
pwAm2.PublicId = "ampw_0123456789"
|
||||
_, err = rw.DoTx(ctx, 0, db.ExpBackoff{}, func(r db.Reader, w db.Writer) error {
|
||||
_, err := w.Exec(ctx, `
|
||||
insert into auth_password_argon2_conf
|
||||
(private_id, password_method_id)
|
||||
values
|
||||
(?, ?)
|
||||
`, []any{"arg2conf_0123456789", pwAm2.PublicId})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Exec(ctx, `
|
||||
insert into auth_password_method
|
||||
(public_id, password_conf_id, scope_id)
|
||||
values
|
||||
(?, ?, ?)
|
||||
`, []any{pwAm2.PublicId, "arg2conf_0123456789", pwAm2.ScopeId})
|
||||
return err
|
||||
})
|
||||
pwAcct2, err := password.NewAccount(pwAm2.GetPublicId(), password.WithLoginName("account2"), password.WithPassword("password"))
|
||||
require.NoError(t, err)
|
||||
pwAcct2, err = pwRepo.CreateAccount(ctx, scope.Global.String(), pwAcct2)
|
||||
pwAcct2.PublicId = "acctpw_0123456789"
|
||||
num, err = rw.Exec(ctx, `
|
||||
insert into auth_password_account
|
||||
(public_id, auth_method_id, scope_id, login_name)
|
||||
values
|
||||
(?, ?, ?, ?)
|
||||
`, []any{pwAcct2.PublicId, pwAm2.PublicId, pwAm2.ScopeId, pwAcct2.LoginName})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
oidcRepo, err := oidc.NewRepository(ctx, rw, rw, kmsCache)
|
||||
require.NoError(t, err)
|
||||
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), scope.Global.String(), kms.KeyPurposeDatabase)
|
||||
oidcAm1 := oidc.TestAuthMethod(t, conn, databaseWrapper, scope.Global.String(), oidc.InactiveState, "alice_rp1",
|
||||
"my-dogs-name", oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://alice1.com")[0]), oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://api1.com")[0]))
|
||||
oidcAm1.PublicId = "amoidc_1234567890"
|
||||
num, err = rw.Exec(ctx, `
|
||||
insert into auth_oidc_method
|
||||
(public_id, scope_id, state, key_id)
|
||||
values
|
||||
(?, ?, ?, ?)
|
||||
`, []any{oidcAm1.PublicId, oidcAm1.ScopeId, oidcAm1.OperationalState, oidcAm1.KeyId})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
oidcAcct1, err := oidc.NewAccount(ctx, oidcAm1.GetPublicId(), "oidcAcct1")
|
||||
require.NoError(t, err)
|
||||
oidcAcct1, err = oidcRepo.CreateAccount(ctx, scope.Global.String(), oidcAcct1)
|
||||
oidcAcct1.PublicId = "acctoidc_0123456789"
|
||||
num, err = rw.Exec(ctx, `
|
||||
insert into auth_oidc_account
|
||||
(public_id, auth_method_id, scope_id, issuer, subject)
|
||||
values
|
||||
(?, ?, ?, ?, ?)
|
||||
`, []any{oidcAcct1.PublicId, oidcAm1.PublicId, oidcAm1.ScopeId, oidcAm1.Issuer, oidcAcct1.Subject})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
oidcAm2 := oidc.TestAuthMethod(t, conn, databaseWrapper, scope.Global.String(), oidc.InactiveState, "alice_rp2",
|
||||
"my-dogs-name", oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://alice2.com")[0]), oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://api2.com")[0]))
|
||||
oidcAm2.PublicId = "amoidc_0123456789"
|
||||
num, err = rw.Exec(ctx, `
|
||||
insert into auth_oidc_method
|
||||
(public_id, scope_id, state, key_id)
|
||||
values
|
||||
(?, ?, ?, ?)
|
||||
`, []any{oidcAm2.PublicId, oidcAm2.ScopeId, oidcAm2.OperationalState, oidcAm2.KeyId})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
oidcAcct2, err := oidc.NewAccount(ctx, oidcAm2.GetPublicId(), "oidcAcct2")
|
||||
require.NoError(t, err)
|
||||
oidcAcct2, err = oidcRepo.CreateAccount(ctx, scope.Global.String(), oidcAcct2)
|
||||
oidcAcct2.PublicId = "acctoidc_1234567890"
|
||||
num, err = rw.Exec(ctx, `
|
||||
insert into auth_oidc_account
|
||||
(public_id, auth_method_id, scope_id, issuer, subject)
|
||||
values
|
||||
(?, ?, ?, ?, ?)
|
||||
`, []any{oidcAcct2.PublicId, oidcAm2.PublicId, oidcAm2.ScopeId, oidcAm2.Issuer, oidcAcct2.Subject})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
_, err = iamRepo.AddUserAccounts(ctx, usr.GetPublicId(), usr.GetVersion(),
|
||||
[]string{
|
||||
pwAcct1.GetPublicId(), pwAcct2.GetPublicId(),
|
||||
oidcAcct1.GetPublicId(), oidcAcct2.GetPublicId(),
|
||||
})
|
||||
num, err = rw.Exec(ctx, `
|
||||
update auth_account set
|
||||
iam_user_id=?
|
||||
where
|
||||
public_id in (?, ?, ?, ?)
|
||||
`, []any{
|
||||
usr.PublicId,
|
||||
pwAcct1.PublicId, pwAcct2.PublicId, oidcAcct1.PublicId, oidcAcct2.PublicId,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 4, num)
|
||||
usr, accts, err := iamRepo.LookupUser(ctx, usr.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, []string{
|
||||
@@ -129,10 +219,20 @@ func TestMigrations_DeleteOrphanedAccounts(t *testing.T) {
|
||||
oidcAcct1.GetPublicId(), oidcAcct2.GetPublicId(),
|
||||
}, accts)
|
||||
|
||||
_, err = pwRepo.DeleteAccount(ctx, scope.Global.String(), pwAcct1.GetPublicId())
|
||||
num, err = rw.Exec(ctx, `
|
||||
delete from auth_password_account
|
||||
where
|
||||
public_id=?
|
||||
`, []any{pwAcct1.PublicId})
|
||||
require.NoError(t, err)
|
||||
_, err = oidcRepo.DeleteAccount(ctx, scope.Global.String(), oidcAcct1.GetPublicId())
|
||||
assert.Equal(t, 1, num)
|
||||
num, err = rw.Exec(ctx, `
|
||||
delete from auth_oidc_account
|
||||
where
|
||||
public_id=?
|
||||
`, []any{oidcAcct1.PublicId})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
// pwAcct1 is still listed as an account for this user! oh no!
|
||||
_, accts, err = iamRepo.LookupUser(ctx, usr.GetPublicId())
|
||||
@@ -170,13 +270,23 @@ func TestMigrations_DeleteOrphanedAccounts(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, []string{pwAcct2.GetPublicId(), oidcAcct2.GetPublicId()}, accts)
|
||||
|
||||
_, err = pwRepo.DeleteAccount(ctx, scope.Global.String(), pwAcct2.GetPublicId())
|
||||
num, err = rw.Exec(ctx, `
|
||||
delete from auth_password_account
|
||||
where
|
||||
public_id=?
|
||||
`, []any{pwAcct2.PublicId})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
_, accts, err = iamRepo.LookupUser(ctx, usr.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, []string{oidcAcct2.GetPublicId()}, accts)
|
||||
_, err = oidcRepo.DeleteAccount(ctx, scope.Global.String(), oidcAcct2.GetPublicId())
|
||||
num, err = rw.Exec(ctx, `
|
||||
delete from auth_oidc_account
|
||||
where
|
||||
public_id=?
|
||||
`, []any{oidcAcct2.PublicId})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
|
||||
// Deleting the account now removes the association with the user
|
||||
_, accts, err = iamRepo.LookupUser(ctx, usr.GetPublicId())
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package oss_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/auth/password"
|
||||
"github.com/hashicorp/boundary/internal/authtoken"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/common"
|
||||
"github.com/hashicorp/boundary/internal/db/schema"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/hashicorp/boundary/testing/dbtest"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMigrations_NewForeignKeys(t *testing.T) {
|
||||
const (
|
||||
priorMigration = 58001
|
||||
currentMigration = 58002
|
||||
)
|
||||
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
dialect := dbtest.Postgres
|
||||
|
||||
c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1))
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, c())
|
||||
})
|
||||
d, err := common.SqlOpen(dialect, u)
|
||||
require.NoError(t, err)
|
||||
|
||||
// migration to the prior migration (before the one we want to test)
|
||||
m, err := schema.NewManager(ctx, schema.Dialect(dialect), d, schema.WithEditions(
|
||||
schema.TestCreatePartialEditions(schema.Dialect(dialect), schema.PartialEditions{"oss": priorMigration}),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = m.ApplyMigrations(ctx)
|
||||
require.NoError(t, err)
|
||||
state, err := m.CurrentState(ctx)
|
||||
require.NoError(t, err)
|
||||
want := &schema.State{
|
||||
Initialized: true,
|
||||
Editions: []schema.EditionState{
|
||||
{
|
||||
Name: "oss",
|
||||
BinarySchemaVersion: priorMigration,
|
||||
DatabaseSchemaVersion: priorMigration,
|
||||
DatabaseSchemaState: schema.Equal,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.Equal(t, want, state)
|
||||
|
||||
// Get a connection
|
||||
dbType, err := db.StringToDbType(dialect)
|
||||
require.NoError(t, err)
|
||||
conn, err := db.Open(ctx, dbType, u)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create project
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
err = kmsCache.CreateKeys(ctx, scope.Global.String())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a password account
|
||||
pwam := password.TestAuthMethod(t, conn, "global")
|
||||
password.TestAccount(t, conn, pwam.PublicId, "user", password.WithPassword("password"))
|
||||
|
||||
// Create an auth token
|
||||
tok := authtoken.TestAuthToken(t, conn, kmsCache, "global")
|
||||
_ = tok
|
||||
|
||||
// now we're ready for the migration we want to test.
|
||||
m, err = schema.NewManager(ctx, schema.Dialect(dialect), d, schema.WithEditions(
|
||||
schema.TestCreatePartialEditions(schema.Dialect(dialect), schema.PartialEditions{"oss": currentMigration}),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = m.ApplyMigrations(ctx)
|
||||
require.NoError(t, err)
|
||||
state, err = m.CurrentState(ctx)
|
||||
require.NoError(t, err)
|
||||
want = &schema.State{
|
||||
Initialized: true,
|
||||
Editions: []schema.EditionState{
|
||||
{
|
||||
Name: "oss",
|
||||
BinarySchemaVersion: currentMigration,
|
||||
DatabaseSchemaVersion: currentMigration,
|
||||
DatabaseSchemaState: schema.Equal,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.Equal(t, want, state)
|
||||
|
||||
// As long as there were no errors, the migration succeeded.
|
||||
}
|
||||
@@ -25,7 +25,8 @@ TESTS ?= tests/setup/*.sql \
|
||||
tests/session/*.sql \
|
||||
tests/account/*/*.sql \
|
||||
tests/target/*.sql \
|
||||
tests/controller/*.sql
|
||||
tests/controller/*.sql \
|
||||
tests/kms/*.sql
|
||||
|
||||
POSTGRES_DOCKER_IMAGE_BASE ?= postgres
|
||||
|
||||
|
||||
@@ -29,6 +29,27 @@ begin;
|
||||
('o_____colors', 'u______carly', 'Carly'),
|
||||
('o_____colors', 'u______ciara', 'Ciara');
|
||||
|
||||
-- Add keys
|
||||
insert into kms_root_key
|
||||
(private_id, scope_id)
|
||||
values
|
||||
('krk_______colors', 'o_____colors');
|
||||
|
||||
insert into kms_root_key_version
|
||||
(private_id, root_key_id, key)
|
||||
values
|
||||
('krkv_______colors', 'krk_______colors', '_______color1'::bytea);
|
||||
|
||||
insert into kms_data_key
|
||||
(private_id, root_key_id, purpose)
|
||||
values
|
||||
('kdk_______colors', 'krk_______colors', 'database');
|
||||
|
||||
insert into kms_data_key_version
|
||||
(private_id, data_key_id, root_key_version_id, key)
|
||||
values
|
||||
('kdkv_______colors', 'kdk_______colors', 'krkv_______colors', '_______color2'::bytea);
|
||||
|
||||
insert into iam_group
|
||||
(scope_id, public_id, name)
|
||||
values
|
||||
@@ -120,10 +141,10 @@ begin;
|
||||
insert into auth_token
|
||||
(key_id, auth_account_id, public_id, token)
|
||||
values
|
||||
('key', 'apa____clare', 'tok____clare', 'tok____clare'::bytea),
|
||||
('key', 'apa____cindy', 'tok____cindy', 'tok____cindy'::bytea),
|
||||
('key', 'apa____ciara', 'tok____ciara', 'tok____ciara'::bytea),
|
||||
('key', 'apa____carly', 'tok____carly', 'tok____carly'::bytea);
|
||||
('kdkv_______colors', 'apa____clare', 'tok____clare', 'tok____clare'::bytea),
|
||||
('kdkv_______colors', 'apa____cindy', 'tok____cindy', 'tok____cindy'::bytea),
|
||||
('kdkv_______colors', 'apa____ciara', 'tok____ciara', 'tok____ciara'::bytea),
|
||||
('kdkv_______colors', 'apa____carly', 'tok____carly', 'tok____carly'::bytea);
|
||||
|
||||
insert into static_host
|
||||
(catalog_id, public_id, address)
|
||||
|
||||
@@ -115,6 +115,16 @@ begin;
|
||||
values
|
||||
('kdkv___widget', 'kdk____widget', 'krkv___widget', 'kdk____widget'::bytea);
|
||||
|
||||
insert into kms_data_key_version_destruction_job
|
||||
(key_id)
|
||||
values
|
||||
('kdkv___widget');
|
||||
|
||||
insert into kms_data_key_version_destruction_job_run
|
||||
(key_id, table_name, total_count)
|
||||
values
|
||||
('kdkv___widget', 'auth_token', 100);
|
||||
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
@@ -156,11 +166,11 @@ begin;
|
||||
insert into auth_token
|
||||
(key_id, auth_account_id, public_id, token)
|
||||
values
|
||||
('key', 'apa___walter', 'tok___walter', 'tok___walter'::bytea),
|
||||
('key', 'apa1__walter', 'tok1__walter', 'tok1__walter'::bytea),
|
||||
('key', 'apa___warren', 'tok___warren', 'tok___warren'::bytea),
|
||||
('key', 'apa___waylon', 'tok___waylon', 'tok___waylon'::bytea),
|
||||
('key', 'apa___wilson', 'tok___wilson', 'tok___wilson'::bytea);
|
||||
('kdkv___widget', 'apa___walter', 'tok___walter', 'tok___walter'::bytea),
|
||||
('kdkv___widget', 'apa1__walter', 'tok1__walter', 'tok1__walter'::bytea),
|
||||
('kdkv___widget', 'apa___warren', 'tok___warren', 'tok___warren'::bytea),
|
||||
('kdkv___widget', 'apa___waylon', 'tok___waylon', 'tok___waylon'::bytea),
|
||||
('kdkv___widget', 'apa___wilson', 'tok___wilson', 'tok___wilson'::bytea);
|
||||
|
||||
insert into auth_oidc_method
|
||||
(scope_id, public_id, client_id, name, state, key_id, issuer)
|
||||
@@ -179,8 +189,8 @@ begin;
|
||||
insert into auth_token
|
||||
(key_id, auth_account_id, public_id, token)
|
||||
values
|
||||
('key', 'aoa___walter', 'oidc__walter', 'oidc__walter'::bytea),
|
||||
('key', 'aoa___warren', 'oidc__warren', 'oidc__warren'::bytea);
|
||||
('kdkv___widget', 'aoa___walter', 'oidc__walter', 'oidc__walter'::bytea),
|
||||
('kdkv___widget', 'aoa___warren', 'oidc__warren', 'oidc__warren'::bytea);
|
||||
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
@@ -14,6 +14,28 @@ begin;
|
||||
('o__foodtruck', 'project', 'p______tacos', 'Tacos'),
|
||||
('o__foodtruck', 'project', 'p_____nachos', 'Nachos');
|
||||
|
||||
-- KMS
|
||||
|
||||
insert into kms_root_key
|
||||
(private_id, scope_id)
|
||||
values
|
||||
('krk___foodtruck', 'o__foodtruck');
|
||||
|
||||
insert into kms_root_key_version
|
||||
(private_id, root_key_id, key)
|
||||
values
|
||||
('krkv___foodtruck', 'krk___foodtruck', 'krk___foodtruck'::bytea);
|
||||
|
||||
insert into kms_data_key
|
||||
(private_id, root_key_id, purpose)
|
||||
values
|
||||
('kdk____foodtruck', 'krk___foodtruck', 'database');
|
||||
|
||||
insert into kms_data_key_version
|
||||
(private_id, data_key_id, root_key_version_id, key)
|
||||
values
|
||||
('kdkv___foodtruck', 'kdk____foodtruck', 'krkv___foodtruck', 'kdk____foodtruck'::bytea);
|
||||
|
||||
-- Host Catalog, Host, Host Set
|
||||
insert into static_host_catalog
|
||||
(project_id, public_id, name)
|
||||
@@ -77,8 +99,8 @@ begin;
|
||||
insert into auth_token
|
||||
(key_id, auth_account_id, public_id, token)
|
||||
values
|
||||
('key', 'apa______jim', 'tok______jim', 'tok______jim'::bytea),
|
||||
('key', 'apa______bob', 'tok______bob', 'tok______bob'::bytea);
|
||||
('kdkv___foodtruck', 'apa______jim', 'tok______jim', 'tok______jim'::bytea),
|
||||
('kdkv___foodtruck', 'apa______bob', 'tok______bob', 'tok______bob'::bytea);
|
||||
|
||||
-- Roles
|
||||
insert into iam_role
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
begin;
|
||||
select plan(15);
|
||||
select wtt_load('widgets', 'iam', 'kms');
|
||||
|
||||
-- Should fail when inserting an unknown key_id
|
||||
prepare job_insert_unknown_key_id as
|
||||
insert into kms_data_key_version_destruction_job
|
||||
(key_id)
|
||||
values
|
||||
('kdkv___unknown');
|
||||
select throws_ok('job_insert_unknown_key_id', '23503', null, 'insert of unknown key_id in kms_data_key_version_destruction_job succeeded');
|
||||
|
||||
-- Should fail when inserting an already existing key_id
|
||||
prepare insert_already_existing_key_id as
|
||||
insert into kms_data_key_version_destruction_job
|
||||
(key_id)
|
||||
values
|
||||
('kdkv___widget');
|
||||
select throws_ok('insert_already_existing_key_id', '23505', null, 'insert of duplicate key_id in kms_data_key_version_destruction_job succeeded');
|
||||
|
||||
-- Should succeed when inserting new key_id
|
||||
prepare insert_new_key_id as
|
||||
insert into kms_data_key_version_destruction_job
|
||||
(key_id)
|
||||
values
|
||||
('kdkv_______colors');
|
||||
select lives_ok('insert_new_key_id', 'insert of valid key_id in kms_data_key_version_destruction_job failed');
|
||||
|
||||
-- Should fail when inserting an unknown table_name
|
||||
prepare run_insert_unknown_table_name as
|
||||
insert into kms_data_key_version_destruction_job_run
|
||||
(key_id, table_name, total_count)
|
||||
values
|
||||
('kdkv___widget', 'unknown_table', 100);
|
||||
select throws_ok('run_insert_unknown_table_name', 'P0001', null, 'insert of unknown table_name in kms_data_key_version_destruction_job_run succeeded');
|
||||
|
||||
-- Should fail when inserting a duplicate (key_id, table_name) tuple
|
||||
prepare run_insert_duplicate_key_id_table_name as
|
||||
insert into kms_data_key_version_destruction_job_run
|
||||
(key_id, table_name, total_count)
|
||||
values
|
||||
('kdkv___widget', 'auth_token', 100);
|
||||
select throws_ok('run_insert_duplicate_key_id_table_name', '23505', null, 'insert of duplicate (key_id, table_name) tuple in kms_data_key_version_destruction_job_run succeeded');
|
||||
|
||||
-- Should succeed when inserting a new, valid, (key_id, table_name) tuple
|
||||
prepare run_insert_key_id_table_name as
|
||||
insert into kms_data_key_version_destruction_job_run
|
||||
(key_id, table_name, total_count)
|
||||
values
|
||||
('kdkv___widget', 'auth_oidc_method', 100);
|
||||
select lives_ok('run_insert_key_id_table_name', 'insert of valid (key_id, table_name) tuple in kms_data_key_version_destruction_job_run failed');
|
||||
|
||||
-- Should fail when setting completed_count > total_count
|
||||
prepare run_update_invalid_completed_count as
|
||||
update kms_data_key_version_destruction_job_run set
|
||||
completed_count=101
|
||||
where
|
||||
key_id='kdkv___widget' and table_name='auth_oidc_method';
|
||||
select throws_ok('run_update_invalid_completed_count', '23514', null, 'setting completed_count > total_count in kms_data_key_version_destruction_job_run succeeded');
|
||||
|
||||
-- Should succeed to set is_running to true
|
||||
prepare run_update_is_running as
|
||||
update kms_data_key_version_destruction_job_run set
|
||||
is_running=true
|
||||
where
|
||||
key_id='kdkv___widget' and table_name='auth_oidc_method';
|
||||
select lives_ok('run_update_is_running', 'setting is_running=true with no other run running in kms_data_key_version_destruction_job_run failed');
|
||||
|
||||
-- Should fail to set is_running to true while another run is running
|
||||
prepare run_update_invalid_is_running as
|
||||
update kms_data_key_version_destruction_job_run set
|
||||
is_running=true
|
||||
where
|
||||
key_id='kdkv___widget' and table_name='auth_token';
|
||||
select throws_ok('run_update_invalid_is_running', '23505', null, 'setting is_running=true while another run is running in kms_data_key_version_destruction_job_run succeeded');
|
||||
|
||||
-- Should fail to set completed_count=total_count without also setting is_running=false
|
||||
prepare run_update_completed_count_without_is_running as
|
||||
update kms_data_key_version_destruction_job_run set
|
||||
completed_count=100
|
||||
where
|
||||
key_id='kdkv___widget' and table_name='auth_oidc_method';
|
||||
select throws_ok('run_update_completed_count_without_is_running','23514', null, 'setting completed_count=total_count without also setting is_running=false in kms_data_key_version_destruction_job_run succeeded');
|
||||
|
||||
-- Should succed when setting completed_count=total_count and is_running=false
|
||||
prepare run_update_completed_count_and_is_running as
|
||||
update kms_data_key_version_destruction_job_run set
|
||||
completed_count=100,
|
||||
is_running=false
|
||||
where
|
||||
key_id='kdkv___widget' and table_name='auth_oidc_method';
|
||||
select lives_ok('run_update_completed_count_and_is_running', 'setting completed_count=total_count and is_running=false kms_data_key_version_destruction_job_run failed');
|
||||
|
||||
-- Should succeed to set is_running to true when no other run is running
|
||||
prepare run_update_is_running_after_other_finished as
|
||||
update kms_data_key_version_destruction_job_run set
|
||||
is_running=true
|
||||
where
|
||||
key_id='kdkv___widget' and table_name='auth_token';
|
||||
select lives_ok('run_update_is_running_after_other_finished', 'setting is_running=true while no other run is running in kms_data_key_version_destruction_job_run failed');
|
||||
|
||||
-- Progress should report 'running' as one of the runs is running
|
||||
prepare list_progress as
|
||||
select key_id, scope_id, status, completed_count, total_count from kms_data_key_version_destruction_job_progress;
|
||||
select results_eq(
|
||||
'list_progress',
|
||||
$$VALUES ('kdkv___widget'::kms_private_id,'o_____widget'::kms_scope_id,'running',100::numeric,200::numeric)$$
|
||||
);
|
||||
|
||||
-- Should succed when setting completed_count=total_count and is_running=false
|
||||
prepare run_update_completed_count_and_is_running_again as
|
||||
update kms_data_key_version_destruction_job_run set
|
||||
completed_count=100,
|
||||
is_running=false
|
||||
where
|
||||
key_id='kdkv___widget' and table_name='auth_token';
|
||||
select lives_ok('run_update_completed_count_and_is_running_again', 'setting completed_count=total_count and is_running=false kms_data_key_version_destruction_job_run failed');
|
||||
|
||||
-- Progress should report 'completed' as all of the completed_count=total_count
|
||||
select results_eq(
|
||||
'list_progress',
|
||||
$$VALUES ('kdkv___widget'::kms_private_id,'o_____widget'::kms_scope_id,'completed',200::numeric,200::numeric)$$
|
||||
);
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
+21
-1
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/oplog/store"
|
||||
"github.com/hashicorp/boundary/testing/dbtest"
|
||||
"github.com/hashicorp/go-dbw"
|
||||
"github.com/hashicorp/go-kms-wrapping/extras/kms/v2"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
aead "github.com/hashicorp/go-kms-wrapping/v2/aead"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -109,7 +110,7 @@ func TestSetupWithMock(t *testing.T) (*DB, sqlmock.Sqlmock) {
|
||||
return ret, mock
|
||||
}
|
||||
|
||||
// TestWrapper initializes an AEAD wrapping.Wrapper for testing the oplog
|
||||
// TestWrapper initializes an AEAD wrapping.Wrapper for testing
|
||||
func TestWrapper(t testing.TB) wrapping.Wrapper {
|
||||
t.Helper()
|
||||
rootKey := make([]byte, 32)
|
||||
@@ -131,6 +132,25 @@ func TestWrapper(t testing.TB) wrapping.Wrapper {
|
||||
return root
|
||||
}
|
||||
|
||||
// TestDBWrapper initializes a DB wrapper for testing
|
||||
func TestDBWrapper(t testing.TB, conn *DB, purpose kms.KeyPurpose) wrapping.Wrapper {
|
||||
t.Helper()
|
||||
purposes := []kms.KeyPurpose{purpose}
|
||||
if purpose != "oplog" {
|
||||
// Always add the oplog purpose, most tests need it
|
||||
purposes = append(purposes, "oplog")
|
||||
}
|
||||
kmsCache, err := kms.New(dbw.New(conn.wrapped.Load()), dbw.New(conn.wrapped.Load()), purposes)
|
||||
require.NoError(t, err)
|
||||
err = kmsCache.AddExternalWrapper(context.Background(), kms.KeyPurposeRootKey, TestWrapper(t))
|
||||
require.NoError(t, err)
|
||||
err = kmsCache.CreateKeys(context.Background(), "global", purposes)
|
||||
require.NoError(t, err)
|
||||
wrapper, err := kmsCache.GetWrapper(context.Background(), "global", purpose)
|
||||
require.NoError(t, err)
|
||||
return wrapper
|
||||
}
|
||||
|
||||
// AssertPublicId is a test helper that asserts that the provided id is in
|
||||
// the format of a public id.
|
||||
func AssertPublicId(t testing.TB, prefix, actual string) {
|
||||
|
||||
@@ -25,6 +25,7 @@ func TestVerifyOplogEntry(t *testing.T) {
|
||||
d, _ := TestSetup(t, "postgres")
|
||||
assert := assert.New(t)
|
||||
TestCreateTables(t, d)
|
||||
wrapper := TestDBWrapper(t, d, "oplog")
|
||||
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
rw := New(d)
|
||||
@@ -37,7 +38,7 @@ func TestVerifyOplogEntry(t *testing.T) {
|
||||
context.Background(),
|
||||
user,
|
||||
WithOplog(
|
||||
TestWrapper(t),
|
||||
wrapper,
|
||||
oplog.Metadata{
|
||||
"op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()},
|
||||
"resource-public-id": []string{user.GetPublicId()},
|
||||
|
||||
@@ -2640,6 +2640,110 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/scopes/{id}:list-keys": {
|
||||
"get": {
|
||||
"summary": "List all keys in a Scope.",
|
||||
"operationId": "ScopeService_ListKeys",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.api.services.v1.ListKeysResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"controller.api.services.v1.ScopeService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/scopes/{scope_id}:list-key-version-destruction-jobs": {
|
||||
"get": {
|
||||
"summary": "Lists all pending key version destruction jobs in a Scope.",
|
||||
"operationId": "ScopeService_ListKeyVersionDestructionJobs",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.api.services.v1.ListKeyVersionDestructionJobsResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "scope_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"controller.api.services.v1.ScopeService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/scopes:destroy-key-version": {
|
||||
"post": {
|
||||
"summary": "Destroy the specified key version in a Scope. This may start an asynchronous job that re-encrypts all data encrypted by the specified key version. Use GET /v1/scopes/{scope_id}:list-key-version-destruction-jobs to monitor pending destruction jobs.",
|
||||
"operationId": "ScopeService_DestroyKeyVersion",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.api.services.v1.DestroyKeyVersionResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.api.services.v1.DestroyKeyVersionRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"controller.api.services.v1.ScopeService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/scopes:rotate-keys": {
|
||||
"post": {
|
||||
"summary": "Rotate all keys in a Scope.",
|
||||
"operationId": "ScopeService_RotateKeys",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.api.services.v1.RotateKeysResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.api.services.v1.RotateKeysRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"controller.api.services.v1.ScopeService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/sessions": {
|
||||
"get": {
|
||||
"summary": "Lists all Sessions.",
|
||||
@@ -4945,6 +5049,93 @@
|
||||
},
|
||||
"title": "Role contains all fields related to a Role resource"
|
||||
},
|
||||
"controller.api.resources.scopes.v1.Key": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the Key."
|
||||
},
|
||||
"scope": {
|
||||
"$ref": "#/definitions/controller.api.resources.scopes.v1.ScopeInfo",
|
||||
"description": "Scope information for this resource."
|
||||
},
|
||||
"purpose": {
|
||||
"type": "string",
|
||||
"description": "The purpose of the Key."
|
||||
},
|
||||
"created_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "The time this Key was created."
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "The type of the Key."
|
||||
},
|
||||
"versions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/controller.api.resources.scopes.v1.KeyVersion"
|
||||
},
|
||||
"description": "The versions of the key."
|
||||
}
|
||||
},
|
||||
"description": "Key contains all fields related to a Key in a Scope."
|
||||
},
|
||||
"controller.api.resources.scopes.v1.KeyVersion": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the key version."
|
||||
},
|
||||
"version": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "The iteration of the Key that this version represents."
|
||||
},
|
||||
"created_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "When this version was created."
|
||||
}
|
||||
},
|
||||
"title": "KeyVersion describes a specific version of a key and holds the actual key material"
|
||||
},
|
||||
"controller.api.resources.scopes.v1.KeyVersionDestructionJob": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key_version_id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the Key version this job relates to."
|
||||
},
|
||||
"scope": {
|
||||
"$ref": "#/definitions/controller.api.resources.scopes.v1.ScopeInfo",
|
||||
"description": "Scope information for this resource."
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "The current status of the key version destruction job. One of \"pending\", \"running\" or \"completed\"."
|
||||
},
|
||||
"created_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "The time this key version destruction job was created."
|
||||
},
|
||||
"completed_count": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "The number of rows that have been successfully re-encrypted with a new key version.\nAll rows must be re-encrypted before the key version can be destroyed."
|
||||
},
|
||||
"total_count": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "The total number of rows that need re-encrypting."
|
||||
}
|
||||
},
|
||||
"description": "KeyVersionDestructionJob holds information about a pending key version destruction job."
|
||||
},
|
||||
"controller.api.resources.scopes.v1.Scope": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -6116,6 +6307,26 @@
|
||||
"controller.api.services.v1.DeleteWorkerResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"controller.api.services.v1.DestroyKeyVersionRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"scope_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"key_version_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"controller.api.services.v1.DestroyKeyVersionResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": "Destruction state. One of \"pending\" or \"completed\".\nUse GET /v1/scopes/{scope_id}:list-key-version-destruction-jobs\nto monitor pending destruction jobs."
|
||||
}
|
||||
}
|
||||
},
|
||||
"controller.api.services.v1.GetAccountResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -6362,6 +6573,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"controller.api.services.v1.ListKeyVersionDestructionJobsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/controller.api.resources.scopes.v1.KeyVersionDestructionJob"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"controller.api.services.v1.ListKeysResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/controller.api.resources.scopes.v1.Key"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"controller.api.services.v1.ListManagedGroupsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -6519,6 +6752,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"controller.api.services.v1.RotateKeysRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"scope_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"rewrap": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"controller.api.services.v1.RotateKeysResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"controller.api.services.v1.SetGroupMembersResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -29,7 +29,7 @@ type GetScopeRequest struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
func (x *GetScopeRequest) Reset() {
|
||||
@@ -123,9 +123,9 @@ type ListScopesRequest struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ScopeId string `protobuf:"bytes,1,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty"`
|
||||
Recursive bool `protobuf:"varint,20,opt,name=recursive,proto3" json:"recursive,omitempty"`
|
||||
Filter string `protobuf:"bytes,30,opt,name=filter,proto3" json:"filter,omitempty"`
|
||||
ScopeId string `protobuf:"bytes,1,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
Recursive bool `protobuf:"varint,20,opt,name=recursive,proto3" json:"recursive,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
Filter string `protobuf:"bytes,30,opt,name=filter,proto3" json:"filter,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
func (x *ListScopesRequest) Reset() {
|
||||
@@ -233,8 +233,8 @@ type CreateScopeRequest struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
SkipAdminRoleCreation bool `protobuf:"varint,1,opt,name=skip_admin_role_creation,json=skipAdminRoleCreation,proto3" json:"skip_admin_role_creation,omitempty"`
|
||||
SkipDefaultRoleCreation bool `protobuf:"varint,2,opt,name=skip_default_role_creation,json=skipDefaultRoleCreation,proto3" json:"skip_default_role_creation,omitempty"`
|
||||
SkipAdminRoleCreation bool `protobuf:"varint,1,opt,name=skip_admin_role_creation,json=skipAdminRoleCreation,proto3" json:"skip_admin_role_creation,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
SkipDefaultRoleCreation bool `protobuf:"varint,2,opt,name=skip_default_role_creation,json=skipDefaultRoleCreation,proto3" json:"skip_default_role_creation,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
Item *scopes.Scope `protobuf:"bytes,3,opt,name=item,proto3" json:"item,omitempty"`
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ type CreateScopeResponse struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Uri string `protobuf:"bytes,1,opt,name=uri,proto3" json:"uri,omitempty"`
|
||||
Uri string `protobuf:"bytes,1,opt,name=uri,proto3" json:"uri,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
Item *scopes.Scope `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"`
|
||||
}
|
||||
|
||||
@@ -351,7 +351,7 @@ type UpdateScopeRequest struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
Item *scopes.Scope `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"`
|
||||
UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,3,opt,name=update_mask,proto3" json:"update_mask,omitempty"`
|
||||
}
|
||||
@@ -461,7 +461,7 @@ type DeleteScopeRequest struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
func (x *DeleteScopeRequest) Reset() {
|
||||
@@ -541,6 +541,392 @@ func (*DeleteScopeResponse) Descriptor() ([]byte, []int) {
|
||||
return file_controller_api_services_v1_scope_service_proto_rawDescGZIP(), []int{9}
|
||||
}
|
||||
|
||||
type ListKeysRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
func (x *ListKeysRequest) Reset() {
|
||||
*x = ListKeysRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_api_services_v1_scope_service_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListKeysRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListKeysRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ListKeysRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_api_services_v1_scope_service_proto_msgTypes[10]
|
||||
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 ListKeysRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ListKeysRequest) Descriptor() ([]byte, []int) {
|
||||
return file_controller_api_services_v1_scope_service_proto_rawDescGZIP(), []int{10}
|
||||
}
|
||||
|
||||
func (x *ListKeysRequest) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ListKeysResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Items []*scopes.Key `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ListKeysResponse) Reset() {
|
||||
*x = ListKeysResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_api_services_v1_scope_service_proto_msgTypes[11]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListKeysResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListKeysResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ListKeysResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_api_services_v1_scope_service_proto_msgTypes[11]
|
||||
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 ListKeysResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ListKeysResponse) Descriptor() ([]byte, []int) {
|
||||
return file_controller_api_services_v1_scope_service_proto_rawDescGZIP(), []int{11}
|
||||
}
|
||||
|
||||
func (x *ListKeysResponse) GetItems() []*scopes.Key {
|
||||
if x != nil {
|
||||
return x.Items
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RotateKeysRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ScopeId string `protobuf:"bytes,1,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
Rewrap bool `protobuf:"varint,2,opt,name=rewrap,proto3" json:"rewrap,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
func (x *RotateKeysRequest) Reset() {
|
||||
*x = RotateKeysRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_api_services_v1_scope_service_proto_msgTypes[12]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *RotateKeysRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RotateKeysRequest) ProtoMessage() {}
|
||||
|
||||
func (x *RotateKeysRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_api_services_v1_scope_service_proto_msgTypes[12]
|
||||
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 RotateKeysRequest.ProtoReflect.Descriptor instead.
|
||||
func (*RotateKeysRequest) Descriptor() ([]byte, []int) {
|
||||
return file_controller_api_services_v1_scope_service_proto_rawDescGZIP(), []int{12}
|
||||
}
|
||||
|
||||
func (x *RotateKeysRequest) GetScopeId() string {
|
||||
if x != nil {
|
||||
return x.ScopeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RotateKeysRequest) GetRewrap() bool {
|
||||
if x != nil {
|
||||
return x.Rewrap
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type RotateKeysResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *RotateKeysResponse) Reset() {
|
||||
*x = RotateKeysResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_api_services_v1_scope_service_proto_msgTypes[13]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *RotateKeysResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RotateKeysResponse) ProtoMessage() {}
|
||||
|
||||
func (x *RotateKeysResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_api_services_v1_scope_service_proto_msgTypes[13]
|
||||
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 RotateKeysResponse.ProtoReflect.Descriptor instead.
|
||||
func (*RotateKeysResponse) Descriptor() ([]byte, []int) {
|
||||
return file_controller_api_services_v1_scope_service_proto_rawDescGZIP(), []int{13}
|
||||
}
|
||||
|
||||
type ListKeyVersionDestructionJobsRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ScopeId string `protobuf:"bytes,1,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
func (x *ListKeyVersionDestructionJobsRequest) Reset() {
|
||||
*x = ListKeyVersionDestructionJobsRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_api_services_v1_scope_service_proto_msgTypes[14]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListKeyVersionDestructionJobsRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListKeyVersionDestructionJobsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ListKeyVersionDestructionJobsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_api_services_v1_scope_service_proto_msgTypes[14]
|
||||
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 ListKeyVersionDestructionJobsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ListKeyVersionDestructionJobsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_controller_api_services_v1_scope_service_proto_rawDescGZIP(), []int{14}
|
||||
}
|
||||
|
||||
func (x *ListKeyVersionDestructionJobsRequest) GetScopeId() string {
|
||||
if x != nil {
|
||||
return x.ScopeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ListKeyVersionDestructionJobsResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Items []*scopes.KeyVersionDestructionJob `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ListKeyVersionDestructionJobsResponse) Reset() {
|
||||
*x = ListKeyVersionDestructionJobsResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_api_services_v1_scope_service_proto_msgTypes[15]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListKeyVersionDestructionJobsResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListKeyVersionDestructionJobsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ListKeyVersionDestructionJobsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_api_services_v1_scope_service_proto_msgTypes[15]
|
||||
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 ListKeyVersionDestructionJobsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ListKeyVersionDestructionJobsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_controller_api_services_v1_scope_service_proto_rawDescGZIP(), []int{15}
|
||||
}
|
||||
|
||||
func (x *ListKeyVersionDestructionJobsResponse) GetItems() []*scopes.KeyVersionDestructionJob {
|
||||
if x != nil {
|
||||
return x.Items
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DestroyKeyVersionRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ScopeId string `protobuf:"bytes,1,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
KeyVersionId string `protobuf:"bytes,2,opt,name=key_version_id,json=keyVersionId,proto3" json:"key_version_id,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
func (x *DestroyKeyVersionRequest) Reset() {
|
||||
*x = DestroyKeyVersionRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_api_services_v1_scope_service_proto_msgTypes[16]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DestroyKeyVersionRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DestroyKeyVersionRequest) ProtoMessage() {}
|
||||
|
||||
func (x *DestroyKeyVersionRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_api_services_v1_scope_service_proto_msgTypes[16]
|
||||
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 DestroyKeyVersionRequest.ProtoReflect.Descriptor instead.
|
||||
func (*DestroyKeyVersionRequest) Descriptor() ([]byte, []int) {
|
||||
return file_controller_api_services_v1_scope_service_proto_rawDescGZIP(), []int{16}
|
||||
}
|
||||
|
||||
func (x *DestroyKeyVersionRequest) GetScopeId() string {
|
||||
if x != nil {
|
||||
return x.ScopeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DestroyKeyVersionRequest) GetKeyVersionId() string {
|
||||
if x != nil {
|
||||
return x.KeyVersionId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type DestroyKeyVersionResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Destruction state. One of "pending" or "completed".
|
||||
// Use GET /v1/scopes/{scope_id}:list-key-version-destruction-jobs
|
||||
// to monitor pending destruction jobs.
|
||||
State string `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
func (x *DestroyKeyVersionResponse) Reset() {
|
||||
*x = DestroyKeyVersionResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_api_services_v1_scope_service_proto_msgTypes[17]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DestroyKeyVersionResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DestroyKeyVersionResponse) ProtoMessage() {}
|
||||
|
||||
func (x *DestroyKeyVersionResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_api_services_v1_scope_service_proto_msgTypes[17]
|
||||
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 DestroyKeyVersionResponse.ProtoReflect.Descriptor instead.
|
||||
func (*DestroyKeyVersionResponse) Descriptor() ([]byte, []int) {
|
||||
return file_controller_api_services_v1_scope_service_proto_rawDescGZIP(), []int{17}
|
||||
}
|
||||
|
||||
func (x *DestroyKeyVersionResponse) GetState() string {
|
||||
if x != nil {
|
||||
return x.State
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_controller_api_services_v1_scope_service_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_api_services_v1_scope_service_proto_rawDesc = []byte{
|
||||
@@ -616,70 +1002,171 @@ var file_controller_api_services_v1_scope_service_proto_rawDesc = []byte{
|
||||
0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x02, 0x69, 0x64, 0x22, 0x15, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x63, 0x6f,
|
||||
0x70, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe6, 0x06, 0x0a, 0x0c, 0x53,
|
||||
0x63, 0x6f, 0x70, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x9d, 0x01, 0x0a, 0x08,
|
||||
0x47, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
|
||||
0x70, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x21, 0x0a, 0x0f, 0x4c, 0x69,
|
||||
0x73, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a,
|
||||
0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x51, 0x0a,
|
||||
0x10, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x3d, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
|
||||
0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
|
||||
0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x63, 0x6f, 0x70,
|
||||
0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73,
|
||||
0x22, 0x46, 0x0a, 0x11, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64,
|
||||
0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x77, 0x72, 0x61, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x06, 0x72, 0x65, 0x77, 0x72, 0x61, 0x70, 0x22, 0x14, 0x0a, 0x12, 0x52, 0x6f, 0x74, 0x61,
|
||||
0x74, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x41,
|
||||
0x0a, 0x24, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x44, 0x65, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x73, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49,
|
||||
0x64, 0x22, 0x7b, 0x0a, 0x25, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x56, 0x65, 0x72, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x44, 0x65, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f,
|
||||
0x62, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x05, 0x69, 0x74,
|
||||
0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75,
|
||||
0x72, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4b,
|
||||
0x65, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x73, 0x74, 0x72, 0x75, 0x63,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x5b,
|
||||
0x0a, 0x18, 0x44, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x4b, 0x65, 0x79, 0x56, 0x65, 0x72, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63,
|
||||
0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63,
|
||||
0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x6b, 0x65, 0x79, 0x5f, 0x76, 0x65, 0x72,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6b,
|
||||
0x65, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x31, 0x0a, 0x19, 0x44,
|
||||
0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x4b, 0x65, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74,
|
||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x32, 0x94,
|
||||
0x0f, 0x0a, 0x0c, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
|
||||
0x9d, 0x01, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x2b, 0x2e, 0x63,
|
||||
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65,
|
||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x63, 0x6f,
|
||||
0x70, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69,
|
||||
0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x92, 0x41, 0x16, 0x12, 0x14, 0x47, 0x65,
|
||||
0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x53, 0x63, 0x6f, 0x70,
|
||||
0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63,
|
||||
0x6f, 0x70, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12,
|
||||
0xbe, 0x01, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x2d,
|
||||
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74,
|
||||
0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73,
|
||||
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53,
|
||||
0x63, 0x6f, 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x92,
|
||||
0x41, 0x3c, 0x12, 0x3a, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x53, 0x63,
|
||||
0x6f, 0x70, 0x65, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20,
|
||||
0x53, 0x63, 0x6f, 0x70, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x69,
|
||||
0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x82, 0xd3,
|
||||
0xe4, 0x93, 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73,
|
||||
0x12, 0xaa, 0x01, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65,
|
||||
0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
|
||||
0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72,
|
||||
0x65, 0x61, 0x74, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
|
||||
0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72,
|
||||
0x65, 0x61, 0x74, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x22, 0x3a, 0x92, 0x41, 0x19, 0x12, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20,
|
||||
0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x82,
|
||||
0xd3, 0xe4, 0x93, 0x02, 0x18, 0x22, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65,
|
||||
0x73, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0xa8, 0x01,
|
||||
0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x2e, 0x2e,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73,
|
||||
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||
0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73,
|
||||
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||
0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38,
|
||||
0x92, 0x41, 0x12, 0x12, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x53,
|
||||
0x63, 0x6f, 0x70, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x32, 0x0f, 0x2f, 0x76, 0x31,
|
||||
0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x04, 0x69, 0x74,
|
||||
0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x9c, 0x01, 0x0a, 0x0b, 0x44, 0x65, 0x6c,
|
||||
0x65, 0x74, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
|
||||
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
|
||||
0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x63, 0x6f, 0x70,
|
||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
|
||||
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x63, 0x6f, 0x70,
|
||||
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, 0x92, 0x41, 0x12, 0x12, 0x10,
|
||||
0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x2e,
|
||||
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x2a, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70,
|
||||
0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xa6, 0x01, 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74,
|
||||
0x4b, 0x65, 0x79, 0x73, 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
|
||||
0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61,
|
||||
0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c,
|
||||
0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||
0x3f, 0x92, 0x41, 0x1b, 0x12, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x6b,
|
||||
0x65, 0x79, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x82,
|
||||
0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, 0x19, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65,
|
||||
0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x6c, 0x69, 0x73, 0x74, 0x2d, 0x6b, 0x65, 0x79, 0x73,
|
||||
0x12, 0xae, 0x01, 0x0a, 0x0a, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x12,
|
||||
0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69,
|
||||
0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x74,
|
||||
0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e,
|
||||
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x74, 0x61,
|
||||
0x74, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x41,
|
||||
0x92, 0x41, 0x1d, 0x12, 0x1b, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x20, 0x61, 0x6c, 0x6c, 0x20,
|
||||
0x6b, 0x65, 0x79, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x2e,
|
||||
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x22, 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70,
|
||||
0x65, 0x73, 0x3a, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x2d, 0x6b, 0x65, 0x79, 0x73, 0x3a, 0x01,
|
||||
0x2a, 0x12, 0xa4, 0x02, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x56, 0x65, 0x72,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4a,
|
||||
0x6f, 0x62, 0x73, 0x12, 0x40, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
|
||||
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31,
|
||||
0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x44,
|
||||
0x65, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x41, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
|
||||
0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e,
|
||||
0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x36, 0x92, 0x41, 0x16, 0x12, 0x14, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61,
|
||||
0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x82, 0xd3,
|
||||
0xe4, 0x93, 0x02, 0x17, 0x12, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73,
|
||||
0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0xbe, 0x01, 0x0a, 0x0a,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e,
|
||||
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76,
|
||||
0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x63, 0x6f, 0x70,
|
||||
0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69,
|
||||
0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65,
|
||||
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x92, 0x41, 0x3c, 0x12, 0x3a,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73,
|
||||
0x20, 0x77, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x53, 0x63, 0x6f, 0x70,
|
||||
0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68,
|
||||
0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c,
|
||||
0x12, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0xaa, 0x01, 0x0a,
|
||||
0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x2e, 0x2e, 0x63,
|
||||
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65,
|
||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||
0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x63,
|
||||
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65,
|
||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||
0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x92,
|
||||
0x41, 0x19, 0x12, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69,
|
||||
0x6e, 0x67, 0x6c, 0x65, 0x20, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02,
|
||||
0x18, 0x22, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x3a, 0x04, 0x69,
|
||||
0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0xa8, 0x01, 0x0a, 0x0b, 0x55, 0x70,
|
||||
0x64, 0x61, 0x74, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69,
|
||||
0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x63, 0x6f,
|
||||
0x70, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69,
|
||||
0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x63, 0x6f,
|
||||
0x70, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, 0x12, 0x12,
|
||||
0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x53, 0x63, 0x6f, 0x70, 0x65,
|
||||
0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x32, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f,
|
||||
0x70, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04,
|
||||
0x69, 0x74, 0x65, 0x6d, 0x12, 0x9c, 0x01, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53,
|
||||
0x63, 0x6f, 0x70, 0x65, 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
|
||||
0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x44, 0x65, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x73,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x7e, 0x92, 0x41, 0x3c, 0x12, 0x3a, 0x4c,
|
||||
0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
|
||||
0x20, 0x6b, 0x65, 0x79, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x65, 0x73,
|
||||
0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6a, 0x6f, 0x62, 0x73, 0x20, 0x69, 0x6e,
|
||||
0x20, 0x61, 0x20, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x39, 0x12,
|
||||
0x37, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x7b, 0x73, 0x63, 0x6f,
|
||||
0x70, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x3a, 0x6c, 0x69, 0x73, 0x74, 0x2d, 0x6b, 0x65, 0x79, 0x2d,
|
||||
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x2d, 0x64, 0x65, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x2d, 0x6a, 0x6f, 0x62, 0x73, 0x12, 0xaa, 0x03, 0x0a, 0x11, 0x44, 0x65, 0x73,
|
||||
0x74, 0x72, 0x6f, 0x79, 0x4b, 0x65, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x34,
|
||||
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x73, 0x74,
|
||||
0x72, 0x6f, 0x79, 0x4b, 0x65, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
|
||||
0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
|
||||
0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, 0x92, 0x41, 0x12, 0x12, 0x10, 0x44, 0x65, 0x6c, 0x65,
|
||||
0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93,
|
||||
0x02, 0x11, 0x2a, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x7b,
|
||||
0x69, 0x64, 0x7d, 0x42, 0x74, 0x5a, 0x4b, 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, 0x67, 0x65,
|
||||
0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69,
|
||||
0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x73, 0x92, 0x41, 0x24, 0x12, 0x1e, 0x0a, 0x1c, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72,
|
||||
0x79, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x20, 0x48, 0x54, 0x54,
|
||||
0x50, 0x20, 0x41, 0x50, 0x49, 0x2a, 0x02, 0x02, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
0x31, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x4b, 0x65, 0x79, 0x56, 0x65, 0x72, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa7, 0x02, 0x92, 0x41,
|
||||
0xfa, 0x01, 0x12, 0xf7, 0x01, 0x44, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x20, 0x74, 0x68, 0x65,
|
||||
0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x76,
|
||||
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x53, 0x63, 0x6f, 0x70,
|
||||
0x65, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x73, 0x74, 0x61, 0x72,
|
||||
0x74, 0x20, 0x61, 0x6e, 0x20, 0x61, 0x73, 0x79, 0x6e, 0x63, 0x68, 0x72, 0x6f, 0x6e, 0x6f, 0x75,
|
||||
0x73, 0x20, 0x6a, 0x6f, 0x62, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x72, 0x65, 0x2d, 0x65, 0x6e,
|
||||
0x63, 0x72, 0x79, 0x70, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20,
|
||||
0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65,
|
||||
0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x76,
|
||||
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x20, 0x55, 0x73, 0x65, 0x20, 0x47, 0x45, 0x54, 0x20,
|
||||
0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x7b, 0x73, 0x63, 0x6f, 0x70,
|
||||
0x65, 0x5f, 0x69, 0x64, 0x7d, 0x3a, 0x6c, 0x69, 0x73, 0x74, 0x2d, 0x6b, 0x65, 0x79, 0x2d, 0x76,
|
||||
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x2d, 0x64, 0x65, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x2d, 0x6a, 0x6f, 0x62, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x6f, 0x6e, 0x69, 0x74,
|
||||
0x6f, 0x72, 0x20, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x64, 0x65, 0x73, 0x74, 0x72,
|
||||
0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6a, 0x6f, 0x62, 0x73, 0x2e, 0x82, 0xd3, 0xe4, 0x93,
|
||||
0x02, 0x23, 0x22, 0x1e, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x3a, 0x64,
|
||||
0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x2d, 0x6b, 0x65, 0x79, 0x2d, 0x76, 0x65, 0x72, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x3a, 0x01, 0x2a, 0x42, 0x74, 0x5a, 0x4b, 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,
|
||||
0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61,
|
||||
0x70, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76,
|
||||
0x69, 0x63, 0x65, 0x73, 0x92, 0x41, 0x24, 0x12, 0x1e, 0x0a, 0x1c, 0x42, 0x6f, 0x75, 0x6e, 0x64,
|
||||
0x61, 0x72, 0x79, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x20, 0x48,
|
||||
0x54, 0x54, 0x50, 0x20, 0x41, 0x50, 0x49, 0x2a, 0x02, 0x02, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -694,44 +1181,64 @@ func file_controller_api_services_v1_scope_service_proto_rawDescGZIP() []byte {
|
||||
return file_controller_api_services_v1_scope_service_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_controller_api_services_v1_scope_service_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
|
||||
var file_controller_api_services_v1_scope_service_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
|
||||
var file_controller_api_services_v1_scope_service_proto_goTypes = []interface{}{
|
||||
(*GetScopeRequest)(nil), // 0: controller.api.services.v1.GetScopeRequest
|
||||
(*GetScopeResponse)(nil), // 1: controller.api.services.v1.GetScopeResponse
|
||||
(*ListScopesRequest)(nil), // 2: controller.api.services.v1.ListScopesRequest
|
||||
(*ListScopesResponse)(nil), // 3: controller.api.services.v1.ListScopesResponse
|
||||
(*CreateScopeRequest)(nil), // 4: controller.api.services.v1.CreateScopeRequest
|
||||
(*CreateScopeResponse)(nil), // 5: controller.api.services.v1.CreateScopeResponse
|
||||
(*UpdateScopeRequest)(nil), // 6: controller.api.services.v1.UpdateScopeRequest
|
||||
(*UpdateScopeResponse)(nil), // 7: controller.api.services.v1.UpdateScopeResponse
|
||||
(*DeleteScopeRequest)(nil), // 8: controller.api.services.v1.DeleteScopeRequest
|
||||
(*DeleteScopeResponse)(nil), // 9: controller.api.services.v1.DeleteScopeResponse
|
||||
(*scopes.Scope)(nil), // 10: controller.api.resources.scopes.v1.Scope
|
||||
(*fieldmaskpb.FieldMask)(nil), // 11: google.protobuf.FieldMask
|
||||
(*GetScopeRequest)(nil), // 0: controller.api.services.v1.GetScopeRequest
|
||||
(*GetScopeResponse)(nil), // 1: controller.api.services.v1.GetScopeResponse
|
||||
(*ListScopesRequest)(nil), // 2: controller.api.services.v1.ListScopesRequest
|
||||
(*ListScopesResponse)(nil), // 3: controller.api.services.v1.ListScopesResponse
|
||||
(*CreateScopeRequest)(nil), // 4: controller.api.services.v1.CreateScopeRequest
|
||||
(*CreateScopeResponse)(nil), // 5: controller.api.services.v1.CreateScopeResponse
|
||||
(*UpdateScopeRequest)(nil), // 6: controller.api.services.v1.UpdateScopeRequest
|
||||
(*UpdateScopeResponse)(nil), // 7: controller.api.services.v1.UpdateScopeResponse
|
||||
(*DeleteScopeRequest)(nil), // 8: controller.api.services.v1.DeleteScopeRequest
|
||||
(*DeleteScopeResponse)(nil), // 9: controller.api.services.v1.DeleteScopeResponse
|
||||
(*ListKeysRequest)(nil), // 10: controller.api.services.v1.ListKeysRequest
|
||||
(*ListKeysResponse)(nil), // 11: controller.api.services.v1.ListKeysResponse
|
||||
(*RotateKeysRequest)(nil), // 12: controller.api.services.v1.RotateKeysRequest
|
||||
(*RotateKeysResponse)(nil), // 13: controller.api.services.v1.RotateKeysResponse
|
||||
(*ListKeyVersionDestructionJobsRequest)(nil), // 14: controller.api.services.v1.ListKeyVersionDestructionJobsRequest
|
||||
(*ListKeyVersionDestructionJobsResponse)(nil), // 15: controller.api.services.v1.ListKeyVersionDestructionJobsResponse
|
||||
(*DestroyKeyVersionRequest)(nil), // 16: controller.api.services.v1.DestroyKeyVersionRequest
|
||||
(*DestroyKeyVersionResponse)(nil), // 17: controller.api.services.v1.DestroyKeyVersionResponse
|
||||
(*scopes.Scope)(nil), // 18: controller.api.resources.scopes.v1.Scope
|
||||
(*fieldmaskpb.FieldMask)(nil), // 19: google.protobuf.FieldMask
|
||||
(*scopes.Key)(nil), // 20: controller.api.resources.scopes.v1.Key
|
||||
(*scopes.KeyVersionDestructionJob)(nil), // 21: controller.api.resources.scopes.v1.KeyVersionDestructionJob
|
||||
}
|
||||
var file_controller_api_services_v1_scope_service_proto_depIdxs = []int32{
|
||||
10, // 0: controller.api.services.v1.GetScopeResponse.item:type_name -> controller.api.resources.scopes.v1.Scope
|
||||
10, // 1: controller.api.services.v1.ListScopesResponse.items:type_name -> controller.api.resources.scopes.v1.Scope
|
||||
10, // 2: controller.api.services.v1.CreateScopeRequest.item:type_name -> controller.api.resources.scopes.v1.Scope
|
||||
10, // 3: controller.api.services.v1.CreateScopeResponse.item:type_name -> controller.api.resources.scopes.v1.Scope
|
||||
10, // 4: controller.api.services.v1.UpdateScopeRequest.item:type_name -> controller.api.resources.scopes.v1.Scope
|
||||
11, // 5: controller.api.services.v1.UpdateScopeRequest.update_mask:type_name -> google.protobuf.FieldMask
|
||||
10, // 6: controller.api.services.v1.UpdateScopeResponse.item:type_name -> controller.api.resources.scopes.v1.Scope
|
||||
0, // 7: controller.api.services.v1.ScopeService.GetScope:input_type -> controller.api.services.v1.GetScopeRequest
|
||||
2, // 8: controller.api.services.v1.ScopeService.ListScopes:input_type -> controller.api.services.v1.ListScopesRequest
|
||||
4, // 9: controller.api.services.v1.ScopeService.CreateScope:input_type -> controller.api.services.v1.CreateScopeRequest
|
||||
6, // 10: controller.api.services.v1.ScopeService.UpdateScope:input_type -> controller.api.services.v1.UpdateScopeRequest
|
||||
8, // 11: controller.api.services.v1.ScopeService.DeleteScope:input_type -> controller.api.services.v1.DeleteScopeRequest
|
||||
1, // 12: controller.api.services.v1.ScopeService.GetScope:output_type -> controller.api.services.v1.GetScopeResponse
|
||||
3, // 13: controller.api.services.v1.ScopeService.ListScopes:output_type -> controller.api.services.v1.ListScopesResponse
|
||||
5, // 14: controller.api.services.v1.ScopeService.CreateScope:output_type -> controller.api.services.v1.CreateScopeResponse
|
||||
7, // 15: controller.api.services.v1.ScopeService.UpdateScope:output_type -> controller.api.services.v1.UpdateScopeResponse
|
||||
9, // 16: controller.api.services.v1.ScopeService.DeleteScope:output_type -> controller.api.services.v1.DeleteScopeResponse
|
||||
12, // [12:17] is the sub-list for method output_type
|
||||
7, // [7:12] is the sub-list for method input_type
|
||||
7, // [7:7] is the sub-list for extension type_name
|
||||
7, // [7:7] is the sub-list for extension extendee
|
||||
0, // [0:7] is the sub-list for field type_name
|
||||
18, // 0: controller.api.services.v1.GetScopeResponse.item:type_name -> controller.api.resources.scopes.v1.Scope
|
||||
18, // 1: controller.api.services.v1.ListScopesResponse.items:type_name -> controller.api.resources.scopes.v1.Scope
|
||||
18, // 2: controller.api.services.v1.CreateScopeRequest.item:type_name -> controller.api.resources.scopes.v1.Scope
|
||||
18, // 3: controller.api.services.v1.CreateScopeResponse.item:type_name -> controller.api.resources.scopes.v1.Scope
|
||||
18, // 4: controller.api.services.v1.UpdateScopeRequest.item:type_name -> controller.api.resources.scopes.v1.Scope
|
||||
19, // 5: controller.api.services.v1.UpdateScopeRequest.update_mask:type_name -> google.protobuf.FieldMask
|
||||
18, // 6: controller.api.services.v1.UpdateScopeResponse.item:type_name -> controller.api.resources.scopes.v1.Scope
|
||||
20, // 7: controller.api.services.v1.ListKeysResponse.items:type_name -> controller.api.resources.scopes.v1.Key
|
||||
21, // 8: controller.api.services.v1.ListKeyVersionDestructionJobsResponse.items:type_name -> controller.api.resources.scopes.v1.KeyVersionDestructionJob
|
||||
0, // 9: controller.api.services.v1.ScopeService.GetScope:input_type -> controller.api.services.v1.GetScopeRequest
|
||||
2, // 10: controller.api.services.v1.ScopeService.ListScopes:input_type -> controller.api.services.v1.ListScopesRequest
|
||||
4, // 11: controller.api.services.v1.ScopeService.CreateScope:input_type -> controller.api.services.v1.CreateScopeRequest
|
||||
6, // 12: controller.api.services.v1.ScopeService.UpdateScope:input_type -> controller.api.services.v1.UpdateScopeRequest
|
||||
8, // 13: controller.api.services.v1.ScopeService.DeleteScope:input_type -> controller.api.services.v1.DeleteScopeRequest
|
||||
10, // 14: controller.api.services.v1.ScopeService.ListKeys:input_type -> controller.api.services.v1.ListKeysRequest
|
||||
12, // 15: controller.api.services.v1.ScopeService.RotateKeys:input_type -> controller.api.services.v1.RotateKeysRequest
|
||||
14, // 16: controller.api.services.v1.ScopeService.ListKeyVersionDestructionJobs:input_type -> controller.api.services.v1.ListKeyVersionDestructionJobsRequest
|
||||
16, // 17: controller.api.services.v1.ScopeService.DestroyKeyVersion:input_type -> controller.api.services.v1.DestroyKeyVersionRequest
|
||||
1, // 18: controller.api.services.v1.ScopeService.GetScope:output_type -> controller.api.services.v1.GetScopeResponse
|
||||
3, // 19: controller.api.services.v1.ScopeService.ListScopes:output_type -> controller.api.services.v1.ListScopesResponse
|
||||
5, // 20: controller.api.services.v1.ScopeService.CreateScope:output_type -> controller.api.services.v1.CreateScopeResponse
|
||||
7, // 21: controller.api.services.v1.ScopeService.UpdateScope:output_type -> controller.api.services.v1.UpdateScopeResponse
|
||||
9, // 22: controller.api.services.v1.ScopeService.DeleteScope:output_type -> controller.api.services.v1.DeleteScopeResponse
|
||||
11, // 23: controller.api.services.v1.ScopeService.ListKeys:output_type -> controller.api.services.v1.ListKeysResponse
|
||||
13, // 24: controller.api.services.v1.ScopeService.RotateKeys:output_type -> controller.api.services.v1.RotateKeysResponse
|
||||
15, // 25: controller.api.services.v1.ScopeService.ListKeyVersionDestructionJobs:output_type -> controller.api.services.v1.ListKeyVersionDestructionJobsResponse
|
||||
17, // 26: controller.api.services.v1.ScopeService.DestroyKeyVersion:output_type -> controller.api.services.v1.DestroyKeyVersionResponse
|
||||
18, // [18:27] is the sub-list for method output_type
|
||||
9, // [9:18] is the sub-list for method input_type
|
||||
9, // [9:9] is the sub-list for extension type_name
|
||||
9, // [9:9] is the sub-list for extension extendee
|
||||
0, // [0:9] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_controller_api_services_v1_scope_service_proto_init() }
|
||||
@@ -860,6 +1367,102 @@ func file_controller_api_services_v1_scope_service_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_api_services_v1_scope_service_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ListKeysRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_api_services_v1_scope_service_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ListKeysResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_api_services_v1_scope_service_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*RotateKeysRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_api_services_v1_scope_service_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*RotateKeysResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_api_services_v1_scope_service_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ListKeyVersionDestructionJobsRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_api_services_v1_scope_service_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ListKeyVersionDestructionJobsResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_api_services_v1_scope_service_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DestroyKeyVersionRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_api_services_v1_scope_service_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DestroyKeyVersionResponse); 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{
|
||||
@@ -867,7 +1470,7 @@ func file_controller_api_services_v1_scope_service_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_controller_api_services_v1_scope_service_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 10,
|
||||
NumMessages: 18,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -323,6 +323,178 @@ func local_request_ScopeService_DeleteScope_0(ctx context.Context, marshaler run
|
||||
|
||||
}
|
||||
|
||||
func request_ScopeService_ListKeys_0(ctx context.Context, marshaler runtime.Marshaler, client ScopeServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ListKeysRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||
}
|
||||
|
||||
protoReq.Id, err = runtime.String(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||
}
|
||||
|
||||
msg, err := client.ListKeys(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_ScopeService_ListKeys_0(ctx context.Context, marshaler runtime.Marshaler, server ScopeServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ListKeysRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||
}
|
||||
|
||||
protoReq.Id, err = runtime.String(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||
}
|
||||
|
||||
msg, err := server.ListKeys(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_ScopeService_RotateKeys_0(ctx context.Context, marshaler runtime.Marshaler, client ScopeServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq RotateKeysRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.RotateKeys(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_ScopeService_RotateKeys_0(ctx context.Context, marshaler runtime.Marshaler, server ScopeServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq RotateKeysRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.RotateKeys(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_ScopeService_ListKeyVersionDestructionJobs_0(ctx context.Context, marshaler runtime.Marshaler, client ScopeServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ListKeyVersionDestructionJobsRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["scope_id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "scope_id")
|
||||
}
|
||||
|
||||
protoReq.ScopeId, err = runtime.String(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "scope_id", err)
|
||||
}
|
||||
|
||||
msg, err := client.ListKeyVersionDestructionJobs(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_ScopeService_ListKeyVersionDestructionJobs_0(ctx context.Context, marshaler runtime.Marshaler, server ScopeServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ListKeyVersionDestructionJobsRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["scope_id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "scope_id")
|
||||
}
|
||||
|
||||
protoReq.ScopeId, err = runtime.String(val)
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "scope_id", err)
|
||||
}
|
||||
|
||||
msg, err := server.ListKeyVersionDestructionJobs(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_ScopeService_DestroyKeyVersion_0(ctx context.Context, marshaler runtime.Marshaler, client ScopeServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq DestroyKeyVersionRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.DestroyKeyVersion(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_ScopeService_DestroyKeyVersion_0(ctx context.Context, marshaler runtime.Marshaler, server ScopeServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq DestroyKeyVersionRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.DestroyKeyVersion(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
// RegisterScopeServiceHandlerServer registers the http handlers for service ScopeService to "mux".
|
||||
// UnaryRPC :call ScopeServiceServer directly.
|
||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||
@@ -454,6 +626,106 @@ func RegisterScopeServiceHandlerServer(ctx context.Context, mux *runtime.ServeMu
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_ScopeService_ListKeys_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
var annotatedContext context.Context
|
||||
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.ScopeService/ListKeys", runtime.WithHTTPPathPattern("/v1/scopes/{id}:list-keys"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_ScopeService_ListKeys_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_ScopeService_ListKeys_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_ScopeService_RotateKeys_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
var annotatedContext context.Context
|
||||
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.ScopeService/RotateKeys", runtime.WithHTTPPathPattern("/v1/scopes:rotate-keys"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_ScopeService_RotateKeys_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_ScopeService_RotateKeys_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_ScopeService_ListKeyVersionDestructionJobs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
var annotatedContext context.Context
|
||||
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.ScopeService/ListKeyVersionDestructionJobs", runtime.WithHTTPPathPattern("/v1/scopes/{scope_id}:list-key-version-destruction-jobs"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_ScopeService_ListKeyVersionDestructionJobs_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_ScopeService_ListKeyVersionDestructionJobs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_ScopeService_DestroyKeyVersion_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
var annotatedContext context.Context
|
||||
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.ScopeService/DestroyKeyVersion", runtime.WithHTTPPathPattern("/v1/scopes:destroy-key-version"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_ScopeService_DestroyKeyVersion_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_ScopeService_DestroyKeyVersion_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -605,6 +877,94 @@ func RegisterScopeServiceHandlerClient(ctx context.Context, mux *runtime.ServeMu
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_ScopeService_ListKeys_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
var annotatedContext context.Context
|
||||
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.ScopeService/ListKeys", runtime.WithHTTPPathPattern("/v1/scopes/{id}:list-keys"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_ScopeService_ListKeys_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_ScopeService_ListKeys_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_ScopeService_RotateKeys_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
var annotatedContext context.Context
|
||||
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.ScopeService/RotateKeys", runtime.WithHTTPPathPattern("/v1/scopes:rotate-keys"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_ScopeService_RotateKeys_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_ScopeService_RotateKeys_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_ScopeService_ListKeyVersionDestructionJobs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
var annotatedContext context.Context
|
||||
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.ScopeService/ListKeyVersionDestructionJobs", runtime.WithHTTPPathPattern("/v1/scopes/{scope_id}:list-key-version-destruction-jobs"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_ScopeService_ListKeyVersionDestructionJobs_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_ScopeService_ListKeyVersionDestructionJobs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_ScopeService_DestroyKeyVersion_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
var annotatedContext context.Context
|
||||
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.ScopeService/DestroyKeyVersion", runtime.WithHTTPPathPattern("/v1/scopes:destroy-key-version"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_ScopeService_DestroyKeyVersion_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_ScopeService_DestroyKeyVersion_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -645,6 +1005,14 @@ var (
|
||||
pattern_ScopeService_UpdateScope_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "scopes", "id"}, ""))
|
||||
|
||||
pattern_ScopeService_DeleteScope_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "scopes", "id"}, ""))
|
||||
|
||||
pattern_ScopeService_ListKeys_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "scopes", "id"}, "list-keys"))
|
||||
|
||||
pattern_ScopeService_RotateKeys_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "scopes"}, "rotate-keys"))
|
||||
|
||||
pattern_ScopeService_ListKeyVersionDestructionJobs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "scopes", "scope_id"}, "list-key-version-destruction-jobs"))
|
||||
|
||||
pattern_ScopeService_DestroyKeyVersion_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "scopes"}, "destroy-key-version"))
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -657,4 +1025,12 @@ var (
|
||||
forward_ScopeService_UpdateScope_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_ScopeService_DeleteScope_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_ScopeService_ListKeys_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_ScopeService_RotateKeys_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_ScopeService_ListKeyVersionDestructionJobs_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_ScopeService_DestroyKeyVersion_0 = runtime.ForwardResponseMessage
|
||||
)
|
||||
@@ -44,6 +44,19 @@ type ScopeServiceClient interface {
|
||||
// DeleteScope remotes a Scope and all child resources from Boundary. If the
|
||||
// provided Scope IDs are malformed or not provided an error is returned.
|
||||
DeleteScope(ctx context.Context, in *DeleteScopeRequest, opts ...grpc.CallOption) (*DeleteScopeResponse, error)
|
||||
// ListKeys lists all the keys found in the scope specified. If the scope
|
||||
// is not found an error is returned.
|
||||
ListKeys(ctx context.Context, in *ListKeysRequest, opts ...grpc.CallOption) (*ListKeysResponse, error)
|
||||
// RotateKeys rotates and optionally rewraps all the keys found in the
|
||||
// scope specified. If the scope is not found an error is returned. If
|
||||
// the scope is empty, the global scope is used.
|
||||
RotateKeys(ctx context.Context, in *RotateKeysRequest, opts ...grpc.CallOption) (*RotateKeysResponse, error)
|
||||
// ListKeyVersionDestructionJobs lists any pending key version destruction jobs in the scope.
|
||||
ListKeyVersionDestructionJobs(ctx context.Context, in *ListKeyVersionDestructionJobsRequest, opts ...grpc.CallOption) (*ListKeyVersionDestructionJobsResponse, error)
|
||||
// DestroyKeyVersion destroys the specified key version. If this requires re-encrypting
|
||||
// existing data, it will start an asynchronous process to complete this operation
|
||||
// before destroying the key. Use ListKeyVersionDestructionJobs to monitor pending destruction jobs.
|
||||
DestroyKeyVersion(ctx context.Context, in *DestroyKeyVersionRequest, opts ...grpc.CallOption) (*DestroyKeyVersionResponse, error)
|
||||
}
|
||||
|
||||
type scopeServiceClient struct {
|
||||
@@ -99,6 +112,42 @@ func (c *scopeServiceClient) DeleteScope(ctx context.Context, in *DeleteScopeReq
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *scopeServiceClient) ListKeys(ctx context.Context, in *ListKeysRequest, opts ...grpc.CallOption) (*ListKeysResponse, error) {
|
||||
out := new(ListKeysResponse)
|
||||
err := c.cc.Invoke(ctx, "/controller.api.services.v1.ScopeService/ListKeys", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *scopeServiceClient) RotateKeys(ctx context.Context, in *RotateKeysRequest, opts ...grpc.CallOption) (*RotateKeysResponse, error) {
|
||||
out := new(RotateKeysResponse)
|
||||
err := c.cc.Invoke(ctx, "/controller.api.services.v1.ScopeService/RotateKeys", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *scopeServiceClient) ListKeyVersionDestructionJobs(ctx context.Context, in *ListKeyVersionDestructionJobsRequest, opts ...grpc.CallOption) (*ListKeyVersionDestructionJobsResponse, error) {
|
||||
out := new(ListKeyVersionDestructionJobsResponse)
|
||||
err := c.cc.Invoke(ctx, "/controller.api.services.v1.ScopeService/ListKeyVersionDestructionJobs", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *scopeServiceClient) DestroyKeyVersion(ctx context.Context, in *DestroyKeyVersionRequest, opts ...grpc.CallOption) (*DestroyKeyVersionResponse, error) {
|
||||
out := new(DestroyKeyVersionResponse)
|
||||
err := c.cc.Invoke(ctx, "/controller.api.services.v1.ScopeService/DestroyKeyVersion", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ScopeServiceServer is the server API for ScopeService service.
|
||||
// All implementations must embed UnimplementedScopeServiceServer
|
||||
// for forward compatibility
|
||||
@@ -129,6 +178,19 @@ type ScopeServiceServer interface {
|
||||
// DeleteScope remotes a Scope and all child resources from Boundary. If the
|
||||
// provided Scope IDs are malformed or not provided an error is returned.
|
||||
DeleteScope(context.Context, *DeleteScopeRequest) (*DeleteScopeResponse, error)
|
||||
// ListKeys lists all the keys found in the scope specified. If the scope
|
||||
// is not found an error is returned.
|
||||
ListKeys(context.Context, *ListKeysRequest) (*ListKeysResponse, error)
|
||||
// RotateKeys rotates and optionally rewraps all the keys found in the
|
||||
// scope specified. If the scope is not found an error is returned. If
|
||||
// the scope is empty, the global scope is used.
|
||||
RotateKeys(context.Context, *RotateKeysRequest) (*RotateKeysResponse, error)
|
||||
// ListKeyVersionDestructionJobs lists any pending key version destruction jobs in the scope.
|
||||
ListKeyVersionDestructionJobs(context.Context, *ListKeyVersionDestructionJobsRequest) (*ListKeyVersionDestructionJobsResponse, error)
|
||||
// DestroyKeyVersion destroys the specified key version. If this requires re-encrypting
|
||||
// existing data, it will start an asynchronous process to complete this operation
|
||||
// before destroying the key. Use ListKeyVersionDestructionJobs to monitor pending destruction jobs.
|
||||
DestroyKeyVersion(context.Context, *DestroyKeyVersionRequest) (*DestroyKeyVersionResponse, error)
|
||||
mustEmbedUnimplementedScopeServiceServer()
|
||||
}
|
||||
|
||||
@@ -151,6 +213,18 @@ func (UnimplementedScopeServiceServer) UpdateScope(context.Context, *UpdateScope
|
||||
func (UnimplementedScopeServiceServer) DeleteScope(context.Context, *DeleteScopeRequest) (*DeleteScopeResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteScope not implemented")
|
||||
}
|
||||
func (UnimplementedScopeServiceServer) ListKeys(context.Context, *ListKeysRequest) (*ListKeysResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListKeys not implemented")
|
||||
}
|
||||
func (UnimplementedScopeServiceServer) RotateKeys(context.Context, *RotateKeysRequest) (*RotateKeysResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RotateKeys not implemented")
|
||||
}
|
||||
func (UnimplementedScopeServiceServer) ListKeyVersionDestructionJobs(context.Context, *ListKeyVersionDestructionJobsRequest) (*ListKeyVersionDestructionJobsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListKeyVersionDestructionJobs not implemented")
|
||||
}
|
||||
func (UnimplementedScopeServiceServer) DestroyKeyVersion(context.Context, *DestroyKeyVersionRequest) (*DestroyKeyVersionResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DestroyKeyVersion not implemented")
|
||||
}
|
||||
func (UnimplementedScopeServiceServer) mustEmbedUnimplementedScopeServiceServer() {}
|
||||
|
||||
// UnsafeScopeServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
@@ -254,6 +328,78 @@ func _ScopeService_DeleteScope_Handler(srv interface{}, ctx context.Context, dec
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ScopeService_ListKeys_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListKeysRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ScopeServiceServer).ListKeys(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/controller.api.services.v1.ScopeService/ListKeys",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ScopeServiceServer).ListKeys(ctx, req.(*ListKeysRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ScopeService_RotateKeys_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(RotateKeysRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ScopeServiceServer).RotateKeys(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/controller.api.services.v1.ScopeService/RotateKeys",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ScopeServiceServer).RotateKeys(ctx, req.(*RotateKeysRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ScopeService_ListKeyVersionDestructionJobs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListKeyVersionDestructionJobsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ScopeServiceServer).ListKeyVersionDestructionJobs(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/controller.api.services.v1.ScopeService/ListKeyVersionDestructionJobs",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ScopeServiceServer).ListKeyVersionDestructionJobs(ctx, req.(*ListKeyVersionDestructionJobsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ScopeService_DestroyKeyVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DestroyKeyVersionRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ScopeServiceServer).DestroyKeyVersion(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/controller.api.services.v1.ScopeService/DestroyKeyVersion",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ScopeServiceServer).DestroyKeyVersion(ctx, req.(*DestroyKeyVersionRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// ScopeService_ServiceDesc is the grpc.ServiceDesc for ScopeService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -281,6 +427,22 @@ var ScopeService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "DeleteScope",
|
||||
Handler: _ScopeService_DeleteScope_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListKeys",
|
||||
Handler: _ScopeService_ListKeys_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "RotateKeys",
|
||||
Handler: _ScopeService_RotateKeys_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListKeyVersionDestructionJobs",
|
||||
Handler: _ScopeService_ListKeyVersionDestructionJobs_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DestroyKeyVersion",
|
||||
Handler: _ScopeService_DestroyKeyVersion_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "controller/api/services/v1/scope_service.proto",
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
kms.RegisterTableRewrapFn("host_plugin_catalog_secret", hostCatalogSecretRewrapFn)
|
||||
}
|
||||
|
||||
func hostCatalogSecretRewrapFn(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kmsRepo *kms.Kms) error {
|
||||
const op = "plugin.hostCatalogSecretRewrapFn"
|
||||
if dataKeyVersionId == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing data key version id")
|
||||
}
|
||||
if scopeId == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
if util.IsNil(reader) {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing database reader")
|
||||
}
|
||||
if util.IsNil(writer) {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing database writer")
|
||||
}
|
||||
if kmsRepo == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing kms repository")
|
||||
}
|
||||
var secrets []*HostCatalogSecret
|
||||
// The only index on this table is on catalog id and there are no references to catalog id.
|
||||
// This is the fastest query we can use without creating a new index on key_id.
|
||||
if err := reader.SearchWhere(ctx, &secrets, "key_id=?", []any{dataKeyVersionId}, db.WithLimit(-1)); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query sql for rows that need rewrapping"))
|
||||
}
|
||||
wrapper, err := kmsRepo.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to fetch kms wrapper for rewrapping"))
|
||||
}
|
||||
for _, secret := range secrets {
|
||||
if err := secret.decrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to decrypt host catalog secret"))
|
||||
}
|
||||
if err := secret.encrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to re-encrypt host catalog secret"))
|
||||
}
|
||||
if _, err := writer.Update(ctx, secret, []string{"CtSecret", "KeyId"}, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to update host catalog secret row with rewrapped fields"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/plugin/host"
|
||||
"github.com/hashicorp/go-kms-wrapping/extras/kms/v2/migrations"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRewrap_hostCatalogSecretRewrapFn(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("errors-on-query-error", func(t *testing.T) {
|
||||
conn, mock := db.TestSetupWithMock(t)
|
||||
wrapper := db.TestWrapper(t)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "kms_schema_version" WHERE 1=1 ORDER BY "kms_schema_version"\."version" LIMIT 1`,
|
||||
).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now()))
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "host_plugin_catalog_secret" WHERE key_id=\$1`,
|
||||
).WillReturnError(errors.New("Query error"))
|
||||
err := hostCatalogSecretRewrapFn(ctx, "some_id", "some_scope", rw, rw, kmsCache)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("success", func(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
plg := host.TestPlugin(t, conn, "test")
|
||||
cat := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId())
|
||||
secret, err := newHostCatalogSecret(ctx, cat.GetPublicId(), mustStruct(map[string]any{
|
||||
"foo": "bar",
|
||||
}))
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, secret)
|
||||
assert.Empty(t, secret.CtSecret)
|
||||
assert.NotEmpty(t, secret.Secret)
|
||||
|
||||
kmsWrapper, err := kmsCache.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase)
|
||||
assert.NoError(t, err)
|
||||
secretSecret := secret.GetSecret() // this is cleared by the encrypt function, so save it for assert
|
||||
assert.NoError(t, secret.encrypt(ctx, kmsWrapper))
|
||||
assert.NoError(t, rw.Create(context.Background(), secret))
|
||||
|
||||
// now things are stored in the db, we can rotate and rewrap
|
||||
assert.NoError(t, kmsCache.RotateKeys(ctx, prj.PublicId))
|
||||
assert.NoError(t, hostCatalogSecretRewrapFn(ctx, secret.GetKeyId(), prj.PublicId, rw, rw, kmsCache))
|
||||
|
||||
// now we pull the credential back from the db, decrypt it with the new key, and ensure things match
|
||||
got := allocHostCatalogSecret()
|
||||
got.CatalogId = cat.GetPublicId()
|
||||
assert.NoError(t, rw.LookupById(ctx, got))
|
||||
|
||||
kmsWrapper2, err := kmsCache.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase, kms.WithKeyId(got.GetKeyId()))
|
||||
assert.NoError(t, err)
|
||||
|
||||
newKeyVersionId, err := kmsWrapper2.KeyId(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// decrypt with the new key version and check to make sure things match
|
||||
assert.NoError(t, got.decrypt(ctx, kmsWrapper2))
|
||||
assert.NotEmpty(t, got.GetKeyId())
|
||||
assert.NotEqual(t, secret.GetKeyId(), got.GetKeyId())
|
||||
assert.Equal(t, newKeyVersionId, got.GetKeyId())
|
||||
assert.Equal(t, secretSecret, got.GetSecret())
|
||||
assert.NotEqual(t, secret.GetCtSecret(), got.GetCtSecret())
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package kms
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/boundary/internal/kms/store"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// DataKeyVersionDestructionJob is used to read and write
|
||||
// data key version destruction jobs in the DB.
|
||||
type DataKeyVersionDestructionJob struct {
|
||||
*store.DataKeyVersionDestructionJob
|
||||
}
|
||||
|
||||
func (k *DataKeyVersionDestructionJob) TableName() string {
|
||||
return "kms_data_key_version_destruction_job"
|
||||
}
|
||||
|
||||
// allocDataKeyVersionDestructionJob makes an empty one in memory.
|
||||
func allocDataKeyVersionDestructionJob() DataKeyVersionDestructionJob {
|
||||
return DataKeyVersionDestructionJob{
|
||||
DataKeyVersionDestructionJob: &store.DataKeyVersionDestructionJob{},
|
||||
}
|
||||
}
|
||||
|
||||
// Clone a DataKeyVersionDestructionJob
|
||||
func (c *DataKeyVersionDestructionJob) Clone() *DataKeyVersionDestructionJob {
|
||||
cp := proto.Clone(c.DataKeyVersionDestructionJob)
|
||||
return &DataKeyVersionDestructionJob{
|
||||
DataKeyVersionDestructionJob: cp.(*store.DataKeyVersionDestructionJob),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package kms
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/boundary/internal/kms/store"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// DataKeyVersionDestructionJobProgress is used to read
|
||||
// data key version destruction job progress from the DB.
|
||||
type DataKeyVersionDestructionJobProgress struct {
|
||||
*store.DataKeyVersionDestructionJobProgress
|
||||
}
|
||||
|
||||
func (k *DataKeyVersionDestructionJobProgress) TableName() string {
|
||||
return "kms_data_key_version_destruction_job_progress"
|
||||
}
|
||||
|
||||
// allocDataKeyVersionDestructionJobProgress makes an empty one in memory.
|
||||
func allocDataKeyVersionDestructionJobProgress() DataKeyVersionDestructionJobProgress {
|
||||
return DataKeyVersionDestructionJobProgress{
|
||||
DataKeyVersionDestructionJobProgress: &store.DataKeyVersionDestructionJobProgress{},
|
||||
}
|
||||
}
|
||||
|
||||
// Clone an DataKeyVersionDestructionJob
|
||||
func (c *DataKeyVersionDestructionJobProgress) Clone() *DataKeyVersionDestructionJobProgress {
|
||||
cp := proto.Clone(c.DataKeyVersionDestructionJobProgress)
|
||||
return &DataKeyVersionDestructionJobProgress{
|
||||
DataKeyVersionDestructionJobProgress: cp.(*store.DataKeyVersionDestructionJobProgress),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package kms
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
)
|
||||
|
||||
func Test_DataKeyVersionDestructionJobProgress(t *testing.T) {
|
||||
progress1 := allocDataKeyVersionDestructionJobProgress()
|
||||
progress1.KeyId = "abcd"
|
||||
progress2 := progress1.Clone()
|
||||
assert.Empty(t, cmp.Diff(progress1, progress2, protocmp.Transform()))
|
||||
progress1.KeyId = "efgh"
|
||||
assert.NotEmpty(t, cmp.Diff(progress1, progress2, protocmp.Transform()))
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package kms
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/boundary/internal/kms/store"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// DataKeyVersionDestructionJobRun is used to read and write
|
||||
// data key version destruction job runs in the DB.
|
||||
type DataKeyVersionDestructionJobRun struct {
|
||||
*store.DataKeyVersionDestructionJobRun
|
||||
}
|
||||
|
||||
func (k *DataKeyVersionDestructionJobRun) TableName() string {
|
||||
return "kms_data_key_version_destruction_job_run"
|
||||
}
|
||||
|
||||
// allocDataKeyVersionDestructionJobRun makes an empty one in memory.
|
||||
func allocDataKeyVersionDestructionJobRun() DataKeyVersionDestructionJobRun {
|
||||
return DataKeyVersionDestructionJobRun{
|
||||
DataKeyVersionDestructionJobRun: &store.DataKeyVersionDestructionJobRun{},
|
||||
}
|
||||
}
|
||||
|
||||
// Clone a DataKeyVersionDestructionJobRun
|
||||
func (c *DataKeyVersionDestructionJobRun) Clone() *DataKeyVersionDestructionJobRun {
|
||||
cp := proto.Clone(c.DataKeyVersionDestructionJobRun)
|
||||
return &DataKeyVersionDestructionJobRun{
|
||||
DataKeyVersionDestructionJobRun: cp.(*store.DataKeyVersionDestructionJobRun),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package kms
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/boundary/internal/kms/store"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// DataKeyVersionDestructionJobRunAllowedTableName is used to read
|
||||
// the names of tables that reference the data key version.
|
||||
type DataKeyVersionDestructionJobRunAllowedTableName struct {
|
||||
*store.DataKeyVersionDestructionJobRunAllowedTableName
|
||||
}
|
||||
|
||||
func (k *DataKeyVersionDestructionJobRunAllowedTableName) TableName() string {
|
||||
return "kms_data_key_version_destruction_job_run_allowed_table_name"
|
||||
}
|
||||
|
||||
// allocDataKeyVersionDestructionJobRunAllowedTableName makes an empty one in memory.
|
||||
func allocDataKeyVersionDestructionJobRunAllowedTableName() DataKeyVersionDestructionJobRunAllowedTableName {
|
||||
return DataKeyVersionDestructionJobRunAllowedTableName{
|
||||
DataKeyVersionDestructionJobRunAllowedTableName: &store.DataKeyVersionDestructionJobRunAllowedTableName{},
|
||||
}
|
||||
}
|
||||
|
||||
// Clone an DataKeyVersionDestructionJob
|
||||
func (c *DataKeyVersionDestructionJobRunAllowedTableName) Clone() *DataKeyVersionDestructionJobRunAllowedTableName {
|
||||
cp := proto.Clone(c.DataKeyVersionDestructionJobRunAllowedTableName)
|
||||
return &DataKeyVersionDestructionJobRunAllowedTableName{
|
||||
DataKeyVersionDestructionJobRunAllowedTableName: cp.(*store.DataKeyVersionDestructionJobRunAllowedTableName),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package kms
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
)
|
||||
|
||||
func Test_DataKeyVersionDestructionJobRunAllowedTableName(t *testing.T) {
|
||||
allowedTableName1 := allocDataKeyVersionDestructionJobRunAllowedTableName()
|
||||
allowedTableName1.DataKeyVersionDestructionJobRunAllowedTableName.TableName = "abcd"
|
||||
allowedTableName2 := allowedTableName1.Clone()
|
||||
assert.Empty(t, cmp.Diff(allowedTableName1, allowedTableName2, protocmp.Transform()))
|
||||
allowedTableName1.DataKeyVersionDestructionJobRunAllowedTableName.TableName = "efgh"
|
||||
assert.NotEmpty(t, cmp.Diff(allowedTableName1, allowedTableName2, protocmp.Transform()))
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package kms
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
)
|
||||
|
||||
func Test_DataKeyVersionDestructionJobRun(t *testing.T) {
|
||||
run1 := allocDataKeyVersionDestructionJobRun()
|
||||
run1.KeyId = "abcd"
|
||||
run2 := run1.Clone()
|
||||
assert.Empty(t, cmp.Diff(run1, run2, protocmp.Transform()))
|
||||
run1.KeyId = "efgh"
|
||||
assert.NotEmpty(t, cmp.Diff(run1, run2, protocmp.Transform()))
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package kms
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
)
|
||||
|
||||
func Test_DataKeyVersionDestructionJob(t *testing.T) {
|
||||
job1 := allocDataKeyVersionDestructionJob()
|
||||
job1.KeyId = "abcd"
|
||||
job2 := job1.Clone()
|
||||
assert.Empty(t, cmp.Diff(job1, job2, protocmp.Transform()))
|
||||
job1.KeyId = "efgh"
|
||||
assert.NotEmpty(t, cmp.Diff(job1, job2, protocmp.Transform()))
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/scheduler"
|
||||
)
|
||||
|
||||
type dataKeyVersionDestructionMonitorJob struct {
|
||||
kmsRepo *kms.Kms
|
||||
}
|
||||
|
||||
func newDataKeyVersionDestructionMonitorJob(ctx context.Context, kmsRepo *kms.Kms) (*dataKeyVersionDestructionMonitorJob, error) {
|
||||
const op = "kms.newDataKeyVersionDestructionMonitorJob"
|
||||
if kmsRepo == nil {
|
||||
return nil, errors.New(ctx, errors.Internal, "nil kms repo", op, errors.WithoutEvent())
|
||||
}
|
||||
|
||||
return &dataKeyVersionDestructionMonitorJob{
|
||||
kmsRepo: kmsRepo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Status reports the job’s current status. We never change these values as
|
||||
// this job never finishes.
|
||||
func (d *dataKeyVersionDestructionMonitorJob) Status() scheduler.JobStatus {
|
||||
return scheduler.JobStatus{}
|
||||
}
|
||||
|
||||
// Run performs the required work depending on the implementation.
|
||||
// The context is used to notify the job that it should exit early.
|
||||
func (r *dataKeyVersionDestructionMonitorJob) Run(ctx context.Context) error {
|
||||
const op = "kmsjob.(dataKeyVersionDestructionMonitorJob).Run"
|
||||
|
||||
if err := r.kmsRepo.MonitorDataKeyVersionDestruction(ctx); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NextRunIn returns the duration until the next job run should be scheduled.
|
||||
// We report as ready 1 second after a successful run. This doesn't mean that
|
||||
// this job will run every second, only about as often as the configured scheduler interval.
|
||||
func (r *dataKeyVersionDestructionMonitorJob) NextRunIn(_ context.Context) (time.Duration, error) {
|
||||
return time.Second, nil
|
||||
}
|
||||
|
||||
// Name is the unique name of the job.
|
||||
func (r *dataKeyVersionDestructionMonitorJob) Name() string {
|
||||
return "data-key-version-destruction-monitor-job"
|
||||
}
|
||||
|
||||
// Description is the human readable description of the job.
|
||||
func (d *dataKeyVersionDestructionMonitorJob) Description() string {
|
||||
return "Destroy a key version once all data has been rewrapped"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_newDataKeyVersionDestructionMonitorJob(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
extWrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, extWrapper)
|
||||
|
||||
_, err := newDataKeyVersionDestructionMonitorJob(context.Background(), nil)
|
||||
require.Error(t, err)
|
||||
job, err := newDataKeyVersionDestructionMonitorJob(context.Background(), kmsCache)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, job)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/scheduler"
|
||||
)
|
||||
|
||||
// RegisterJobs registers kms related jobs with the provided scheduler.
|
||||
func RegisterJobs(ctx context.Context, s *scheduler.Scheduler, kmsRepo *kms.Kms) error {
|
||||
const op = "kmsjob.RegisterJobs"
|
||||
if s == nil {
|
||||
return errors.New(ctx, errors.Internal, "nil scheduler", op, errors.WithoutEvent())
|
||||
}
|
||||
if kmsRepo == nil {
|
||||
return errors.New(ctx, errors.Internal, "nil kms repo", op, errors.WithoutEvent())
|
||||
}
|
||||
|
||||
dataKeyVersionDestructionMonitorJob, err := newDataKeyVersionDestructionMonitorJob(ctx, kmsRepo)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if err := s.RegisterJob(ctx, dataKeyVersionDestructionMonitorJob); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
for _, tableName := range kms.ListTablesSupportingRewrap() {
|
||||
tableRewrappingJob, err := newTableRewrappingJob(ctx, kmsRepo, tableName)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if err := s.RegisterJob(ctx, tableRewrappingJob); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/scheduler"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_RegisterJobs(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
extWrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, extWrapper)
|
||||
s := scheduler.TestScheduler(t, conn, extWrapper)
|
||||
|
||||
err := RegisterJobs(context.Background(), nil, nil)
|
||||
require.Error(t, err)
|
||||
err = RegisterJobs(context.Background(), s, nil)
|
||||
require.Error(t, err)
|
||||
err = RegisterJobs(context.Background(), nil, kmsCache)
|
||||
require.Error(t, err)
|
||||
err = RegisterJobs(context.Background(), s, kmsCache)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/scheduler"
|
||||
)
|
||||
|
||||
type tableRewrappingJob struct {
|
||||
kmsRepo *kms.Kms
|
||||
tableName string
|
||||
}
|
||||
|
||||
func newTableRewrappingJob(ctx context.Context, kmsRepo *kms.Kms, tableName string) (*tableRewrappingJob, error) {
|
||||
const op = "kms.newTableRewrappingJob"
|
||||
if kmsRepo == nil {
|
||||
return nil, errors.New(ctx, errors.Internal, "nil kms repo", op, errors.WithoutEvent())
|
||||
}
|
||||
if tableName == "" {
|
||||
return nil, errors.New(ctx, errors.Internal, "no table name provided", op, errors.WithoutEvent())
|
||||
}
|
||||
|
||||
return &tableRewrappingJob{
|
||||
kmsRepo: kmsRepo,
|
||||
tableName: tableName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Status reports the job’s current status. We never change these values as
|
||||
// this job never finishes.
|
||||
func (d *tableRewrappingJob) Status() scheduler.JobStatus {
|
||||
return scheduler.JobStatus{}
|
||||
}
|
||||
|
||||
// Run performs the required work depending on the implementation.
|
||||
// The context is used to notify the job that it should exit early.
|
||||
func (r *tableRewrappingJob) Run(ctx context.Context) error {
|
||||
const op = "kmsjob.(tableRewrappingJob).Run"
|
||||
|
||||
if err := r.kmsRepo.MonitorTableRewrappingRuns(ctx, r.tableName); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NextRunIn returns the duration until the next job run should be scheduled.
|
||||
// We report as ready 1 second after a successful run. This doesn't mean that
|
||||
// this job will run every second, only about as often as the configured scheduler interval.
|
||||
func (r *tableRewrappingJob) NextRunIn(_ context.Context) (time.Duration, error) {
|
||||
return time.Second, nil
|
||||
}
|
||||
|
||||
// Name is the unique name of the job.
|
||||
func (r *tableRewrappingJob) Name() string {
|
||||
return fmt.Sprintf("%s-rewrapping-job", r.tableName)
|
||||
}
|
||||
|
||||
// Description is the human readable description of the job.
|
||||
func (d *tableRewrappingJob) Description() string {
|
||||
return "Re-encrypt all data encrypted in a table using a specific key"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_newTableRewrappingJob(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
extWrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, extWrapper)
|
||||
|
||||
_, err := newTableRewrappingJob(context.Background(), nil, "")
|
||||
require.Error(t, err)
|
||||
_, err = newTableRewrappingJob(context.Background(), kmsCache, "")
|
||||
require.Error(t, err)
|
||||
_, err = newTableRewrappingJob(context.Background(), nil, "someTable")
|
||||
require.Error(t, err)
|
||||
job, err := newTableRewrappingJob(context.Background(), kmsCache, "someTable")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, job)
|
||||
}
|
||||
+356
-1
@@ -2,6 +2,7 @@ package kms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
@@ -11,8 +12,10 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/hashicorp/go-dbw"
|
||||
wrappingKms "github.com/hashicorp/go-kms-wrapping/extras/kms/v2"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Kms is a way to access wrappers for a given scope and purpose. Since keys can
|
||||
@@ -21,6 +24,7 @@ import (
|
||||
type Kms struct {
|
||||
underlying *wrappingKms.Kms
|
||||
reader db.Reader
|
||||
writer db.Writer
|
||||
derivedPurposeCache sync.Map
|
||||
}
|
||||
|
||||
@@ -43,6 +47,7 @@ func New(ctx context.Context, reader *db.Db, writer *db.Db, _ ...Option) (*Kms,
|
||||
return &Kms{
|
||||
underlying: k,
|
||||
reader: reader,
|
||||
writer: writer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -178,7 +183,7 @@ func (k *Kms) ReconcileKeys(ctx context.Context, randomReader io.Reader, opt ...
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateKeys creates the root key and DEKs returns a map of the new keys.
|
||||
// CreateKeys creates the root key and DEKs.
|
||||
// Supports the WithRandomReader(...) and WithReaderWriter(...) options.
|
||||
// When WithReaderWriter(...) is used the caller is responsible for managing the
|
||||
// transaction which allows this capability to be shared with the iam repo when
|
||||
@@ -216,6 +221,356 @@ func (k *Kms) CreateKeys(ctx context.Context, scopeId string, opt ...Option) err
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListKeys lists all keys in the scope.
|
||||
// Options are ignored.
|
||||
func (k *Kms) ListKeys(ctx context.Context, scopeId string, _ ...Option) ([]wrappingKms.Key, error) {
|
||||
const op = "kms.(Kms).ListKeys"
|
||||
keys, err := k.underlying.ListKeys(ctx, scopeId)
|
||||
if err != nil {
|
||||
if errors.Is(err, dbw.ErrRecordNotFound) {
|
||||
return nil, errors.E(ctx, errors.WithCode(errors.RecordNotFound), errors.WithOp(op))
|
||||
}
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// RotateKeys rotates all keys in a given scope.
|
||||
// Options supported: withRandomReader, withRewrap, withReader, withWriter
|
||||
// When withReader or withWriter is used, both must be passed and the caller will
|
||||
// be responsible for managing the underlying db transactions.
|
||||
func (k *Kms) RotateKeys(ctx context.Context, scopeId string, opt ...Option) error {
|
||||
const op = "kms.(Kms).RotateKeys"
|
||||
|
||||
opts := getOpts(opt...)
|
||||
kmsOpts := []wrappingKms.Option{
|
||||
wrappingKms.WithRandomReader(opts.withRandomReader),
|
||||
wrappingKms.WithRewrap(opts.withRewrap),
|
||||
}
|
||||
|
||||
switch {
|
||||
case !isNil(opts.withReader) && isNil(opts.withWriter):
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing writer")
|
||||
case isNil(opts.withReader) && !isNil(opts.withWriter):
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing reader")
|
||||
case !isNil(opts.withReader) && !isNil(opts.withWriter):
|
||||
r, ok := opts.withReader.(*db.Db)
|
||||
if !ok {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "unable to convert reader to db.Db")
|
||||
}
|
||||
w, ok := opts.withWriter.(*db.Db)
|
||||
if !ok {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "unable to convert writer to db.Db")
|
||||
}
|
||||
kmsOpts = append(kmsOpts, wrappingKms.WithReaderWriter(db.NewChangeSafeDbwReader(r), db.NewChangeSafeDbwWriter(w)))
|
||||
}
|
||||
|
||||
err := k.underlying.RotateKeys(ctx, scopeId, kmsOpts...)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListDataKeyVersionReferencers will list the names of all tables
|
||||
// referencing the private_id column of the data key version table.
|
||||
// Supported options:
|
||||
// - WithReader (requires WithWriter)
|
||||
// - WithWriter (requires WithReader)
|
||||
func (k *Kms) ListDataKeyVersionReferencers(ctx context.Context, opt ...Option) ([]string, error) {
|
||||
const op = "kms.(Kms).ListDataKeyVersionReferencers"
|
||||
|
||||
opts := getOpts(opt...)
|
||||
kmsOpts := []wrappingKms.Option{}
|
||||
|
||||
switch {
|
||||
case !isNil(opts.withReader) && isNil(opts.withWriter):
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing writer")
|
||||
case isNil(opts.withReader) && !isNil(opts.withWriter):
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing reader")
|
||||
case !isNil(opts.withReader) && !isNil(opts.withWriter):
|
||||
r, ok := opts.withReader.(*db.Db)
|
||||
if !ok {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "unable to convert reader to db.Db")
|
||||
}
|
||||
w, ok := opts.withWriter.(*db.Db)
|
||||
if !ok {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "unable to convert writer to db.Db")
|
||||
}
|
||||
kmsOpts = append(kmsOpts, wrappingKms.WithReaderWriter(db.NewChangeSafeDbwReader(r), db.NewChangeSafeDbwWriter(w)))
|
||||
}
|
||||
|
||||
refs, err := k.underlying.ListDataKeyVersionReferencers(ctx, kmsOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
return refs, nil
|
||||
}
|
||||
|
||||
// ListDataKeyVersionDestructionJobs lists any in-progress data key destruction jobs in the scope.
|
||||
// Options are ignored.
|
||||
func (k *Kms) ListDataKeyVersionDestructionJobs(ctx context.Context, scopeId string, _ ...Option) ([]*DataKeyVersionDestructionJobProgress, error) {
|
||||
const op = "kms.(Kms).ListDataKeyVersionDestructionJobs"
|
||||
if scopeId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
var jobs []*DataKeyVersionDestructionJobProgress
|
||||
if err := k.reader.SearchWhere(ctx, &jobs, "scope_id=?", []any{scopeId}); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
// MonitorTableRewrappingRuns checks for pending rewrapping job runs for the
|
||||
// specified table name, and attempts to execute each job run and start rewrapping
|
||||
// data in the specified table. This may be a long running operation.
|
||||
// Options are ignored.
|
||||
func (k *Kms) MonitorTableRewrappingRuns(ctx context.Context, tableName string, _ ...Option) (retErr error) {
|
||||
const op = "kms.(Kms).MonitorTableRewrappingRuns"
|
||||
if tableName == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing table name")
|
||||
}
|
||||
|
||||
rewrapFn, ok := tableNameToRewrapFn[tableName]
|
||||
if !ok {
|
||||
return errors.E(ctx, errors.WithMsg("no rewrapper for table %q", tableName), errors.WithOp(op))
|
||||
}
|
||||
|
||||
run := allocDataKeyVersionDestructionJobRun()
|
||||
// Check if there is already another run running for another table name. If so,
|
||||
// we exit early since we are limited to 1 run at a time. Note that we exclude
|
||||
// our own table name from the search, since if there is a running run for our
|
||||
// table name, thanks to the scheduler guaranteeing that only one instance of a job
|
||||
// is running at a time, it means we were interrupted in our processing last time
|
||||
// and should simply resume that running run.
|
||||
err := k.reader.LookupWhere(ctx, &run, "is_running=true and table_name!=?", []any{tableName})
|
||||
switch {
|
||||
case err == nil:
|
||||
// Another run was already running
|
||||
return nil
|
||||
case errors.Match(errors.T(errors.RecordNotFound), err):
|
||||
// Great, no running run, lets continue
|
||||
default:
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to find any running runs"))
|
||||
}
|
||||
// Find the oldest or currently running run for our tablename
|
||||
run = allocDataKeyVersionDestructionJobRun()
|
||||
rows, err := k.reader.Query(ctx, oldestPendingOrRunningRun, []any{tableName})
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query pending runs for %q", tableName))
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
if err := k.reader.ScanRows(ctx, rows, &run); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to scan pending run for %q", tableName))
|
||||
}
|
||||
}
|
||||
if run.KeyId == "" {
|
||||
// No queued runs, lets try again later
|
||||
return nil
|
||||
}
|
||||
if !run.IsRunning {
|
||||
// No running job, lets try to become the running job
|
||||
run.IsRunning = true
|
||||
if _, err := k.writer.Update(ctx, &run, []string{"IsRunning"}, nil); err != nil {
|
||||
// Unique error means some other run started running since we last checked.
|
||||
if errors.Match(errors.T(errors.NotUnique), err) {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to become running run for %q", tableName))
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
// Create new context in case we failed because the context was canceled
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
// Update the progress of this run
|
||||
args := []any{
|
||||
sql.Named("key_id", run.KeyId),
|
||||
sql.Named("table_name", tableName),
|
||||
}
|
||||
if _, err := k.writer.Exec(ctx, fmt.Sprintf(updateCompletedCountQueryTemplate, tableName), args); err != nil {
|
||||
if retErr != nil {
|
||||
// Just emit an error, don't mutate the returned error
|
||||
_ = errors.E(ctx, errors.WithWrap(err))
|
||||
} else {
|
||||
retErr = err
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
rows, err = k.reader.Query(ctx, dataKeyVersionIdScopeIdQuery, []any{run.KeyId})
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to get scope id for data key version"))
|
||||
}
|
||||
defer rows.Close()
|
||||
var scopeId string
|
||||
for rows.Next() {
|
||||
if err := k.reader.ScanRows(ctx, rows, &scopeId); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to scan scope id for data key version"))
|
||||
}
|
||||
}
|
||||
|
||||
// Call the function to rewrap the data in the table. The progress will be automatically
|
||||
// updated by the deferred function.
|
||||
if err := rewrapFn(ctx, run.KeyId, scopeId, k.reader, k.writer, k); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MonitorDataKeyVersionDestruction monitors any pending destruction jobs. If
|
||||
// a job has finished rewrapping all rows, it will destroy the key version
|
||||
// by calling RevokeKeyVersion on the underlying kms.
|
||||
func (k *Kms) MonitorDataKeyVersionDestruction(ctx context.Context) error {
|
||||
const op = "kms.(Kms).MonitorDataKeyVersionDestruction"
|
||||
|
||||
rows, err := k.reader.Query(ctx, finishedDestructionJobsQuery, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to find completed destruction jobs"))
|
||||
}
|
||||
defer rows.Close()
|
||||
var completedDataKeyVersionIds []string
|
||||
for rows.Next() {
|
||||
if err := k.reader.ScanRows(ctx, rows, &completedDataKeyVersionIds); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
}
|
||||
for _, dataKeyVersionId := range completedDataKeyVersionIds {
|
||||
// Finally, revoke the key, deleting it from the database.
|
||||
// This will error if anything still references it that isn't
|
||||
// meant to be cascade deleted.
|
||||
if err := k.underlying.RevokeKeyVersion(ctx, dataKeyVersionId); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DestroyKeyVersion starts the process of destroying a key version in the scope.
|
||||
// If the key version is a KEK, this simply rewraps any existing DEK version's encrypted with
|
||||
// the KEK and destroys the key version. If the key version is a DEK, this creates a
|
||||
// key version destruction job, which will rewrap existing data and destroy the key
|
||||
// asynchronously. As a special case, if the DEK currently encrypts no data, the DEK is
|
||||
// immediately destroyed. The boolean return value indicates whether the key was immediately
|
||||
// destroyed.
|
||||
// Options are ignored.
|
||||
func (k *Kms) DestroyKeyVersion(ctx context.Context, scopeId string, keyVersionId string, _ ...Option) (bool, error) {
|
||||
const op = "kms.(Kms).DestroyKeyVersion"
|
||||
if scopeId == "" {
|
||||
return false, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
if keyVersionId == "" {
|
||||
return false, errors.New(ctx, errors.InvalidParameter, op, "missing key version id")
|
||||
}
|
||||
|
||||
scopeKeys, err := k.underlying.ListKeys(ctx, scopeId)
|
||||
if err != nil {
|
||||
if errors.Is(err, dbw.ErrRecordNotFound) {
|
||||
return false, errors.New(ctx, errors.RecordNotFound, op, "scope ID not found")
|
||||
}
|
||||
return false, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
var foundKey *wrappingKms.Key
|
||||
keyLoop:
|
||||
for _, key := range scopeKeys {
|
||||
for _, version := range key.Versions {
|
||||
if version.Id == keyVersionId {
|
||||
foundKey = &key
|
||||
break keyLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
if foundKey == nil {
|
||||
return false, errors.New(ctx, errors.KeyNotFound, op, "key version was not found in the scope")
|
||||
}
|
||||
// Sort versions just in case they aren't already sorted
|
||||
slices.SortFunc(foundKey.Versions, func(i, j wrappingKms.KeyVersion) bool {
|
||||
return i.Version < j.Version
|
||||
})
|
||||
if foundKey.Versions[len(foundKey.Versions)-1].Id == keyVersionId {
|
||||
// Attempted to destroy currently active key
|
||||
return false, errors.New(ctx, errors.InvalidParameter, op, "cannot destroy a currently active key version")
|
||||
}
|
||||
switch foundKey.Purpose {
|
||||
case wrappingKms.KeyPurpose(KeyPurposeOplog.String()):
|
||||
return false, errors.New(ctx, errors.InvalidParameter, op, "oplog key versions cannot be destroyed")
|
||||
case wrappingKms.KeyPurposeRootKey:
|
||||
// Simple case, just rewrap and destroy synchronously
|
||||
if err := k.underlying.RewrapKeys(ctx, scopeId); err != nil {
|
||||
return false, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if err := k.underlying.RevokeKeyVersion(ctx, keyVersionId); err != nil {
|
||||
return false, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
tablesNeedingRewrapping := 0
|
||||
if _, err := k.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(r db.Reader, w db.Writer) error {
|
||||
var tables []*DataKeyVersionDestructionJobRunAllowedTableName
|
||||
if err := r.SearchWhere(ctx, &tables, "1=1", nil, db.WithLimit(-1)); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to look up allowed table names"))
|
||||
}
|
||||
// Start by adding any job runs necessary by looking up the number of
|
||||
// rows encrypted with the key that's being destroyed.
|
||||
for _, table := range tables {
|
||||
rows, err := r.Query(ctx, fmt.Sprintf(findAffectedRowsForKeyQueryTemplate, table.GetTableName()), []any{keyVersionId})
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to get affected rows for %q", table.GetTableName()))
|
||||
}
|
||||
defer rows.Close()
|
||||
var numRows uint
|
||||
for rows.Next() {
|
||||
if err := r.ScanRows(ctx, rows, &numRows); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to scan number of rows for %q", table.GetTableName()))
|
||||
}
|
||||
}
|
||||
if numRows == 0 {
|
||||
// No rows to rewrap 🎉
|
||||
continue
|
||||
}
|
||||
run := allocDataKeyVersionDestructionJobRun()
|
||||
run.KeyId = keyVersionId
|
||||
run.DataKeyVersionDestructionJobRun.TableName = table.GetTableName()
|
||||
run.TotalCount = int64(numRows)
|
||||
if err := w.Create(ctx, &run); err != nil {
|
||||
// Unique error means the key version ID already existed
|
||||
if errors.Match(errors.T(errors.NotUnique), err) {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "key version is already destroying", errors.WithWrap(err))
|
||||
}
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to insert new key version destruction job run"))
|
||||
}
|
||||
tablesNeedingRewrapping++
|
||||
}
|
||||
if tablesNeedingRewrapping == 0 {
|
||||
// Data key encrypted no data, just return
|
||||
return nil
|
||||
}
|
||||
job := allocDataKeyVersionDestructionJob()
|
||||
job.KeyId = keyVersionId
|
||||
// Create the destruction job since we know we have at least one job run.
|
||||
if err := w.Create(ctx, &job); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to insert new key version destruction job"))
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if tablesNeedingRewrapping == 0 {
|
||||
// DEK encrypted no data, just destroy synchronously
|
||||
if err := k.underlying.RevokeKeyVersion(ctx, keyVersionId); err != nil {
|
||||
return false, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// VerifyGlobalRoot will verify that the global root wrapper is reasonable.
|
||||
func (k *Kms) VerifyGlobalRoot(ctx context.Context) error {
|
||||
const op = "kms.(Kms).VerifyGlobalRoot"
|
||||
|
||||
@@ -3,6 +3,7 @@ package kms
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
@@ -46,6 +47,7 @@ func Test_New(t *testing.T) {
|
||||
w: rw,
|
||||
want: &Kms{
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
underlying: func() *wrappingKms.Kms {
|
||||
purposes := make([]wrappingKms.KeyPurpose, 0, len(ValidDekPurposes()))
|
||||
for _, p := range ValidDekPurposes() {
|
||||
@@ -163,6 +165,776 @@ func Test_NewUsingReaderWriter(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ListKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCtx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
extWrapper := db.TestWrapper(t)
|
||||
kmsCache := TestKms(t, conn, extWrapper)
|
||||
err := kmsCache.CreateKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
keys, err := kmsCache.ListKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, keys, len(ValidDekPurposes())+1)
|
||||
})
|
||||
t.Run("unknown-scope", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := kmsCache.ListKeys(testCtx, "myscope")
|
||||
assert.True(t, errors.IsNotFoundError(err))
|
||||
})
|
||||
t.Run("empty-scope", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := kmsCache.ListKeys(testCtx, "")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_RotateKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCtx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
extWrapper := db.TestWrapper(t)
|
||||
kmsCache := TestKms(t, conn, extWrapper)
|
||||
err := kmsCache.CreateKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
rw := db.New(conn)
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// arrange
|
||||
// we're not trying to test the ListKeys function, although we need to use it to validate the rotation
|
||||
keys, err := kmsCache.ListKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, keys, len(ValidDekPurposes())+1)
|
||||
|
||||
// act
|
||||
err = kmsCache.RotateKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
|
||||
// assert
|
||||
keys, err = kmsCache.ListKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
|
||||
keyCount := 0
|
||||
for _, key := range keys {
|
||||
keyCount += len(key.Versions)
|
||||
}
|
||||
|
||||
require.Equal(t, keyCount, 14)
|
||||
})
|
||||
|
||||
t.Run("reader provided, missing writer", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// arrange
|
||||
WithReader := func(reader db.Reader) Option {
|
||||
return func(o *options) {
|
||||
o.withReader = reader
|
||||
}
|
||||
}
|
||||
|
||||
// act
|
||||
err := kmsCache.RotateKeys(testCtx, "global", WithReader(rw))
|
||||
|
||||
// assert
|
||||
assert.ErrorContains(t, err, "missing writer")
|
||||
})
|
||||
|
||||
t.Run("writer provided, missing reader", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// arrange
|
||||
WithWriter := func(writer db.Writer) Option {
|
||||
return func(o *options) {
|
||||
o.withWriter = writer
|
||||
}
|
||||
}
|
||||
|
||||
// act
|
||||
err := kmsCache.RotateKeys(testCtx, "global", WithWriter(rw))
|
||||
|
||||
// assert
|
||||
assert.ErrorContains(t, err, "missing reader")
|
||||
})
|
||||
|
||||
t.Run("invalid reader", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// act
|
||||
err := kmsCache.RotateKeys(testCtx, "global", WithReaderWriter(&invalidReader{}, rw))
|
||||
|
||||
// assert
|
||||
assert.ErrorContains(t, err, "unable to convert reader")
|
||||
})
|
||||
|
||||
t.Run("invalid writer", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// act
|
||||
err := kmsCache.RotateKeys(testCtx, "global", WithReaderWriter(rw, &invalidWriter{}))
|
||||
|
||||
// assert
|
||||
assert.ErrorContains(t, err, "unable to convert writer")
|
||||
})
|
||||
|
||||
t.Run("both reader and writer succeed", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
err = kmsCache.RotateKeys(testCtx, "global", WithReaderWriter(rw, rw))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// other options are passed directly and shouldn't need to be tested
|
||||
}
|
||||
|
||||
func Test_ListDataKeyVersionReferencers(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCtx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
extWrapper := db.TestWrapper(t)
|
||||
kmsCache := TestKms(t, conn, extWrapper)
|
||||
t.Run("returns-something", func(t *testing.T) {
|
||||
referencers, err := kmsCache.ListDataKeyVersionReferencers(testCtx)
|
||||
require.NoError(t, err)
|
||||
// Don't want to check the length or explicit tables
|
||||
// as this will make the test fail whenever we add or
|
||||
// remove a reference. Lets just sanity check that it's
|
||||
// got at least one element in it.
|
||||
assert.NotEmpty(t, referencers)
|
||||
})
|
||||
t.Run("errors when just a writer is specified", func(t *testing.T) {
|
||||
withWriter := func(writer db.Writer) Option {
|
||||
return func(o *options) {
|
||||
o.withWriter = writer
|
||||
}
|
||||
}
|
||||
_, err := kmsCache.ListDataKeyVersionReferencers(testCtx, withWriter(rw))
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("errors when just a reader is specified", func(t *testing.T) {
|
||||
withReader := func(reader db.Reader) Option {
|
||||
return func(o *options) {
|
||||
o.withReader = reader
|
||||
}
|
||||
}
|
||||
_, err := kmsCache.ListDataKeyVersionReferencers(testCtx, withReader(rw))
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("errors when an invalid writer is specified", func(t *testing.T) {
|
||||
_, err := kmsCache.ListDataKeyVersionReferencers(testCtx, WithReaderWriter(rw, &invalidWriter{}))
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("errors when an invalid reader is specified", func(t *testing.T) {
|
||||
_, err := kmsCache.ListDataKeyVersionReferencers(testCtx, WithReaderWriter(&invalidReader{}, rw))
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("succeeds-with-reader-writer", func(t *testing.T) {
|
||||
referencers, err := kmsCache.ListDataKeyVersionReferencers(testCtx, WithReaderWriter(rw, rw))
|
||||
require.NoError(t, err)
|
||||
// Don't want to check the length or explicit tables
|
||||
// as this will make the test fail whenever we add or
|
||||
// remove a reference. Lets just sanity check that it's
|
||||
// got at least one element in it.
|
||||
assert.NotEmpty(t, referencers)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ListDataKeyVersionDestructionJobs(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCtx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
extWrapper := db.TestWrapper(t)
|
||||
kmsCache := TestKms(t, conn, extWrapper)
|
||||
err := kmsCache.CreateKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
err = kmsCache.RotateKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
keys, err := kmsCache.ListKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("errors-on-empty-scope-ids", func(t *testing.T) {
|
||||
_, err := kmsCache.ListDataKeyVersionDestructionJobs(testCtx, "")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
t.Run("lists-no-jobs-when-the-scope-id-is-invalid", func(t *testing.T) {
|
||||
jobs, err := kmsCache.ListDataKeyVersionDestructionJobs(testCtx, "notascope")
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, jobs)
|
||||
})
|
||||
t.Run("lists-no-jobs-when-there-are-none", func(t *testing.T) {
|
||||
jobs, err := kmsCache.ListDataKeyVersionDestructionJobs(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, jobs)
|
||||
})
|
||||
t.Run("lists-jobs-when-there-are-some", func(t *testing.T) {
|
||||
var kvToDestroy wrappingKms.KeyVersion
|
||||
for _, key := range keys {
|
||||
if key.Purpose == wrappingKms.KeyPurpose(KeyPurposeDatabase.String()) {
|
||||
kvToDestroy = key.Versions[0]
|
||||
}
|
||||
}
|
||||
sqldb, err := conn.SqlDB(testCtx)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job(key_id) values ($1)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job_run(key_id, table_name, total_count) values ($1, 'auth_token', 100)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job_run(key_id, table_name, total_count) values ($1, 'auth_oidc_method', 200)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "update kms_data_key_version_destruction_job_run set is_running=true where key_id=$1 and table_name='auth_token'", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, err = sqldb.ExecContext(testCtx, "truncate kms_data_key_version_destruction_job, kms_data_key_version_destruction_job_run CASCADE")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
jobs, err := kmsCache.ListDataKeyVersionDestructionJobs(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, jobs, 1)
|
||||
job := jobs[0]
|
||||
assert.Equal(t, 0, int(job.CompletedCount))
|
||||
assert.Equal(t, 300, int(job.TotalCount))
|
||||
assert.Equal(t, "running", job.Status)
|
||||
assert.Equal(t, kvToDestroy.Id, job.KeyId)
|
||||
assert.Equal(t, "global", job.ScopeId)
|
||||
})
|
||||
t.Run("lists-no-jobs-when-given-unknown-scope", func(t *testing.T) {
|
||||
var kvToDestroy wrappingKms.KeyVersion
|
||||
for _, key := range keys {
|
||||
if key.Purpose == wrappingKms.KeyPurpose(KeyPurposeDatabase.String()) {
|
||||
kvToDestroy = key.Versions[0]
|
||||
}
|
||||
}
|
||||
sqldb, err := conn.SqlDB(testCtx)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job(key_id) values ($1)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job_run(key_id, table_name, total_count) values ($1, 'auth_token', 100)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job_run(key_id, table_name, total_count) values ($1, 'auth_oidc_method', 200)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "update kms_data_key_version_destruction_job_run set is_running=true where key_id=$1 and table_name='auth_token'", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, err = sqldb.ExecContext(testCtx, "truncate kms_data_key_version_destruction_job, kms_data_key_version_destruction_job_run CASCADE")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
jobs, err := kmsCache.ListDataKeyVersionDestructionJobs(testCtx, "myscope")
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, jobs)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMonitorTableRewrappingRuns(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCtx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
extWrapper := db.TestWrapper(t)
|
||||
kmsCache := TestKms(t, conn, extWrapper)
|
||||
err := kmsCache.CreateKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
err = kmsCache.RotateKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
keys, err := kmsCache.ListKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
sqldb, err := conn.SqlDB(testCtx)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("does-nothing-when-no-run-available", func(t *testing.T) {
|
||||
callbackCalled := false
|
||||
tableNameToRewrapFn["auth_token"] = func(ctx context.Context, dataKeyVersionId string, scopeId string, reader db.Reader, writer db.Writer, kms *Kms) error {
|
||||
callbackCalled = true
|
||||
return nil
|
||||
}
|
||||
err = kmsCache.MonitorTableRewrappingRuns(testCtx, "auth_token")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, callbackCalled, "auth_token callback should not have been called")
|
||||
})
|
||||
t.Run("returns-an-error-when-given-empty-table-name", func(t *testing.T) {
|
||||
err = kmsCache.MonitorTableRewrappingRuns(testCtx, "")
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("returns-an-error-when-no-rewrapping-function-registered", func(t *testing.T) {
|
||||
tableNameToRewrapFn = make(map[string]RewrapFn)
|
||||
err = kmsCache.MonitorTableRewrappingRuns(testCtx, "auth_token")
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("does-nothing-when-another-run-is-running", func(t *testing.T) {
|
||||
var kvToDestroy wrappingKms.KeyVersion
|
||||
for _, key := range keys {
|
||||
if key.Purpose == wrappingKms.KeyPurpose(KeyPurposeDatabase.String()) {
|
||||
kvToDestroy = key.Versions[0]
|
||||
}
|
||||
}
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job(key_id) values ($1)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job_run(key_id, table_name, total_count) values ($1, 'auth_token', 100)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job_run(key_id, table_name, total_count) values ($1, 'auth_oidc_method', 100)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "update kms_data_key_version_destruction_job_run set is_running=true where key_id=$1 and table_name='auth_oidc_method'", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, err = sqldb.ExecContext(testCtx, "truncate kms_data_key_version_destruction_job, kms_data_key_version_destruction_job_run CASCADE")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
callbackCalled := false
|
||||
tableNameToRewrapFn["auth_token"] = func(ctx context.Context, dataKeyVersionId string, scopeId string, reader db.Reader, writer db.Writer, kms *Kms) error {
|
||||
callbackCalled = true
|
||||
return nil
|
||||
}
|
||||
err = kmsCache.MonitorTableRewrappingRuns(testCtx, "auth_token")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, callbackCalled, "auth_token callback should not have been called")
|
||||
})
|
||||
t.Run("chooses-one-when-one-run-available", func(t *testing.T) {
|
||||
var kvToDestroy wrappingKms.KeyVersion
|
||||
for _, key := range keys {
|
||||
if key.Purpose == wrappingKms.KeyPurpose(KeyPurposeDatabase.String()) {
|
||||
kvToDestroy = key.Versions[0]
|
||||
}
|
||||
}
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job(key_id) values ($1)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job_run(key_id, table_name, total_count) values ($1, 'auth_token', 100)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, err = sqldb.ExecContext(testCtx, "truncate kms_data_key_version_destruction_job, kms_data_key_version_destruction_job_run CASCADE")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
callbackCalled := make(chan struct{})
|
||||
returnFromCallback := make(chan struct{})
|
||||
tableNameToRewrapFn["auth_token"] = func(ctx context.Context, dataKeyVersionId string, scopeId string, reader db.Reader, writer db.Writer, kms *Kms) error {
|
||||
close(callbackCalled)
|
||||
assert.Equal(t, "global", scopeId)
|
||||
// Block here until we want it to return
|
||||
<-returnFromCallback
|
||||
return nil
|
||||
}
|
||||
monitorErrCh := make(chan error)
|
||||
// Run in goroutine so we can check status while the job is running
|
||||
go func() {
|
||||
monitorErrCh <- kmsCache.MonitorTableRewrappingRuns(testCtx, "auth_token")
|
||||
}()
|
||||
// Wait for callback to have been called
|
||||
select {
|
||||
case <-callbackCalled:
|
||||
case err := <-monitorErrCh:
|
||||
require.NoError(t, err)
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatalf("Callback had not been called after 5 seconds")
|
||||
return
|
||||
}
|
||||
// Now we know that the job is waiting for the callback to return, lets
|
||||
// do some db inspection
|
||||
row := sqldb.QueryRowContext(testCtx, "select is_running from kms_data_key_version_destruction_job_run where key_id=$1 and table_name='auth_token'", kvToDestroy.Id)
|
||||
isRunning := false
|
||||
err = row.Scan(&isRunning)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, isRunning, "is_running should be set to true")
|
||||
// Trigger callback to return
|
||||
close(returnFromCallback)
|
||||
// Wait for function to return
|
||||
select {
|
||||
case err := <-monitorErrCh:
|
||||
require.NoError(t, err)
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatalf("Function has not returned 5 seconds after callback finished")
|
||||
return
|
||||
}
|
||||
// Lets look at the db state again after the function has returned
|
||||
row = sqldb.QueryRowContext(testCtx, "select completed_count, is_running from kms_data_key_version_destruction_job_run where key_id=$1 and table_name='auth_token'", kvToDestroy.Id)
|
||||
completedCount := 0
|
||||
isRunning = false
|
||||
err = row.Scan(&completedCount, &isRunning)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, completedCount, 100, "completed_count should have been updated")
|
||||
assert.False(t, isRunning, "is_running should be set to false")
|
||||
})
|
||||
t.Run("chooses-oldest-when-two-runs-available", func(t *testing.T) {
|
||||
var kvsToDestroy []wrappingKms.KeyVersion
|
||||
for _, key := range keys {
|
||||
switch key.Purpose {
|
||||
case wrappingKms.KeyPurpose(KeyPurposeDatabase.String()), wrappingKms.KeyPurpose(KeyPurposeTokens.String()):
|
||||
kvsToDestroy = append(kvsToDestroy, key.Versions[0])
|
||||
}
|
||||
}
|
||||
require.Len(t, kvsToDestroy, 2)
|
||||
for _, kvToDestroy := range kvsToDestroy {
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job(key_id) values ($1)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job_run(key_id, table_name, total_count) values ($1, 'auth_token', 100)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_, err = sqldb.ExecContext(testCtx, "truncate kms_data_key_version_destruction_job, kms_data_key_version_destruction_job_run CASCADE")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
rewrappedKeyVersion := ""
|
||||
tableNameToRewrapFn["auth_token"] = func(ctx context.Context, dataKeyVersionId string, scopeId string, reader db.Reader, writer db.Writer, kms *Kms) error {
|
||||
rewrappedKeyVersion = dataKeyVersionId
|
||||
return nil
|
||||
}
|
||||
err = kmsCache.MonitorTableRewrappingRuns(testCtx, "auth_token")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, rewrappedKeyVersion, kvsToDestroy[0].Id, "auth_token callback should have been called with the oldest job")
|
||||
})
|
||||
t.Run("resumes-running-one-when-two-runs-available-even-if-not-oldest", func(t *testing.T) {
|
||||
var kvsToDestroy []wrappingKms.KeyVersion
|
||||
for _, key := range keys {
|
||||
switch key.Purpose {
|
||||
case wrappingKms.KeyPurpose(KeyPurposeDatabase.String()), wrappingKms.KeyPurpose(KeyPurposeTokens.String()):
|
||||
kvsToDestroy = append(kvsToDestroy, key.Versions[0])
|
||||
}
|
||||
}
|
||||
require.Len(t, kvsToDestroy, 2)
|
||||
for i, kvToDestroy := range kvsToDestroy {
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job(key_id) values ($1)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job_run(key_id, table_name, total_count) values ($1, 'auth_token', 100)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
if i == 1 {
|
||||
_, err = sqldb.ExecContext(testCtx, "update kms_data_key_version_destruction_job_run set is_running=true where key_id=$1 and table_name='auth_token'", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_, err = sqldb.ExecContext(testCtx, "truncate kms_data_key_version_destruction_job, kms_data_key_version_destruction_job_run CASCADE")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
rewrappedKeyVersion := ""
|
||||
tableNameToRewrapFn["auth_token"] = func(ctx context.Context, dataKeyVersionId string, scopeId string, reader db.Reader, writer db.Writer, kms *Kms) error {
|
||||
rewrappedKeyVersion = dataKeyVersionId
|
||||
return nil
|
||||
}
|
||||
err = kmsCache.MonitorTableRewrappingRuns(testCtx, "auth_token")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, rewrappedKeyVersion, kvsToDestroy[1].Id, "auth_token callback should have been called with the already running job")
|
||||
})
|
||||
t.Run("updates-the-running-state-even-when-context-canceled", func(t *testing.T) {
|
||||
var kvToDestroy wrappingKms.KeyVersion
|
||||
for _, key := range keys {
|
||||
if key.Purpose == wrappingKms.KeyPurpose(KeyPurposeDatabase.String()) {
|
||||
kvToDestroy = key.Versions[0]
|
||||
}
|
||||
}
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job(key_id) values ($1)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job_run(key_id, table_name, total_count) values ($1, 'auth_token', 100)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, err = sqldb.ExecContext(testCtx, "truncate kms_data_key_version_destruction_job, kms_data_key_version_destruction_job_run CASCADE")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
callbackCalled := make(chan struct{})
|
||||
tableNameToRewrapFn["auth_token"] = func(ctx context.Context, dataKeyVersionId string, scopeId string, reader db.Reader, writer db.Writer, kms *Kms) error {
|
||||
close(callbackCalled)
|
||||
// Block here until we want it to return
|
||||
<-ctx.Done()
|
||||
return ctx.Err()
|
||||
}
|
||||
monitorErrCh := make(chan error)
|
||||
newCtx, cancel := context.WithCancel(testCtx)
|
||||
defer cancel()
|
||||
// Run in goroutine so we can check status while the job is running
|
||||
go func() {
|
||||
monitorErrCh <- kmsCache.MonitorTableRewrappingRuns(newCtx, "auth_token")
|
||||
}()
|
||||
// Wait for callback to have been called
|
||||
select {
|
||||
case <-callbackCalled:
|
||||
case err := <-monitorErrCh:
|
||||
require.NoError(t, err)
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatalf("Callback had not been called after 5 seconds")
|
||||
return
|
||||
}
|
||||
// Now we know that the job is waiting for the callback to return, lets
|
||||
// do some db inspection
|
||||
row := sqldb.QueryRowContext(testCtx, "select is_running from kms_data_key_version_destruction_job_run where key_id=$1 and table_name='auth_token'", kvToDestroy.Id)
|
||||
isRunning := false
|
||||
err = row.Scan(&isRunning)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, isRunning, "is_running should be set to true")
|
||||
// Trigger callback to return
|
||||
cancel()
|
||||
// Wait for function to return
|
||||
select {
|
||||
case err := <-monitorErrCh:
|
||||
require.True(t, errors.Is(err, context.Canceled))
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatalf("Function has not returned 5 seconds after callback finished")
|
||||
return
|
||||
}
|
||||
// Lets look at the db state again after the function has returned
|
||||
row = sqldb.QueryRowContext(testCtx, "select completed_count, is_running from kms_data_key_version_destruction_job_run where key_id=$1 and table_name='auth_token'", kvToDestroy.Id)
|
||||
completedCount := 0
|
||||
isRunning = false
|
||||
err = row.Scan(&completedCount, &isRunning)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, completedCount, 100, "completed_count should have been updated")
|
||||
assert.False(t, isRunning, "is_running should be set to false")
|
||||
})
|
||||
}
|
||||
|
||||
func TestMonitorDataKeyVersionDestruction(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCtx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
extWrapper := db.TestWrapper(t)
|
||||
kmsCache := TestKms(t, conn, extWrapper)
|
||||
err := kmsCache.CreateKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
err = kmsCache.RotateKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
keys, err := kmsCache.ListKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
sqldb, err := conn.SqlDB(testCtx)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("does-nothing-when-no-jobs-available", func(t *testing.T) {
|
||||
err = kmsCache.MonitorDataKeyVersionDestruction(testCtx)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("does-nothing-when-the-job-isnt-completed", func(t *testing.T) {
|
||||
var kvToDestroy wrappingKms.KeyVersion
|
||||
for _, key := range keys {
|
||||
if key.Purpose == wrappingKms.KeyPurpose(KeyPurposeDatabase.String()) {
|
||||
kvToDestroy = key.Versions[0]
|
||||
}
|
||||
}
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job(key_id) values ($1)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job_run(key_id, table_name, total_count) values ($1, 'auth_token', 100)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, err = sqldb.ExecContext(testCtx, "truncate kms_data_key_version_destruction_job, kms_data_key_version_destruction_job_run CASCADE")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
err = kmsCache.MonitorDataKeyVersionDestruction(testCtx)
|
||||
require.NoError(t, err)
|
||||
row := sqldb.QueryRowContext(testCtx, "select exists(select 1 from kms_data_key_version_destruction_job where key_id=$1)", kvToDestroy.Id)
|
||||
exists := false
|
||||
err = row.Scan(&exists)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists, "the job should still exist")
|
||||
})
|
||||
t.Run("deletes-the-key-when-the-job-is-completed", func(t *testing.T) {
|
||||
var kvToDestroy wrappingKms.KeyVersion
|
||||
for _, key := range keys {
|
||||
if key.Purpose == wrappingKms.KeyPurpose(KeyPurposeDatabase.String()) {
|
||||
kvToDestroy = key.Versions[0]
|
||||
}
|
||||
}
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job(key_id) values ($1)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into kms_data_key_version_destruction_job_run(key_id, table_name, total_count) values ($1, 'auth_token', 100)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
_, err = sqldb.ExecContext(testCtx, "update kms_data_key_version_destruction_job_run set completed_count=100 where key_id=$1 and table_name='auth_token'", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, err = sqldb.ExecContext(testCtx, "truncate kms_data_key_version_destruction_job, kms_data_key_version_destruction_job_run CASCADE")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
err = kmsCache.MonitorDataKeyVersionDestruction(testCtx)
|
||||
require.NoError(t, err)
|
||||
row := sqldb.QueryRowContext(testCtx, "select exists(select 1 from kms_data_key_version_destruction_job where key_id=$1)", kvToDestroy.Id)
|
||||
exists := false
|
||||
err = row.Scan(&exists)
|
||||
require.NoError(t, err)
|
||||
// The job is deleted by virtue of cascading foreign key references
|
||||
assert.False(t, exists, "the job should be deleted")
|
||||
})
|
||||
}
|
||||
|
||||
func TestDestroyKeyVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCtx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
extWrapper := db.TestWrapper(t)
|
||||
kmsCache := TestKms(t, conn, extWrapper)
|
||||
err := kmsCache.CreateKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
err = kmsCache.RotateKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
keys, err := kmsCache.ListKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
sqldb, err := conn.SqlDB(testCtx)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("returns-an-error-when-given-an-empty-scope-id", func(t *testing.T) {
|
||||
_, err := kmsCache.DestroyKeyVersion(testCtx, "", "krkv_DoesntExist")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
t.Run("returns-an-error-when-given-an-empty-key-version-id", func(t *testing.T) {
|
||||
_, err := kmsCache.DestroyKeyVersion(testCtx, "global", "")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
t.Run("returns-an-error-when-attempting-to-destroy-an-unknown-key-version", func(t *testing.T) {
|
||||
_, err := kmsCache.DestroyKeyVersion(testCtx, "global", "krkv_DoesntExist")
|
||||
require.Error(t, err)
|
||||
assert.True(t, errors.Match(errors.T(errors.KeyNotFound), err))
|
||||
})
|
||||
t.Run("returns-an-error-when-attempting-to-destroy-a-key-version-in-an-unknown-scope", func(t *testing.T) {
|
||||
var kvToDestroy wrappingKms.KeyVersion
|
||||
for _, key := range keys {
|
||||
if key.Purpose == wrappingKms.KeyPurpose(KeyPurposeDatabase.String()) {
|
||||
kvToDestroy = key.Versions[len(key.Versions)-1]
|
||||
}
|
||||
}
|
||||
_, err := kmsCache.DestroyKeyVersion(testCtx, "p_DoesntExist", kvToDestroy.Id)
|
||||
require.Error(t, err)
|
||||
assert.True(t, errors.Match(errors.T(errors.RecordNotFound), err))
|
||||
})
|
||||
t.Run("returns-an-error-when-attempting-to-destroy-latest-key-version", func(t *testing.T) {
|
||||
var kvToDestroy wrappingKms.KeyVersion
|
||||
for _, key := range keys {
|
||||
if key.Purpose == wrappingKms.KeyPurpose(KeyPurposeDatabase.String()) {
|
||||
kvToDestroy = key.Versions[len(key.Versions)-1]
|
||||
}
|
||||
}
|
||||
_, err := kmsCache.DestroyKeyVersion(testCtx, "global", kvToDestroy.Id)
|
||||
require.Error(t, err)
|
||||
assert.True(t, errors.Match(errors.T(errors.InvalidParameter), err))
|
||||
})
|
||||
t.Run("returns-an-error-when-attempting-to-destroy-oplog-key-version", func(t *testing.T) {
|
||||
var kvToDestroy wrappingKms.KeyVersion
|
||||
for _, key := range keys {
|
||||
if key.Purpose == wrappingKms.KeyPurpose(KeyPurposeOplog.String()) {
|
||||
kvToDestroy = key.Versions[0]
|
||||
}
|
||||
}
|
||||
_, err := kmsCache.DestroyKeyVersion(testCtx, "global", kvToDestroy.Id)
|
||||
require.Error(t, err)
|
||||
assert.True(t, errors.Match(errors.T(errors.InvalidParameter), err))
|
||||
})
|
||||
t.Run("succeeds-synchronously-when-destroying-old-root-key-version", func(t *testing.T) {
|
||||
var chosenKey wrappingKms.Key
|
||||
var kvToDestroy wrappingKms.KeyVersion
|
||||
for _, key := range keys {
|
||||
if key.Purpose == wrappingKms.KeyPurpose(KeyPurposeRootKey.String()) {
|
||||
kvToDestroy = key.Versions[0]
|
||||
chosenKey = key
|
||||
}
|
||||
}
|
||||
destroyed, err := kmsCache.DestroyKeyVersion(testCtx, "global", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, destroyed)
|
||||
// Check that the key version is destroyed
|
||||
keys, err := kmsCache.ListKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
for _, key := range keys {
|
||||
if key.Id == chosenKey.Id {
|
||||
require.Len(t, key.Versions, 1, "the root key should only have one version left")
|
||||
assert.NotEqual(t, key.Versions[0].Id, kvToDestroy.Id)
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("succeeds-synchronously-when-destroying-old-data-key-version-that-encrypts-no-data", func(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
var chosenKey wrappingKms.Key
|
||||
var kvToDestroy wrappingKms.KeyVersion
|
||||
for _, key := range keys {
|
||||
if key.Purpose == wrappingKms.KeyPurpose(KeyPurposeAudit.String()) {
|
||||
kvToDestroy = key.Versions[0]
|
||||
chosenKey = key
|
||||
}
|
||||
}
|
||||
destroyed, err := kmsCache.DestroyKeyVersion(testCtx, "global", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, destroyed)
|
||||
// Check that the key version is destroyed
|
||||
keys, err := kmsCache.ListKeys(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
for _, key := range keys {
|
||||
if key.Id == chosenKey.Id {
|
||||
require.Len(t, key.Versions, 1, "the data key should only have one version left")
|
||||
assert.NotEqual(t, key.Versions[0].Id, kvToDestroy.Id)
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("creates-asynchronous-job-and-runs-for-data-key-version-that-encrypt-data", func(t *testing.T) {
|
||||
var kvToDestroy wrappingKms.KeyVersion
|
||||
for _, key := range keys {
|
||||
if key.Purpose == wrappingKms.KeyPurpose(KeyPurposeDatabase.String()) {
|
||||
kvToDestroy = key.Versions[0]
|
||||
}
|
||||
}
|
||||
// Create a row that references the key we want to destroy.
|
||||
// Note: This could break if the underlying table definitions change.
|
||||
// Future contributors; forgive me, but we do really want to test this
|
||||
// functionality here, and we can't import any of the normal test helpers
|
||||
// since it would create circular dependencies.
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into worker_auth_ca_certificate(certificate, not_valid_after, public_key, private_key, state, issuing_ca, key_id) values ('certificate', CURRENT_TIMESTAMP+'1h'::interval, 'public_key', 'private_key', 'current', 'roots', $1)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, err = sqldb.ExecContext(testCtx, "delete from worker_auth_ca_certificate")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
destroyed, err := kmsCache.DestroyKeyVersion(testCtx, "global", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, err = sqldb.ExecContext(testCtx, "delete from kms_data_key_version_destruction_job; delete from kms_data_key_version_destruction_job_run")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
assert.False(t, destroyed)
|
||||
// Check that the data key version destruction job was created
|
||||
jobs, err := kmsCache.ListDataKeyVersionDestructionJobs(testCtx, "global")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, jobs, 1)
|
||||
assert.Equal(t, jobs[0].KeyId, kvToDestroy.Id)
|
||||
assert.Equal(t, jobs[0].CompletedCount, int64(0))
|
||||
assert.Equal(t, jobs[0].TotalCount, int64(1))
|
||||
assert.Equal(t, jobs[0].Status, "pending")
|
||||
assert.Equal(t, jobs[0].ScopeId, "global")
|
||||
assert.True(t, jobs[0].CreateTime.AsTime().Before(time.Now()))
|
||||
})
|
||||
t.Run("errors-when-destroying-key-that-is-already-destroying", func(t *testing.T) {
|
||||
var kvToDestroy wrappingKms.KeyVersion
|
||||
for _, key := range keys {
|
||||
if key.Purpose == wrappingKms.KeyPurpose(KeyPurposeDatabase.String()) {
|
||||
kvToDestroy = key.Versions[0]
|
||||
}
|
||||
}
|
||||
_, err = sqldb.ExecContext(testCtx, "insert into worker_auth_ca_certificate(certificate, not_valid_after, public_key, private_key, state, issuing_ca, key_id) values ('certificate', CURRENT_TIMESTAMP+'1h'::interval, 'public_key', 'private_key', 'current', 'roots', $1)", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, err = sqldb.ExecContext(testCtx, "delete from worker_auth_ca_certificate")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
destroyed, err := kmsCache.DestroyKeyVersion(testCtx, "global", kvToDestroy.Id)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, destroyed)
|
||||
_, err = kmsCache.DestroyKeyVersion(testCtx, "global", kvToDestroy.Id)
|
||||
require.Error(t, err)
|
||||
assert.True(t, errors.Match(errors.T(errors.InvalidParameter), err), "error did not match InvalidParameter as expected: %v", err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_RegisterTableRewrapFn(t *testing.T) {
|
||||
rewrapFn := func(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kms *Kms) error {
|
||||
return nil
|
||||
}
|
||||
RegisterTableRewrapFn("a_table", rewrapFn)
|
||||
t.Cleanup(func() {
|
||||
tableNameToRewrapFn = map[string]RewrapFn{}
|
||||
})
|
||||
assert.Panics(t, func() {
|
||||
RegisterTableRewrapFn("a_table", rewrapFn)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ListTablesSupportingRewrap(t *testing.T) {
|
||||
assert.Empty(t, ListTablesSupportingRewrap())
|
||||
rewrapFn := func(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kms *Kms) error {
|
||||
return nil
|
||||
}
|
||||
RegisterTableRewrapFn("a_table", rewrapFn)
|
||||
t.Cleanup(func() {
|
||||
tableNameToRewrapFn = map[string]RewrapFn{}
|
||||
})
|
||||
registeredTables := ListTablesSupportingRewrap()
|
||||
assert.Len(t, registeredTables, 1)
|
||||
assert.Equal(t, registeredTables[0], "a_table")
|
||||
}
|
||||
|
||||
type invalidReader struct {
|
||||
db.Reader
|
||||
}
|
||||
|
||||
@@ -42,4 +42,11 @@ func Test_GetOpts(t *testing.T) {
|
||||
testOpts.withKeyId = "100"
|
||||
assert.Equal(opts, testOpts)
|
||||
})
|
||||
t.Run("WithRewrap", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
opts := getOpts(WithRewrap(true))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withRewrap = true
|
||||
assert.Equal(opts, testOpts)
|
||||
})
|
||||
}
|
||||
Loaded 100 of 158 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user