feat: static json credentials (#2423)
This commit is contained in:
1 parent
8c53d931aa
commit
546c5dc5be
42 files changed
+3207
-165
No files matched your search
@@ -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)
|
||||
}
|
||||
@@ -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"]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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+)?$`)
|
||||
|
||||
|
||||
@@ -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": {
|
||||
{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
@@ -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(×tamp.Timestamp{}),
|
||||
cmpopts.IgnoreFields(SshPrivateKeyCredential{}, "PassphraseUnneeded"),
|
||||
cmpopts.SortSlices(func(x, y credential.Static) bool {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user