diff --git a/api/hostcatalogs/host_catalog.gen.go b/api/hostcatalogs/host_catalog.gen.go index ae121ae42..d196f7355 100644 --- a/api/hostcatalogs/host_catalog.gen.go +++ b/api/hostcatalogs/host_catalog.gen.go @@ -27,6 +27,7 @@ type HostCatalog struct { Type string `json:"type,omitempty"` Attributes map[string]interface{} `json:"attributes,omitempty"` Secrets map[string]interface{} `json:"secrets,omitempty"` + SecretsHmac string `json:"secrets_hmac,omitempty"` AuthorizedActions []string `json:"authorized_actions,omitempty"` AuthorizedCollectionActions map[string][]string `json:"authorized_collection_actions,omitempty"` diff --git a/globals/fields.go b/globals/fields.go index 20a7045b6..35b8891be 100644 --- a/globals/fields.go +++ b/globals/fields.go @@ -65,6 +65,7 @@ const ( PluginNameField = "plugin_name" IpAddressesField = "ip_addresses" DnsNamesField = "dns_names" + SecretsHmacField = "secrets_hmac" ExternalIdField = "external_id" EgressCredentialSourceIdsField = "egress_credential_source_ids" EgressCredentialSourcesField = "egress_credential_sources" diff --git a/internal/cmd/commands/hostcatalogscmd/funcs.go b/internal/cmd/commands/hostcatalogscmd/funcs.go index 273ab3d1f..d1a1fd683 100644 --- a/internal/cmd/commands/hostcatalogscmd/funcs.go +++ b/internal/cmd/commands/hostcatalogscmd/funcs.go @@ -146,6 +146,9 @@ func printItemTable(result api.GenericResult) string { if item.Type != "" { nonAttributeMap["Type"] = item.Type } + if item.SecretsHmac != "" { + nonAttributeMap["Secrets HMAC"] = item.SecretsHmac + } maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap) diff --git a/internal/db/read_writer.go b/internal/db/read_writer.go index a666561f2..87ce6104f 100644 --- a/internal/db/read_writer.go +++ b/internal/db/read_writer.go @@ -508,24 +508,24 @@ func (rw *Db) CreateItems(ctx context.Context, createItems []interface{}, opt .. // Update an object in the db, fieldMask is required and provides // field_mask.proto paths for fields that should be updated. The i interface -// parameter is the type the caller wants to update in the db and its -// fields are set to the update values. setToNullPaths is optional and -// provides field_mask.proto paths for the fields that should be set to -// null. fieldMaskPaths and setToNullPaths must not intersect. The caller -// is responsible for the transaction life cycle of the writer and if an -// error is returned the caller must decide what to do with the transaction, -// which almost always should be to rollback. Update returns the number of -// rows updated. +// parameter is the type the caller wants to update in the db and its fields are +// set to the update values. setToNullPaths is optional and provides +// field_mask.proto paths for the fields that should be set to null. +// fieldMaskPaths and setToNullPaths must not intersect. The caller is +// responsible for the transaction life cycle of the writer and if an error is +// returned the caller must decide what to do with the transaction, which almost +// always should be to rollback. Update returns the number of rows updated. // -// Supported options: WithOplog, NewOplogMsg, WithWhere and WithVersion. -// WithOplog will write an oplog entry for the update. NewOplogMsg -// will return in-memory oplog message. WithOplog and NewOplogMsg cannot be -// used together. If WithVersion is used, then the update will include the -// version number in the update where clause, which basically makes the update -// use optimistic locking and the update will only succeed if the existing rows -// version matches the WithVersion option. Zero is not a valid value for the +// Supported options: WithOplog, NewOplogMsg, WithWhere, WithDebug, and +// WithVersion. WithOplog will write an oplog entry for the update. NewOplogMsg +// will return in-memory oplog message. WithOplog and NewOplogMsg cannot be used +// together. If WithVersion is used, then the update will include the version +// number in the update where clause, which basically makes the update use +// optimistic locking and the update will only succeed if the existing rows +// version matches the WithVersion option. Zero is not a valid value for the // WithVersion option and will return an error. WithWhere allows specifying an -// additional constraint on the operation in addition to the PKs. +// additional constraint on the operation in addition to the PKs. WithDebug will +// turn on debugging for the update call. func (rw *Db) Update(ctx context.Context, i interface{}, fieldMaskPaths []string, setToNullPaths []string, opt ...Option) (int, error) { const op = "db.Update" if rw.underlying == nil { @@ -603,16 +603,19 @@ func (rw *Db) Update(ctx context.Context, i interface{}, fieldMaskPaths []string return NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) } } - var underlying *gorm.DB + underlying := rw.underlying.Model(i) + if opts.withDebug { + underlying = underlying.Debug() + } switch { case opts.WithVersion != nil || opts.withWhereClause != "": where, args, err := rw.whereClausesFromOpts(ctx, i, opts) if err != nil { return NoRowsAffected, errors.Wrap(ctx, err, op) } - underlying = rw.underlying.Model(i).Where(where, args...).Updates(updateFields) + underlying = underlying.Where(where, args...).Updates(updateFields) default: - underlying = rw.underlying.Model(i).Updates(updateFields) + underlying = underlying.Updates(updateFields) } if underlying.Error != nil { if underlying.Error == gorm.ErrRecordNotFound { diff --git a/internal/db/schema/migrations/oss/postgres/20/06_plugin_host_views.up.sql b/internal/db/schema/migrations/oss/postgres/20/06_plugin_host_views.up.sql index 95f76646e..3bc5c2730 100644 --- a/internal/db/schema/migrations/oss/postgres/20/06_plugin_host_views.up.sql +++ b/internal/db/schema/migrations/oss/postgres/20/06_plugin_host_views.up.sql @@ -30,8 +30,7 @@ create view host_plugin_host_set_with_value_obj as comment on view host_plugin_host_set_with_value_obj is 'host plugin host set with its associated value objects'; --- host_plugin_catalog_with_secret is useful for reading a plugin catalog with --- its associated persisted data. +-- REPLACED in 10_plugin_host_secrets_hmac.up.sql create view host_plugin_catalog_with_secret as select hc.public_id, diff --git a/internal/db/schema/migrations/oss/postgres/20/08_plugin_host_views.up.sql b/internal/db/schema/migrations/oss/postgres/20/08_plugin_host_views.up.sql index 52e4ba9e1..76920b224 100644 --- a/internal/db/schema/migrations/oss/postgres/20/08_plugin_host_views.up.sql +++ b/internal/db/schema/migrations/oss/postgres/20/08_plugin_host_views.up.sql @@ -6,12 +6,11 @@ alter table host_plugin_host create trigger update_version_column after update on host_plugin_host for each row execute procedure update_version_column(); -drop view host_plugin_host_with_value_obj_and_set_memberships; - -- host_plugin_host_with_value_obj_and_set_memberships is useful for reading a -- plugin host with its associated value objects (ip addresses, dns names) and -- set membership as columns with delimited values. The delimiter depends on -- the value objects (e.g. if they need ordering). +drop view host_plugin_host_with_value_obj_and_set_memberships; create view host_plugin_host_with_value_obj_and_set_memberships as select h.public_id, diff --git a/internal/db/schema/migrations/oss/postgres/20/10_plugin_host_secrets_hmac.up.sql b/internal/db/schema/migrations/oss/postgres/20/10_plugin_host_secrets_hmac.up.sql new file mode 100644 index 000000000..5cbb66552 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/20/10_plugin_host_secrets_hmac.up.sql @@ -0,0 +1,33 @@ +begin; + +alter table host_plugin_catalog + add column secrets_hmac bytea + constraint secrets_hmac_null_or_not_empty + check(secrets_hmac is null or length(secrets_hmac) > 0); + +-- host_plugin_catalog_with_secret is useful for reading a plugin catalog with +-- its associated persisted data. +drop view host_plugin_catalog_with_secret; +create view host_plugin_catalog_with_secret as +select + hc.public_id, + hc.scope_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'; + +commit; \ No newline at end of file diff --git a/internal/gen/controller.swagger.json b/internal/gen/controller.swagger.json index 8a0ce1bdc..19a41be77 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -4146,6 +4146,11 @@ "type": "object", "description": "Secrets specific to the catalog type. These are never output." }, + "secrets_hmac": { + "type": "string", + "description": "Output only. The HMAC of the last secrets supplied via the API, if any.", + "readOnly": true + }, "authorized_actions": { "type": "array", "items": { diff --git a/internal/host/plugin/host_catalog.go b/internal/host/plugin/host_catalog.go index 8a29da5f8..961bbdea3 100644 --- a/internal/host/plugin/host_catalog.go +++ b/internal/host/plugin/host_catalog.go @@ -5,11 +5,14 @@ package plugin import ( "context" + "encoding/json" "github.com/hashicorp/boundary/internal/db/timestamp" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/host/plugin/store" + "github.com/hashicorp/boundary/internal/libs/crypto" "github.com/hashicorp/boundary/internal/oplog" + wrapping "github.com/hashicorp/go-kms-wrapping" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" ) @@ -42,6 +45,7 @@ func NewHostCatalog(ctx context.Context, scopeId, pluginId string, opt ...Option Name: opts.withName, Description: opts.withDescription, Attributes: attrs, + SecretsHmac: opts.withSecretsHmac, }, Secrets: opts.withSecrets, } @@ -54,6 +58,35 @@ func allocHostCatalog() *HostCatalog { } } +// hmacSecrets before writing it to the db +func (c *HostCatalog) hmacSecrets(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "plugin.(HostCatalog).hmacSecrets" + if c.Secrets == nil { + c.SecretsHmac = nil + return nil + } + secretsMap := c.Secrets.AsMap() + if len(secretsMap) == 0 { + c.SecretsHmac = nil + return nil + } + if cipher == nil { + return errors.New(ctx, errors.InvalidParameter, op, "missing cipher") + } + // Go's JSON encoding is stable (that is, it alphabetizes keys) so it's a + // good option to produce an HMAC-able string. + jsonSecrets, err := json.Marshal(secretsMap) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithCode(errors.Code(errors.Encryption))) + } + hm, err := crypto.HmacSha256(ctx, jsonSecrets, cipher, []byte(c.PublicId), nil) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithCode(errors.Code(errors.Encryption))) + } + c.SecretsHmac = []byte(hm) + return nil +} + // clone provides a deep copy of the HostCatalog with the exception of the // secret. The secret shallow copied. func (c *HostCatalog) clone() *HostCatalog { @@ -107,6 +140,7 @@ type catalogAgg struct { CreateTime *timestamp.Timestamp UpdateTime *timestamp.Timestamp Version uint32 + SecretsHmac []byte Attributes []byte Secret []byte KeyId string @@ -127,6 +161,7 @@ func (agg *catalogAgg) toCatalogAndPersisted() (*HostCatalog, *HostCatalogSecret c.CreateTime = agg.CreateTime c.UpdateTime = agg.UpdateTime c.Version = agg.Version + c.SecretsHmac = agg.SecretsHmac c.Attributes = agg.Attributes var s *HostCatalogSecret diff --git a/internal/host/plugin/host_catalog_test.go b/internal/host/plugin/host_catalog_test.go index cd7d90578..4a42080ca 100644 --- a/internal/host/plugin/host_catalog_test.go +++ b/internal/host/plugin/host_catalog_test.go @@ -9,7 +9,11 @@ import ( "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/host/plugin/store" "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/plugin/host" + wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/hashicorp/go-kms-wrapping/wrappers/aead" + "github.com/mr-tron/base58" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" @@ -326,3 +330,114 @@ func TestHostCatalog_SetTableName(t *testing.T) { }) } } + +func TestHostCatalog_SecretsHmac(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rootWrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, rootWrapper) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, rootWrapper)) + databaseWrapper, err := kmsCache.GetWrapper(ctx, prj.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + + plg := host.TestPlugin(t, conn, "testplugin") + cat := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId()) + + // stableValue checks that HMACing the same value returns the same result. + // The first time it's called to match it will be empty and thus assign the + // expected value; the second time it will assert that value. + var stableValue string + tests := []struct { + name string + hcFn func() *HostCatalog + hmacWrapper wrapping.Wrapper + emptyHmac bool + matchStableValue bool + wantHmacErrMatch *errors.Template + }{ + { + name: "valid-empty-secrets", + hcFn: func() *HostCatalog { + cat.Secrets = nil + cat.SecretsHmac = []byte("foobar") + return cat + }, + emptyHmac: true, + hmacWrapper: databaseWrapper, + }, + { + name: "valid", + hcFn: func() *HostCatalog { + cat.Secrets = mustStruct(map[string]interface{}{"foo": "bar"}) + cat.SecretsHmac = nil + return cat + }, + hmacWrapper: databaseWrapper, + matchStableValue: true, + }, + { + name: "valid-different-val", + hcFn: func() *HostCatalog { + cat.Secrets = mustStruct(map[string]interface{}{"zip": "zap"}) + cat.SecretsHmac = nil + return cat + }, + hmacWrapper: databaseWrapper, + }, + { + name: "valid-original-val", + hcFn: func() *HostCatalog { + cat.Secrets = mustStruct(map[string]interface{}{"foo": "bar"}) + cat.SecretsHmac = nil + return cat + }, + hmacWrapper: databaseWrapper, + matchStableValue: true, + }, + { + name: "hmac-missing-wrapper", + hcFn: func() *HostCatalog { + cat.Secrets = mustStruct(map[string]interface{}{"foo": "bar"}) + cat.SecretsHmac = []byte("foobar") + return cat + }, + wantHmacErrMatch: errors.T(errors.InvalidParameter), + }, + { + name: "hmac-bad-wrapper", + hcFn: func() *HostCatalog { + cat.Secrets = mustStruct(map[string]interface{}{"foo": "bar"}) + cat.SecretsHmac = nil + return cat + }, + hmacWrapper: &aead.Wrapper{}, + wantHmacErrMatch: errors.T(errors.InvalidParameter), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + hmacCat := tt.hcFn().clone() + err = hmacCat.hmacSecrets(ctx, tt.hmacWrapper) + if tt.wantHmacErrMatch != nil { + require.Error(err) + return + } + require.NoError(err) + if tt.emptyHmac { + assert.Empty(hmacCat.SecretsHmac) + stableValue = "" + return + } + assert.NotEmpty(hmacCat.SecretsHmac) + if tt.matchStableValue { + if stableValue == "" { + stableValue = base58.Encode(hmacCat.SecretsHmac) + } else { + assert.Equal(stableValue, base58.Encode(hmacCat.SecretsHmac)) + } + } + }) + } +} diff --git a/internal/host/plugin/options.go b/internal/host/plugin/options.go index 6536cebcf..d15928ed9 100644 --- a/internal/host/plugin/options.go +++ b/internal/host/plugin/options.go @@ -28,6 +28,7 @@ type options struct { withDnsNames []string withLimit int withSetIds []string + withSecretsHmac []byte } func getDefaultOptions() options { @@ -122,3 +123,10 @@ func WithSetIds(with []string) Option { o.withSetIds = with } } + +// WithSecretsHmac provides an optional HMAC of secrets. Used for testing. +func WithSecretsHmac(secretsHmac []byte) Option { + return func(o *options) { + o.withSecretsHmac = secretsHmac + } +} diff --git a/internal/host/plugin/options_test.go b/internal/host/plugin/options_test.go index e55321cf1..a86e89427 100644 --- a/internal/host/plugin/options_test.go +++ b/internal/host/plugin/options_test.go @@ -68,4 +68,10 @@ func Test_GetOpts(t *testing.T) { testOpts.withSetIds = []string{"foo"} assert.EqualValues(t, opts, testOpts) }) + t.Run("WithSecretsHmac", func(t *testing.T) { + opts := getOpts(WithSecretsHmac([]byte("secrets-hmac"))) + testOpts := getDefaultOptions() + testOpts.withSecretsHmac = []byte("secrets-hmac") + assert.Equal(t, opts, testOpts) + }) } diff --git a/internal/host/plugin/repository_host_catalog.go b/internal/host/plugin/repository_host_catalog.go index 74464774a..113910978 100644 --- a/internal/host/plugin/repository_host_catalog.go +++ b/internal/host/plugin/repository_host_catalog.go @@ -16,6 +16,7 @@ import ( pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs" pbset "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets" plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/mr-tron/base58" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" @@ -68,6 +69,17 @@ func (r *Repository) CreateCatalog(ctx context.Context, c *HostCatalog, _ ...Opt return nil, nil, errors.Wrap(ctx, err, op) } + // If secrets were passed in, HMAC 'em + if c.Secrets != nil && len(c.Secrets.GetFields()) > 0 { + databaseWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper")) + } + if err := c.hmacSecrets(ctx, databaseWrapper); err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("error hmac'ing passed-in secrets")) + } + } + plgHc, err := toPluginCatalog(ctx, c) if err != nil { return nil, nil, errors.Wrap(ctx, err, op) @@ -226,6 +238,7 @@ func (r *Repository) UpdateCatalog(ctx context.Context, c *HostCatalog, version newCatalog := currentCatalog.clone() var updateAttributes bool var dbMask, nullFields []string + var alreadySetSecrets bool for _, f := range fieldMask { switch { case strings.EqualFold("name", f) && c.Name == "": @@ -245,10 +258,29 @@ func (r *Repository) UpdateCatalog(ctx context.Context, c *HostCatalog, version // sent, we only need to do this once. updateAttributes = true case strings.EqualFold("secrets", strings.Split(f, ".")[0]): + if alreadySetSecrets { + continue + } + alreadySetSecrets = true // While in a similar format, secrets are passed along // wholesale (for the time being). Don't append this mask // field, as secrets do not have a database entry. newCatalog.Secrets = c.Secrets + switch { + case newCatalog.Secrets == nil, + len(newCatalog.Secrets.GetFields()) == 0: + nullFields = append(nullFields, "SecretsHmac") + default: + // If secrets were passed in, HMAC 'em + databaseWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper")) + } + if err := newCatalog.hmacSecrets(ctx, databaseWrapper); err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("error hmac'ing passed-in secrets")) + } + dbMask = append(dbMask, "SecretsHmac") + } default: return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f)) } @@ -639,6 +671,9 @@ func toPluginCatalog(ctx context.Context, in *HostCatalog) (*pb.HostCatalog, err Name: name, Description: description, } + if len(in.GetSecretsHmac()) > 0 { + hc.SecretsHmac = base58.Encode(in.GetSecretsHmac()) + } if in.GetAttributes() != nil { attrs := &structpb.Struct{} if err := proto.Unmarshal(in.GetAttributes(), attrs); err != nil { diff --git a/internal/host/plugin/repository_host_catalog_test.go b/internal/host/plugin/repository_host_catalog_test.go index 832a07964..c46ca951a 100644 --- a/internal/host/plugin/repository_host_catalog_test.go +++ b/internal/host/plugin/repository_host_catalog_test.go @@ -576,6 +576,18 @@ func TestRepository_UpdateCatalog(t *testing.T) { } } + checkSecretsHmac := func(notNil bool) checkFunc { + return func(t *testing.T, ctx context.Context) { + t.Helper() + assert := assert.New(t) + if notNil { + assert.NotEmpty(gotCatalog.SecretsHmac) + } else { + assert.Empty(gotCatalog.SecretsHmac) + } + } + } + checkAttributes := func(want map[string]interface{}) checkFunc { return func(t *testing.T, ctx context.Context) { t.Helper() @@ -804,6 +816,7 @@ func TestRepository_UpdateCatalog(t *testing.T) { fieldMask: []string{"name"}, wantCheckFuncs: []checkFunc{ checkVersion(3), + checkSecretsHmac(true), checkUpdateCatalogRequestCurrentNameNil(), checkUpdateCatalogRequestNewName("foo"), checkName("foo"), @@ -821,6 +834,7 @@ func TestRepository_UpdateCatalog(t *testing.T) { fieldMask: []string{"name"}, wantCheckFuncs: []checkFunc{ checkVersion(2), // Version remains same even though row is updated + checkSecretsHmac(true), checkUpdateCatalogRequestCurrentNameNil(), checkUpdateCatalogRequestNewNameNil(), checkName(""), @@ -838,6 +852,7 @@ func TestRepository_UpdateCatalog(t *testing.T) { fieldMask: []string{"name"}, wantCheckFuncs: []checkFunc{ checkVersion(3), + checkSecretsHmac(true), checkUpdateCatalogRequestCurrentNameNil(), checkUpdateCatalogRequestNewName(testDuplicateCatalogNameAltScope), checkName(testDuplicateCatalogNameAltScope), @@ -855,6 +870,7 @@ func TestRepository_UpdateCatalog(t *testing.T) { fieldMask: []string{"description"}, wantCheckFuncs: []checkFunc{ checkVersion(3), + checkSecretsHmac(true), checkUpdateCatalogRequestCurrentDescriptionNil(), checkUpdateCatalogRequestNewDescription("foo"), checkDescription("foo"), @@ -872,6 +888,7 @@ func TestRepository_UpdateCatalog(t *testing.T) { fieldMask: []string{"description"}, wantCheckFuncs: []checkFunc{ checkVersion(2), // Version remains same even though row is updated + checkSecretsHmac(true), checkUpdateCatalogRequestCurrentDescriptionNil(), checkUpdateCatalogRequestNewDescriptionNil(), checkDescription(""), @@ -891,6 +908,7 @@ func TestRepository_UpdateCatalog(t *testing.T) { fieldMask: []string{"attributes"}, wantCheckFuncs: []checkFunc{ checkVersion(3), + checkSecretsHmac(true), checkUpdateCatalogRequestCurrentAttributes(map[string]interface{}{ "foo": "bar", }), @@ -918,6 +936,7 @@ func TestRepository_UpdateCatalog(t *testing.T) { fieldMask: []string{"attributes"}, wantCheckFuncs: []checkFunc{ checkVersion(3), + checkSecretsHmac(true), checkUpdateCatalogRequestCurrentAttributes(map[string]interface{}{ "foo": "bar", }), @@ -943,6 +962,7 @@ func TestRepository_UpdateCatalog(t *testing.T) { fieldMask: []string{"attributes"}, wantCheckFuncs: []checkFunc{ checkVersion(3), + checkSecretsHmac(true), checkUpdateCatalogRequestCurrentAttributes(map[string]interface{}{ "foo": "bar", }), @@ -965,6 +985,7 @@ func TestRepository_UpdateCatalog(t *testing.T) { fieldMask: []string{"attributes.a", "attributes.foo"}, wantCheckFuncs: []checkFunc{ checkVersion(3), + checkSecretsHmac(true), checkUpdateCatalogRequestCurrentAttributes(map[string]interface{}{ "foo": "bar", }), @@ -993,6 +1014,7 @@ func TestRepository_UpdateCatalog(t *testing.T) { fieldMask: []string{"secrets.three", "secrets.five"}, wantCheckFuncs: []checkFunc{ checkVersion(3), + checkSecretsHmac(true), checkUpdateCatalogRequestPersistedSecrets(map[string]interface{}{ "one": "two", }), @@ -1017,7 +1039,8 @@ func TestRepository_UpdateCatalog(t *testing.T) { version: 2, fieldMask: []string{"secrets.three"}, wantCheckFuncs: []checkFunc{ - checkVersion(2), // no-op + checkVersion(3), // incremented due to secrets_hmac + checkSecretsHmac(true), checkUpdateCatalogRequestPersistedSecrets(map[string]interface{}{ "one": "two", }), @@ -1037,6 +1060,7 @@ func TestRepository_UpdateCatalog(t *testing.T) { fieldMask: []string{"secrets"}, wantCheckFuncs: []checkFunc{ checkVersion(3), + checkSecretsHmac(false), checkUpdateCatalogRequestPersistedSecrets(map[string]interface{}{ "one": "two", }), @@ -1058,6 +1082,7 @@ func TestRepository_UpdateCatalog(t *testing.T) { fieldMask: []string{"name", "secrets.three"}, wantCheckFuncs: []checkFunc{ checkVersion(3), + checkSecretsHmac(true), checkUpdateCatalogRequestCurrentNameNil(), checkUpdateCatalogRequestNewName("foo"), checkUpdateCatalogRequestSecrets(map[string]interface{}{ @@ -1081,22 +1106,15 @@ func TestRepository_UpdateCatalog(t *testing.T) { require := require.New(t) cat := TestCatalog(t, dbConn, projectScope.PublicId, testPlugin.GetPublicId()) - // Set some (default) attributes on our test catalog - cat.Attributes = mustMarshal(map[string]interface{}{ - "foo": "bar", - }) - - numCatUpdated, err := dbRW.Update(ctx, cat, []string{"attributes"}, []string{}) - require.NoError(err) - require.Equal(1, numCatUpdated) // Set up some secrets - cSecretProto := mustStruct(map[string]interface{}{ + scopeWrapper, err := dbKmsCache.GetWrapper(ctx, cat.GetScopeId(), kms.KeyPurposeDatabase) + require.NoError(err) + cat.Secrets = mustStruct(map[string]interface{}{ "one": "two", }) - cSecret, err := newHostCatalogSecret(ctx, cat.GetPublicId(), cSecretProto) - require.NoError(err) - scopeWrapper, err := dbKmsCache.GetWrapper(ctx, cat.GetScopeId(), kms.KeyPurposeDatabase) + require.NoError(cat.hmacSecrets(ctx, scopeWrapper)) + cSecret, err := newHostCatalogSecret(ctx, cat.GetPublicId(), cat.Secrets) require.NoError(err) require.NoError(cSecret.encrypt(ctx, scopeWrapper)) cSecretQ, cSecretV := cSecret.upsertQuery() @@ -1104,6 +1122,14 @@ func TestRepository_UpdateCatalog(t *testing.T) { require.NoError(err) require.Equal(1, secretsUpdated) + // Set some (default) attributes on our test catalog and update SecretsHmac at the same time + cat.Attributes = mustMarshal(map[string]interface{}{ + "foo": "bar", + }) + numCatUpdated, err := dbRW.Update(ctx, cat, []string{"attributes", "SecretsHmac"}, []string{}) + require.NoError(err) + require.Equal(1, numCatUpdated) + t.Cleanup(func() { t.Helper() assert := assert.New(t) diff --git a/internal/host/plugin/store/host.pb.go b/internal/host/plugin/store/host.pb.go index df65c552f..488dbdb20 100644 --- a/internal/host/plugin/store/host.pb.go +++ b/internal/host/plugin/store/host.pb.go @@ -55,9 +55,14 @@ type HostCatalog struct { // version allows optimistic locking of the resource // @inject_tag: `gorm:"default:null"` Version uint32 `protobuf:"varint,8,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"` + // secrets_hmac is a sha256-hmac of the unencrypted secrets that is returned + // from the API for read. It is recalculated everytime the raw secrets are + // updated. + // @inject_tag: `gorm:"default:null"` + SecretsHmac []byte `protobuf:"bytes,9,opt,name=secrets_hmac,json=secretsHmac,proto3" json:"secrets_hmac,omitempty" gorm:"default:null"` // attributes is a jsonb formatted field. // @inject_tag: `gorm:"not_null"` - Attributes []byte `protobuf:"bytes,9,opt,name=attributes,proto3" json:"attributes,omitempty" gorm:"not_null"` + Attributes []byte `protobuf:"bytes,10,opt,name=attributes,proto3" json:"attributes,omitempty" gorm:"not_null"` } func (x *HostCatalog) Reset() { @@ -148,6 +153,13 @@ func (x *HostCatalog) GetVersion() uint32 { return 0 } +func (x *HostCatalog) GetSecretsHmac() []byte { + if x != nil { + return x.SecretsHmac + } + return nil +} + func (x *HostCatalog) GetAttributes() []byte { if x != nil { return x.Attributes @@ -644,7 +656,7 @@ var file_controller_storage_host_plugin_store_v1_host_proto_rawDesc = []byte{ 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9e, 0x03, 0x0a, 0x0b, 0x48, 0x6f, 0x73, + 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc1, 0x03, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, @@ -668,106 +680,108 @@ var file_controller_storage_host_plugin_store_v1_host_proto_rawDesc = []byte{ 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x61, - 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x22, 0xb6, 0x05, 0x0a, 0x07, 0x48, 0x6f, - 0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x73, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0b, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x48, 0x6d, 0x61, 0x63, 0x12, 0x1e, 0x0a, 0x0a, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x22, 0xb6, 0x05, 0x0a, + 0x07, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, + 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, - 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, - 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x50, 0x0a, 0x0e, - 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, - 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x6e, 0x65, 0x65, 0x64, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x08, 0x6e, 0x65, 0x65, 0x64, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x24, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, - 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, - 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, - 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x5e, 0x0a, 0x13, - 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x42, 0x2d, 0xc2, 0xdd, 0x29, 0x29, 0x0a, - 0x12, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x73, 0x12, 0x13, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x65, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x12, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, - 0x72, 0x65, 0x64, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x64, 0x0a, 0x15, - 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, - 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x42, 0x30, 0xc2, 0xdd, 0x29, - 0x2c, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, - 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x15, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x52, 0x13, 0x73, - 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x6f, 0x6e, - 0x64, 0x73, 0x22, 0x98, 0x02, 0x0a, 0x11, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, - 0x6f, 0x67, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, - 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, - 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, + 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x50, 0x0a, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x65, 0x65, 0x64, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x65, 0x65, 0x64, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x24, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, + 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, + 0x67, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, + 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, + 0x5e, 0x0a, 0x13, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x64, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x42, 0x2d, 0xc2, 0xdd, + 0x29, 0x29, 0x0a, 0x12, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x45, 0x6e, 0x64, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x13, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, + 0x64, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x12, 0x70, 0x72, 0x65, + 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, + 0x64, 0x0a, 0x15, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, + 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x42, 0x30, + 0xc2, 0xdd, 0x29, 0x2c, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, + 0x61, 0x6c, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x15, 0x73, 0x79, 0x6e, 0x63, 0x5f, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, + 0x52, 0x13, 0x73, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, 0x65, + 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x22, 0x98, 0x02, 0x0a, 0x11, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, + 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, + 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, + 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x54, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, + 0x63, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x08, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, + 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, + 0x22, 0x8d, 0x03, 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, - 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x74, 0x5f, - 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x74, - 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x22, 0x8d, 0x03, - 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, - 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x12, - 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, - 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, - 0x67, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, - 0x0c, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x09, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, - 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6e, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0a, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x5e, 0x0a, - 0x0d, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x17, - 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x68, 0x6f, 0x73, 0x74, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1d, - 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x42, 0x40, 0x5a, - 0x3e, 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, 0x68, 0x6f, 0x73, 0x74, 0x2f, 0x70, 0x6c, 0x75, - 0x67, 0x69, 0x6e, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, + 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, + 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, + 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6e, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, + 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, + 0x22, 0x5e, 0x0a, 0x0d, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, + 0x72, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x68, 0x6f, 0x73, 0x74, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x64, + 0x42, 0x40, 0x5a, 0x3e, 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, 0x68, 0x6f, 0x73, 0x74, 0x2f, + 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/libs/crypto/hmac_sha256.go b/internal/libs/crypto/hmac_sha256.go index 05491e6ef..256861c25 100644 --- a/internal/libs/crypto/hmac_sha256.go +++ b/internal/libs/crypto/hmac_sha256.go @@ -10,6 +10,7 @@ import ( "io" wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/mr-tron/base58" "golang.org/x/crypto/blake2b" ) @@ -81,10 +82,12 @@ func HmacSha256(ctx context.Context, data []byte, cipher wrapping.Wrapper, salt, hmac := mac.Sum(nil) var hmacString string - switch opts.withBase64Encoding { - case true: + switch { + case opts.withBase64Encoding: hmacString = base64.RawURLEncoding.EncodeToString(hmac) - case false: + case opts.withBase58Encoding: + hmacString = base58.Encode(hmac) + default: hmacString = string(hmac) } if opts.withPrefix != "" { diff --git a/internal/libs/crypto/hmac_sha256_test.go b/internal/libs/crypto/hmac_sha256_test.go index 41782c830..c21581f35 100644 --- a/internal/libs/crypto/hmac_sha256_test.go +++ b/internal/libs/crypto/hmac_sha256_test.go @@ -10,6 +10,7 @@ import ( "testing" wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/mr-tron/base58" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/crypto/blake2b" @@ -65,12 +66,19 @@ func Test_HmacSha256(t *testing.T) { wantHmac: testWithBlake2b(t, []byte("test"), testWrapper, nil, nil, WithPrefix("prefix:")), }, { - name: "blake2b-with-prefix-with-bas64", + name: "blake2b-with-prefix-with-base64", data: []byte("test"), wrapper: testWrapper, opts: []Option{WithPrefix("prefix:"), WithBase64Encoding()}, wantHmac: testWithBlake2b(t, []byte("test"), testWrapper, nil, nil, WithPrefix("prefix:"), WithBase64Encoding()), }, + { + name: "blake2b-with-prefix-with-base58", + data: []byte("test"), + wrapper: testWrapper, + opts: []Option{WithPrefix("prefix:"), WithBase58Encoding()}, + wantHmac: testWithBlake2b(t, []byte("test"), testWrapper, nil, nil, WithPrefix("prefix:"), WithBase58Encoding()), + }, { name: "with-prk", data: []byte("test"), @@ -155,10 +163,12 @@ func testHmac(t *testing.T, key, data []byte, opt ...Option) string { var hmacString string opts, err := getOpts(opt...) require.NoError(err) - switch opts.withBase64Encoding { - case true: + switch { + case opts.withBase64Encoding: hmacString = base64.RawURLEncoding.EncodeToString(hmac) - case false: + case opts.withBase58Encoding: + hmacString = base58.Encode(hmac) + default: hmacString = string(hmac) } if opts.withPrefix != "" { diff --git a/internal/libs/crypto/options.go b/internal/libs/crypto/options.go index e3a88e312..66e3bf51a 100644 --- a/internal/libs/crypto/options.go +++ b/internal/libs/crypto/options.go @@ -22,6 +22,7 @@ type options struct { withPrk []byte withEd25519 bool withBase64Encoding bool + withBase58Encoding bool } func getDefaultOptions() *options { @@ -61,3 +62,11 @@ func WithBase64Encoding() Option { return nil } } + +// WithBase58Encoding allows an optional request to base58 encode the data returned +func WithBase58Encoding() Option { + return func(o *options) error { + o.withBase58Encoding = true + return nil + } +} diff --git a/internal/libs/crypto/options_test.go b/internal/libs/crypto/options_test.go index f44b87eda..be4c61d44 100644 --- a/internal/libs/crypto/options_test.go +++ b/internal/libs/crypto/options_test.go @@ -42,4 +42,12 @@ func Test_GetOpts(t *testing.T) { testOpts.withBase64Encoding = true assert.Equal(opts, testOpts) }) + t.Run("WithBase58Encoding", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + opts, err := getOpts(WithBase58Encoding()) + require.NoError(err) + testOpts := getDefaultOptions() + testOpts.withBase58Encoding = true + assert.Equal(opts, testOpts) + }) } diff --git a/internal/proto/local/controller/api/resources/hostcatalogs/v1/host_catalog.proto b/internal/proto/local/controller/api/resources/hostcatalogs/v1/host_catalog.proto index c2c8f917b..603dd86d6 100644 --- a/internal/proto/local/controller/api/resources/hostcatalogs/v1/host_catalog.proto +++ b/internal/proto/local/controller/api/resources/hostcatalogs/v1/host_catalog.proto @@ -53,6 +53,9 @@ message HostCatalog { // Secrets specific to the catalog type. These are never output. google.protobuf.Struct secrets = 110 [(custom_options.v1.generate_sdk_option) = true]; + // Output only. The HMAC of the last secrets supplied via the API, if any. + string secrets_hmac = 120; + // Output only. The available actions on this resource for this user. repeated string authorized_actions = 300 [json_name="authorized_actions"]; diff --git a/internal/proto/local/controller/storage/host/plugin/store/v1/host.proto b/internal/proto/local/controller/storage/host/plugin/store/v1/host.proto index b965ab49e..f33ca81a4 100644 --- a/internal/proto/local/controller/storage/host/plugin/store/v1/host.proto +++ b/internal/proto/local/controller/storage/host/plugin/store/v1/host.proto @@ -42,9 +42,15 @@ message HostCatalog { // @inject_tag: `gorm:"default:null"` uint32 version = 8; + // secrets_hmac is a sha256-hmac of the unencrypted secrets that is returned + // from the API for read. It is recalculated everytime the raw secrets are + // updated. + // @inject_tag: `gorm:"default:null"` + bytes secrets_hmac = 9; + // attributes is a jsonb formatted field. // @inject_tag: `gorm:"not_null"` - bytes attributes = 9; + bytes attributes = 10; } message HostSet { @@ -184,4 +190,4 @@ message HostSetMember { // @inject_tag: `gorm:"default:null"` string catalog_id = 3; -} \ No newline at end of file +} diff --git a/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go b/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go index a301b1368..97e59b491 100644 --- a/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go +++ b/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go @@ -9,6 +9,7 @@ import ( pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" "github.com/hashicorp/boundary/internal/host" "github.com/hashicorp/boundary/internal/host/plugin" + pluginstore "github.com/hashicorp/boundary/internal/host/plugin/store" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/host/static/store" "github.com/hashicorp/boundary/internal/perms" @@ -26,6 +27,7 @@ import ( "github.com/hashicorp/boundary/internal/types/subtypes" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/plugins" + "github.com/mr-tron/base58" "google.golang.org/grpc/codes" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" @@ -33,7 +35,8 @@ import ( ) var ( - maskManager handlers.MaskManager + staticMaskManager handlers.MaskManager + pluginMaskManager handlers.MaskManager // IdActions contains the set of actions that can be performed on // individual resources @@ -67,7 +70,10 @@ var ( func init() { var err error - if maskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.HostCatalog{}}, handlers.MaskSource{&pb.HostCatalog{}}); err != nil { + if staticMaskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.HostCatalog{}}, handlers.MaskSource{&pb.HostCatalog{}}); err != nil { + panic(err) + } + if pluginMaskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&pluginstore.HostCatalog{}}, handlers.MaskSource{&pb.HostCatalog{}}); err != nil { panic(err) } } @@ -521,7 +527,7 @@ func (s Service) updateStaticInRepo(ctx context.Context, projId, id string, mask } version := item.GetVersion() h.PublicId = id - dbMask := maskManager.Translate(mask) + dbMask := staticMaskManager.Translate(mask) if len(dbMask) == 0 { return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."}) } @@ -547,7 +553,7 @@ func (s Service) updatePluginInRepo(ctx context.Context, projId, id string, mask } version := item.GetVersion() h.PublicId = id - dbMask := maskManager.Translate(mask, "attributes", "secrets") + dbMask := pluginMaskManager.Translate(mask, "attributes", "secrets") if len(dbMask) == 0 { return nil, nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."}) } @@ -693,12 +699,6 @@ func toProto(ctx context.Context, in host.Catalog, opt ...handlers.Option) (*pb. if outputFields.Has(globals.ScopeIdField) { out.ScopeId = in.GetScopeId() } - if outputFields.Has(globals.PluginIdField) { - switch hc := in.(type) { - case *plugin.HostCatalog: - out.PluginId = hc.GetPluginId() - } - } if outputFields.Has(globals.TypeField) { switch in.(type) { case *static.HostCatalog: @@ -734,9 +734,15 @@ func toProto(ctx context.Context, in host.Catalog, opt ...handlers.Option) (*pb. if outputFields.Has(globals.AuthorizedCollectionActionsField) { out.AuthorizedCollectionActions = opts.WithAuthorizedCollectionActions } - if outputFields.Has(globals.AttributesField) { - switch h := in.(type) { - case *plugin.HostCatalog: + switch h := in.(type) { + case *plugin.HostCatalog: + if outputFields.Has(globals.PluginIdField) { + out.PluginId = h.GetPluginId() + } + if outputFields.Has(globals.SecretsHmacField) { + out.SecretsHmac = base58.Encode(h.GetSecretsHmac()) + } + if outputFields.Has(globals.AttributesField) { attrs := &structpb.Struct{} err := proto.Unmarshal(h.Attributes, attrs) if err != nil { diff --git a/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go b/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go index 95863b6be..20e1f6d72 100644 --- a/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go +++ b/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go @@ -27,6 +27,7 @@ import ( "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/plugins" scopepb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/mr-tron/base58" "google.golang.org/genproto/protobuf/field_mask" "google.golang.org/grpc/codes" "google.golang.org/protobuf/proto" @@ -183,7 +184,7 @@ func TestGet_Plugin(t *testing.T) { } name := "test" plg := host.TestPlugin(t, conn, name) - hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) + hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId(), plugin.WithSecretsHmac([]byte("foobar"))) toMerge := &pbs.GetHostCatalogRequest{ Id: hc.GetPublicId(), @@ -208,6 +209,7 @@ func TestGet_Plugin(t *testing.T) { Type: plugin.Subtype.String(), AuthorizedActions: testAuthorizedActions, AuthorizedCollectionActions: authorizedCollectionActions[plugin.Subtype], + SecretsHmac: base58.Encode([]byte("foobar")), } cases := []struct { diff --git a/sdk/pbs/controller/api/resources/hostcatalogs/host_catalog.pb.go b/sdk/pbs/controller/api/resources/hostcatalogs/host_catalog.pb.go index 2b318fed2..4c66da56a 100644 --- a/sdk/pbs/controller/api/resources/hostcatalogs/host_catalog.pb.go +++ b/sdk/pbs/controller/api/resources/hostcatalogs/host_catalog.pb.go @@ -59,6 +59,8 @@ type HostCatalog struct { Attributes *structpb.Struct `protobuf:"bytes,100,opt,name=attributes,proto3" json:"attributes,omitempty"` // Secrets specific to the catalog type. These are never output. Secrets *structpb.Struct `protobuf:"bytes,110,opt,name=secrets,proto3" json:"secrets,omitempty"` + // Output only. The HMAC of the last secrets supplied via the API, if any. + SecretsHmac string `protobuf:"bytes,120,opt,name=secrets_hmac,json=secretsHmac,proto3" json:"secrets_hmac,omitempty"` // Output only. The available actions on this resource for this user. AuthorizedActions []string `protobuf:"bytes,300,rep,name=authorized_actions,proto3" json:"authorized_actions,omitempty"` // Output only. The authorized actions for the scope's collections. @@ -188,6 +190,13 @@ func (x *HostCatalog) GetSecrets() *structpb.Struct { return nil } +func (x *HostCatalog) GetSecretsHmac() string { + if x != nil { + return x.SecretsHmac + } + return "" +} + func (x *HostCatalog) GetAuthorizedActions() []string { if x != nil { return x.AuthorizedActions @@ -226,7 +235,7 @@ var file_controller_api_resources_hostcatalogs_v1_host_catalog_proto_rawDesc = [ 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfa, 0x07, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9d, 0x08, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, @@ -270,33 +279,35 @@ var file_controller_api_resources_hostcatalogs_v1_host_catalog_proto_rawDesc = [ 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x07, 0x73, 0x65, 0x63, - 0x72, 0x65, 0x74, 0x73, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, - 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x9d, 0x01, 0x0a, 0x1d, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb6, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x56, - 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, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, - 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, - 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, - 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x1d, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, - 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x6a, 0x0a, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x72, 0x65, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x5f, + 0x68, 0x6d, 0x61, 0x63, 0x18, 0x78, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x73, 0x48, 0x6d, 0x61, 0x63, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, + 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x9d, 0x01, 0x0a, 0x1d, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb6, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x56, 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, 0x68, 0x6f, 0x73, + 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, + 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x42, 0x5a, 0x5a, 0x58, 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, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, - 0x3b, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x1d, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x6a, 0x0a, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x42, 0x5a, 0x5a, 0x58, 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, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, + 0x6f, 0x67, 0x73, 0x3b, 0x68, 0x6f, 0x73, 0x74, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var (