feat: static json credentials (#2423)

This commit is contained in:
Damian Debkowski authored and GitHub committed 2022-09-16 15:00:04 -07:00
1 parent 8c53d931aa
commit 546c5dc5be
42 files changed
+3207 -165

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
// Code generated by "make api"; DO NOT EDIT.
package credentials
import (
"fmt"
"github.com/mitchellh/mapstructure"
)
type JsonAttributes struct {
Object map[string]interface{} `json:"object,omitempty"`
ObjectHmac string `json:"object_hmac,omitempty"`
}
func AttributesMapToJsonAttributes(in map[string]interface{}) (*JsonAttributes, error) {
if in == nil {
return nil, fmt.Errorf("nil input map")
}
var out JsonAttributes
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: &out,
TagName: "json",
})
if err != nil {
return nil, fmt.Errorf("error creating mapstructure decoder: %w", err)
}
if err := dec.Decode(in); err != nil {
return nil, fmt.Errorf("error decoding: %w", err)
}
return &out, nil
}
func (pt *Credential) GetJsonAttributes() (*JsonAttributes, error) {
if pt.Type != "json" {
return nil, fmt.Errorf("asked to fetch %s-type attributes but credential is of type %s", "json", pt.Type)
}
return AttributesMapToJsonAttributes(pt.Attributes)
}
+12
View File
@@ -110,6 +110,18 @@ func DefaultName() Option {
}
}
func WithJsonCredentialObject(inObject map[string]interface{}) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
val["object"] = inObject
o.postMap["attributes"] = val
}
}
func WithUsernamePasswordCredentialPassword(inPassword string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
+15
View File
@@ -549,6 +549,21 @@ var inputStructs = []*structInfo{
mapstructureConversionTemplate,
},
},
{
inProto: &credentials.JsonAttributes{},
outFile: "credentials/json_attributes.gen.go",
subtypeName: "JsonCredential",
fieldOverrides: []fieldInfo{
{
Name: "Object",
SkipDefault: true,
},
},
parentTypeName: "Credential",
templates: []*template.Template{
mapstructureConversionTemplate,
},
},
{
inProto: &credentials.Credential{},
outFile: "credentials/credential.gen.go",
+4
View File
@@ -123,6 +123,10 @@ type Command struct {
FlagSecrets string
FlagScrts []CombinedSliceFlagValue
// Object values
FlagObject string
FlagKv []CombinedSliceFlagValue
client *api.Client
// This will be intialized, if needed, in Config() when instantiating a
+12
View File
@@ -478,6 +478,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
Func: "create",
}, nil
},
"credentials create json": func() (cli.Command, error) {
return &credentialscmd.JsonCommand{
Command: base.NewCommand(ui),
Func: "create",
}, nil
},
"credentials update": func() (cli.Command, error) {
return &credentialscmd.Command{
Command: base.NewCommand(ui),
@@ -496,6 +502,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
Func: "update",
}, nil
},
"credentials update json": func() (cli.Command, error) {
return &credentialscmd.JsonCommand{
Command: base.NewCommand(ui),
Func: "update",
}, nil
},
"groups": func() (cli.Command, error) {
return &groupscmd.Command{
@@ -78,6 +78,24 @@ var (
},
}
staticKv = &targets.SessionCredential{
CredentialSource: &targets.CredentialSource{
Type: "static",
CredentialType: "json",
CredentialStoreId: "csst_id",
Description: "test",
Name: "test unspecified json cred",
Id: "credjson_id",
},
Secret: &targets.SessionSecret{
Decoded: map[string]interface{}{
"secret": map[string]interface{}{
"username": "password",
},
},
},
}
unspecifiedCred = &targets.SessionCredential{
CredentialSource: &targets.CredentialSource{
Type: "static",
@@ -236,11 +254,24 @@ func Test_parseCredentials(t *testing.T) {
},
wantErr: false,
},
{
name: "unspecified-static-json",
creds: []*targets.SessionCredential{
staticKv,
},
wantCreds: credentials{
unspecified: []*targets.SessionCredential{
staticKv,
},
},
wantErr: false,
},
{
name: "mixed",
creds: []*targets.SessionCredential{
staticSshPrivateKey, unspecifiedCred1, vaultSshPrivateKey, typedUsernamePassword,
unspecifiedCred, vaultUsernamePassword, typedSshPrivateKey, staticUsernamePassword,
staticKv,
},
wantCreds: credentials{
sshPrivateKey: []sshPrivateKey{
@@ -278,7 +309,7 @@ func Test_parseCredentials(t *testing.T) {
},
},
unspecified: []*targets.SessionCredential{
unspecifiedCred, unspecifiedCred1,
unspecifiedCred, unspecifiedCred1, staticKv,
},
},
wantErr: false,
@@ -14,6 +14,7 @@ const (
passwordFlagName = "password"
privateKeyFlagName = "private-key"
privateKeyPassphraseFlagName = "private-key-passphrase"
secretFlagName = "secret"
)
func (c *Command) extraHelpFunc(helpMap map[string]func() string) string {
@@ -0,0 +1,294 @@
// Code generated by "make cli"; DO NOT EDIT.
package credentialscmd
import (
"errors"
"fmt"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/credentials"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/cmd/common"
"github.com/hashicorp/go-secure-stdlib/strutil"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
func initJsonFlags() {
flagsOnce.Do(func() {
extraFlags := extraJsonActionsFlagsMapFunc()
for k, v := range extraFlags {
flagsJsonMap[k] = append(flagsJsonMap[k], v...)
}
})
}
var (
_ cli.Command = (*JsonCommand)(nil)
_ cli.CommandAutocomplete = (*JsonCommand)(nil)
)
type JsonCommand struct {
*base.Command
Func string
plural string
}
func (c *JsonCommand) AutocompleteArgs() complete.Predictor {
initJsonFlags()
return complete.PredictAnything
}
func (c *JsonCommand) AutocompleteFlags() complete.Flags {
initJsonFlags()
return c.Flags().Completions()
}
func (c *JsonCommand) Synopsis() string {
if extra := extraJsonSynopsisFunc(c); extra != "" {
return extra
}
synopsisStr := "credential"
synopsisStr = fmt.Sprintf("%s %s", "json-type", synopsisStr)
return common.SynopsisFunc(c.Func, synopsisStr)
}
func (c *JsonCommand) Help() string {
initJsonFlags()
var helpStr string
helpMap := common.HelpMap("credential")
switch c.Func {
default:
helpStr = c.extraJsonHelpFunc(helpMap)
}
// Keep linter from complaining if we don't actually generate code using it
_ = helpMap
return helpStr
}
var flagsJsonMap = map[string][]string{
"create": {"credential-store-id", "name", "description", "object", "kv", "string-kv", "bool-kv", "num-kv"},
"update": {"id", "name", "description", "version", "object", "kv", "string-kv", "bool-kv", "num-kv"},
}
func (c *JsonCommand) Flags() *base.FlagSets {
if len(flagsJsonMap[c.Func]) == 0 {
return c.FlagSet(base.FlagSetNone)
}
set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat)
f := set.NewFlagSet("Command Options")
common.PopulateCommonFlags(c.Command, f, "json-type credential", flagsJsonMap, c.Func)
f = set.NewFlagSet("Object Options")
common.PopulateObjectFlags(c.Command, f, flagsJsonMap, c.Func)
extraJsonFlagsFunc(c, set, f)
return set
}
func (c *JsonCommand) Run(args []string) int {
initJsonFlags()
switch c.Func {
case "":
return cli.RunResultHelp
}
c.plural = "json-type credential"
switch c.Func {
case "list":
c.plural = "json-type credentials"
}
f := c.Flags()
if err := f.Parse(args); err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
if strutil.StrListContains(flagsJsonMap[c.Func], "id") && c.FlagId == "" {
c.PrintCliError(errors.New("ID is required but not passed in via -id"))
return base.CommandUserError
}
if c.FlagObject != "" && len(c.FlagKv) > 0 {
c.PrintCliError(errors.New("-object flag cannot be used along with the following flags: kv, bool-kv, num-kv, string-kv"))
return base.CommandUserError
}
var opts []credentials.Option
if strutil.StrListContains(flagsJsonMap[c.Func], "credential-store-id") {
switch c.Func {
case "create":
if c.FlagCredentialStoreId == "" {
c.PrintCliError(errors.New("CredentialStore ID must be passed in via -credential-store-id or BOUNDARY_CREDENTIAL_STORE_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
}
credentialsClient := credentials.NewClient(client)
switch c.FlagName {
case "":
case "null":
opts = append(opts, credentials.DefaultName())
default:
opts = append(opts, credentials.WithName(c.FlagName))
}
switch c.FlagDescription {
case "":
case "null":
opts = append(opts, credentials.DefaultDescription())
default:
opts = append(opts, credentials.WithDescription(c.FlagDescription))
}
if c.FlagFilter != "" {
opts = append(opts, credentials.WithFilter(c.FlagFilter))
}
var version uint32
switch c.Func {
case "update":
switch c.FlagVersion {
case 0:
opts = append(opts, credentials.WithAutomaticVersioning(true))
default:
version = uint32(c.FlagVersion)
}
}
if err := common.HandleAttributeFlags(
c.Command,
"kv",
c.FlagObject,
c.FlagKv,
func() {},
func(in map[string]interface{}) {
opts = append(opts, credentials.WithJsonCredentialObject(in))
}); err != nil {
c.PrintCliError(fmt.Errorf("Error evaluating kv flags to: %s", err.Error()))
return base.CommandCliError
}
if ok := extraJsonFlagsHandlingFunc(c, f, &opts); !ok {
return base.CommandUserError
}
var resp *api.Response
var item *credentials.Credential
var createResult *credentials.CredentialCreateResult
var updateResult *credentials.CredentialUpdateResult
switch c.Func {
case "create":
createResult, err = credentialsClient.Create(c.Context, "json", c.FlagCredentialStoreId, opts...)
if exitCode := c.checkFuncError(err); exitCode > 0 {
return exitCode
}
resp = createResult.GetResponse()
item = createResult.GetItem()
case "update":
updateResult, err = credentialsClient.Update(c.Context, c.FlagId, version, opts...)
if exitCode := c.checkFuncError(err); exitCode > 0 {
return exitCode
}
resp = updateResult.GetResponse()
item = updateResult.GetItem()
}
resp, item, err = executeExtraJsonActions(c, resp, item, err, credentialsClient, version, opts)
if exitCode := c.checkFuncError(err); exitCode > 0 {
return exitCode
}
output, err := printCustomJsonActionOutput(c)
if err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
if output {
return base.CommandSuccess
}
switch c.Func {
}
switch base.Format(c.UI) {
case "table":
c.UI.Output(printItemTable(item, resp))
case "json":
if ok := c.PrintJsonItem(resp); !ok {
return base.CommandCliError
}
}
return base.CommandSuccess
}
func (c *JsonCommand) checkFuncError(err error) int {
if err == nil {
return 0
}
if apiErr := api.AsServerError(err); apiErr != nil {
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural))
return base.CommandApiError
}
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
return base.CommandCliError
}
var (
extraJsonActionsFlagsMapFunc = func() map[string][]string { return nil }
extraJsonSynopsisFunc = func(*JsonCommand) string { return "" }
extraJsonFlagsFunc = func(*JsonCommand, *base.FlagSets, *base.FlagSet) {}
extraJsonFlagsHandlingFunc = func(*JsonCommand, *base.FlagSets, *[]credentials.Option) bool { return true }
executeExtraJsonActions = func(_ *JsonCommand, inResp *api.Response, inItem *credentials.Credential, inErr error, _ *credentials.Client, _ uint32, _ []credentials.Option) (*api.Response, *credentials.Credential, error) {
return inResp, inItem, inErr
}
printCustomJsonActionOutput = func(*JsonCommand) (bool, error) { return false, nil }
)
@@ -0,0 +1,33 @@
package credentialscmd
import (
"github.com/hashicorp/boundary/internal/cmd/base"
)
func (c *JsonCommand) extraJsonHelpFunc(_ map[string]func() string) string {
var helpStr string
switch c.Func {
case "create":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary credentials create json -credential-store-id [options] [args]",
"",
" Create a json credential. Example:",
"",
` $ boundary credentials create json -credential-store-id csst_1234567890 -object file:///home/user/secret`,
"",
"",
})
case "update":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary credentials update json [options] [args]",
"",
" Update a json credential given its ID. Example:",
"",
` $ boundary credentials update json -id csst_1234567890 -name devops -description "For DevOps usage"`,
"",
"",
})
}
return helpStr + c.Flags().Help()
}
@@ -131,6 +131,16 @@ func (c *PluginCommand) Run(args []string) int {
return base.CommandUserError
}
if c.FlagAttributes != "" && len(c.FlagAttrs) > 0 {
c.PrintCliError(errors.New("-attributes flag cannot be used along with the following flags: attr, bool-attr, num-attr, string-attr"))
return base.CommandUserError
}
if c.FlagSecrets != "" && len(c.FlagScrts) > 0 {
c.PrintCliError(errors.New("-secrets flag cannot be used along with the following flags: secret, bool-secret, num-secret, string-secret"))
return base.CommandUserError
}
var opts []hostcatalogs.Option
if strutil.StrListContains(flagsPluginMap[c.Func], "scope-id") {
@@ -130,6 +130,11 @@ func (c *PluginCommand) Run(args []string) int {
return base.CommandUserError
}
if c.FlagAttributes != "" && len(c.FlagAttrs) > 0 {
c.PrintCliError(errors.New("-attributes flag cannot be used along with the following flags: attr, bool-attr, num-attr, string-attr"))
return base.CommandUserError
}
var opts []hostsets.Option
if strutil.StrListContains(flagsPluginMap[c.Func], "host-catalog-id") {
+52 -2
View File
@@ -124,7 +124,7 @@ func PopulateAttributeFlags(c *base.Command, f *base.FlagSet, flagNames map[stri
f.StringVar(&base.StringVar{
Name: "attributes",
Target: &c.FlagAttributes,
Usage: `A JSON map value to use as the entirety of the request's attributes map. Usually this will be sourced from a file via "file://" syntax.`,
Usage: `A JSON map value to use as the entirety of the request's attributes map. Usually this will be sourced from a file via "file://" syntax. Is exclusive with the other attr flags.`,
})
case "attr":
f.CombinationSliceVar(&base.CombinationSliceVar{
@@ -174,7 +174,7 @@ func PopulateSecretFlags(c *base.Command, f *base.FlagSet, flagNames map[string]
f.StringVar(&base.StringVar{
Name: "secrets",
Target: &c.FlagSecrets,
Usage: `A JSON map value to use as the entirety of the request's secrets map. Usually this will be sourced from a file via "file://" syntax.`,
Usage: `A JSON map value to use as the entirety of the request's secrets map. Usually this will be sourced from a file via "file://" syntax. Is exclusive with the other secret flags.`,
})
case "secret":
f.CombinationSliceVar(&base.CombinationSliceVar{
@@ -216,6 +216,56 @@ func PopulateSecretFlags(c *base.Command, f *base.FlagSet, flagNames map[string]
}
}
func PopulateObjectFlags(c *base.Command, f *base.FlagSet, flagNames map[string][]string, command string) {
keyDelimiter := "."
for _, name := range flagNames[command] {
switch name {
case "object":
f.StringVar(&base.StringVar{
Name: "object",
Target: &c.FlagObject,
Usage: `A JSON map value to use as the entirety of the request's object map. Usually this will be sourced from a file via "file://" syntax. Is exclusive with the other kv flags.`,
})
case "kv":
f.CombinationSliceVar(&base.CombinationSliceVar{
Name: "kv",
Target: &c.FlagKv,
KvSplit: true,
KeyDelimiter: &keyDelimiter,
ProtoCompatKey: true,
Usage: `A key=value pair to add to the request's object map. The type is automatically inferred. Use -string-kv, -bool-kv, or -num-kv if the type needs to be overridden. Can be specified multiple times. Supports sourcing values from files via "file://" and env vars via "env://"`,
})
case "string-kv":
f.CombinationSliceVar(&base.CombinationSliceVar{
Name: "string-kv",
Target: &c.FlagKv,
KvSplit: true,
KeyDelimiter: &keyDelimiter,
ProtoCompatKey: true,
Usage: `A key=value string value to add to the request's object map. Can be specified multiple times. Supports sourcing values from files via "file://" and env vars via "env://"`,
})
case "bool-kv":
f.CombinationSliceVar(&base.CombinationSliceVar{
Name: "bool-kv",
Target: &c.FlagKv,
KvSplit: true,
KeyDelimiter: &keyDelimiter,
ProtoCompatKey: true,
Usage: `A key=value bool value to add to the request's object map. Can be specified multiple times. Supports sourcing values from files via "file://" and env vars via "env://"`,
})
case "num-kv":
f.CombinationSliceVar(&base.CombinationSliceVar{
Name: "num-kv",
Target: &c.FlagKv,
KvSplit: true,
KeyDelimiter: &keyDelimiter,
ProtoCompatKey: true,
Usage: `A key=value numeric value to add to the request's object map. Can be specified multiple times. Supports sourcing values from files via "file://" and env vars via "env://"`,
})
}
}
}
// From https://stackoverflow.com/a/13340826, modified to remove exponents
var jsonNumberRegex = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?$`)
+20
View File
@@ -73,6 +73,10 @@ type cmdInfo struct {
// -secret, -string-secret, etc.
HasGenericSecrets bool
// HasJsonObject controls whether to generate flags for -object,
// -kv, -string-kv, -bool-kv, -num-kv.
HasJsonObject bool
// IsPluginType controls whether standard plugin flags are generated
IsPluginType bool
@@ -279,6 +283,22 @@ var inputStructs = map[string][]*cmdInfo{
NeedsSubtypeInCreate: true,
PrefixAttributeFieldErrorsWithSubactionPrefix: true,
},
{
ResourceType: resource.Credential.String(),
Pkg: "credentials",
StdActions: []string{"create", "update"},
SubActionPrefix: "json",
SkipNormalHelp: true,
HasExtraHelpFunc: true,
HasId: true,
HasName: true,
HasDescription: true,
Container: "CredentialStore",
VersionedActions: []string{"update"},
NeedsSubtypeInCreate: true,
PrefixAttributeFieldErrorsWithSubactionPrefix: true,
HasJsonObject: true,
},
},
"groups": {
{
+45 -2
View File
@@ -171,15 +171,16 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Help() string {
var flags{{ camelCase .SubActionPrefix }}Map = map[string][]string{
{{ with $attrFlags := ", \"attributes\", \"attr\", \"string-attr\", \"bool-attr\", \"num-attr\"" }}
{{ with $secretFlags := ", \"secrets\", \"secret\", \"string-secret\", \"bool-secret\", \"num-secret\"" }}
{{ with $objectFlags := ", \"object\", \"kv\", \"string-kv\", \"bool-kv\", \"num-kv\"" }}
{{ range $i, $action := $input.StdActions }}
{{ if eq $action "create" }}
"create": { "{{ kebabCase $input.Container }}-id", "name", "description" {{ if $input.IsPluginType }} , "plugin-id", "plugin-name" {{ end }} {{ if $input.HasGenericAttributes }} {{ $attrFlags }} {{ end }} {{ if $input.HasGenericSecrets }} {{ $secretFlags }} {{ end }} },
"create": { "{{ kebabCase $input.Container }}-id", "name", "description" {{ if $input.IsPluginType }} , "plugin-id", "plugin-name" {{ end }} {{ if $input.HasGenericAttributes }} {{ $attrFlags }} {{ end }} {{ if $input.HasGenericSecrets }} {{ $secretFlags }} {{ end }} {{ if $input.HasJsonObject }} {{ $objectFlags }} {{ end }} },
{{ end }}
{{ if eq $action "read" }}
"read": {"id"},
{{ end }}
{{ if eq $action "update" }}
"update": {"id", "name", "description" {{ if hasAction $input.VersionedActions "update" }}, "version" {{ end }} {{ if $input.HasGenericAttributes }} {{ $attrFlags }} {{ end }} {{ if $input.HasGenericSecrets }} {{ $secretFlags }} {{ end }} },
"update": {"id", "name", "description" {{ if hasAction $input.VersionedActions "update" }}, "version" {{ end }} {{ if $input.HasGenericAttributes }} {{ $attrFlags }} {{ end }} {{ if $input.HasGenericSecrets }} {{ $secretFlags }} {{ end }} {{ if $input.HasJsonObject }} {{ $objectFlags }} {{ end }} },
{{ end }}
{{ if eq $action "delete" }}
"delete": {"id"},
@@ -190,6 +191,7 @@ var flags{{ camelCase .SubActionPrefix }}Map = map[string][]string{
{{ end }}
{{ end }}
{{ end }}
{{ end }}
}
func (c *{{ camelCase .SubActionPrefix }}Command) Flags() *base.FlagSets {
@@ -211,6 +213,11 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Flags() *base.FlagSets {
common.PopulateSecretFlags(c.Command, f, flags{{ camelCase .SubActionPrefix }}Map, c.Func)
{{ end }}
{{ if .HasJsonObject }}
f = set.NewFlagSet("Object Options")
common.PopulateObjectFlags(c.Command, f, flags{{ camelCase .SubActionPrefix }}Map, c.Func)
{{ end }}
extra{{ camelCase .SubActionPrefix }}FlagsFunc(c, set, f)
return set
@@ -259,6 +266,27 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int {
}
{{ end }}
{{ if .HasGenericAttributes }}
if c.FlagAttributes != "" && len(c.FlagAttrs) > 0 {
c.PrintCliError(errors.New("-attributes flag cannot be used along with the following flags: attr, bool-attr, num-attr, string-attr"))
return base.CommandUserError
}
{{ end }}
{{ if .HasGenericSecrets }}
if c.FlagSecrets != "" && len(c.FlagScrts) > 0 {
c.PrintCliError(errors.New("-secrets flag cannot be used along with the following flags: secret, bool-secret, num-secret, string-secret"))
return base.CommandUserError
}
{{ end }}
{{ if .HasJsonObject }}
if c.FlagObject != "" && len(c.FlagKv) > 0 {
c.PrintCliError(errors.New("-object flag cannot be used along with the following flags: kv, bool-kv, num-kv, string-kv"))
return base.CommandUserError
}
{{ end }}
var opts []{{ .Pkg }}.Option
if strutil.StrListContains(flags{{ camelCase .SubActionPrefix }}Map[c.Func], "{{ kebabCase .Container }}-id") {
@@ -395,6 +423,21 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int {
}
{{ end }}
{{ if .HasJsonObject }}
if err := common.HandleAttributeFlags(
c.Command,
"kv",
c.FlagObject,
c.FlagKv,
func() {},
func(in map[string]interface{}) {
opts = append(opts, {{ .Pkg }}.WithJsonCredentialObject(in))
}); err != nil {
c.PrintCliError(fmt.Errorf("Error evaluating kv flags to: %s", err.Error()))
return base.CommandCliError
}
{{ end }}
if ok := extra{{ camelCase .SubActionPrefix }}FlagsHandlingFunc(c, f, &opts, ); !ok {
return base.CommandUserError
}
+7
View File
@@ -6,6 +6,7 @@ import (
"context"
"github.com/hashicorp/boundary/internal/boundary"
"google.golang.org/protobuf/types/known/structpb"
)
// Domain defines the domain for the credential package.
@@ -29,6 +30,7 @@ const (
UnspecifiedType Type = "unspecified"
UsernamePasswordType Type = "username_password"
SshPrivateKeyType Type = "ssh_private_key"
JsonType Type = "json"
)
// A Library is a resource that provides credentials that are of the same
@@ -119,6 +121,11 @@ type Password string
// PrivateKey represents a secret private key.
type PrivateKey []byte
// JsonObject represents a JSON object that is serialized.
type JsonObject struct {
structpb.Struct
}
// UsernamePassword is a credential containing a username and a password.
type UsernamePassword interface {
Credential
+14
View File
@@ -15,6 +15,9 @@ func init() {
if err := subtypes.Register(Domain, SshPrivateKeySubtype, SshPrivateKeyCredentialPrefix); err != nil {
panic(err)
}
if err := subtypes.Register(Domain, JsonSubtype, JsonCredentialPrefix); err != nil {
panic(err)
}
}
const (
@@ -24,6 +27,9 @@ const (
SshPrivateKeyCredentialPrefix = "credspk"
SshPrivateKeySubtype = subtypes.Subtype("ssh_private_key")
JsonCredentialPrefix = "credjson"
JsonSubtype = subtypes.Subtype("json")
)
func NewUsernamePasswordCredentialId(ctx context.Context) (string, error) {
@@ -41,3 +47,11 @@ func NewSshPrivateKeyCredentialId(ctx context.Context) (string, error) {
}
return id, nil
}
func NewJsonCredentialId(ctx context.Context) (string, error) {
id, err := db.NewPublicId(JsonCredentialPrefix)
if err != nil {
return "", errors.Wrap(ctx, err, "credential.NewJsonCredentialId")
}
return id, nil
}
+16
View File
@@ -5,6 +5,7 @@ import "encoding/json"
const (
redactedPassword = "[REDACTED: password]"
redactedPrivateKey = "[REDACTED: private key]"
redactedJson = "[REDACTED: json]"
)
// String returns a string with the password redacted.
@@ -37,3 +38,18 @@ func (s PrivateKey) GoString() string {
func (s PrivateKey) MarshalJSON() ([]byte, error) {
return json.Marshal([]byte(redactedPrivateKey))
}
// String returns a string with the json secret redacted.
func (s *JsonObject) String() string {
return redactedJson
}
// GoString returns a string with the json secret redacted.
func (s *JsonObject) GoString() string {
return redactedJson
}
// MarshalJSON returns a JSON-encoded byte slice with the json redacted.
func (s *JsonObject) MarshalJSON() ([]byte, error) {
return json.Marshal([]byte(redactedJson))
}
+55
View File
@@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"
)
func TestPassword_String(t *testing.T) {
@@ -130,3 +131,57 @@ func TestPrivateKey_MarshalJSON(t *testing.T) {
assert.Equal(testB, sec.B)
})
}
func TestJsonObject_String(t *testing.T) {
t.Parallel()
t.Run("redacted", func(t *testing.T) {
assert := assert.New(t)
const want = redactedJson
json := &JsonObject{}
assert.Equalf(want, json.String(), "JsonObject.String() = %v, want %v", json.String(), want)
// Verify stringer is called
s := fmt.Sprintf("%s", json)
assert.Equalf(want, s, "JsonObject.String() = %v, want %v", s, want)
})
}
func TestJsonObject_GoString(t *testing.T) {
t.Parallel()
t.Run("redacted", func(t *testing.T) {
assert := assert.New(t)
const want = redactedJson
json := &JsonObject{}
assert.Equalf(want, json.GoString(), "JsonObject.GoString() = %v, want %v", json.GoString(), want)
// Verify gostringer is called
s := fmt.Sprintf("%#v", json)
assert.Equalf(want, s, "JsonObject.GoString() = %v, want %v", s, want)
})
}
func TestJsonObject_MarshalJSON(t *testing.T) {
t.Parallel()
t.Run("redacted", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
want, err := json.Marshal([]byte(redactedJson))
require.NoError(err)
json := &JsonObject{}
got, err := json.MarshalJSON()
require.NoError(err)
assert.Equalf(want, got, "JsonObject.MarshalJSON() = %s, want %s", got, want)
})
t.Run("within-struct", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
secret := JsonObject{
structpb.Struct{
Fields: map[string]*structpb.Value{
"secret": structpb.NewStringValue("password"),
},
},
}
bSecret, err := secret.MarshalJSON()
require.NoError(err)
assert.Equal("\"W1JFREFDVEVEOiBqc29uXQ==\"", string(bSecret)) // The marshaled value is in base64
})
}
+1
View File
@@ -8,4 +8,5 @@ const (
passwordField = "Password"
privateKeyField = "PrivateKey"
PrivateKeyPassphraseField = "PrivateKeyPassphrase"
objectField = "Object"
)
@@ -0,0 +1,138 @@
package static
import (
"context"
"encoding/json"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/static/store"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/libs/crypto"
"github.com/hashicorp/boundary/internal/oplog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
var _ credential.Static = (*JsonCredential)(nil)
// A JsonCredential contains the credential with a json secret.
// It is owned by a credential store.
type JsonCredential struct {
*store.JsonCredential
tableName string `gorm:"-"`
}
// NewJsonCredential creates a new in memory static Credential containing a
// json secret that is assigned to storeId. Name and description are the only
// valid options. All other options are ignored.
func NewJsonCredential(
ctx context.Context,
storeId string,
object credential.JsonObject,
opt ...Option,
) (*JsonCredential, error) {
const op = "static.NewJsonCredential"
var objectB []byte
var err error
// Since the secret is an unordered map of dynamically typed values, the hmac value will not be consistent.
// In order to calculate a consistent hmac value, the input must be deterministic,
// which is done by marshalling the secret.
if len(object.AsMap()) > 0 {
objectB, err = json.Marshal(object.AsMap())
if err != nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "invalid secret")
}
}
opts := getOpts(opt...)
jsonCred := &JsonCredential{
JsonCredential: &store.JsonCredential{
StoreId: storeId,
Name: opts.withName,
Description: opts.withDescription,
Object: objectB,
},
}
return jsonCred, nil
}
func allocJsonCredential() *JsonCredential {
return &JsonCredential{
JsonCredential: &store.JsonCredential{},
}
}
func (c *JsonCredential) clone() *JsonCredential {
cp := proto.Clone(c.JsonCredential)
return &JsonCredential{
JsonCredential: cp.(*store.JsonCredential),
}
}
// TableName returns the table name.
func (c *JsonCredential) TableName() string {
if c.tableName != "" {
return c.tableName
}
return "credential_static_json_credential"
}
// SetTableName sets the table name.
func (c *JsonCredential) SetTableName(n string) {
c.tableName = n
}
func (c *JsonCredential) oplog(op oplog.OpType) oplog.Metadata {
metadata := oplog.Metadata{
"resource-public-id": []string{c.PublicId},
"resource-type": []string{"credential-static-json"},
"op-type": []string{op.String()},
}
if c.StoreId != "" {
metadata["store-id"] = []string{c.StoreId}
}
return metadata
}
func (c *JsonCredential) encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "static.(JsonCredential).encrypt"
if len(c.Object) == 0 {
return errors.New(ctx, errors.InvalidParameter, op, "no object defined")
}
if err := structwrapping.WrapStruct(ctx, cipher, c.JsonCredential, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
}
keyId, err := cipher.KeyId(ctx)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("error reading cipher key id"))
}
c.KeyId = keyId
if err := c.hmacObject(ctx, cipher); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
}
func (c *JsonCredential) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "static.(JsonCredential).decrypt"
if err := structwrapping.UnwrapStruct(ctx, cipher, c.JsonCredential, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
func (c *JsonCredential) hmacObject(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "static.(JsonCredential).hmacObject"
if cipher == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
}
hm, err := crypto.HmacSha256(ctx, c.Object, cipher, []byte(c.StoreId), nil)
if err != nil {
return errors.Wrap(ctx, err, op)
}
c.ObjectHmac = []byte(hm)
return nil
}
@@ -0,0 +1,162 @@
package static
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"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/boundary/internal/libs/crypto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/testing/protocmp"
)
func TestJsonCredential_New(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kkms := kms.TestKms(t, conn, wrapper)
rw := db.New(conn)
obj, objBytes, err := TestJsonObject()
assert.NoError(t, err)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStore(t, conn, wrapper, prj.PublicId)
type args struct {
object credential.JsonObject
storeId string
options []Option
}
tests := []struct {
name string
args args
want *JsonCredential
wantCreateErr bool
wantEncryptErr bool
wantAllocError bool
}{
{
name: "missing-store-id",
args: args{
object: obj,
},
want: allocJsonCredential(),
wantCreateErr: true,
},
{
name: "valid-no-options",
args: args{
object: obj,
storeId: cs.PublicId,
},
want: &JsonCredential{
JsonCredential: &store.JsonCredential{
Object: objBytes,
StoreId: cs.PublicId,
},
},
},
{
name: "valid-with-name",
args: args{
object: obj,
storeId: cs.PublicId,
options: []Option{WithName("json-credential")},
},
want: &JsonCredential{
JsonCredential: &store.JsonCredential{
Object: objBytes,
StoreId: cs.PublicId,
Name: "json-credential",
},
},
},
{
name: "valid-with-description",
args: args{
object: obj,
storeId: cs.PublicId,
options: []Option{WithDescription("key-value secrets")},
},
want: &JsonCredential{
JsonCredential: &store.JsonCredential{
Object: objBytes,
StoreId: cs.PublicId,
Description: "key-value secrets",
},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
got, err := NewJsonCredential(ctx, tt.args.storeId, tt.args.object, tt.args.options...)
if tt.wantAllocError {
assert.Error(err)
return
}
require.NoError(err)
require.NotNil(got)
assert.Emptyf(got.PublicId, "PublicId set")
id, err := credential.NewJsonCredentialId(ctx)
require.NoError(err)
tt.want.PublicId = id
got.PublicId = id
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase)
require.NoError(err)
err = got.encrypt(ctx, databaseWrapper)
if tt.wantEncryptErr {
require.Error(err)
return
}
assert.NoError(err)
err = rw.Create(context.Background(), got)
if tt.wantCreateErr {
require.Error(err)
return
}
assert.NoError(err)
got2 := allocJsonCredential()
got2.PublicId = id
assert.Equal(id, got2.GetPublicId())
require.NoError(rw.LookupById(ctx, got2))
err = got2.decrypt(ctx, databaseWrapper)
require.NoError(err)
// Timestamps and version are automatically set
tt.want.CreateTime = got2.CreateTime
tt.want.UpdateTime = got2.UpdateTime
tt.want.Version = got2.Version
// KeyId is allocated via kms no need to validate in this test
tt.want.KeyId = got2.KeyId
got2.ObjectEncrypted = nil
// encrypt also calculates the hmac, validate it is correct
hm, err := crypto.HmacSha256(ctx, got.Object, databaseWrapper, []byte(got.StoreId), nil)
require.NoError(err)
tt.want.ObjectHmac = []byte(hm)
assert.Empty(cmp.Diff(tt.want, got2.clone(), protocmp.Transform()))
})
}
}
@@ -192,6 +192,89 @@ func (r *Repository) CreateSshPrivateKeyCredential(
return newCred, nil
}
// CreateJsonCredential inserts c into the repository and returns a new
// JsonCredential containing the credential's PublicId. c is not
// changed. c must not contain a PublicId. The PublicId is generated and
// assigned by this method. c must contain a valid StoreId.
//
// The object is encrypted and a HmacSha256 of the object is
// calculated. Only the ObjectHmac is returned, the plain-text and encrypted
// object is not returned.
//
// Both c.Name and c.Description are optional. If c.Name is set, it must be
// unique within c.ProjectId. Both c.CreateTime and c.UpdateTime are ignored.
func (r *Repository) CreateJsonCredential(
ctx context.Context,
projectId string,
c *JsonCredential,
_ ...Option,
) (*JsonCredential, error) {
const op = "static.(Repository).CreateJsonCredential"
if c == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing credential")
}
if c.JsonCredential == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing embedded credential")
}
if projectId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing project id")
}
if c.Object == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing object")
}
if c.StoreId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing store id")
}
if c.PublicId != "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty")
}
c = c.clone()
id, err := credential.NewJsonCredentialId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
c.PublicId = id
oplogWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeOplog)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
}
// encrypt
databaseWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeDatabase)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper"))
}
if err := c.encrypt(ctx, databaseWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var newCred *JsonCredential
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
newCred = c.clone()
if err := w.Create(ctx, newCred,
db.WithOplog(oplogWrapper, newCred.oplog(oplog.OpType_OP_TYPE_CREATE))); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
if errors.IsUniqueError(err) {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in store: %s: name %s already exists", c.StoreId, c.Name)))
}
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in store: %s", c.StoreId)))
}
// Clear object fields, only ObjectHmac should be returned
newCred.ObjectEncrypted = nil
newCred.Object = nil
return newCred, nil
}
// LookupCredential returns the Credential for the publicId. Returns
// nil, nil if no Credential is found for the publicId.
// TODO: This should hit a view and return the interface type...
@@ -234,6 +317,20 @@ func (r *Repository) LookupCredential(ctx context.Context, publicId string, _ ..
spkCred.PrivateKeyPassphraseEncrypted = nil
spkCred.PrivateKeyPassphrase = nil
cred = spkCred
case credential.JsonSubtype:
jsonCred := allocJsonCredential()
jsonCred.PublicId = publicId
if err := r.reader.LookupByPublicId(ctx, jsonCred); err != nil {
if errors.IsNotFoundError(err) {
return nil, nil
}
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for : %s", publicId)))
}
// Clear object fields, only ObjectHmac should be returned
jsonCred.ObjectEncrypted = nil
jsonCred.Object = nil
cred = jsonCred
}
return cred, nil
@@ -485,8 +582,137 @@ func (r *Repository) UpdateSshPrivateKeyCredential(ctx context.Context,
return returnedCredential, rowsUpdated, nil
}
// ListCredentials returns a slice of UsernamePasswordCredentials for the
// storeId. WithLimit is the only option supported.
// UpdateJsonCredential updates the repository entry for c.PublicId
// with the values in c for the fields listed in fieldMaskPaths. It returns a
// new JsonCredential containing the updated values and a count of the
// number of records updated. c is not changed.
//
// c must contain a valid PublicId. Only Name, Description and
// Json can be changed. If c.Name is set to a non-empty string, it must be
// unique within c.ProjectId.
//
// An attribute of c will be set to NULL in the database if the attribute in c
// is the zero value and it is included in fieldMaskPaths.
func (r *Repository) UpdateJsonCredential(ctx context.Context,
projectId string,
c *JsonCredential,
version uint32,
fieldMaskPaths []string,
_ ...Option,
) (*JsonCredential, int, error) {
const op = "static.(Repository).UpdateJsonCredential"
if c == nil {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing credential")
}
if c.JsonCredential == nil {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing embedded credential")
}
if c.PublicId == "" {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidPublicId, op, "missing public id")
}
if version == 0 {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version")
}
if projectId == "" {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing project id")
}
if c.StoreId == "" {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing store id")
}
c = c.clone()
// each field in the json secret will be passed into the fieldMaskPaths as an individual path
// that shares the same prefix: attributes.object.
// for example attributes.object.username & attributes.object.password
// fieldMaskPaths elements will be deduped if sharing the same prefix attributes.object.
// and the values will be substituted with a single value Object.
var hasSecret bool
reducedFieldMaskPaths := []string{}
for _, f := range fieldMaskPaths {
if strings.HasPrefix(f, "attributes.object.") {
hasSecret = true
continue
}
reducedFieldMaskPaths = append(reducedFieldMaskPaths, f)
}
if hasSecret {
reducedFieldMaskPaths = append(reducedFieldMaskPaths, objectField)
}
for _, f := range reducedFieldMaskPaths {
switch {
case strings.EqualFold(nameField, f):
case strings.EqualFold(descriptionField, f):
case strings.EqualFold(objectField, f):
default:
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, f)
}
}
dbMask, nullFields := dbw.BuildUpdatePaths(
map[string]interface{}{
nameField: c.Name,
descriptionField: c.Description,
objectField: c.Object,
},
reducedFieldMaskPaths,
nil,
)
if len(dbMask) == 0 && len(nullFields) == 0 {
return nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "missing field mask")
}
if hasSecret {
// Json secret has been updated, re-encrypt and recalculate hmac
databaseWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeDatabase)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper"))
}
if err := c.encrypt(ctx, databaseWrapper); err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op)
}
// Set ObjectHmac and ObjectEncrypted masks for update.
dbMask = append(dbMask, "ObjectHmac", "ObjectEncrypted")
}
oplogWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeOplog)
if err != nil {
return nil, db.NoRowsAffected,
errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var rowsUpdated int
var returnedCredential *JsonCredential
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedCredential = c.clone()
var err error
rowsUpdated, err = w.Update(ctx, returnedCredential,
dbMask, nullFields,
db.WithOplog(oplogWrapper, returnedCredential.oplog(oplog.OpType_OP_TYPE_UPDATE)),
db.WithVersion(&version))
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsUpdated > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been updated")
}
return nil
},
)
if err != nil {
return nil, db.NoRowsAffected, err
}
// Clear object fields, only ObjectHmac should be returned
returnedCredential.ObjectEncrypted = nil
returnedCredential.Object = nil
return returnedCredential, rowsUpdated, nil
}
// ListCredentials returns a slice of UsernamePasswordCredentials, SshPrivateKeyCredentials, and JsonCredentials
// for the storeId. WithLimit is the only option supported.
// TODO: This should hit a view and return the interface type...
func (r *Repository) ListCredentials(ctx context.Context, storeId string, opt ...Option) ([]credential.Static, error) {
const op = "static.(Repository).ListCredentials"
@@ -512,7 +738,13 @@ func (r *Repository) ListCredentials(ctx context.Context, storeId string, opt ..
return nil, errors.Wrap(ctx, err, op)
}
ret := make([]credential.Static, 0, len(upCreds)+len(spkCreds))
var jsonCreds []*JsonCredential
err = r.reader.SearchWhere(ctx, &jsonCreds, "store_id = ?", []interface{}{storeId}, db.WithLimit(limit))
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
ret := make([]credential.Static, 0, len(upCreds)+len(spkCreds)+len(jsonCreds))
for _, c := range upCreds {
// Clear password fields, only PasswordHmac should be returned
@@ -532,6 +764,13 @@ func (r *Repository) ListCredentials(ctx context.Context, storeId string, opt ..
ret = append(ret, c)
}
for _, c := range jsonCreds {
// Clear the object fields, only ObjectHmac should be returned
c.ObjectEncrypted = nil
c.Object = nil
ret = append(ret, c)
}
return ret, nil
}
@@ -560,6 +799,11 @@ func (r *Repository) DeleteCredential(ctx context.Context, projectId, id string,
c.PublicId = id
input = c
md = c.oplog(oplog.OpType_OP_TYPE_DELETE)
case credential.JsonSubtype:
c := allocJsonCredential()
c.PublicId = id
input = c
md = c.oplog(oplog.OpType_OP_TYPE_DELETE)
default:
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "unknown type")
}
@@ -2,6 +2,7 @@ package static
import (
"context"
"encoding/json"
"testing"
"time"
@@ -17,6 +18,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/ssh/testdata"
"google.golang.org/protobuf/types/known/structpb"
)
func TestRepository_CreateUsernamePasswordCredential(t *testing.T) {
@@ -398,6 +400,165 @@ func TestRepository_CreateSshPrivateKeyCredential(t *testing.T) {
})
}
func TestRepository_CreateJsonCredential(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
obj, objBytes, err := TestJsonObject()
assert.NoError(t, err)
cs := TestCredentialStore(t, conn, wrapper, prj.PublicId)
tests := []struct {
name string
projectId string
cred *JsonCredential
wantErr bool
wantErrCode errors.Code
}{
{
name: "missing-store",
wantErr: true,
wantErrCode: errors.InvalidParameter,
},
{
name: "missing-embedded-cred",
cred: &JsonCredential{},
wantErr: true,
wantErrCode: errors.InvalidParameter,
},
{
name: "missing-project-id",
cred: &JsonCredential{
JsonCredential: &store.JsonCredential{
Object: objBytes,
StoreId: cs.PublicId,
},
},
wantErr: true,
wantErrCode: errors.InvalidParameter,
},
{
name: "missing-secret",
projectId: prj.PublicId,
cred: &JsonCredential{
JsonCredential: &store.JsonCredential{
StoreId: cs.PublicId,
},
},
wantErr: true,
wantErrCode: errors.InvalidParameter,
},
{
name: "missing-store-id",
projectId: prj.PublicId,
cred: &JsonCredential{
JsonCredential: &store.JsonCredential{
Object: objBytes,
},
},
wantErr: true,
wantErrCode: errors.InvalidParameter,
},
{
name: "valid",
projectId: prj.PublicId,
cred: &JsonCredential{
JsonCredential: &store.JsonCredential{
Object: objBytes,
StoreId: cs.PublicId,
},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.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)
require.NotNil(repo)
got, err := repo.CreateJsonCredential(ctx, tt.projectId, tt.cred)
if tt.wantErr {
assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err)
assert.Nil(got)
return
}
require.NoError(err)
assertPublicId(t, credential.JsonCredentialPrefix, got.PublicId)
assert.Nil(got.Object)
assert.Nil(got.ObjectEncrypted)
// Validate secret
lookupCred := allocJsonCredential()
lookupCred.PublicId = got.PublicId
require.NoError(rw.LookupById(ctx, lookupCred))
databaseWrapper, err := kkms.GetWrapper(context.Background(), tt.projectId, kms.KeyPurposeDatabase)
require.NoError(err)
require.NoError(lookupCred.decrypt(ctx, databaseWrapper))
assert.Equal(tt.cred.Object, lookupCred.Object)
assert.Empty(got.Object)
assert.Empty(got.ObjectEncrypted)
assert.NotEmpty(got.ObjectHmac)
// Validate hmac
hm, err := crypto.HmacSha256(ctx, tt.cred.Object, databaseWrapper, []byte(tt.cred.StoreId), nil)
require.NoError(err)
assert.Equal([]byte(hm), got.ObjectHmac)
// Validate oplog
assert.NoError(db.TestVerifyOplog(t, rw, got.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)))
})
}
t.Run("duplicate-names", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
kms := kms.TestKms(t, conn, wrapper)
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(err)
require.NotNil(repo)
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
prj2 := iam.TestProject(t, iam.TestRepo(t, conn, wrapper), org.PublicId)
require.NoError(err)
prjCs := TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
prj2Cs := TestCredentialStore(t, conn, wrapper, prj2.GetPublicId())
in, err := NewJsonCredential(ctx, prjCs.GetPublicId(), obj, WithName("my-name"), WithDescription("original"))
assert.NoError(err)
got, err := repo.CreateJsonCredential(ctx, prj.PublicId, in)
require.NoError(err)
assert.Equal(in.Name, got.Name)
assert.Equal(in.Description, got.Description)
in2, err := NewJsonCredential(ctx, prjCs.GetPublicId(), obj, WithName("my-name"), WithDescription("different"))
require.NoError(err)
got2, err := repo.CreateJsonCredential(ctx, prj.GetPublicId(), in2)
assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "want err code: %v got err: %v", errors.NotUnique, err)
assert.Nil(got2)
// Creating credential in different scope should not conflict
in3, err := NewJsonCredential(ctx, prj2Cs.GetPublicId(), obj, WithName("my-name"), WithDescription("different"))
require.NoError(err)
got3, err := repo.CreateJsonCredential(ctx, org.GetPublicId(), in3)
require.NoError(err)
assert.Equal(in3.Name, got3.Name)
assert.Equal(in3.Description, got3.Description)
assert.NotEqual(got.PublicId, got3.PublicId)
})
}
func TestRepository_LookupCredential(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
@@ -411,6 +572,11 @@ func TestRepository_LookupCredential(t *testing.T) {
spkCredWithPass := TestSshPrivateKeyCredential(t, conn, wrapper, "username", string(testdata.PEMEncryptedKeys[0].PEMBytes),
store.PublicId, prj.PublicId, WithPrivateKeyPassphrase([]byte(testdata.PEMEncryptedKeys[0].EncryptionKey)))
obj, _, err := TestJsonObject()
assert.NoError(t, err)
jsonCred := TestJsonCredential(t, conn, wrapper, store.PublicId, prj.PublicId, obj)
tests := []struct {
name string
id string
@@ -432,6 +598,11 @@ func TestRepository_LookupCredential(t *testing.T) {
id: spkCredWithPass.GetPublicId(),
want: spkCredWithPass,
},
{
name: "json-valid",
id: jsonCred.GetPublicId(),
want: jsonCred,
},
{
name: "empty-public-id",
id: "",
@@ -476,7 +647,6 @@ func TestRepository_LookupCredential(t *testing.T) {
assert.Empty(v.PrivateKey)
assert.Empty(v.PrivateKeyEncrypted)
assert.NotEmpty(v.PrivateKeyHmac)
want, ok := tt.want.(*SshPrivateKeyCredential)
require.True(ok)
assert.Empty(v.PrivateKeyPassphrase)
@@ -484,6 +654,10 @@ func TestRepository_LookupCredential(t *testing.T) {
if len(want.PrivateKeyPassphrase) > 0 {
assert.NotEmpty(v.PrivateKeyPassphraseHmac)
}
case *JsonCredential:
assert.Empty(v.Object)
assert.Empty(v.ObjectEncrypted)
assert.NotEmpty(v.ObjectHmac)
default:
require.Fail("unknown type")
}
@@ -499,11 +673,16 @@ func TestRepository_ListCredentials(t *testing.T) {
kms := kms.TestKms(t, conn, wrapper)
defaultLimit := 5
total := 20
total := 30
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
store := TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId(), total/2)
TestSshPrivateKeyCredentials(t, conn, wrapper, "user", TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId(), total/2)
TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId(), total/3)
TestSshPrivateKeyCredentials(t, conn, wrapper, "user", TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId(), total/3)
obj, _, err := TestJsonObject()
assert.NoError(t, err)
TestJsonCredentials(t, conn, wrapper, store.GetPublicId(), prj.GetPublicId(), obj, total/3)
type args struct {
storeId string
@@ -527,7 +706,7 @@ func TestRepository_ListCredentials(t *testing.T) {
args: args{
storeId: store.PublicId,
},
wantCnt: defaultLimit * 2,
wantCnt: defaultLimit * 3,
},
{
name: "custom-limit",
@@ -535,7 +714,7 @@ func TestRepository_ListCredentials(t *testing.T) {
storeId: store.PublicId,
opt: []Option{WithLimit(3)},
},
wantCnt: 3 * 2,
wantCnt: 9,
},
{
name: "bad-store",
@@ -568,6 +747,10 @@ func TestRepository_ListCredentials(t *testing.T) {
assert.Empty(v.PrivateKey)
assert.Empty(v.PrivateKeyEncrypted)
assert.NotEmpty(v.PrivateKeyHmac)
case *JsonCredential:
assert.Empty(v.Object)
assert.Empty(v.ObjectEncrypted)
assert.NotEmpty(v.ObjectHmac)
default:
require.Fail("unknown type")
}
@@ -589,6 +772,11 @@ func TestRepository_DeleteCredential(t *testing.T) {
upCred := TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId())
spkCred := TestSshPrivateKeyCredential(t, conn, wrapper, "user", TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId())
obj, _, err := TestJsonObject()
assert.NoError(t, err)
jsonCred := TestJsonCredential(t, conn, wrapper, store.PublicId, prj.PublicId, obj)
tests := []struct {
name string
in string
@@ -616,6 +804,11 @@ func TestRepository_DeleteCredential(t *testing.T) {
in: spkCred.GetPublicId(),
want: 1,
},
{
name: "with existing json id",
in: jsonCred.GetPublicId(),
want: 1,
},
}
for _, tt := range tests {
@@ -1700,14 +1893,14 @@ Hdtbe1Kk0rHxN0yIKqXNAAAACWplZmZAYXJjaAECAwQ=
}
func TestSshPrivateKeyConstraints(t *testing.T) {
t.Parallel()
ctx := context.Background()
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)
assert, require := assert.New(t), require.New(t)
assert.NoError(err)
require.NotNil(repo)
@@ -1771,3 +1964,440 @@ func TestSshPrivateKeyConstraints(t *testing.T) {
})
}
}
func TestRepository_UpdateJsonCredential(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
_, objBytes, err := TestJsonObject()
assert.NoError(t, err)
secondJsonSecret := &structpb.Struct{
Fields: map[string]*structpb.Value{
"username": structpb.NewStringValue("new-user"),
"password": structpb.NewStringValue("new-password"),
"hash": structpb.NewStringValue("0987654321"),
},
}
secondBSecret, err := json.Marshal(secondJsonSecret)
assert.NoError(t, err)
changeName := func(n string) func(credential *JsonCredential) *JsonCredential {
return func(c *JsonCredential) *JsonCredential {
c.Name = n
return c
}
}
changeDescription := func(d string) func(*JsonCredential) *JsonCredential {
return func(c *JsonCredential) *JsonCredential {
c.Description = d
return c
}
}
makeNil := func() func(*JsonCredential) *JsonCredential {
return func(_ *JsonCredential) *JsonCredential {
return nil
}
}
makeEmbeddedNil := func() func(*JsonCredential) *JsonCredential {
return func(_ *JsonCredential) *JsonCredential {
return &JsonCredential{}
}
}
setPublicId := func(n string) func(*JsonCredential) *JsonCredential {
return func(c *JsonCredential) *JsonCredential {
c.PublicId = n
return c
}
}
deleteStoreId := func() func(*JsonCredential) *JsonCredential {
return func(c *JsonCredential) *JsonCredential {
c.StoreId = ""
return c
}
}
deleteVersion := func() func(*JsonCredential) *JsonCredential {
return func(c *JsonCredential) *JsonCredential {
c.Version = 0
return c
}
}
changeObject := func(s []byte) func(*JsonCredential) *JsonCredential {
return func(c *JsonCredential) *JsonCredential {
c.Object = s
return c
}
}
combine := func(fns ...func(cs *JsonCredential) *JsonCredential) func(*JsonCredential) *JsonCredential {
return func(c *JsonCredential) *JsonCredential {
for _, fn := range fns {
c = fn(c)
}
return c
}
}
tests := []struct {
name string
orig *JsonCredential
chgFn func(*JsonCredential) *JsonCredential
masks []string
want *JsonCredential
wantCount int
wantErr errors.Code
}{
{
name: "nil-credential",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Object: objBytes,
},
},
chgFn: makeNil(),
masks: []string{"Name", "Description"},
wantErr: errors.InvalidParameter,
},
{
name: "nil-embedded-credential",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Object: objBytes,
},
},
chgFn: makeEmbeddedNil(),
masks: []string{"Name", "Description"},
wantErr: errors.InvalidParameter,
},
{
name: "no-public-id",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Object: objBytes,
},
},
chgFn: setPublicId(""),
masks: []string{"Name", "Description"},
wantErr: errors.InvalidPublicId,
},
{
name: "no-store-id",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Object: objBytes,
},
},
chgFn: deleteStoreId(),
masks: []string{"Name", "Description"},
wantErr: errors.InvalidParameter,
},
{
name: "no-version",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Object: objBytes,
},
},
chgFn: deleteVersion(),
masks: []string{"Name", "Description"},
wantErr: errors.InvalidParameter,
},
{
name: "updating-non-existent-credential",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Name: "test-name-repo",
Object: objBytes,
},
},
chgFn: combine(setPublicId("abcd_OOOOOOOOOO"), changeName("test-update-name-repo")),
masks: []string{"Name"},
wantErr: errors.RecordNotFound,
},
{
name: "empty-field-mask",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Name: "test-name-repo",
Object: objBytes,
},
},
chgFn: changeName("test-update-name-repo"),
wantErr: errors.EmptyFieldMask,
},
{
name: "read-only-fields-in-field-mask",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Name: "test-name-repo",
Object: objBytes,
},
},
chgFn: changeName("test-update-name-repo"),
masks: []string{"PublicId", "CreateTime", "UpdateTime", "ScopeId"},
wantErr: errors.InvalidFieldMask,
},
{
name: "unknown-field-in-field-mask",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Name: "test-name-repo",
Object: objBytes,
},
},
chgFn: changeName("test-update-name-repo"),
masks: []string{"FIELD_DNE"},
wantErr: errors.InvalidFieldMask,
},
{
name: "change-name",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Name: "test-name-repo",
Object: objBytes,
},
},
chgFn: changeName("test-update-name-repo"),
masks: []string{"Name"},
want: &JsonCredential{
JsonCredential: &store.JsonCredential{
Name: "test-update-name-repo",
Object: objBytes,
},
},
wantCount: 1,
},
{
name: "change-description",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Description: "test-description-repo",
Object: objBytes,
},
},
chgFn: changeDescription("test-update-description-repo"),
masks: []string{"Description"},
want: &JsonCredential{
JsonCredential: &store.JsonCredential{
Description: "test-update-description-repo",
Object: objBytes,
},
},
wantCount: 1,
},
{
name: "change-name-and-description",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Name: "test-name-repo",
Description: "test-description-repo",
Object: objBytes,
},
},
chgFn: combine(changeDescription("test-update-description-repo"), changeName("test-update-name-repo")),
masks: []string{"Name", "Description"},
want: &JsonCredential{
JsonCredential: &store.JsonCredential{
Name: "test-update-name-repo",
Description: "test-update-description-repo",
Object: objBytes,
},
},
wantCount: 1,
},
{
name: "change-json-secret",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Object: objBytes,
},
},
chgFn: changeObject(secondBSecret),
masks: []string{"attributes.object.username", "attributes.object.password", "attributes.object.hash"},
want: &JsonCredential{
JsonCredential: &store.JsonCredential{
Object: secondBSecret,
},
},
wantCount: 1,
},
{
name: "do-not-delete-json-secret",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Object: objBytes,
},
},
masks: []string{},
chgFn: changeObject(nil),
want: &JsonCredential{
JsonCredential: &store.JsonCredential{
Object: objBytes,
},
},
wantErr: errors.EmptyFieldMask,
wantCount: 0,
},
{
name: "delete-name",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Name: "test-name-repo",
Description: "test-description-repo",
Object: objBytes,
},
},
masks: []string{"Name"},
chgFn: combine(changeDescription("test-update-description-repo"), changeName("")),
want: &JsonCredential{
JsonCredential: &store.JsonCredential{
Description: "test-description-repo",
Object: objBytes,
},
},
wantCount: 1,
},
{
name: "delete-description",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Name: "test-name-repo",
Description: "test-description-repo",
Object: objBytes,
},
},
masks: []string{"Description"},
chgFn: combine(changeDescription(""), changeName("test-update-name-repo")),
want: &JsonCredential{
JsonCredential: &store.JsonCredential{
Name: "test-name-repo",
Object: objBytes,
},
},
wantCount: 1,
},
{
name: "do-not-delete-name",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Name: "test-name-repo",
Description: "test-description-repo",
Object: objBytes,
},
},
masks: []string{"Description"},
chgFn: combine(changeDescription("test-update-description-repo"), changeName("")),
want: &JsonCredential{
JsonCredential: &store.JsonCredential{
Name: "test-name-repo",
Description: "test-update-description-repo",
Object: objBytes,
},
},
wantCount: 1,
},
{
name: "do-not-delete-description",
orig: &JsonCredential{
JsonCredential: &store.JsonCredential{
Name: "test-name-repo",
Description: "test-description-repo",
Object: objBytes,
},
},
masks: []string{"Name"},
chgFn: combine(changeDescription(""), changeName("test-update-name-repo")),
want: &JsonCredential{
JsonCredential: &store.JsonCredential{
Name: "test-update-name-repo",
Description: "test-description-repo",
Object: objBytes,
},
},
wantCount: 1,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.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)
assert.NoError(err)
require.NotNil(repo)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
store := TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
tt.orig.StoreId = store.PublicId
orig, err := repo.CreateJsonCredential(ctx, prj.GetPublicId(), tt.orig)
assert.NoError(err)
require.NotNil(orig)
if tt.chgFn != nil {
orig = tt.chgFn(orig)
}
var version uint32
if orig != nil {
version = orig.GetVersion()
}
got, gotCount, err := repo.UpdateJsonCredential(ctx, prj.GetPublicId(), orig, version, tt.masks)
if tt.wantErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err)
assert.Equal(tt.wantCount, gotCount, "row count")
assert.Nil(got)
return
}
assert.NoError(err)
assert.Empty(tt.orig.PublicId)
require.NotNil(got)
assertPublicId(t, credential.JsonCredentialPrefix, got.PublicId)
assert.Equal(tt.wantCount, gotCount, "row count")
assert.NotSame(tt.orig, got)
assert.Equal(tt.orig.StoreId, got.StoreId)
underlyingDB, err := conn.SqlDB(ctx)
require.NoError(err)
dbassert := dbassert.New(t, underlyingDB)
if tt.want.Name == "" {
got := got.clone()
dbassert.IsNull(got, "name")
} else {
assert.Equal(tt.want.Name, got.Name)
}
if tt.want.Description == "" {
got := got.clone()
dbassert.IsNull(got, "description")
} else {
assert.Equal(tt.want.Description, got.Description)
}
// Validate only SecretHmac is returned
assert.Empty(got.Object)
assert.Empty(got.ObjectEncrypted)
assert.NotEmpty(got.ObjectHmac)
// Validate hmac
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase)
require.NoError(err)
hm, err := crypto.HmacSha256(ctx, tt.want.Object, databaseWrapper, []byte(store.GetPublicId()), nil)
require.NoError(err)
assert.Equal([]byte(hm), got.ObjectHmac)
if tt.wantCount > 0 {
assert.NoError(db.TestVerifyOplog(t, rw, got.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
}
})
}
}
@@ -27,10 +27,15 @@ func (r *Repository) Retrieve(ctx context.Context, projectId string, ids []strin
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var jsonCreds []*JsonCredential
err = r.reader.SearchWhere(ctx, &jsonCreds, "public_id in (?)", []interface{}{ids})
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
if len(upCreds)+len(spkCreds) != len(ids) {
if len(upCreds)+len(spkCreds)+len(jsonCreds) != len(ids) {
return nil, errors.New(ctx, errors.NotSpecificIntegrity, op,
fmt.Sprintf("mismatch between creds and number of ids requested, expected %d got %d", len(ids), len(upCreds)+len(spkCreds)))
fmt.Sprintf("mismatch between creds and number of ids requested, expected %d got %d", len(ids), len(upCreds)+len(spkCreds)+len(jsonCreds)))
}
out := make([]credential.Static, 0, len(ids))
@@ -60,5 +65,18 @@ func (r *Repository) Retrieve(ctx context.Context, projectId string, ids []strin
out = append(out, c)
}
for _, c := range jsonCreds {
// decrypt credential
databaseWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeDatabase)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper"))
}
if err := c.decrypt(ctx, databaseWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
out = append(out, c)
}
return out, nil
}
@@ -15,6 +15,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/ssh/testdata"
"google.golang.org/protobuf/types/known/structpb"
)
func TestRepository_Retrieve(t *testing.T) {
@@ -39,9 +40,25 @@ func TestRepository_Retrieve(t *testing.T) {
string(testdata.PEMEncryptedKeys[0].PEMBytes), staticStore.GetPublicId(), prj.GetPublicId(),
WithPrivateKeyPassphrase([]byte(testdata.PEMEncryptedKeys[0].EncryptionKey)))
obj, _, err := TestJsonObject()
assert.NoError(err)
secondObj := credential.JsonObject{
structpb.Struct{
Fields: map[string]*structpb.Value{
"username": structpb.NewStringValue("new-user"),
"password": structpb.NewStringValue("new-password"),
"hash": structpb.NewStringValue("0987654321"),
},
},
}
jsonCred1 := TestJsonCredential(t, conn, wrapper, staticStore.GetPublicId(), prj.GetPublicId(), obj)
jsonCred2 := TestJsonCredential(t, conn, wrapper, staticStore.GetPublicId(), prj.GetPublicId(), secondObj)
type args struct {
credIds []string
projectIds string
credIds []string
projectId string
}
tests := []struct {
name string
@@ -59,16 +76,16 @@ func TestRepository_Retrieve(t *testing.T) {
{
name: "invalid-project-id",
args: args{
projectIds: org.GetPublicId(),
credIds: []string{upCred1.GetPublicId()},
projectId: org.GetPublicId(),
credIds: []string{upCred1.GetPublicId()},
},
wantErr: true,
},
{
name: "valid-one-up-cred",
args: args{
projectIds: prj.GetPublicId(),
credIds: []string{upCred1.GetPublicId()},
projectId: prj.GetPublicId(),
credIds: []string{upCred1.GetPublicId()},
},
wantCreds: []credential.Static{
upCred1,
@@ -77,8 +94,8 @@ func TestRepository_Retrieve(t *testing.T) {
{
name: "valid-multiple-up-creds",
args: args{
projectIds: prj.GetPublicId(),
credIds: []string{upCred1.GetPublicId(), upCred2.GetPublicId()},
projectId: prj.GetPublicId(),
credIds: []string{upCred1.GetPublicId(), upCred2.GetPublicId()},
},
wantCreds: []credential.Static{
upCred1, upCred2,
@@ -87,8 +104,8 @@ func TestRepository_Retrieve(t *testing.T) {
{
name: "valid-ssh-pk-cred",
args: args{
projectIds: prj.GetPublicId(),
credIds: []string{spkCred1.GetPublicId()},
projectId: prj.GetPublicId(),
credIds: []string{spkCred1.GetPublicId()},
},
wantCreds: []credential.Static{
spkCred1,
@@ -97,28 +114,48 @@ func TestRepository_Retrieve(t *testing.T) {
{
name: "valid-multiple-ssh-pk-creds",
args: args{
projectIds: prj.GetPublicId(),
credIds: []string{spkCred1.GetPublicId(), spkCred2.GetPublicId(), spkCredWithPass.GetPublicId()},
projectId: prj.GetPublicId(),
credIds: []string{spkCred1.GetPublicId(), spkCred2.GetPublicId(), spkCredWithPass.GetPublicId()},
},
wantCreds: []credential.Static{
spkCred1, spkCred2, spkCredWithPass,
},
},
{
name: "valid-mixed-creds",
name: "valid-json-cred",
args: args{
projectIds: prj.GetPublicId(),
credIds: []string{upCred1.GetPublicId(), spkCred1.GetPublicId(), spkCredWithPass.GetPublicId(), spkCred2.GetPublicId(), upCred2.GetPublicId()},
projectId: prj.GetPublicId(),
credIds: []string{jsonCred1.GetPublicId()},
},
wantCreds: []credential.Static{
upCred1, spkCred1, spkCred2, upCred2, spkCredWithPass,
jsonCred1,
},
},
{
name: "valid-multiple-json-creds",
args: args{
projectId: prj.GetPublicId(),
credIds: []string{jsonCred1.GetPublicId(), jsonCred2.GetPublicId()},
},
wantCreds: []credential.Static{
jsonCred1, jsonCred2,
},
},
{
name: "valid-mixed-creds",
args: args{
projectId: prj.GetPublicId(),
credIds: []string{upCred1.GetPublicId(), spkCred1.GetPublicId(), spkCredWithPass.GetPublicId(), spkCred2.GetPublicId(), upCred2.GetPublicId(), jsonCred1.GetPublicId(), jsonCred2.GetPublicId()},
},
wantCreds: []credential.Static{
upCred1, spkCred1, spkCredWithPass, spkCred2, upCred2, jsonCred1, jsonCred2,
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
gotCreds, err := repo.Retrieve(context.Background(), tt.args.projectIds, tt.args.credIds)
gotCreds, err := repo.Retrieve(context.Background(), tt.args.projectId, tt.args.credIds)
if tt.wantErr {
require.Error(err)
assert.Nil(gotCreds)
@@ -131,7 +168,8 @@ func TestRepository_Retrieve(t *testing.T) {
gotCreds,
cmpopts.IgnoreUnexported(
UsernamePasswordCredential{}, store.UsernamePasswordCredential{},
SshPrivateKeyCredential{}, store.SshPrivateKeyCredential{}),
SshPrivateKeyCredential{}, store.SshPrivateKeyCredential{},
JsonCredential{}, store.JsonCredential{}),
cmpopts.IgnoreTypes(&timestamp.Timestamp{}),
cmpopts.IgnoreFields(SshPrivateKeyCredential{}, "PassphraseUnneeded"),
cmpopts.SortSlices(func(x, y credential.Static) bool {
+224 -20
View File
@@ -499,6 +499,160 @@ func (x *SshPrivateKeyCredential) GetPrivateKeyPassphraseHmac() []byte {
return nil
}
type JsonCredential struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"`
// create_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
CreateTime *timestamp.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
// update_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
UpdateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"`
// name is optional. If set, it must be unique within scope_id.
// @inject_tag: `gorm:"default:null"`
Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"`
// description is optional.
// @inject_tag: `gorm:"default:null"`
Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"`
// store_id of the owning static credential store.
// It must be set.
// @inject_tag: `gorm:"not_null"`
StoreId string `protobuf:"bytes,6,opt,name=store_id,json=storeId,proto3" json:"store_id,omitempty" gorm:"not_null"`
// version allows optimistic locking of the resource.
// @inject_tag: `gorm:"default:null"`
Version uint32 `protobuf:"varint,7,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"`
// object is the plain-text of the json associated with the
// credential. We are not storing this plain-text object in the database.
// @inject_tag: `gorm:"-" wrapping:"pt,object"`
Object []byte `protobuf:"bytes,8,opt,name=object,proto3" json:"object,omitempty" gorm:"-" wrapping:"pt,object"`
// object_encrypted is the ciphertext of the object. It is stored in
// the database.
// @inject_tag: `gorm:"column:object_encrypted;not_null" wrapping:"ct,object"`
ObjectEncrypted []byte `protobuf:"bytes,9,opt,name=object_encrypted,json=objectEncrypted,proto3" json:"object_encrypted,omitempty" gorm:"column:object_encrypted;not_null" wrapping:"ct,object"`
// object_hmac is a sha256-hmac of the unencrypted object. It is recalculated
// everytime the object is updated.
// @inject_tag: `gorm:"not_null"`
ObjectHmac []byte `protobuf:"bytes,10,opt,name=object_hmac,json=objectHmac,proto3" json:"object_hmac,omitempty" gorm:"not_null"`
// The key_id of the kms database key used for encrypting this entry.
// It must be set.
// @inject_tag: `gorm:"not_null"`
KeyId string `protobuf:"bytes,11,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty" gorm:"not_null"`
}
func (x *JsonCredential) Reset() {
*x = JsonCredential{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *JsonCredential) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*JsonCredential) ProtoMessage() {}
func (x *JsonCredential) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[3]
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 JsonCredential.ProtoReflect.Descriptor instead.
func (*JsonCredential) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_static_store_v1_static_proto_rawDescGZIP(), []int{3}
}
func (x *JsonCredential) GetPublicId() string {
if x != nil {
return x.PublicId
}
return ""
}
func (x *JsonCredential) GetCreateTime() *timestamp.Timestamp {
if x != nil {
return x.CreateTime
}
return nil
}
func (x *JsonCredential) GetUpdateTime() *timestamp.Timestamp {
if x != nil {
return x.UpdateTime
}
return nil
}
func (x *JsonCredential) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *JsonCredential) GetDescription() string {
if x != nil {
return x.Description
}
return ""
}
func (x *JsonCredential) GetStoreId() string {
if x != nil {
return x.StoreId
}
return ""
}
func (x *JsonCredential) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
func (x *JsonCredential) GetObject() []byte {
if x != nil {
return x.Object
}
return nil
}
func (x *JsonCredential) GetObjectEncrypted() []byte {
if x != nil {
return x.ObjectEncrypted
}
return nil
}
func (x *JsonCredential) GetObjectHmac() []byte {
if x != nil {
return x.ObjectHmac
}
return nil
}
func (x *JsonCredential) GetKeyId() string {
if x != nil {
return x.KeyId
}
return ""
}
var File_controller_storage_credential_static_store_v1_static_proto protoreflect.FileDescriptor
var file_controller_storage_credential_static_store_v1_static_proto_rawDesc = []byte{
@@ -639,12 +793,47 @@ var file_controller_storage_credential_static_store_v1_static_proto_rawDesc = []
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65,
0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x5f,
0x68, 0x6d, 0x61, 0x63, 0x52, 0x18, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79,
0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x48, 0x6d, 0x61, 0x63, 0x42, 0x46,
0x5a, 0x44, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74,
0x69, 0x61, 0x6c, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65,
0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x48, 0x6d, 0x61, 0x63, 0x22, 0xaa,
0x04, 0x0a, 0x0e, 0x4a, 0x73, 0x6f, 0x6e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61,
0x6c, 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, 0x75, 0x70,
0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x4e, 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, 0x05, 0x20,
0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x44, 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, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01,
0x28, 0x09, 0x52, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18,
0x08, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x1f, 0xc2, 0xdd, 0x29, 0x1b, 0x0a, 0x06, 0x4f, 0x62, 0x6a,
0x65, 0x63, 0x74, 0x12, 0x11, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e,
0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x29,
0x0a, 0x10, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74,
0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x0b, 0x6f, 0x62, 0x6a,
0x65, 0x63, 0x74, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x28,
0xc2, 0xdd, 0x29, 0x24, 0x0a, 0x0a, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x6d, 0x61, 0x63,
0x12, 0x16, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x6f, 0x62, 0x6a,
0x65, 0x63, 0x74, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74,
0x48, 0x6d, 0x61, 0x63, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0b,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x42, 0x46, 0x5a, 0x44, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,
0x2f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74,
0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -659,25 +848,28 @@ func file_controller_storage_credential_static_store_v1_static_proto_rawDescGZIP
return file_controller_storage_credential_static_store_v1_static_proto_rawDescData
}
var file_controller_storage_credential_static_store_v1_static_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_controller_storage_credential_static_store_v1_static_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_controller_storage_credential_static_store_v1_static_proto_goTypes = []interface{}{
(*CredentialStore)(nil), // 0: controller.storage.credential.static.store.v1.CredentialStore
(*UsernamePasswordCredential)(nil), // 1: controller.storage.credential.static.store.v1.UsernamePasswordCredential
(*SshPrivateKeyCredential)(nil), // 2: controller.storage.credential.static.store.v1.SshPrivateKeyCredential
(*timestamp.Timestamp)(nil), // 3: controller.storage.timestamp.v1.Timestamp
(*JsonCredential)(nil), // 3: controller.storage.credential.static.store.v1.JsonCredential
(*timestamp.Timestamp)(nil), // 4: controller.storage.timestamp.v1.Timestamp
}
var file_controller_storage_credential_static_store_v1_static_proto_depIdxs = []int32{
3, // 0: controller.storage.credential.static.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
3, // 1: controller.storage.credential.static.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
3, // 2: controller.storage.credential.static.store.v1.UsernamePasswordCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
3, // 3: controller.storage.credential.static.store.v1.UsernamePasswordCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
3, // 4: controller.storage.credential.static.store.v1.SshPrivateKeyCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
3, // 5: controller.storage.credential.static.store.v1.SshPrivateKeyCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // [6:6] is the sub-list for method output_type
6, // [6:6] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
4, // 0: controller.storage.credential.static.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
4, // 1: controller.storage.credential.static.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
4, // 2: controller.storage.credential.static.store.v1.UsernamePasswordCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
4, // 3: controller.storage.credential.static.store.v1.UsernamePasswordCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
4, // 4: controller.storage.credential.static.store.v1.SshPrivateKeyCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
4, // 5: controller.storage.credential.static.store.v1.SshPrivateKeyCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
4, // 6: controller.storage.credential.static.store.v1.JsonCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
4, // 7: controller.storage.credential.static.store.v1.JsonCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // [8:8] is the sub-list for method output_type
8, // [8:8] is the sub-list for method input_type
8, // [8:8] is the sub-list for extension type_name
8, // [8:8] is the sub-list for extension extendee
0, // [0:8] is the sub-list for field type_name
}
func init() { file_controller_storage_credential_static_store_v1_static_proto_init() }
@@ -722,6 +914,18 @@ func file_controller_storage_credential_static_store_v1_static_proto_init() {
return nil
}
}
file_controller_storage_credential_static_store_v1_static_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*JsonCredential); 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{
@@ -729,7 +933,7 @@ func file_controller_storage_credential_static_store_v1_static_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_storage_credential_static_store_v1_static_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumMessages: 4,
NumExtensions: 0,
NumServices: 0,
},
+90
View File
@@ -2,6 +2,7 @@ package static
import (
"context"
"encoding/json"
"testing"
"github.com/hashicorp/boundary/internal/credential"
@@ -10,6 +11,7 @@ import (
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"
)
const (
@@ -64,6 +66,21 @@ do/lpv8N1+5Eb3lOB3DrqcEqRwXzSQcO2QcpikNSHyPquGR689I3xUm6kWmpKs49aacTUx
`
)
// TestJsonObject returns a json object and it's marshalled format to be used for testing
func TestJsonObject() (credential.JsonObject, []byte, error) {
object := credential.JsonObject{
structpb.Struct{
Fields: map[string]*structpb.Value{
"username": structpb.NewStringValue("user"),
"password": structpb.NewStringValue("password"),
"hash": structpb.NewStringValue("1234567890"),
},
},
}
b, err := json.Marshal(object.AsMap())
return object, b, err
}
// TestCredentialStore creates a static credential store in the provided DB with
// the provided project id and any values passed in through the Options vars.
// If any errors are encountered during the creation of the store, the test will fail.
@@ -262,3 +279,76 @@ func TestSshPrivateKeyCredentials(
}
return creds
}
// TestJsonCredential creates a json credential in the
// provided DB with the provided scope and any values passed in. If any
// errors are encountered during the creation of the store, the test will fail.
func TestJsonCredential(
t testing.TB,
conn *db.DB,
wrapper wrapping.Wrapper,
storeId, scopeId string,
object credential.JsonObject,
opt ...Option,
) *JsonCredential {
t.Helper()
ctx := context.Background()
kmsCache := kms.TestKms(t, conn, wrapper)
w := db.New(conn)
opts := getOpts(opt...)
databaseWrapper, err := kmsCache.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
assert.NoError(t, err)
require.NotNil(t, databaseWrapper)
cred, err := NewJsonCredential(ctx, storeId, object, opt...)
require.NoError(t, err)
require.NotNil(t, cred)
id := opts.withPublicId
if id == "" {
id, err = credential.NewJsonCredentialId(ctx)
require.NoError(t, err)
}
cred.PublicId = id
err = cred.encrypt(ctx, databaseWrapper)
require.NoError(t, err)
_, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, iw db.Writer) error {
require.NoError(t, iw.Create(ctx, cred))
return nil
},
)
require.NoError(t, err2)
return cred
}
// TestJsonCredentials creates count number of json
// credentials in the provided DB with the provided scope id. If any errors are
// encountered during the creation of the credentials, the test will fail.
func TestJsonCredentials(
t testing.TB,
conn *db.DB,
wrapper wrapping.Wrapper,
storeId, scopeId string,
object credential.JsonObject,
count int,
) []*JsonCredential {
t.Helper()
ctx := context.Background()
kmsCache := kms.TestKms(t, conn, wrapper)
databaseWrapper, err := kmsCache.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
assert.NoError(t, err)
require.NotNil(t, databaseWrapper)
creds := make([]*JsonCredential, 0, count)
for i := 0; i < count; i++ {
creds = append(creds, TestJsonCredential(t, conn, wrapper, storeId, scopeId, object))
}
return creds
}
@@ -85,3 +85,52 @@ func Test_TestUsernamePasswordCredentials(t *testing.T) {
creds := TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId(), count)
assert.Len(creds, count)
}
func Test_TestJsonCredential(t *testing.T) {
t.Parallel()
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kkms := kms.TestKms(t, conn, wrapper)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
require.NotNil(prj)
assert.NotEmpty(prj.GetPublicId())
store := TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
obj, objBytes, err := TestJsonObject()
assert.NoError(err)
cred := TestJsonCredential(t, conn, wrapper, store.GetPublicId(), prj.GetPublicId(), obj, WithName("my-name"), WithDescription("my-description"))
require.NotNil(cred)
assert.NotEmpty(cred.GetPublicId())
assert.Equal(cred.Name, "my-name")
assert.Equal(cred.Description, "my-description")
assert.Equal(cred.Object, objBytes)
// Validate hmac
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase)
require.NoError(err)
hm, err := crypto.HmacSha256(context.Background(), cred.Object, databaseWrapper, []byte(cred.StoreId), nil)
require.NoError(err)
assert.Equal([]byte(hm), cred.ObjectHmac)
}
func Test_TestJsonCredentials(t *testing.T) {
t.Parallel()
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
require.NotNil(prj)
assert.NotEmpty(prj.GetPublicId())
store := TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
obj, _, err := TestJsonObject()
assert.NoError(err)
count := 3
creds := TestJsonCredentials(t, conn, wrapper, store.GetPublicId(), prj.GetPublicId(), obj, count)
assert.Len(creds, count)
}
@@ -4,6 +4,7 @@ import (
"context"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/hashicorp/boundary/globals"
@@ -24,6 +25,7 @@ import (
"golang.org/x/crypto/ssh"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
@@ -32,12 +34,14 @@ const (
passwordField = "attributes.password"
privateKeyField = "attributes.private_key"
privateKeyPassphraseField = "attributes.private_key_passphrase"
objectField = "attributes.object"
domain = "credential"
)
var (
upMaskManager handlers.MaskManager
spkMaskManager handlers.MaskManager
upMaskManager handlers.MaskManager
spkMaskManager handlers.MaskManager
jsonMaskManager handlers.MaskManager
// IdActions contains the set of actions that can be performed on
// individual resources
@@ -66,6 +70,10 @@ func init() {
handlers.MaskSource{&pb.Credential{}, &pb.SshPrivateKeyAttributes{}}); err != nil {
panic(err)
}
if jsonMaskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.JsonCredential{}},
handlers.MaskSource{&pb.Credential{}, &pb.JsonAttributes{}}); err != nil {
panic(err)
}
}
// Service handles request as described by the pbs.CredentialServiceServer interface.
@@ -360,6 +368,23 @@ func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Cred
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create credential but no error returned from repository.")
}
return out, nil
case credential.JsonSubtype.String():
cred, err := toJsonStorageCredential(ctx, item.GetCredentialStoreId(), item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
out, err := repo.CreateJsonCredential(ctx, scopeId, cred)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create credential"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create credential but no error returned from repository.")
}
return out, nil
default:
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, fmt.Sprintf("Unsupported credential type %q", item.GetType()))
}
@@ -435,6 +460,30 @@ func (s Service) updateInRepo(
}
return out, nil
case credential.JsonSubtype:
dbMasks = append(dbMasks, jsonMaskManager.Translate(masks, "attributes", "object")...)
if len(dbMasks) == 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."})
}
cred, err := toJsonStorageCredential(ctx, storeId, in)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to convert to json storage credential"))
}
cred.PublicId = id
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
out, rowsUpdated, err := repo.UpdateJsonCredential(ctx, scopeId, cred, item.GetVersion(), dbMasks)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential"))
}
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Credential %q doesn't exist or incorrect version provided.", id)
}
return out, nil
default:
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, fmt.Sprintf("Unsupported credential type %q", item.GetType()))
@@ -527,6 +576,8 @@ func toProto(in credential.Static, opt ...handlers.Option) (*pb.Credential, erro
out.Type = credential.UsernamePasswordSubtype.String()
case *static.SshPrivateKeyCredential:
out.Type = credential.SshPrivateKeySubtype.String()
case *static.JsonCredential:
out.Type = credential.JsonSubtype.String()
}
}
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
@@ -571,6 +622,14 @@ func toProto(in credential.Static, opt ...handlers.Option) (*pb.Credential, erro
},
}
}
case *static.JsonCredential:
if outputFields.Has(globals.AttributesField) {
out.Attrs = &pb.Credential_JsonAttributes{
JsonAttributes: &pb.JsonAttributes{
ObjectHmac: base64.RawURLEncoding.EncodeToString(cred.GetObjectHmac()),
},
}
}
}
return &out, nil
}
@@ -625,6 +684,38 @@ func toSshPrivateKeyStorageCredential(ctx context.Context, storeId string, in *p
return cs, err
}
func toJsonStorageCredential(ctx context.Context, storeId string, in *pb.Credential) (out *static.JsonCredential, err error) {
const op = "credentials.toJsonStorageCredential"
var opts []static.Option
if in.GetName() != nil {
opts = append(opts, static.WithName(in.GetName().GetValue()))
}
if in.GetDescription() != nil {
opts = append(opts, static.WithDescription(in.GetDescription().GetValue()))
}
attrs := in.GetJsonAttributes()
object := attrs.GetObject()
if object == nil {
object, err = structpb.NewStruct(map[string]interface{}{})
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to build credential"))
}
}
cs, err := static.NewJsonCredential(
ctx,
storeId,
credential.JsonObject{
*object,
},
opts...)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to build credential"))
}
return cs, err
}
// 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
@@ -637,6 +728,7 @@ func validateGetRequest(req *pbs.GetCredentialRequest) error {
credential.UsernamePasswordCredentialPrefix,
credential.PreviousUsernamePasswordCredentialPrefix,
credential.SshPrivateKeyCredentialPrefix,
credential.JsonCredentialPrefix,
)
}
@@ -685,6 +777,18 @@ func validateCreateRequest(req *pbs.CreateCredentialRequest) error {
}
}
case credential.JsonSubtype.String():
object := req.GetItem().GetJsonAttributes().GetObject()
if object == nil && len(object.AsMap()) <= 0 {
badFields[objectField] = "Field required for creating a json credential."
}
objectBytes, err := json.Marshal(object)
if err != nil {
badFields[objectField] = "Unable to parse given json value"
} else if len(objectBytes) <= 0 {
badFields[objectField] = "Field required for creating a json credential."
}
default:
badFields[globals.TypeField] = fmt.Sprintf("Unsupported credential type %q", req.Item.GetType())
}
@@ -738,6 +842,21 @@ func validateUpdateRequest(req *pbs.UpdateCredentialRequest) error {
}
}
case credential.JsonSubtype:
object := req.GetItem().GetJsonAttributes().GetObject()
if object != nil {
objectBytes, err := json.Marshal(object.AsMap())
if err != nil {
badFields[objectField] = "Unable to parse given json value"
}
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), objectField) && len(objectBytes) <= 0 {
badFields[objectField] = "This is a required field and cannot be set to empty."
}
}
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), objectField) && object == nil {
badFields[objectField] = "This is a required field and cannot be set to empty."
}
default:
badFields[globals.IdField] = "Unknown credential type."
}
@@ -747,6 +866,7 @@ func validateUpdateRequest(req *pbs.UpdateCredentialRequest) error {
credential.UsernamePasswordCredentialPrefix,
credential.PreviousUsernamePasswordCredentialPrefix,
credential.SshPrivateKeyCredentialPrefix,
credential.JsonCredentialPrefix,
)
}
@@ -757,6 +877,7 @@ func validateDeleteRequest(req *pbs.DeleteCredentialRequest) error {
credential.UsernamePasswordCredentialPrefix,
credential.PreviousUsernamePasswordCredentialPrefix,
credential.SshPrivateKeyCredentialPrefix,
credential.JsonCredentialPrefix,
)
}
@@ -3,6 +3,7 @@ package credentials
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"testing"
@@ -30,6 +31,7 @@ import (
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/fieldmaskpb"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
@@ -100,6 +102,28 @@ func TestList(t *testing.T) {
},
},
})
obj, objBytes, err := static.TestJsonObject()
assert.NoError(t, err)
credJson := static.TestJsonCredential(t, conn, wrapper, store.GetPublicId(), prj.GetPublicId(), obj)
hm, err = crypto.HmacSha256(context.Background(), objBytes, databaseWrapper, []byte(store.GetPublicId()), nil)
require.NoError(t, err)
wantCreds = append(wantCreds, &pb.Credential{
Id: credJson.GetPublicId(),
CredentialStoreId: store.GetPublicId(),
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
CreatedTime: credJson.GetCreateTime().GetTimestamp(),
UpdatedTime: credJson.GetUpdateTime().GetTimestamp(),
Version: credJson.GetVersion(),
Type: credential.JsonSubtype.String(),
AuthorizedActions: testAuthorizedActions,
Attrs: &pb.Credential_JsonAttributes{
JsonAttributes: &pb.JsonAttributes{
ObjectHmac: base64.RawURLEncoding.EncodeToString([]byte(hm)),
},
},
})
}
cases := []struct {
@@ -129,8 +153,8 @@ func TestList(t *testing.T) {
},
{
name: "Filter on Attribute",
req: &pbs.ListCredentialsRequest{CredentialStoreId: store.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/username"==%q`, wantCreds[2].GetUsernamePasswordAttributes().GetUsername().Value)},
res: &pbs.ListCredentialsResponse{Items: wantCreds[2:4]},
req: &pbs.ListCredentialsRequest{CredentialStoreId: store.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/username"==%q`, wantCreds[3].GetUsernamePasswordAttributes().GetUsername().Value)},
res: &pbs.ListCredentialsResponse{Items: wantCreds[3:5]},
anonRes: &pbs.ListCredentialsResponse{}, // anonymous user does not have access to attributes
},
{
@@ -213,6 +237,12 @@ func TestGet(t *testing.T) {
spkWithPassHm, err := crypto.HmacSha256(context.Background(), []byte(testdata.PEMEncryptedKeys[0].PEMBytes), databaseWrapper, []byte(store.GetPublicId()), nil)
require.NoError(t, err)
passHm, err := crypto.HmacSha256(context.Background(), []byte(testdata.PEMEncryptedKeys[0].EncryptionKey), databaseWrapper, []byte(store.GetPublicId()), nil)
obj, objBytes, err := static.TestJsonObject()
assert.NoError(t, err)
jsonCred := static.TestJsonCredential(t, conn, wrapper, store.GetPublicId(), prj.GetPublicId(), obj)
objectHmac, err := crypto.HmacSha256(context.Background(), objBytes, databaseWrapper, []byte(store.GetPublicId()), nil)
require.NoError(t, err)
cases := []struct {
@@ -310,6 +340,27 @@ func TestGet(t *testing.T) {
},
},
},
{
name: "success-json",
id: jsonCred.GetPublicId(),
res: &pbs.GetCredentialResponse{
Item: &pb.Credential{
Id: jsonCred.GetPublicId(),
CredentialStoreId: jsonCred.GetStoreId(),
Scope: &scopepb.ScopeInfo{Id: store.GetProjectId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
Type: credential.JsonSubtype.String(),
AuthorizedActions: testAuthorizedActions,
CreatedTime: jsonCred.CreateTime.GetTimestamp(),
UpdatedTime: jsonCred.UpdateTime.GetTimestamp(),
Version: 1,
Attrs: &pb.Credential_JsonAttributes{
JsonAttributes: &pb.JsonAttributes{
ObjectHmac: base64.RawURLEncoding.EncodeToString([]byte(objectHmac)),
},
},
},
},
},
{
name: "not found error",
id: fmt.Sprintf("%s_1234567890", credential.UsernamePasswordCredentialPrefix),
@@ -370,6 +421,11 @@ func TestDelete(t *testing.T) {
upCred := static.TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId())
spkCred := static.TestSshPrivateKeyCredential(t, conn, wrapper, "user", static.TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId())
obj, _, err := static.TestJsonObject()
assert.NoError(t, err)
jsonCred := static.TestJsonCredential(t, conn, wrapper, store.GetPublicId(), prj.GetPublicId(), obj)
cases := []struct {
name string
id string
@@ -384,6 +440,10 @@ func TestDelete(t *testing.T) {
name: "success-spk",
id: spkCred.GetPublicId(),
},
{
name: "success-json",
id: jsonCred.GetPublicId(),
},
{
name: "not found error",
id: fmt.Sprintf("%s_1234567890", credential.UsernamePasswordCredentialPrefix),
@@ -429,6 +489,9 @@ func TestCreate(t *testing.T) {
_, prj := iam.TestScopes(t, iamRepo)
store := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
obj, objBytes, err := static.TestJsonObject()
assert.NoError(t, err)
cases := []struct {
name string
req *pbs.CreateCredentialRequest
@@ -555,6 +618,18 @@ func TestCreate(t *testing.T) {
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Must provide json secret",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
CredentialStoreId: store.GetPublicId(),
Type: credential.JsonSubtype.String(),
Attrs: &pb.Credential_JsonAttributes{
JsonAttributes: &pb.JsonAttributes{},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "valid-up",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
@@ -637,6 +712,32 @@ func TestCreate(t *testing.T) {
},
},
},
{
name: "valid-json",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
CredentialStoreId: store.GetPublicId(),
Type: credential.JsonSubtype.String(),
Attrs: &pb.Credential_JsonAttributes{
JsonAttributes: &pb.JsonAttributes{
Object: &obj.Struct,
},
},
}},
idPrefix: credential.JsonCredentialPrefix + "_",
res: &pbs.CreateCredentialResponse{
Uri: fmt.Sprintf("credentials/%s_", credential.JsonCredentialPrefix),
Item: &pb.Credential{
Id: store.GetPublicId(),
CredentialStoreId: store.GetPublicId(),
CreatedTime: store.GetCreateTime().GetTimestamp(),
UpdatedTime: store.GetUpdateTime().GetTimestamp(),
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()},
Version: 1,
Type: credential.JsonSubtype.String(),
AuthorizedActions: testAuthorizedActions,
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
@@ -702,6 +803,14 @@ func TestCreate(t *testing.T) {
assert.Empty(got.GetItem().GetSshPrivateKeyAttributes().GetPrivateKeyPassphrase())
}
case credential.JsonSubtype.String():
hm, err := crypto.HmacSha256(context.Background(), objBytes, databaseWrapper, []byte(store.GetPublicId()), nil)
require.NoError(err)
// Validate attributes equal
assert.Equal(base64.RawURLEncoding.EncodeToString([]byte(hm)), got.GetItem().GetJsonAttributes().GetObjectHmac())
assert.Empty(got.GetItem().GetJsonAttributes().GetObject())
default:
require.Fail("unknown type")
}
@@ -775,6 +884,28 @@ func TestUpdate(t *testing.T) {
return cred, clean
}
freshCredJson := func() (*static.JsonCredential, func()) {
t.Helper()
obj, _, err := static.TestJsonObject()
assert.NoError(t, err)
cred := static.TestJsonCredential(t, conn, wrapper, store.GetPublicId(), prj.GetPublicId(), obj)
clean := func() {
_, err := s.DeleteCredential(ctx, &pbs.DeleteCredentialRequest{Id: cred.GetPublicId()})
require.NoError(t, err)
}
return cred, clean
}
secondSecret := &structpb.Struct{
Fields: map[string]*structpb.Value{
"username": structpb.NewStringValue("new-user"),
"password": structpb.NewStringValue("new-password"),
"hash": structpb.NewStringValue("0123456789"),
},
}
secondSecretBytes, err := json.Marshal(secondSecret)
require.NoError(t, err)
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase)
require.NoError(t, err)
@@ -994,6 +1125,27 @@ func TestUpdate(t *testing.T) {
return out
},
},
{
name: "update-json",
req: &pbs.UpdateCredentialRequest{
UpdateMask: fieldmask("attributes.object.username", "attributes.object.password", "attributes.object.hash"),
Item: &pb.Credential{
Attrs: &pb.Credential_JsonAttributes{
JsonAttributes: &pb.JsonAttributes{
Object: secondSecret,
},
},
},
},
res: func(in *pb.Credential) *pb.Credential {
out := proto.Clone(in).(*pb.Credential)
hm, err := crypto.HmacSha256(context.Background(), secondSecretBytes, databaseWrapper, []byte(store.GetPublicId()), nil)
require.NoError(t, err)
out.GetJsonAttributes().ObjectHmac = base64.RawURLEncoding.EncodeToString([]byte(hm))
return out
},
},
{
name: "update-spk-with-bad-passphrase",
req: &pbs.UpdateCredentialRequest{
@@ -1043,6 +1195,7 @@ func TestUpdate(t *testing.T) {
require.NoError(t, err)
out.GetSshPrivateKeyAttributes().PrivateKeyHmac = base64.RawURLEncoding.EncodeToString([]byte(hm))
out.GetSshPrivateKeyAttributes().PrivateKeyPassphraseHmac = ""
return out
},
},
@@ -1053,10 +1206,12 @@ func TestUpdate(t *testing.T) {
assert, require := assert.New(t), require.New(t)
var cred credential.Static
var cleanup func()
switch {
case strings.Contains(tc.name, "spk"):
if strings.Contains(tc.name, "spk") {
cred, cleanup = freshCredSpk("user")
default:
} else if strings.Contains(tc.name, "json") {
cred, cleanup = freshCredJson()
} else {
cred, cleanup = freshCredUp("user", "pass")
}
defer cleanup()
@@ -1095,8 +1250,12 @@ func TestUpdate(t *testing.T) {
}
// cant update read only fields
cred, cleanupKv := freshCredUp("user", "pass")
defer cleanupKv()
credJson, cleanupJson := freshCredJson()
defer cleanupJson()
// cant update read only fields
credUp, cleanUp := freshCredUp("user", "pass")
defer cleanUp()
newStore := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
@@ -1129,11 +1288,11 @@ func TestUpdate(t *testing.T) {
for _, tc := range roCases {
t.Run(fmt.Sprintf("ReadOnlyField/%s", tc.path), func(t *testing.T) {
req := &pbs.UpdateCredentialRequest{
Id: cred.GetPublicId(),
Id: credUp.GetPublicId(),
Item: tc.item,
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{tc.path}},
}
req.Item.Version = cred.Version
req.Item.Version = credUp.Version
got, gErr := s.UpdateCredential(ctx, req)
assert.Error(t, gErr)
@@ -1145,6 +1304,24 @@ func TestUpdate(t *testing.T) {
}
matcher(t, gErr)
assert.Nil(t, got)
req = &pbs.UpdateCredentialRequest{
Id: credJson.GetPublicId(),
Item: tc.item,
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{tc.path}},
}
req.Item.Version = credJson.Version
got, gErr = s.UpdateCredential(ctx, req)
assert.Error(t, gErr)
matcher = tc.matcher
if matcher == nil {
matcher = func(t *testing.T, e error) {
assert.Truef(t, errors.Is(gErr, handlers.ApiErrorWithCode(codes.InvalidArgument)), "got error %v, wanted invalid argument", gErr)
}
}
matcher(t, gErr)
assert.Nil(t, got)
})
}
}
@@ -168,7 +168,7 @@ func staticToWorkerCredential(ctx context.Context, cred credential.Static) (sess
data, err := proto.Marshal(workerCred)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("marshalling dynamic secret to proto"))
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("marshalling static secret to proto"))
}
return data, nil
}
@@ -218,6 +218,21 @@ func staticToSessionCredential(ctx context.Context, cred credential.Static) (*pb
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for ssh private key credential"))
}
case *credstatic.JsonCredential:
var err error
credType = string(credential.JsonType)
object := map[string]interface{}{}
err = json.Unmarshal(c.GetObject(), &object)
if err != nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "unmarshalling json")
}
credData, err = structpb.NewStruct(object)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for json credential"))
}
secret = object
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported credential %T", c))
}
@@ -1624,7 +1624,8 @@ func validateAddCredentialSourcesRequest(req *pbs.AddTargetCredentialSourcesRequ
vault.CredentialLibraryPrefix,
credential.UsernamePasswordCredentialPrefix,
credential.PreviousUsernamePasswordCredentialPrefix,
credential.SshPrivateKeyCredentialPrefix) {
credential.SshPrivateKeyCredentialPrefix,
credential.JsonCredentialPrefix) {
badFields[globals.ApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
break
}
@@ -1634,7 +1635,8 @@ func validateAddCredentialSourcesRequest(req *pbs.AddTargetCredentialSourcesRequ
vault.CredentialLibraryPrefix,
credential.UsernamePasswordCredentialPrefix,
credential.PreviousUsernamePasswordCredentialPrefix,
credential.SshPrivateKeyCredentialPrefix) {
credential.SshPrivateKeyCredentialPrefix,
credential.JsonCredentialPrefix) {
badFields[globals.BrokeredCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
break
}
@@ -1669,7 +1671,8 @@ func validateSetCredentialSourcesRequest(req *pbs.SetTargetCredentialSourcesRequ
vault.CredentialLibraryPrefix,
credential.UsernamePasswordCredentialPrefix,
credential.PreviousUsernamePasswordCredentialPrefix,
credential.SshPrivateKeyCredentialPrefix) {
credential.SshPrivateKeyCredentialPrefix,
credential.JsonCredentialPrefix) {
badFields[globals.ApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
break
}
@@ -1679,7 +1682,8 @@ func validateSetCredentialSourcesRequest(req *pbs.SetTargetCredentialSourcesRequ
vault.CredentialLibraryPrefix,
credential.UsernamePasswordCredentialPrefix,
credential.PreviousUsernamePasswordCredentialPrefix,
credential.SshPrivateKeyCredentialPrefix) {
credential.SshPrivateKeyCredentialPrefix,
credential.JsonCredentialPrefix) {
badFields[globals.BrokeredCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
break
}
@@ -1718,7 +1722,8 @@ func validateRemoveCredentialSourcesRequest(req *pbs.RemoveTargetCredentialSourc
vault.CredentialLibraryPrefix,
credential.UsernamePasswordCredentialPrefix,
credential.PreviousUsernamePasswordCredentialPrefix,
credential.SshPrivateKeyCredentialPrefix) {
credential.SshPrivateKeyCredentialPrefix,
credential.JsonCredentialPrefix) {
badFields[globals.ApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
break
}
@@ -1728,7 +1733,8 @@ func validateRemoveCredentialSourcesRequest(req *pbs.RemoveTargetCredentialSourc
vault.CredentialLibraryPrefix,
credential.UsernamePasswordCredentialPrefix,
credential.PreviousUsernamePasswordCredentialPrefix,
credential.SshPrivateKeyCredentialPrefix) {
credential.SshPrivateKeyCredentialPrefix,
credential.JsonCredentialPrefix) {
badFields[globals.BrokeredCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
break
}
@@ -1738,7 +1744,8 @@ func validateRemoveCredentialSourcesRequest(req *pbs.RemoveTargetCredentialSourc
vault.CredentialLibraryPrefix,
credential.UsernamePasswordCredentialPrefix,
credential.PreviousUsernamePasswordCredentialPrefix,
credential.SshPrivateKeyCredentialPrefix) {
credential.SshPrivateKeyCredentialPrefix,
credential.JsonCredentialPrefix) {
badFields[globals.InjectedApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
break
}
@@ -0,0 +1,64 @@
begin;
create table credential_static_json_credential (
public_id wt_public_id primary key,
store_id wt_public_id not null
constraint credential_static_store_fkey
references credential_static_store (public_id)
on delete cascade
on update cascade,
project_id wt_public_id not null,
name wt_name,
description wt_description,
create_time wt_timestamp,
update_time wt_timestamp,
version wt_version,
object_encrypted bytea not null
constraint object_encrypted_must_not_be_empty
check(length(object_encrypted) > 0),
object_hmac bytea not null
constraint object_hmac_must_not_be_empty
check(length(object_hmac) > 0),
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,
constraint credential_static_fkey
foreign key (project_id, store_id, public_id)
references credential_static (project_id, store_id, public_id)
on delete cascade
on update cascade,
constraint credential_static_json_credential_store_id_name_uq
unique(store_id, name),
constraint credential_static_json_credential_store_id_public_id_uq
unique(store_id, public_id)
);
comment on table credential_static_json_credential is
'credential_static_json_credential is a table where each row is a resource that represents a static json credential. '
'It is a credential_static subtype and an aggregate root.';
create trigger update_version_column after update on credential_static_json_credential
for each row execute procedure update_version_column();
create trigger update_time_column before update on credential_static_json_credential
for each row execute procedure update_time_column();
create trigger default_create_time_column before insert on credential_static_json_credential
for each row execute procedure default_create_time();
create trigger immutable_columns before update on credential_static_json_credential
for each row execute procedure immutable_columns('public_id', 'store_id', 'project_id', 'create_time');
create trigger insert_credential_static_subtype before insert on credential_static_json_credential
for each row execute procedure insert_credential_static_subtype();
create trigger delete_credential_static_subtype after delete on credential_static_json_credential
for each row execute procedure delete_credential_static_subtype();
insert into oplog_ticket (name, version)
values
('credential_static_json_credential', 1);
commit;
@@ -416,6 +416,11 @@ begin;
values
('cs______wup1', 'cred____wup1', 'widget static username password credential', 'None', 'b_user', 'encrypted_password', 'hmac-value', 'kdkv___widget'),
('cs______wup2', 'cred____wup2', 'widget static username password credential', 'None', 's_user', 'encrypted_password', 'hmac-value', 'kdkv___widget');
insert into credential_static_json_credential
(store_id, public_id, name, description, object_encrypted, object_hmac, key_id)
values
('cs______wup1', 'cred____wjson1', 'widget static json credential', 'None', 'json_value'::bytea, 'hmac-value', 'kdkv___widget');
end;
$$ language plpgsql;
@@ -19,7 +19,8 @@ begin;
insert into target_static_credential
(project_id, target_id, credential_static_id, credential_purpose)
values
('p____bwidget', 'test______wb', 'cred____wup1', 'brokered');
('p____bwidget', 'test______wb', 'cred____wup1', 'brokered'),
('p____bwidget', 'test______wb', 'cred____wjson1', 'brokered');
select lives_ok('insert_valid_target_static_credential', 'insert valid target_static_credential failed');
-- create a credential_static_store and target in a different project
@@ -69,6 +69,11 @@ message Credential {
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.subtype) = "ssh_private_key"
];
JsonAttributes json_attributes = 103 [
(google.api.field_visibility).restriction = "INTERNAL",
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.subtype) = "json"
];
}
// Output only. The available actions on this resource for this user.
@@ -155,3 +160,25 @@ message SshPrivateKeyAttributes {
}
]; // @gotags: `class:"public"`
}
// The attributes of a JSON Credential.
message JsonAttributes {
// Input only. The secret associated with the credential.
google.protobuf.Struct object = 10 [
json_name = "object",
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.mask_mapping) = {
this: "attributes.object"
that: "Object"
}
]; // @gotags: `class:"secret"`
// Output only. The hmac value of the object.
string object_hmac = 20 [
json_name = "object_hmac",
(custom_options.v1.mask_mapping) = {
this: "attributes.object_hmac"
that: "ObjectHmac"
}
]; // @gotags: `class:"public"`
}
@@ -209,3 +209,66 @@ message SshPrivateKeyCredential {
that: "attributes.private_key_passphrase_hmac"
}];
}
message JsonCredential {
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
string public_id = 1;
// create_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp create_time = 2;
// update_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp update_time = 3;
// name is optional. If set, it must be unique within scope_id.
// @inject_tag: `gorm:"default:null"`
string name = 4 [(custom_options.v1.mask_mapping) = {
this: "Name"
that: "name"
}];
// description is optional.
// @inject_tag: `gorm:"default:null"`
string description = 5 [(custom_options.v1.mask_mapping) = {
this: "Description"
that: "description"
}];
// store_id of the owning static credential store.
// It must be set.
// @inject_tag: `gorm:"not_null"`
string store_id = 6;
// version allows optimistic locking of the resource.
// @inject_tag: `gorm:"default:null"`
uint32 version = 7;
// object is the plain-text of the json associated with the
// credential. We are not storing this plain-text object in the database.
// @inject_tag: `gorm:"-" wrapping:"pt,object"`
bytes object = 8 [(custom_options.v1.mask_mapping) = {
this: "Object"
that: "attributes.object"
}];
// object_encrypted is the ciphertext of the object. It is stored in
// the database.
// @inject_tag: `gorm:"column:object_encrypted;not_null" wrapping:"ct,object"`
bytes object_encrypted = 9;
// object_hmac is a sha256-hmac of the unencrypted object. It is recalculated
// everytime the object is updated.
// @inject_tag: `gorm:"not_null"`
bytes object_hmac = 10 [(custom_options.v1.mask_mapping) = {
this: "ObjectHmac"
that: "attributes.object_hmac"
}];
// The key_id of the kms database key used for encrypting this entry.
// It must be set.
// @inject_tag: `gorm:"not_null"`
string key_id = 11;
}
@@ -234,6 +234,102 @@ func TestCrudSpk(t *testing.T) {
assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode())
}
func TestCrudJson(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
client := tc.Client()
token := tc.Token()
client.SetToken(token.Token)
_, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId))
cs, err := credentialstores.NewClient(client).Create(tc.Context(), "static", proj.GetPublicId())
require.NoError(err)
require.NotNil(cs)
checkResource := func(step string, c *credentials.Credential, wantedName string, wantVersion uint32) {
assert.NotNil(c, "returned no resource", step)
assert.Equal(wantedName, c.Name, step)
assert.Equal(wantVersion, c.Version)
}
credClient := credentials.NewClient(client)
obj := map[string]interface{}{
"username": "admin",
"password": "pass",
}
cred, err := credClient.Create(tc.Context(), credential.JsonSubtype.String(), cs.Item.Id, credentials.WithName("foo"), credentials.WithJsonCredentialObject(obj))
require.NoError(err)
require.NotNil(cred)
checkResource("create", cred.Item, "foo", 1)
jsonAttributes, err := cred.GetItem().GetJsonAttributes()
require.NoError(err)
require.Nil(jsonAttributes.Object)
require.NotEmpty(jsonAttributes.ObjectHmac)
sshAttributes, err := cred.GetItem().GetSshPrivateKeyAttributes()
require.Error(err)
require.Nil(sshAttributes)
upAttributes, err := cred.GetItem().GetUsernamePasswordAttributes()
require.Error(err)
require.Nil(upAttributes)
// Validate object hmac was set and object is not set
originalObjectHmac, ok := cred.GetItem().Attributes["object_hmac"].(string)
require.True(ok)
require.NotNil(originalObjectHmac)
object, ok := cred.GetItem().Attributes["object"].(string)
require.False(ok)
require.Empty(object)
cred, err = credClient.Read(tc.Context(), cred.Item.Id)
require.NoError(err)
require.NotNil(cs)
checkResource("read", cred.Item, "foo", 1)
// Validate object hmac was set and object is not set
objectHmac, ok := cred.GetItem().Attributes["object_hmac"].(string)
require.True(ok)
require.NotNil(objectHmac)
object, ok = cred.GetItem().Attributes["object"].(string)
require.False(ok)
require.Empty(object)
cred, err = credClient.Update(tc.Context(), cred.Item.Id, cred.Item.Version, credentials.WithName("bar"))
require.NoError(err)
require.NotNil(cs)
checkResource("update", cred.Item, "bar", 2)
cred, err = credClient.Update(tc.Context(), cred.Item.Id, cred.Item.Version, credentials.WithJsonCredentialObject(map[string]interface{}{
"username": "not_admin",
"password": "not_password",
}))
require.NoError(err)
require.NotNil(cs)
checkResource("update", cred.Item, "bar", 3)
// Validate secret hmac was set & is not the same as the original value & secret is not set
objectHmac, ok = cred.GetItem().Attributes["object_hmac"].(string)
require.True(ok)
require.NotNil(objectHmac)
require.NotEqual(originalObjectHmac, objectHmac)
object, ok = cred.GetItem().Attributes["secrets"].(string)
require.False(ok)
require.Empty(object)
_, err = credClient.Delete(tc.Context(), cred.Item.Id)
assert.NoError(err)
_, err = credClient.Delete(tc.Context(), cred.Item.Id)
require.Error(err)
apiErr := api.AsServerError(err)
assert.NotNil(apiErr)
assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode())
}
func TestErrors(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tc := controller.NewTestController(t, nil)
@@ -15,6 +15,18 @@ function create_username_password_credential() {
-password env://BP
}
function create_json_credential() {
local name=$1
local sid=$2
local args=$3
boundary credentials create json \
-name $name \
-description 'test json credential' \
-credential-store-id $sid \
$args
}
function read_credential() {
boundary credentials read -id $1 -format json
}
+69 -1
View File
@@ -6,7 +6,8 @@ load _credential_stores
load _credentials
export NEW_STORE='credentials-test-store'
export NEW_CREDENTIAL='test'
export NEW_CREDENTIAL='test-up'
export NEW_JSON_CREDENTIAL='test-json'
@test "boundary/login: can login as default user" {
run login $DEFAULT_LOGIN
@@ -27,6 +28,13 @@ export NEW_CREDENTIAL='test'
[ "$status" -eq 0 ]
}
@test "boundary/credentials: can create $NEW_JSON_CREDENTIAL credential in $NEW_STORE store" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run create_json_credential $NEW_JSON_CREDENTIAL $csid '-string-kv username=admin -string-kv password=pass'
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/credentials: can not create already created $NEW_CREDENTIAL credential" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run create_username_password_credential $NEW_CREDENTIAL $csid 'username' 'password'
@@ -34,6 +42,13 @@ export NEW_CREDENTIAL='test'
[ "$status" -eq 1 ]
}
@test "boundary/credentials: can not create already created $NEW_JSON_CREDENTIAL credential" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run create_json_credential $NEW_JSON_CREDENTIAL $csid '-string-kv username=admin -string-kv password=pass'
echo "$output"
[ "$status" -eq 1 ]
}
@test "boundary/credentials: can read $NEW_CREDENTIAL credential" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local cid=$(credential_id $NEW_CREDENTIAL $csid)
@@ -42,6 +57,14 @@ export NEW_CREDENTIAL='test'
[ "$status" -eq 0 ]
}
@test "boundary/credentials: can read $NEW_JSON_CREDENTIAL credential" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local cid=$(credential_id $NEW_JSON_CREDENTIAL $csid)
run read_credential $cid
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/credentials: can delete $NEW_CREDENTIAL credential" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local cid=$(credential_id $NEW_CREDENTIAL $csid)
@@ -51,6 +74,43 @@ export NEW_CREDENTIAL='test'
[ "$status" -eq 0 ]
}
@test "boundary/credentials: can delete $NEW_JSON_CREDENTIAL credential" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local cid=$(credential_id $NEW_JSON_CREDENTIAL $csid)
run delete_credential $cid
echo "$output"
run has_status_code "$output" "204"
[ "$status" -eq 0 ]
}
@test "boundary/credentials: can not use object flag with kv flags for json credential" {
echo "{\"key\":\"value\"}" > cred_json_object
local object_file_path="file://$(pwd)/cred_json_object"
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local name='object & kv flag test'
run create_json_credential $name $csid "-object $object_file_path -kv password=pass"
echo "$output"
[ "$status" -eq 1 ]
local name='object & bool-kv flag test'
run create_json_credential $name $csid "-object $object_file_path -bool-kv password=true"
echo "$output"
[ "$status" -eq 1 ]
local name='object & num-kv flag test'
run create_json_credential $name $csid "-object $object_file_path -num-kv password=1234"
echo "$output"
[ "$status" -eq 1 ]
local name='object & string-kv flag test'
run create_json_credential $name $csid "-object $object_file_path -string-kv password=pass"
echo "$output"
[ "$status" -eq 1 ]
rm cred_json_object
}
@test "boundary/credential-stores: can not read deleted $NEW_CREDENTIAL credential" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local cid=$(credential_id $NEW_CREDENTIAL $csid)
@@ -72,4 +132,12 @@ export NEW_CREDENTIAL='test'
run read_credential_store $csid
echo "$output"
[ "$status" -eq 1 ]
}
@test "boundary/credential-stores: can not read deleted $NEW_JSON_CREDENTIAL credential" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local cid=$(credential_id $NEW_JSON_CREDENTIAL $csid)
run read_credential $cid
echo "$output"
[ "$status" -eq 1 ]
}
@@ -12,6 +12,7 @@ export NEW_STORE='test-for-add-credential-sources'
export NEW_CREDENTIAL='first-credential'
export NEW_CREDENTIAL1='second-credential'
export NEW_CREDENTIAL2='third-credential'
export NEW_JSON_CREDENTIAL='json-credential'
@test "boundary/login: can login as admin user" {
run login $DEFAULT_LOGIN
@@ -31,6 +32,13 @@ export NEW_CREDENTIAL2='third-credential'
[ "$status" -eq 0 ]
}
@test "boundary/credentials: can create $NEW_JSON_CREDENTIAL credential in $NEW_STORE store" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run create_json_credential $NEW_JSON_CREDENTIAL $csid '-string-kv username=admin -string-kv password=pass'
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/target: can add $NEW_CREDENTIAL credential source" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local cid=$(credential_id $NEW_CREDENTIAL $csid)
@@ -47,6 +55,26 @@ export NEW_CREDENTIAL2='third-credential'
[ "$status" -eq 0 ]
}
@test "boundary/target: can add $NEW_JSON_CREDENTIAL credential source" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local cid=$(credential_id $NEW_JSON_CREDENTIAL $csid)
run add_target_brokered_credential_sources $DEFAULT_TARGET $cid
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/target: validate $NEW_CREDENTIAL & $NEW_JSON_CREDENTIAL credential source present" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local credup_id=$(credential_id $NEW_CREDENTIAL $csid)
local credjson_id=$(credential_id $NEW_JSON_CREDENTIAL $csid)
run validate_credential_sources $DEFAULT_TARGET $credup_id
echo "$output"
[ "$status" -eq 0 ]
run validate_credential_sources $DEFAULT_TARGET $credjson_id
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/target: cannot add duplicate credential source" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local cid=$(credential_id $NEW_CREDENTIAL $csid)
@@ -71,6 +99,22 @@ export NEW_CREDENTIAL2='third-credential'
[ "$status" -eq 0 ]
}
@test "boundary/target: can delete $NEW_JSON_CREDENTIAL credential source" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local cid=$(credential_id $NEW_JSON_CREDENTIAL $csid)
run remove_target_brokered_credential_sources $DEFAULT_TARGET $cid
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/target: validate $NEW_JSON_CREDENTIAL credential source removed" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local cid=$(credential_id $NEW_JSON_CREDENTIAL $csid)
run validate_credential_sources_not_present $DEFAULT_TARGET $cid
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/credentials: can create additional credentials in $NEW_STORE store" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run create_username_password_credential $NEW_CREDENTIAL1 $csid 'username' 'password'
@@ -56,6 +56,7 @@ type Credential struct {
// *Credential_Attributes
// *Credential_UsernamePasswordAttributes
// *Credential_SshPrivateKeyAttributes
// *Credential_JsonAttributes
Attrs isCredential_Attrs `protobuf_oneof:"attrs"`
// 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" class:"public"` // @gotags: `class:"public"`
@@ -184,6 +185,13 @@ func (x *Credential) GetSshPrivateKeyAttributes() *SshPrivateKeyAttributes {
return nil
}
func (x *Credential) GetJsonAttributes() *JsonAttributes {
if x, ok := x.GetAttrs().(*Credential_JsonAttributes); ok {
return x.JsonAttributes
}
return nil
}
func (x *Credential) GetAuthorizedActions() []string {
if x != nil {
return x.AuthorizedActions
@@ -208,12 +216,18 @@ type Credential_SshPrivateKeyAttributes struct {
SshPrivateKeyAttributes *SshPrivateKeyAttributes `protobuf:"bytes,102,opt,name=ssh_private_key_attributes,json=sshPrivateKeyAttributes,proto3,oneof"`
}
type Credential_JsonAttributes struct {
JsonAttributes *JsonAttributes `protobuf:"bytes,103,opt,name=json_attributes,json=jsonAttributes,proto3,oneof"`
}
func (*Credential_Attributes) isCredential_Attrs() {}
func (*Credential_UsernamePasswordAttributes) isCredential_Attrs() {}
func (*Credential_SshPrivateKeyAttributes) isCredential_Attrs() {}
func (*Credential_JsonAttributes) isCredential_Attrs() {}
// The attributes of a UsernamePassword Credential.
type UsernamePasswordAttributes struct {
state protoimpl.MessageState
@@ -366,6 +380,64 @@ func (x *SshPrivateKeyAttributes) GetPrivateKeyPassphraseHmac() string {
return ""
}
// The attributes of a JSON Credential.
type JsonAttributes struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Input only. The secret associated with the credential.
Object *structpb.Struct `protobuf:"bytes,10,opt,name=object,proto3" json:"object,omitempty" class:"secret"` // @gotags: `class:"secret"`
// Output only. The hmac value of the object.
ObjectHmac string `protobuf:"bytes,20,opt,name=object_hmac,proto3" json:"object_hmac,omitempty" class:"public"` // @gotags: `class:"public"`
}
func (x *JsonAttributes) Reset() {
*x = JsonAttributes{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_api_resources_credentials_v1_credential_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *JsonAttributes) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*JsonAttributes) ProtoMessage() {}
func (x *JsonAttributes) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_resources_credentials_v1_credential_proto_msgTypes[3]
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 JsonAttributes.ProtoReflect.Descriptor instead.
func (*JsonAttributes) Descriptor() ([]byte, []int) {
return file_controller_api_resources_credentials_v1_credential_proto_rawDescGZIP(), []int{3}
}
func (x *JsonAttributes) GetObject() *structpb.Struct {
if x != nil {
return x.Object
}
return nil
}
func (x *JsonAttributes) GetObjectHmac() string {
if x != nil {
return x.ObjectHmac
}
return ""
}
var File_controller_api_resources_credentials_v1_credential_proto protoreflect.FileDescriptor
var file_controller_api_resources_credentials_v1_credential_proto_rawDesc = []byte{
@@ -388,7 +460,7 @@ var file_controller_api_resources_credentials_v1_credential_proto_rawDesc = []by
0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65,
0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61,
0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd3, 0x07, 0x0a, 0x0a,
0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd6, 0x08, 0x0a, 0x0a,
0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x30, 0x0a, 0x13, 0x63, 0x72,
0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69,
@@ -446,75 +518,95 @@ var file_controller_api_resources_credentials_v1_credential_proto_rawDesc = []by
0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0xfa, 0xd2, 0xe4, 0x93,
0x02, 0x0a, 0x12, 0x08, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x48, 0x00, 0x52, 0x17,
0x73, 0x73, 0x68, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x74, 0x74,
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 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, 0x42, 0x07, 0x0a, 0x05, 0x61, 0x74, 0x74, 0x72,
0x73, 0x22, 0xb6, 0x02, 0x0a, 0x1a, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x50, 0x61,
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
0x12, 0x61, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65,
0x42, 0x27, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1f, 0x0a, 0x13, 0x61, 0x74, 0x74, 0x72,
0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12,
0x08, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e,
0x61, 0x6d, 0x65, 0x12, 0x61, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18,
0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x80, 0x01, 0x0a, 0x0f, 0x6a, 0x73, 0x6f, 0x6e,
0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x67, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x37, 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, 0x63, 0x72, 0x65,
0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4a, 0x73, 0x6f, 0x6e,
0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x42, 0x1c, 0xa0, 0xda, 0x29, 0x01,
0x9a, 0xe3, 0x29, 0x04, 0x6a, 0x73, 0x6f, 0x6e, 0xfa, 0xd2, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08,
0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x48, 0x00, 0x52, 0x0e, 0x6a, 0x73, 0x6f, 0x6e,
0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 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, 0x42, 0x07, 0x0a, 0x05, 0x61,
0x74, 0x74, 0x72, 0x73, 0x22, 0xb6, 0x02, 0x0a, 0x1a, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d,
0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75,
0x74, 0x65, 0x73, 0x12, 0x61, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18,
0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61,
0x6c, 0x75, 0x65, 0x42, 0x27, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1f, 0x0a, 0x13, 0x61,
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f,
0x72, 0x64, 0x12, 0x08, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x08, 0x70, 0x61,
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x52, 0x0a, 0x0d, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f,
0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2c, 0xc2,
0xdd, 0x29, 0x28, 0x0a, 0x18, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e,
0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x12, 0x0c, 0x50,
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x48, 0x6d, 0x61, 0x63, 0x52, 0x0d, 0x70, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x22, 0xee, 0x04, 0x0a, 0x17, 0x53,
0x73, 0x68, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x74, 0x74, 0x72,
0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x61, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,
0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,
0x6d, 0x65, 0x12, 0x08, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x08, 0x75, 0x73,
0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x61, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f,
0x72, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e,
0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x27, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1f,
0x0a, 0x13, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65,
0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x08, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x52,
0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x6c, 0x0a, 0x0b, 0x70, 0x72, 0x69,
0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x2c, 0xa0, 0xda,
0x29, 0x01, 0xc2, 0xdd, 0x29, 0x24, 0x0a, 0x16, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
0x65, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x0a,
0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x0b, 0x70, 0x72, 0x69, 0x76,
0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x5d, 0x0a, 0x10, 0x70, 0x72, 0x69, 0x76, 0x61,
0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x1e, 0x20, 0x01, 0x28,
0x09, 0x42, 0x31, 0xc2, 0xdd, 0x29, 0x2d, 0x0a, 0x1b, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75,
0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f,
0x68, 0x6d, 0x61, 0x63, 0x12, 0x0e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79,
0x48, 0x6d, 0x61, 0x63, 0x52, 0x10, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65,
0x79, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x12, 0x97, 0x01, 0x0a, 0x16, 0x70, 0x72, 0x69, 0x76, 0x61,
0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73,
0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67,
0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x41, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x39, 0x0a,
0x21, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x76,
0x0a, 0x13, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x08, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52,
0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x52, 0x0a, 0x0d, 0x70, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09,
0x42, 0x2c, 0xc2, 0xdd, 0x29, 0x28, 0x0a, 0x18, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
0x65, 0x73, 0x2e, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63,
0x12, 0x0c, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x48, 0x6d, 0x61, 0x63, 0x52, 0x0d,
0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x22, 0xee, 0x04,
0x0a, 0x17, 0x53, 0x73, 0x68, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41,
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x61, 0x0a, 0x08, 0x75, 0x73, 0x65,
0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74,
0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x27, 0xa0, 0xda, 0x29, 0x01, 0xc2,
0xdd, 0x29, 0x1f, 0x0a, 0x13, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e,
0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x08, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61,
0x6d, 0x65, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x6c, 0x0a, 0x0b,
0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x14, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42,
0x2c, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x24, 0x0a, 0x16, 0x61, 0x74, 0x74, 0x72, 0x69,
0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65,
0x79, 0x12, 0x0a, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x0b, 0x70,
0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x5d, 0x0a, 0x10, 0x70, 0x72,
0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x1e,
0x20, 0x01, 0x28, 0x09, 0x42, 0x31, 0xc2, 0xdd, 0x29, 0x2d, 0x0a, 0x1b, 0x61, 0x74, 0x74, 0x72,
0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b,
0x65, 0x79, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x12, 0x0e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65,
0x4b, 0x65, 0x79, 0x48, 0x6d, 0x61, 0x63, 0x52, 0x10, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65,
0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x12, 0x97, 0x01, 0x0a, 0x16, 0x70, 0x72,
0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68,
0x72, 0x61, 0x73, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72,
0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x41, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd,
0x29, 0x39, 0x0a, 0x21, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70,
0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70,
0x68, 0x72, 0x61, 0x73, 0x65, 0x12, 0x14, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65,
0x79, 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x52, 0x16, 0x70, 0x72, 0x69,
0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72,
0x61, 0x73, 0x65, 0x12, 0x88, 0x01, 0x0a, 0x1b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f,
0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x5f, 0x68,
0x6d, 0x61, 0x63, 0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x42, 0x46, 0xc2, 0xdd, 0x29, 0x42, 0x0a,
0x26, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x76,
0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61,
0x73, 0x65, 0x12, 0x14, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x50, 0x61,
0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x52, 0x16, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74,
0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65,
0x12, 0x88, 0x01, 0x0a, 0x1b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79,
0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x5f, 0x68, 0x6d, 0x61, 0x63,
0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x42, 0x46, 0xc2, 0xdd, 0x29, 0x42, 0x0a, 0x26, 0x61, 0x74,
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65,
0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x5f,
0x68, 0x6d, 0x61, 0x63, 0x12, 0x18, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79,
0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x48, 0x6d, 0x61, 0x63, 0x52, 0x1b,
0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x73, 0x73,
0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x42, 0x58, 0x5a, 0x56, 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, 0x63, 0x72,
0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x3b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e,
0x74, 0x69, 0x61, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x73, 0x65, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x12, 0x18, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65,
0x4b, 0x65, 0x79, 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x48, 0x6d, 0x61,
0x63, 0x52, 0x1b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x70,
0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x22, 0xb2,
0x01, 0x0a, 0x0e, 0x4a, 0x73, 0x6f, 0x6e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
0x73, 0x12, 0x54, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x0a, 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, 0x23, 0xa0, 0xda, 0x29, 0x01,
0xc2, 0xdd, 0x29, 0x1b, 0x0a, 0x11, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x06, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52,
0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x4a, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63,
0x74, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x42, 0x28, 0xc2, 0xdd,
0x29, 0x24, 0x0a, 0x16, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x6f,
0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x12, 0x0a, 0x4f, 0x62, 0x6a, 0x65,
0x63, 0x74, 0x48, 0x6d, 0x61, 0x63, 0x52, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x68,
0x6d, 0x61, 0x63, 0x42, 0x58, 0x5a, 0x56, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,
0x73, 0x3b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -529,35 +621,38 @@ func file_controller_api_resources_credentials_v1_credential_proto_rawDescGZIP()
return file_controller_api_resources_credentials_v1_credential_proto_rawDescData
}
var file_controller_api_resources_credentials_v1_credential_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_controller_api_resources_credentials_v1_credential_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_controller_api_resources_credentials_v1_credential_proto_goTypes = []interface{}{
(*Credential)(nil), // 0: controller.api.resources.credentials.v1.Credential
(*UsernamePasswordAttributes)(nil), // 1: controller.api.resources.credentials.v1.UsernamePasswordAttributes
(*SshPrivateKeyAttributes)(nil), // 2: controller.api.resources.credentials.v1.SshPrivateKeyAttributes
(*scopes.ScopeInfo)(nil), // 3: controller.api.resources.scopes.v1.ScopeInfo
(*wrapperspb.StringValue)(nil), // 4: google.protobuf.StringValue
(*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp
(*structpb.Struct)(nil), // 6: google.protobuf.Struct
(*JsonAttributes)(nil), // 3: controller.api.resources.credentials.v1.JsonAttributes
(*scopes.ScopeInfo)(nil), // 4: controller.api.resources.scopes.v1.ScopeInfo
(*wrapperspb.StringValue)(nil), // 5: google.protobuf.StringValue
(*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp
(*structpb.Struct)(nil), // 7: google.protobuf.Struct
}
var file_controller_api_resources_credentials_v1_credential_proto_depIdxs = []int32{
3, // 0: controller.api.resources.credentials.v1.Credential.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
4, // 1: controller.api.resources.credentials.v1.Credential.name:type_name -> google.protobuf.StringValue
4, // 2: controller.api.resources.credentials.v1.Credential.description:type_name -> google.protobuf.StringValue
5, // 3: controller.api.resources.credentials.v1.Credential.created_time:type_name -> google.protobuf.Timestamp
5, // 4: controller.api.resources.credentials.v1.Credential.updated_time:type_name -> google.protobuf.Timestamp
6, // 5: controller.api.resources.credentials.v1.Credential.attributes:type_name -> google.protobuf.Struct
4, // 0: controller.api.resources.credentials.v1.Credential.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
5, // 1: controller.api.resources.credentials.v1.Credential.name:type_name -> google.protobuf.StringValue
5, // 2: controller.api.resources.credentials.v1.Credential.description:type_name -> google.protobuf.StringValue
6, // 3: controller.api.resources.credentials.v1.Credential.created_time:type_name -> google.protobuf.Timestamp
6, // 4: controller.api.resources.credentials.v1.Credential.updated_time:type_name -> google.protobuf.Timestamp
7, // 5: controller.api.resources.credentials.v1.Credential.attributes:type_name -> google.protobuf.Struct
1, // 6: controller.api.resources.credentials.v1.Credential.username_password_attributes:type_name -> controller.api.resources.credentials.v1.UsernamePasswordAttributes
2, // 7: controller.api.resources.credentials.v1.Credential.ssh_private_key_attributes:type_name -> controller.api.resources.credentials.v1.SshPrivateKeyAttributes
4, // 8: controller.api.resources.credentials.v1.UsernamePasswordAttributes.username:type_name -> google.protobuf.StringValue
4, // 9: controller.api.resources.credentials.v1.UsernamePasswordAttributes.password:type_name -> google.protobuf.StringValue
4, // 10: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.username:type_name -> google.protobuf.StringValue
4, // 11: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.private_key:type_name -> google.protobuf.StringValue
4, // 12: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.private_key_passphrase:type_name -> google.protobuf.StringValue
13, // [13:13] is the sub-list for method output_type
13, // [13:13] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
3, // 8: controller.api.resources.credentials.v1.Credential.json_attributes:type_name -> controller.api.resources.credentials.v1.JsonAttributes
5, // 9: controller.api.resources.credentials.v1.UsernamePasswordAttributes.username:type_name -> google.protobuf.StringValue
5, // 10: controller.api.resources.credentials.v1.UsernamePasswordAttributes.password:type_name -> google.protobuf.StringValue
5, // 11: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.username:type_name -> google.protobuf.StringValue
5, // 12: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.private_key:type_name -> google.protobuf.StringValue
5, // 13: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.private_key_passphrase:type_name -> google.protobuf.StringValue
7, // 14: controller.api.resources.credentials.v1.JsonAttributes.object:type_name -> google.protobuf.Struct
15, // [15:15] is the sub-list for method output_type
15, // [15:15] is the sub-list for method input_type
15, // [15:15] is the sub-list for extension type_name
15, // [15:15] is the sub-list for extension extendee
0, // [0:15] is the sub-list for field type_name
}
func init() { file_controller_api_resources_credentials_v1_credential_proto_init() }
@@ -602,11 +697,24 @@ func file_controller_api_resources_credentials_v1_credential_proto_init() {
return nil
}
}
file_controller_api_resources_credentials_v1_credential_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*JsonAttributes); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_controller_api_resources_credentials_v1_credential_proto_msgTypes[0].OneofWrappers = []interface{}{
(*Credential_Attributes)(nil),
(*Credential_UsernamePasswordAttributes)(nil),
(*Credential_SshPrivateKeyAttributes)(nil),
(*Credential_JsonAttributes)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -614,7 +722,7 @@ func file_controller_api_resources_credentials_v1_credential_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_api_resources_credentials_v1_credential_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumMessages: 4,
NumExtensions: 0,
NumServices: 0,
},