Merge pull request #6207 from hashicorp/llb-password-credential-type

Password credential type
This commit is contained in:
Justin Nguyen authored and GitHub committed 2025-11-14 12:00:23 -06:00
commit dc7f0b00fc
66 files changed
+5784 -947

No files matched your search

+12
View File
@@ -179,6 +179,18 @@ func WithJsonCredentialObject(inObject map[string]interface{}) Option {
}
}
func WithPasswordCredentialPassword(inPassword string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
if !ok {
raw = any(map[string]any{})
}
val := raw.(map[string]any)
val["password"] = inPassword
o.postMap["attributes"] = val
}
}
func WithUsernamePasswordCredentialPassword(inPassword string) Option {
return func(o *options) {
raw, ok := o.postMap["attributes"]
@@ -0,0 +1,41 @@
// Code generated by "make api"; DO NOT EDIT.
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package credentials
import (
"fmt"
"github.com/mitchellh/mapstructure"
)
type PasswordAttributes struct {
Password string `json:"password,omitempty"`
PasswordHmac string `json:"password_hmac,omitempty"`
}
func AttributesMapToPasswordAttributes(in map[string]any) (*PasswordAttributes, error) {
if in == nil {
return nil, fmt.Errorf("nil input map")
}
var out PasswordAttributes
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) GetPasswordAttributes() (*PasswordAttributes, error) {
if pt.Type != "password" {
return nil, fmt.Errorf("asked to fetch %s-type attributes but credential is of type %s", "password", pt.Type)
}
return AttributesMapToPasswordAttributes(pt.Attributes)
}
+40
View File
@@ -12,6 +12,7 @@ import (
const (
usernamePasswordCredentialType = "username_password"
passwordCredentialType = "password"
sshPrivateKeyCredentialType = "ssh_private_key"
)
@@ -26,6 +27,16 @@ type UsernamePassword struct {
Consumed bool
}
// Password contains a password credential
type Password struct {
Password string `mapstructure:"password"`
Raw *targets.SessionCredential
// Consumed can be set by the caller to indicate that the credential has
// been used, e.g. displayed to the user
Consumed bool
}
// SshPrivateKey contains the username and private key with optional passphrase
// for the key
type SshPrivateKey struct {
@@ -41,6 +52,7 @@ type SshPrivateKey struct {
type Credentials struct {
UsernamePassword []UsernamePassword
Password []Password
SshPrivateKey []SshPrivateKey
// Unspecified are credentials that do not match one of the types above
Unspecified []*targets.SessionCredential
@@ -62,6 +74,11 @@ func (c Credentials) UnconsumedSessionCredentials() []*targets.SessionCredential
out = append(out, c.Raw)
}
}
for _, c := range c.Password {
if !c.Consumed {
out = append(out, c.Raw)
}
}
return out
}
@@ -76,6 +93,7 @@ func ParseCredentials(creds []*targets.SessionCredential) (Credentials, error) {
}
var upCred UsernamePassword
var pCred Password
var spkCred SshPrivateKey
switch cred.CredentialSource.CredentialType {
case usernamePasswordCredentialType:
@@ -90,6 +108,18 @@ func ParseCredentials(creds []*targets.SessionCredential) (Credentials, error) {
continue
}
case passwordCredentialType:
// Decode attributes from credential struct
if err := mapstructure.Decode(cred.Credential, &pCred); err != nil {
return Credentials{}, err
}
if pCred.Password != "" {
pCred.Raw = cred
out.Password = append(out.Password, pCred)
continue
}
case sshPrivateKeyCredentialType:
// Decode attributes from credential struct
if err := mapstructure.Decode(cred.Credential, &spkCred); err != nil {
@@ -116,6 +146,16 @@ func ParseCredentials(creds []*targets.SessionCredential) (Credentials, error) {
continue
}
// Attempt unmarshaling into password creds
if err := mapstructure.Decode(cred.Secret.Decoded, &pCred); err != nil {
return Credentials{}, err
}
if pCred.Password != "" {
pCred.Raw = cred
out.Password = append(out.Password, pCred)
continue
}
// Attempt unmarshaling into ssh private key creds
if err := mapstructure.Decode(cred.Secret.Decoded, &spkCred); err != nil {
return Credentials{}, err
+154 -1
View File
@@ -29,6 +29,15 @@ var (
},
}
typedPassword = &targets.SessionCredential{
CredentialSource: &targets.CredentialSource{
CredentialType: passwordCredentialType,
},
Credential: map[string]any{
"password": "pass",
},
}
typedSshPrivateKey = &targets.SessionCredential{
CredentialSource: &targets.CredentialSource{
CredentialType: sshPrivateKeyCredentialType,
@@ -75,6 +84,17 @@ var (
},
}
vaultPassword = &targets.SessionCredential{
CredentialSource: &targets.CredentialSource{
Type: vaultGenericLibrarySubtype,
},
Secret: &targets.SessionSecret{
Decoded: map[string]any{
"password": "vault-decoded-pass",
},
},
}
vaultSshPrivateKey = &targets.SessionCredential{
CredentialSource: &targets.CredentialSource{
Type: vaultGenericLibrarySubtype,
@@ -119,6 +139,17 @@ var (
},
}
staticPassword = &targets.SessionCredential{
CredentialSource: &targets.CredentialSource{
Type: staticSubtype,
},
Secret: &targets.SessionSecret{
Decoded: map[string]any{
"password": "static-decoded-pass",
},
},
}
staticSshPrivateKey = &targets.SessionCredential{
CredentialSource: &targets.CredentialSource{
Type: staticSubtype,
@@ -215,6 +246,21 @@ func Test_parseCredentials(t *testing.T) {
},
wantErr: false,
},
{
name: "password-typed",
creds: []*targets.SessionCredential{
typedPassword,
},
wantCreds: Credentials{
Password: []Password{
{
Password: "pass",
Raw: typedPassword,
},
},
},
wantErr: false,
},
{
name: "ssh-private-key-typed",
creds: []*targets.SessionCredential{
@@ -247,6 +293,21 @@ func Test_parseCredentials(t *testing.T) {
},
wantErr: false,
},
{
name: "vault-password-decoded",
creds: []*targets.SessionCredential{
vaultPassword,
},
wantCreds: Credentials{
Password: []Password{
{
Password: "vault-decoded-pass",
Raw: vaultPassword,
},
},
},
wantErr: false,
},
{
name: "vault-private-key-decoded",
creds: []*targets.SessionCredential{
@@ -343,6 +404,21 @@ func Test_parseCredentials(t *testing.T) {
},
wantErr: false,
},
{
name: "static-password-decoded",
creds: []*targets.SessionCredential{
staticPassword,
},
wantCreds: Credentials{
Password: []Password{
{
Password: "static-decoded-pass",
Raw: staticPassword,
},
},
},
wantErr: false,
},
{
name: "static-private-key-decoded",
creds: []*targets.SessionCredential{
@@ -388,7 +464,7 @@ func Test_parseCredentials(t *testing.T) {
creds: []*targets.SessionCredential{
staticSshPrivateKey, UnspecifiedCred1, vaultSshPrivateKey, typedUsernamePassword,
UnspecifiedCred, vaultUsernamePassword, typedSshPrivateKey, staticUsernamePassword,
staticKv,
staticKv, typedPassword, vaultPassword, staticPassword,
},
wantCreds: Credentials{
SshPrivateKey: []SshPrivateKey{
@@ -425,6 +501,20 @@ func Test_parseCredentials(t *testing.T) {
Raw: typedUsernamePassword,
},
},
Password: []Password{
{
Password: "static-decoded-pass",
Raw: staticPassword,
},
{
Password: "vault-decoded-pass",
Raw: vaultPassword,
},
{
Password: "pass",
Raw: typedPassword,
},
},
Unspecified: []*targets.SessionCredential{
UnspecifiedCred, UnspecifiedCred1, staticKv,
},
@@ -447,6 +537,7 @@ func Test_parseCredentials(t *testing.T) {
assert.ElementsMatch(tt.wantCreds.UsernamePassword, creds.UsernamePassword)
assert.ElementsMatch(tt.wantCreds.SshPrivateKey, creds.SshPrivateKey)
assert.ElementsMatch(tt.wantCreds.Unspecified, creds.Unspecified)
assert.ElementsMatch(tt.wantCreds.Password, creds.Password)
})
}
}
@@ -507,6 +598,29 @@ func Test_unconsumedSessionCredentials(t *testing.T) {
},
wantCreds: nil,
},
{
name: "p",
creds: Credentials{
Password: []Password{
{
Raw: vaultPassword,
},
},
},
wantCreds: []*targets.SessionCredential{vaultPassword},
},
{
name: "p-consumed",
creds: Credentials{
Password: []Password{
{
Raw: vaultPassword,
Consumed: true,
},
},
},
wantCreds: nil,
},
{
name: "Unspecified",
creds: Credentials{
@@ -542,10 +656,23 @@ func Test_unconsumedSessionCredentials(t *testing.T) {
Consumed: true,
},
},
Password: []Password{
{
Raw: staticPassword,
},
{
Raw: vaultPassword,
},
{
Raw: typedPassword,
Consumed: true,
},
},
Unspecified: []*targets.SessionCredential{UnspecifiedCred, UnspecifiedCred1},
},
wantCreds: []*targets.SessionCredential{
vaultSshPrivateKey, typedSshPrivateKey, vaultUsernamePassword, UnspecifiedCred, UnspecifiedCred1,
staticPassword, vaultPassword,
},
},
{
@@ -579,6 +706,20 @@ func Test_unconsumedSessionCredentials(t *testing.T) {
Consumed: true,
},
},
Password: []Password{
{
Raw: staticPassword,
Consumed: true,
},
{
Raw: vaultPassword,
Consumed: true,
},
{
Raw: typedPassword,
Consumed: true,
},
},
Unspecified: []*targets.SessionCredential{UnspecifiedCred, UnspecifiedCred1},
},
wantCreds: []*targets.SessionCredential{
@@ -610,11 +751,23 @@ func Test_unconsumedSessionCredentials(t *testing.T) {
Raw: typedUsernamePassword,
},
},
Password: []Password{
{
Raw: staticPassword,
},
{
Raw: vaultPassword,
},
{
Raw: typedPassword,
},
},
Unspecified: []*targets.SessionCredential{UnspecifiedCred, UnspecifiedCred1},
},
wantCreds: []*targets.SessionCredential{
staticSshPrivateKey, UnspecifiedCred1, vaultSshPrivateKey, typedUsernamePassword,
UnspecifiedCred, vaultUsernamePassword, typedSshPrivateKey, staticUsernamePassword,
staticPassword, vaultPassword, typedPassword,
},
},
}
@@ -0,0 +1,40 @@
// Code generated by "make api"; DO NOT EDIT.
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package sessionrecordings
import (
"fmt"
"github.com/mitchellh/mapstructure"
)
type PasswordCredentialAttributes struct {
PasswordHmac string `json:"password_hmac,omitempty"`
}
func AttributesMapToPasswordCredentialAttributes(in map[string]any) (*PasswordCredentialAttributes, error) {
if in == nil {
return nil, fmt.Errorf("nil input map")
}
var out PasswordCredentialAttributes
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) GetPasswordCredentialAttributes() (*PasswordCredentialAttributes, error) {
if pt.Type != "password" {
return nil, fmt.Errorf("asked to fetch %s-type attributes but credential is of type %s", "password", pt.Type)
}
return AttributesMapToPasswordCredentialAttributes(pt.Attributes)
}
+1
View File
@@ -11,6 +11,7 @@ const (
UnspecifiedCredentialType CredentialType = "unspecified"
UsernamePasswordCredentialType CredentialType = "username_password"
UsernamePasswordDomainCredentialType CredentialType = "username_password_domain"
PasswordCredentialType CredentialType = "password"
SshPrivateKeyCredentialType CredentialType = "ssh_private_key"
SshCertificateCredentialType CredentialType = "ssh_certificate"
JsonCredentialType CredentialType = "json"
+6
View File
@@ -83,6 +83,8 @@ const (
// UsernamePasswordCredentialPreviousPrefix is the previous prefix for
// username/password creds
UsernamePasswordCredentialPreviousPrefix = "cred"
// PasswordCredentialPrefix is the prefix for password creds
PasswordCredentialPrefix = "credp"
// SshPrivateKeyCredentialPrefix is the prefix for SSH private key creds
SshPrivateKeyCredentialPrefix = "credspk"
// JsonCredentialPrefix is the prefix for generic JSON creds
@@ -266,6 +268,10 @@ var prefixToResourceType = map[string]ResourceInfo{
Type: resource.Credential,
Subtype: UnknownSubtype,
},
PasswordCredentialPrefix: {
Type: resource.Credential,
Subtype: UnknownSubtype,
},
SshPrivateKeyCredentialPrefix: {
Type: resource.Credential,
Subtype: UnknownSubtype,
+25
View File
@@ -692,6 +692,22 @@ var inputStructs = []*structInfo{
versionEnabled: true,
createResponseTypes: []string{CreateResponseType, ReadResponseType, UpdateResponseType, DeleteResponseType, ListResponseType},
},
{
inProto: &credentials.PasswordAttributes{},
outFile: "credentials/password_attributes.gen.go",
subtypeName: "PasswordCredential",
subtype: "password",
fieldOverrides: []fieldInfo{
{
Name: "Password",
SkipDefault: true,
},
},
parentTypeName: "Credential",
templates: []*template.Template{
mapstructureConversionTemplate,
},
},
{
inProto: &credentials.UsernamePasswordAttributes{},
outFile: "credentials/username_password_attributes.gen.go",
@@ -1278,6 +1294,15 @@ var inputStructs = []*structInfo{
inProto: &session_recordings.Credential{},
outFile: "sessionrecordings/credential.gen.go",
},
{
inProto: &session_recordings.PasswordCredentialAttributes{},
outFile: "sessionrecordings/password_credential_attributes.gen.go",
subtype: "password",
parentTypeName: "Credential",
templates: []*template.Template{
mapstructureConversionTemplate,
},
},
{
inProto: &session_recordings.UsernamePasswordCredentialAttributes{},
outFile: "sessionrecordings/username_password_credential_attributes.gen.go",
+12
View File
@@ -621,6 +621,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
Func: "create",
}
}),
"credentials create password": wrapper.Wrap(func() wrapper.WrappableCommand {
return &credentialscmd.PasswordCommand{
Command: base.NewCommand(ui, opts...),
Func: "create",
}
}),
"credentials create username-password": wrapper.Wrap(func() wrapper.WrappableCommand {
return &credentialscmd.UsernamePasswordCommand{
Command: base.NewCommand(ui, opts...),
@@ -651,6 +657,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
Func: "update",
}
}),
"credentials update password": wrapper.Wrap(func() wrapper.WrappableCommand {
return &credentialscmd.PasswordCommand{
Command: base.NewCommand(ui, opts...),
Func: "update",
}
}),
"credentials update username-password": wrapper.Wrap(func() wrapper.WrappableCommand {
return &credentialscmd.UsernamePasswordCommand{
Command: base.NewCommand(ui, opts...),
+13 -3
View File
@@ -49,13 +49,21 @@ func (r *redisFlags) buildArgs(c *Command, port, ip, _ string, creds proxy.Crede
var username, password string
retCreds = creds
if len(retCreds.UsernamePassword) > 0 {
switch {
case len(retCreds.UsernamePassword) > 0:
// Mark credential as consumed, such that it is not printed to the user
retCreds.UsernamePassword[0].Consumed = true
// Grab the first available username/password credential brokered
username = retCreds.UsernamePassword[0].Username
password = retCreds.UsernamePassword[0].Password
case len(retCreds.Password) > 0:
// Mark credential as consumed, such that it is not printed to the user
retCreds.Password[0].Consumed = true
// Grab the first available password credential brokered
password = retCreds.Password[0].Password
}
switch r.flagRedisStyle {
@@ -69,12 +77,14 @@ func (r *redisFlags) buildArgs(c *Command, port, ip, _ string, creds proxy.Crede
case username != "":
args = append(args, "--user", username)
case c.flagUsername != "":
args = append(args, "--user", c.flagUsername, "--askpass")
args = append(args, "--user", c.flagUsername)
}
// Password is read by redis-cli via environment variable. The password disappears after the command exits.
if password != "" {
envs = append(envs, fmt.Sprintf("REDISCLI_AUTH=%s", password))
} else {
// prompt for password if it wasn't provided
envs = append(envs, "--askpass")
}
}
@@ -0,0 +1,278 @@
// Code generated by "make cli"; DO NOT EDIT.
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
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 initPasswordFlags() {
flagsOnce.Do(func() {
extraFlags := extraPasswordActionsFlagsMapFunc()
for k, v := range extraFlags {
flagsPasswordMap[k] = append(flagsPasswordMap[k], v...)
}
})
}
var (
_ cli.Command = (*PasswordCommand)(nil)
_ cli.CommandAutocomplete = (*PasswordCommand)(nil)
)
type PasswordCommand struct {
*base.Command
Func string
plural string
extraPasswordCmdVars
}
func (c *PasswordCommand) AutocompleteArgs() complete.Predictor {
initPasswordFlags()
return complete.PredictAnything
}
func (c *PasswordCommand) AutocompleteFlags() complete.Flags {
initPasswordFlags()
return c.Flags().Completions()
}
func (c *PasswordCommand) Synopsis() string {
if extra := extraPasswordSynopsisFunc(c); extra != "" {
return extra
}
synopsisStr := "credential"
synopsisStr = fmt.Sprintf("%s %s", "password-type", synopsisStr)
return common.SynopsisFunc(c.Func, synopsisStr)
}
func (c *PasswordCommand) Help() string {
initPasswordFlags()
var helpStr string
helpMap := common.HelpMap("credential")
switch c.Func {
default:
helpStr = c.extraPasswordHelpFunc(helpMap)
}
// Keep linter from complaining if we don't actually generate code using it
_ = helpMap
return helpStr
}
var flagsPasswordMap = map[string][]string{
"create": {"credential-store-id", "name", "description"},
"update": {"id", "name", "description", "version"},
}
func (c *PasswordCommand) Flags() *base.FlagSets {
if len(flagsPasswordMap[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, "password-type credential", flagsPasswordMap, c.Func)
extraPasswordFlagsFunc(c, set, f)
return set
}
func (c *PasswordCommand) Run(args []string) int {
initPasswordFlags()
switch c.Func {
case "":
return cli.RunResultHelp
}
c.plural = "password-type credential"
switch c.Func {
case "list":
c.plural = "password-type credentials"
}
f := c.Flags()
if err := f.Parse(args); err != nil {
c.PrintCliError(err)
return base.CommandUserError
}
if strutil.StrListContains(flagsPasswordMap[c.Func], "id") && c.FlagId == "" {
c.PrintCliError(errors.New("ID is required but not passed in via -id"))
return base.CommandUserError
}
var opts []credentials.Option
if strutil.StrListContains(flagsPasswordMap[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 ok := extraPasswordFlagsHandlingFunc(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, "password", 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 = executeExtraPasswordActions(c, resp, item, err, credentialsClient, version, opts)
if exitCode := c.checkFuncError(err); exitCode > 0 {
return exitCode
}
output, err := printCustomPasswordActionOutput(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 *PasswordCommand) 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 (
extraPasswordActionsFlagsMapFunc = func() map[string][]string { return nil }
extraPasswordSynopsisFunc = func(*PasswordCommand) string { return "" }
extraPasswordFlagsFunc = func(*PasswordCommand, *base.FlagSets, *base.FlagSet) {}
extraPasswordFlagsHandlingFunc = func(*PasswordCommand, *base.FlagSets, *[]credentials.Option) bool { return true }
executeExtraPasswordActions = func(_ *PasswordCommand, inResp *api.Response, inItem *credentials.Credential, inErr error, _ *credentials.Client, _ uint32, _ []credentials.Option) (*api.Response, *credentials.Credential, error) {
return inResp, inItem, inErr
}
printCustomPasswordActionOutput = func(*PasswordCommand) (bool, error) { return false, nil }
)
@@ -0,0 +1,96 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package credentialscmd
import (
"errors"
"fmt"
"github.com/hashicorp/boundary/api/credentials"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/go-secure-stdlib/parseutil"
)
func init() {
extraPasswordFlagsFunc = extraPasswordFlagsFuncImpl
extraPasswordActionsFlagsMapFunc = extraPasswordActionsFlagsMapFuncImpl
extraPasswordFlagsHandlingFunc = extraPasswordFlagHandlingFuncImpl
}
type extraPasswordCmdVars struct {
flagPassword string
}
func extraPasswordActionsFlagsMapFuncImpl() map[string][]string {
flags := map[string][]string{
"create": {
passwordFlagName,
},
}
flags["update"] = flags["create"]
return flags
}
func extraPasswordFlagsFuncImpl(c *PasswordCommand, set *base.FlagSets, _ *base.FlagSet) {
f := set.NewFlagSet("Password Credential Options")
for _, name := range flagsPasswordMap[c.Func] {
switch name {
case passwordFlagName:
f.StringVar(&base.StringVar{
Name: passwordFlagName,
Target: &c.flagPassword,
Usage: "The password associated with the credential. This can be a file on disk (file://) from which the value will be read, or an env var (env://) from which the value will be read.",
})
}
}
}
func extraPasswordFlagHandlingFuncImpl(c *PasswordCommand, _ *base.FlagSets, opts *[]credentials.Option) bool {
switch c.flagPassword {
case "":
default:
password, err := parseutil.MustParsePath(c.flagPassword)
switch {
case err == nil:
case errors.Is(err, parseutil.ErrNotParsed):
c.UI.Error("Password flag must be used with env:// or file:// syntax")
return false
default:
c.UI.Error(fmt.Sprintf("Error parsing password flag: %v", err))
return false
}
*opts = append(*opts, credentials.WithPasswordCredentialPassword(password))
}
return true
}
func (c *PasswordCommand) extraPasswordHelpFunc(_ map[string]func() string) string {
var helpStr string
switch c.Func {
case "create":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary credentials create password -credential-store-id [options] [args]",
"",
" Create a password credential. Example:",
"",
` $ boundary credentials create password -credential-store-id csvlt_1234567890 -password pass`,
"",
"",
})
case "update":
helpStr = base.WrapForHelpText([]string{
"Usage: boundary credentials update password [options] [args]",
"",
" Update a password credential given its ID. Example:",
"",
` $ boundary credentials update password -id clvlt_1234567890 -name devops -description "For DevOps usage"`,
"",
"",
})
}
return helpStr + c.Flags().Help()
}
+16
View File
@@ -383,6 +383,22 @@ var inputStructs = map[string][]*cmdInfo{
Container: "CredentialStore",
HasId: true,
},
{
ResourceType: resource.Credential.String(),
Pkg: "credentials",
StdActions: []string{"create", "update"},
SubActionPrefix: "password",
HasExtraCommandVars: true,
SkipNormalHelp: true,
HasExtraHelpFunc: true,
HasId: true,
HasName: true,
HasDescription: true,
Container: "CredentialStore",
VersionedActions: []string{"update"},
NeedsSubtypeInCreate: true,
PrefixAttributeFieldErrorsWithSubactionPrefix: true,
},
{
ResourceType: resource.Credential.String(),
Pkg: "credentials",
+7
View File
@@ -143,6 +143,13 @@ type UsernamePasswordDomain interface {
Domain() string
}
// PasswordOnly is a credential containing a password.
// Does not follow naming convention to avoid conflict with existing Password type.
type PasswordOnly interface {
Credential
Password() Password
}
// SshPrivateKey is a credential containing a username an SSH private key and
// an optional private key passphrase.
type SshPrivateKey interface {
+12
View File
@@ -16,6 +16,7 @@ func init() {
globals.RegisterPrefixToResourceInfo(globals.UsernamePasswordCredentialPrefix, resource.Credential, Domain, UsernamePasswordSubtype)
globals.RegisterPrefixToResourceInfo(globals.UsernamePasswordDomainCredentialPrefix, resource.Credential, Domain, UsernamePasswordDomainSubtype)
globals.RegisterPrefixToResourceInfo(globals.UsernamePasswordCredentialPreviousPrefix, resource.Credential, Domain, UsernamePasswordSubtype)
globals.RegisterPrefixToResourceInfo(globals.PasswordCredentialPrefix, resource.Credential, Domain, PasswordSubtype)
globals.RegisterPrefixToResourceInfo(globals.SshPrivateKeyCredentialPrefix, resource.Credential, Domain, SshPrivateKeySubtype)
globals.RegisterPrefixToResourceInfo(globals.JsonCredentialPrefix, resource.Credential, Domain, JsonSubtype)
}
@@ -25,6 +26,8 @@ const (
UsernamePasswordDomainSubtype = globals.Subtype("username_password_domain")
PasswordSubtype = globals.Subtype("password")
SshPrivateKeySubtype = globals.Subtype("ssh_private_key")
JsonSubtype = globals.Subtype("json")
@@ -48,6 +51,15 @@ func NewUsernamePasswordDomainCredentialId(ctx context.Context) (string, error)
return id, nil
}
// NewPasswordCredentialId generates a new public ID for a password credential.
func NewPasswordCredentialId(ctx context.Context) (string, error) {
id, err := db.NewPublicId(ctx, globals.PasswordCredentialPrefix)
if err != nil {
return "", errors.Wrap(ctx, err, "credential.NewPasswordCredentialId")
}
return id, nil
}
// New SshPrivateKeyCredentialId generates a new public ID for an SSH private key credential.
func NewSshPrivateKeyCredentialId(ctx context.Context) (string, error) {
id, err := db.NewPublicId(ctx, globals.SshPrivateKeyCredentialPrefix)
+18
View File
@@ -92,6 +92,24 @@ func (c *listCredentialResult) toCredential(ctx context.Context) (credential.Sta
cred.PasswordHmac = []byte(c.Hmac1)
}
return cred, nil
case "p":
cred := &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
PublicId: c.PublicId,
StoreId: c.StoreId,
Name: c.Name,
Description: c.Description,
CreateTime: c.CreateTime,
UpdateTime: c.UpdateTime,
Version: uint32(c.Version),
KeyId: c.KeyId,
},
}
// Assign byte slices only if the string isn't empty
if c.Hmac1 != "" {
cred.PasswordHmac = []byte(c.Hmac1)
}
return cred, nil
case "ssh":
cred := &SshPrivateKeyCredential{
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
@@ -0,0 +1,141 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package static
import (
"context"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/static/store"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/libs/crypto"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/boundary/internal/types/resource"
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 = (*PasswordCredential)(nil)
// PasswordCredential contains the credential with a password.
// It is owned by a credential store.
type PasswordCredential struct {
*store.PasswordCredential
tableName string `gorm:"-"`
}
// NewPasswordCredential creates a new in memory static Credential containing a
// password that is assigned to storeId. Name and description are the only
// valid options. All other options are ignored.
func NewPasswordCredential(
storeId string,
password credential.Password,
opt ...Option,
) (*PasswordCredential, error) {
opts := getOpts(opt...)
l := &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
StoreId: storeId,
Name: opts.withName,
Description: opts.withDescription,
Password: []byte(password),
},
}
return l, nil
}
func allocPasswordCredential() *PasswordCredential {
return &PasswordCredential{
PasswordCredential: &store.PasswordCredential{},
}
}
func (c *PasswordCredential) clone() *PasswordCredential {
cp := proto.Clone(c.PasswordCredential)
return &PasswordCredential{
PasswordCredential: cp.(*store.PasswordCredential),
}
}
// TableName returns the table name.
func (c *PasswordCredential) TableName() string {
if c.tableName != "" {
return c.tableName
}
return "credential_static_password_credential"
}
// SetTableName sets the table name.
func (c *PasswordCredential) SetTableName(n string) {
c.tableName = n
}
// GetResourceType returns the resource type of the Credential
func (c *PasswordCredential) GetResourceType() resource.Type {
return resource.Credential
}
func (c *PasswordCredential) oplog(op oplog.OpType) oplog.Metadata {
metadata := oplog.Metadata{
"resource-public-id": []string{c.PublicId},
"resource-type": []string{"credential-static-password"},
"op-type": []string{op.String()},
}
if c.StoreId != "" {
metadata["store-id"] = []string{c.StoreId}
}
return metadata
}
func (c *PasswordCredential) encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "static.(PasswordCredential).encrypt"
if len(c.Password) == 0 {
return errors.New(ctx, errors.InvalidParameter, op, "no password defined")
}
if err := structwrapping.WrapStruct(ctx, cipher, c.PasswordCredential, 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.hmacPassword(ctx, cipher); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
}
func (c *PasswordCredential) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "static.(PasswordCredential).decrypt"
if err := structwrapping.UnwrapStruct(ctx, cipher, c.PasswordCredential, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
func (c *PasswordCredential) hmacPassword(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "static.(PasswordCredential).hmacPassword"
if cipher == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
}
hm, err := crypto.HmacSha256(ctx, c.Password, cipher, []byte(c.StoreId), nil, crypto.WithEd25519())
if err != nil {
return errors.Wrap(ctx, err, op)
}
c.PasswordHmac = []byte(hm)
return nil
}
type deletedPasswordCredential struct {
PublicId string `gorm:"primary_key"`
DeleteTime *timestamp.Timestamp
}
// TableName returns the tablename to override the default gorm table name
func (s *deletedPasswordCredential) TableName() string {
return "credential_static_password_credential_deleted"
}
@@ -0,0 +1,164 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
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 TestPasswordCredential_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)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStore(t, conn, wrapper, prj.PublicId)
type args struct {
password credential.Password
storeId string
options []Option
}
tests := []struct {
name string
args args
want *PasswordCredential
wantCreateErr bool
wantEncryptErr bool
}{
{
name: "missing-password",
args: args{
storeId: cs.PublicId,
},
want: allocPasswordCredential(),
wantEncryptErr: true,
},
{
name: "missing-store-id",
args: args{
password: "test-pass",
},
want: allocPasswordCredential(),
wantCreateErr: true,
},
{
name: "valid-no-options",
args: args{
password: "test-pass",
storeId: cs.PublicId,
},
want: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Password: []byte("test-pass"),
StoreId: cs.PublicId,
},
},
},
{
name: "valid-with-name",
args: args{
password: "test-pass",
storeId: cs.PublicId,
options: []Option{WithName("my-credential")},
},
want: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Password: []byte("test-pass"),
StoreId: cs.PublicId,
Name: "my-credential",
},
},
},
{
name: "valid-with-description",
args: args{
password: "test-pass",
storeId: cs.PublicId,
options: []Option{WithDescription("my-credential-description")},
},
want: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Password: []byte("test-pass"),
StoreId: cs.PublicId,
Description: "my-credential-description",
},
},
},
}
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 := NewPasswordCredential(tt.args.storeId, tt.args.password, tt.args.options...)
require.NoError(err)
require.NotNil(got)
assert.Emptyf(got.PublicId, "PublicId set")
id, err := credential.NewPasswordCredentialId(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 := allocPasswordCredential()
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.CtPassword = nil
// encrypt also calculates the hmac, validate it is correct
hm, err := crypto.HmacSha256(ctx, got.Password, databaseWrapper, []byte(got.StoreId), nil, crypto.WithEd25519())
require.NoError(err)
tt.want.PasswordHmac = []byte(hm)
assert.Empty(cmp.Diff(tt.want, got2.clone(), protocmp.Transform()))
})
}
}
+95
View File
@@ -26,6 +26,16 @@ select distinct upd.public_id,
and upd.key_id = ?;
`
credStaticPasswordRewrapQuery = `
select distinct pass.public_id,
pass.password_encrypted,
pass.key_id
from credential_static_password_credential pass
inner join credential_static_store store
on store.public_id = pass.store_id
where store.project_id = ?
and pass.key_id = ?;
`
credStaticSshPrivKeyRewrapQuery = `
select distinct ssh.public_id,
ssh.private_key_encrypted,
@@ -56,6 +66,7 @@ select sum(reltuples::bigint) as estimate
'credential_static_json_credential'::regclass,
'credential_static_username_password_credential'::regclass,
'credential_static_username_password_domain_credential'::regclass,
'credential_static_password_credential'::regclass,
'credential_static_ssh_private_key_credential'::regclass
)
`
@@ -83,6 +94,11 @@ upd_creds as (
from credential_static_username_password_domain_credential
where public_id in (select public_id from credentials)
),
p_creds as (
select *
from credential_static_password_credential
where public_id in (select public_id from credentials)
),
ssh_creds as (
select *
from credential_static_ssh_private_key_credential
@@ -137,6 +153,22 @@ final as (
'upd' as type
from upd_creds
union
select public_id,
store_id,
project_id,
name,
description,
create_time,
update_time,
version,
null as username, -- Add this to make the union uniform
null as domain, -- Add this to make the union uniform
key_id,
password_hmac as hmac1,
null::bytea as hmac2, -- Add this to make the union uniform
'p' as type
from p_creds
union
select public_id,
store_id,
project_id,
@@ -182,6 +214,11 @@ upd_creds as (
from credential_static_username_password_domain_credential
where public_id in (select public_id from credentials)
),
p_creds as (
select *
from credential_static_password_credential
where public_id in (select public_id from credentials)
),
ssh_creds as (
select *
from credential_static_ssh_private_key_credential
@@ -236,6 +273,22 @@ final as (
'upd' as type
from upd_creds
union
select public_id,
store_id,
project_id,
name,
description,
create_time,
update_time,
version,
null as username, -- Add this to make the union uniform
null as domain, -- Add this to make the union uniform
key_id,
password_hmac as hmac1,
null::bytea as hmac2, -- Add this to make the union uniform
'p' as type
from p_creds
union
select public_id,
store_id,
project_id,
@@ -281,6 +334,11 @@ upd_creds as (
from credential_static_username_password_domain_credential
where public_id in (select public_id from credentials)
),
p_creds as (
select *
from credential_static_password_credential
where public_id in (select public_id from credentials)
),
ssh_creds as (
select *
from credential_static_ssh_private_key_credential
@@ -335,6 +393,22 @@ final as (
'upd' as type
from upd_creds
union
select public_id,
store_id,
project_id,
name,
description,
create_time,
update_time,
version,
null as username, -- Add this to make the union uniform
null as domain, -- Add this to make the union uniform
key_id,
password_hmac as hmac1,
null::bytea as hmac2, -- Add this to make the union uniform
'p' as type
from p_creds
union
select public_id,
store_id,
project_id,
@@ -381,6 +455,11 @@ upd_creds as (
from credential_static_username_password_domain_credential
where public_id in (select public_id from credentials)
),
p_creds as (
select *
from credential_static_password_credential
where public_id in (select public_id from credentials)
),
ssh_creds as (
select *
from credential_static_ssh_private_key_credential
@@ -435,6 +514,22 @@ final as (
'upd' as type
from upd_creds
union
select public_id,
store_id,
project_id,
name,
description,
create_time,
update_time,
version,
null as username, -- Add this to make the union uniform
null as domain, -- Add this to make the union uniform
key_id,
password_hmac as hmac1,
null::bytea as hmac2, -- Add this to make the union uniform
'p' as type
from p_creds
union
select public_id,
store_id,
project_id,
@@ -196,6 +196,89 @@ func (r *Repository) CreateUsernamePasswordDomainCredential(
return newCred, nil
}
// CreatePasswordCredential inserts c into the repository and returns a new
// PasswordCredential 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 password is encrypted and a HmacSha256 of the password is calculated. Only the
// PasswordHmac is returned, the plain-text and encrypted password 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) CreatePasswordCredential(
ctx context.Context,
projectId string,
c *PasswordCredential,
_ ...Option,
) (*PasswordCredential, error) {
const op = "static.(Repository).CreatePasswordCredential"
if c == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing credential")
}
if c.PasswordCredential == 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.Password == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing password")
}
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.NewPasswordCredentialId(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 *PasswordCredential
_, 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 password fields, only PasswordHmac should be returned
newCred.CtPassword = nil
newCred.Password = nil
return newCred, nil
}
// CreateSshPrivateKeyCredential inserts c into the repository and returns a new
// SshPrivateKeyCredential containing the credential's PublicId. c is not
// changed. c must not contain a PublicId. The PublicId is generated and
@@ -411,6 +494,20 @@ func (r *Repository) LookupCredential(ctx context.Context, publicId string, _ ..
updCred.Password = nil
cred = updCred
case credential.PasswordSubtype:
pCred := allocPasswordCredential()
pCred.PublicId = publicId
if err := r.reader.LookupByPublicId(ctx, pCred); 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 password fields, only passwordHmac should be returned
pCred.CtPassword = nil
pCred.Password = nil
cred = pCred
case credential.SshPrivateKeySubtype:
spkCred := allocSshPrivateKeyCredential()
spkCred.PublicId = publicId
@@ -676,6 +773,118 @@ func (r *Repository) UpdateUsernamePasswordDomainCredential(ctx context.Context,
return returnedCredential, rowsUpdated, nil
}
// UpdatePasswordCredential updates the repository entry for c.PublicId with
// the values in c for the fields listed in fieldMaskPaths. It returns a
// new PasswordCredential 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 Password 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) UpdatePasswordCredential(ctx context.Context,
projectId string,
c *PasswordCredential,
version uint32,
fieldMaskPaths []string,
_ ...Option,
) (*PasswordCredential, int, error) {
const op = "static.(Repository).UpdatePasswordCredential"
if c == nil {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing credential")
}
if c.PasswordCredential == 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()
for _, f := range fieldMaskPaths {
switch {
case strings.EqualFold(nameField, f):
case strings.EqualFold(descriptionField, f):
case strings.EqualFold(passwordField, f):
default:
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, f)
}
}
dbMask, nullFields := dbw.BuildUpdatePaths(
map[string]any{
nameField: c.Name,
descriptionField: c.Description,
passwordField: c.Password,
},
fieldMaskPaths,
nil,
)
if len(dbMask) == 0 && len(nullFields) == 0 {
return nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "missing field mask")
}
for _, f := range fieldMaskPaths {
if strings.EqualFold(passwordField, f) {
// Password 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 PasswordHmac and CtPassword masks for update.
dbMask = append(dbMask, "PasswordHmac", "CtPassword", "KeyId")
}
}
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 *PasswordCredential
_, 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 password fields, only PasswordHmac should be returned
returnedCredential.CtPassword = nil
returnedCredential.Password = nil
return returnedCredential, rowsUpdated, nil
}
// UpdateSshPrivateKeyCredential updates the repository entry for c.PublicId
// with the values in c for the fields listed in fieldMaskPaths. It returns a
// new SshPrivateKeyCredential containing the updated values and a count of the
@@ -1089,6 +1298,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.PasswordSubtype:
c := allocPasswordCredential()
c.PublicId = id
input = c
md = c.oplog(oplog.OpType_OP_TYPE_DELETE)
case credential.SshPrivateKeySubtype:
c := allocSshPrivateKeyCredential()
c.PublicId = id
@@ -1177,6 +1391,13 @@ func (r *Repository) ListDeletedCredentialIds(ctx context.Context, since time.Ti
for _, cl := range deletedUsernamePasswordDomainCredentials {
credentialStoreIds = append(credentialStoreIds, cl.PublicId)
}
var deletedPasswordCredentials []*deletedPasswordCredential
if err := r.SearchWhere(ctx, &deletedPasswordCredentials, "delete_time >= ?", []any{since}); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query deleted password credentials"))
}
for _, cl := range deletedPasswordCredentials {
credentialStoreIds = append(credentialStoreIds, cl.PublicId)
}
var deletedSSHPrivateKeyCredentials []*deletedSSHPrivateKeyCredential
if err := r.SearchWhere(ctx, &deletedSSHPrivateKeyCredentials, "delete_time >= ?", []any{since}); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query deleted ssh private key credentials"))
@@ -391,6 +391,162 @@ func TestRepository_CreateUsernamePasswordDomainCredential(t *testing.T) {
})
}
func TestRepository_CreatePasswordCredential(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))
cs := TestCredentialStore(t, conn, wrapper, prj.PublicId)
tests := []struct {
name string
projectId string
cred *PasswordCredential
wantErr bool
wantErrCode errors.Code
}{
{
name: "missing-store",
wantErr: true,
wantErrCode: errors.InvalidParameter,
},
{
name: "missing-embedded-cred",
cred: &PasswordCredential{},
wantErr: true,
wantErrCode: errors.InvalidParameter,
},
{
name: "missing-project-id",
cred: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Password: []byte("secret"),
StoreId: cs.PublicId,
},
},
wantErr: true,
wantErrCode: errors.InvalidParameter,
},
{
name: "missing-password",
projectId: prj.PublicId,
cred: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
StoreId: cs.PublicId,
},
},
wantErr: true,
wantErrCode: errors.InvalidParameter,
},
{
name: "missing-store-id",
projectId: prj.PublicId,
cred: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Password: []byte("secret"),
},
},
wantErr: true,
wantErrCode: errors.InvalidParameter,
},
{
name: "valid",
projectId: prj.PublicId,
cred: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Password: []byte("secret"),
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.CreatePasswordCredential(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, globals.PasswordCredentialPrefix, got.PublicId)
assert.Nil(got.Password)
assert.Nil(got.CtPassword)
// Validate password
lookupCred := allocPasswordCredential()
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.Password, lookupCred.Password)
assert.Empty(got.Password)
assert.Empty(got.CtPassword)
assert.NotEmpty(got.PasswordHmac)
// Validate hmac
hm, err := crypto.HmacSha256(ctx, tt.cred.Password, databaseWrapper, []byte(tt.cred.StoreId), nil, crypto.WithEd25519())
require.NoError(err)
assert.Equal([]byte(hm), got.PasswordHmac)
// 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.GetPublicId())
require.NoError(err)
prjCs := TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
prj2Cs := TestCredentialStore(t, conn, wrapper, prj2.GetPublicId())
in, err := NewPasswordCredential(prjCs.GetPublicId(), "pass", WithName("my-name"), WithDescription("original"))
assert.NoError(err)
got, err := repo.CreatePasswordCredential(ctx, prj.PublicId, in)
require.NoError(err)
assert.Equal(in.Name, got.Name)
assert.Equal(in.Description, got.Description)
in2, err := NewPasswordCredential(prjCs.GetPublicId(), "pass", WithName("my-name"), WithDescription("different"))
require.NoError(err)
got2, err := repo.CreatePasswordCredential(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 project should not conflict
in3, err := NewPasswordCredential(prj2Cs.GetPublicId(), "pass", WithName("my-name"), WithDescription("different"))
require.NoError(err)
got3, err := repo.CreatePasswordCredential(ctx, prj2.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_CreateSshPrivateKeyCredential(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
@@ -766,6 +922,7 @@ func TestRepository_LookupCredential(t *testing.T) {
store := TestCredentialStore(t, conn, wrapper, prj.PublicId)
upCred := TestUsernamePasswordCredential(t, conn, wrapper, "username", "password", store.PublicId, prj.PublicId)
updCred := TestUsernamePasswordDomainCredential(t, conn, wrapper, "username", "password", "domain.com", store.PublicId, prj.PublicId)
pCred := TestPasswordCredential(t, conn, wrapper, "password", store.PublicId, prj.PublicId)
spkCred := TestSshPrivateKeyCredential(t, conn, wrapper, "username", TestSshPrivateKeyPem, store.PublicId, prj.PublicId)
spkCredWithPass := TestSshPrivateKeyCredential(t, conn, wrapper, "username", string(testdata.PEMEncryptedKeys[0].PEMBytes),
store.PublicId, prj.PublicId, WithPrivateKeyPassphrase([]byte(testdata.PEMEncryptedKeys[0].EncryptionKey)))
@@ -790,6 +947,11 @@ func TestRepository_LookupCredential(t *testing.T) {
id: updCred.GetPublicId(),
want: updCred,
},
{
name: "p-valid",
id: pCred.GetPublicId(),
want: pCred,
},
{
name: "spk-valid",
id: spkCred.GetPublicId(),
@@ -849,6 +1011,10 @@ func TestRepository_LookupCredential(t *testing.T) {
assert.Empty(v.Password)
assert.Empty(v.CtPassword)
assert.NotEmpty(v.PasswordHmac)
case *PasswordCredential:
assert.Empty(v.Password)
assert.Empty(v.CtPassword)
assert.NotEmpty(v.PasswordHmac)
case *SshPrivateKeyCredential:
assert.Empty(v.PrivateKey)
assert.Empty(v.PrivateKeyEncrypted)
@@ -884,6 +1050,7 @@ func TestRepository_ListCredentials(t *testing.T) {
store := TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId(), total/4)
TestUsernamePasswordDomainCredentials(t, conn, wrapper, "user", "pass", "domain.com", store.GetPublicId(), prj.GetPublicId(), total/4)
TestPasswordCredentials(t, conn, wrapper, "pass", store.GetPublicId(), prj.GetPublicId(), total/4)
TestSshPrivateKeyCredentials(t, conn, wrapper, "user", TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId(), total/4)
obj, _ := TestJsonObject(t)
@@ -948,6 +1115,10 @@ func TestRepository_ListCredentials(t *testing.T) {
assert.Empty(v.Password)
assert.Empty(v.CtPassword)
assert.NotEmpty(v.PasswordHmac)
case *PasswordCredential:
assert.Empty(v.Password)
assert.Empty(v.CtPassword)
assert.NotEmpty(v.PasswordHmac)
case *SshPrivateKeyCredential:
assert.Empty(v.PrivateKey)
assert.Empty(v.PrivateKeyEncrypted)
@@ -979,6 +1150,7 @@ func TestRepository_ListCredentials_Pagination(t *testing.T) {
_ = TestSshPrivateKeyCredentials(t, conn, wrapper, "username", TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId(), 2)
_ = TestUsernamePasswordCredentials(t, conn, wrapper, "username", "testpassword", store.GetPublicId(), prj.GetPublicId(), 2)
_ = TestUsernamePasswordDomainCredentials(t, conn, wrapper, "username", "testpassword", "domain.com", store.GetPublicId(), prj.GetPublicId(), 1)
_ = TestPasswordCredentials(t, conn, wrapper, "testpassword", store.GetPublicId(), prj.GetPublicId(), 1)
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(err)
@@ -1015,15 +1187,16 @@ func TestRepository_ListCredentials_Pagination(t *testing.T) {
page4, ttime, err := repo.ListCredentials(ctx, store.GetPublicId(), credential.WithLimit(2), credential.WithStartPageAfterItem(page3[1]))
require.NoError(err)
require.Len(page4, 1)
require.Len(page4, 2)
pages = append(pages, page3...)
for _, item := range pages {
assert.NotEqual(item.GetPublicId(), page4[0].GetPublicId())
assert.NotEqual(item.GetPublicId(), page4[1].GetPublicId())
}
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
page5, ttime, err := repo.ListCredentials(ctx, store.GetPublicId(), credential.WithLimit(2), credential.WithStartPageAfterItem(page4[0]))
page5, ttime, err := repo.ListCredentials(ctx, store.GetPublicId(), credential.WithLimit(2), credential.WithStartPageAfterItem(page4[1]))
require.NoError(err)
require.Empty(page5)
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
@@ -2292,6 +2465,432 @@ func TestRepository_UpdateUsernamePasswordDomainCredential(t *testing.T) {
}
}
func TestRepository_UpdatePasswordCredential(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
changeName := func(n string) func(credential *PasswordCredential) *PasswordCredential {
return func(c *PasswordCredential) *PasswordCredential {
c.Name = n
return c
}
}
changeDescription := func(d string) func(*PasswordCredential) *PasswordCredential {
return func(c *PasswordCredential) *PasswordCredential {
c.Description = d
return c
}
}
makeNil := func() func(*PasswordCredential) *PasswordCredential {
return func(_ *PasswordCredential) *PasswordCredential {
return nil
}
}
makeEmbeddedNil := func() func(*PasswordCredential) *PasswordCredential {
return func(_ *PasswordCredential) *PasswordCredential {
return &PasswordCredential{}
}
}
setPublicId := func(n string) func(*PasswordCredential) *PasswordCredential {
return func(c *PasswordCredential) *PasswordCredential {
c.PublicId = n
return c
}
}
deleteStoreId := func() func(*PasswordCredential) *PasswordCredential {
return func(c *PasswordCredential) *PasswordCredential {
c.StoreId = ""
return c
}
}
deleteVersion := func() func(*PasswordCredential) *PasswordCredential {
return func(c *PasswordCredential) *PasswordCredential {
c.Version = 0
return c
}
}
changePassword := func(d string) func(*PasswordCredential) *PasswordCredential {
return func(c *PasswordCredential) *PasswordCredential {
c.Password = []byte(d)
return c
}
}
combine := func(fns ...func(cs *PasswordCredential) *PasswordCredential) func(*PasswordCredential) *PasswordCredential {
return func(c *PasswordCredential) *PasswordCredential {
for _, fn := range fns {
c = fn(c)
}
return c
}
}
tests := []struct {
name string
orig *PasswordCredential
chgFn func(*PasswordCredential) *PasswordCredential
masks []string
want *PasswordCredential
wantCount int
wantErr errors.Code
}{
{
name: "nil-credential",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Password: []byte("pass"),
},
},
chgFn: makeNil(),
masks: []string{"Name", "Description"},
wantErr: errors.InvalidParameter,
},
{
name: "nil-embedded-credential",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Password: []byte("pass"),
},
},
chgFn: makeEmbeddedNil(),
masks: []string{"Name", "Description"},
wantErr: errors.InvalidParameter,
},
{
name: "no-public-id",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Password: []byte("pass"),
},
},
chgFn: setPublicId(""),
masks: []string{"Name", "Description"},
wantErr: errors.InvalidPublicId,
},
{
name: "no-store-id",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Password: []byte("pass"),
},
},
chgFn: deleteStoreId(),
masks: []string{"Name", "Description"},
wantErr: errors.InvalidParameter,
},
{
name: "no-version",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Password: []byte("pass"),
},
},
chgFn: deleteVersion(),
masks: []string{"Name", "Description"},
wantErr: errors.InvalidParameter,
},
{
name: "updating-non-existent-credential",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Name: "test-name-repo",
Password: []byte("pass"),
},
},
chgFn: combine(setPublicId("abcd_OOOOOOOOOO"), changeName("test-update-name-repo")),
masks: []string{"Name"},
wantErr: errors.RecordNotFound,
},
{
name: "empty-field-mask",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Name: "test-name-repo",
Password: []byte("pass"),
},
},
chgFn: changeName("test-update-name-repo"),
wantErr: errors.EmptyFieldMask,
},
{
name: "read-only-fields-in-field-mask",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Name: "test-name-repo",
Password: []byte("pass"),
},
},
chgFn: changeName("test-update-name-repo"),
masks: []string{"PublicId", "CreateTime", "UpdateTime", "ProjectId"},
wantErr: errors.InvalidFieldMask,
},
{
name: "unknown-field-in-field-mask",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Name: "test-name-repo",
Password: []byte("pass"),
},
},
chgFn: changeName("test-update-name-repo"),
masks: []string{"Bilbo"},
wantErr: errors.InvalidFieldMask,
},
{
name: "change-name",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Name: "test-name-repo",
Password: []byte("pass"),
},
},
chgFn: changeName("test-update-name-repo"),
masks: []string{"Name"},
want: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Name: "test-update-name-repo",
Password: []byte("pass"),
},
},
wantCount: 1,
},
{
name: "change-description",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Description: "test-description-repo",
Password: []byte("pass"),
},
},
chgFn: changeDescription("test-update-description-repo"),
masks: []string{"Description"},
want: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Description: "test-update-description-repo",
Password: []byte("pass"),
},
},
wantCount: 1,
},
{
name: "change-name-and-description",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Name: "test-name-repo",
Description: "test-description-repo",
Password: []byte("pass"),
},
},
chgFn: combine(changeDescription("test-update-description-repo"), changeName("test-update-name-repo")),
masks: []string{"Name", "Description"},
want: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Name: "test-update-name-repo",
Description: "test-update-description-repo",
Password: []byte("pass"),
},
},
wantCount: 1,
},
{
name: "change-password",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Password: []byte("pass"),
},
},
chgFn: changePassword("test-update-pass"),
masks: []string{"Password"},
want: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Password: []byte("test-update-pass"),
},
},
wantCount: 1,
},
{
name: "do-not-delete-password",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Password: []byte("pass"),
},
},
masks: []string{"Name"},
chgFn: combine(changeName("new-name"), changePassword("")),
want: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Name: "new-name",
Password: []byte("pass"),
},
},
wantCount: 1,
},
{
name: "delete-name",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Name: "test-name-repo",
Description: "test-description-repo",
Password: []byte("pass"),
},
},
masks: []string{"Name"},
chgFn: combine(changeDescription("test-update-description-repo"), changeName("")),
want: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Description: "test-description-repo",
Password: []byte("pass"),
},
},
wantCount: 1,
},
{
name: "delete-description",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Name: "test-name-repo",
Description: "test-description-repo",
Password: []byte("pass"),
},
},
masks: []string{"Description"},
chgFn: combine(changeDescription(""), changeName("test-update-name-repo")),
want: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Name: "test-name-repo",
Password: []byte("pass"),
},
},
wantCount: 1,
},
{
name: "do-not-delete-name",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Name: "test-name-repo",
Description: "test-description-repo",
Password: []byte("pass"),
},
},
masks: []string{"Description"},
chgFn: combine(changeDescription("test-update-description-repo"), changeName("")),
want: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Name: "test-name-repo",
Description: "test-update-description-repo",
Password: []byte("pass"),
},
},
wantCount: 1,
},
{
name: "do-not-delete-description",
orig: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Name: "test-name-repo",
Description: "test-description-repo",
Password: []byte("pass"),
},
},
masks: []string{"Name"},
chgFn: combine(changeDescription(""), changeName("test-update-name-repo")),
want: &PasswordCredential{
PasswordCredential: &store.PasswordCredential{
Name: "test-update-name-repo",
Description: "test-description-repo",
Password: []byte("pass"),
},
},
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.CreatePasswordCredential(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.UpdatePasswordCredential(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, globals.PasswordCredentialPrefix, 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)
}
assert.Equal(tt.want.Name, got.Name)
// Validate only passwordHmac is returned
assert.Empty(got.Password)
assert.Empty(got.CtPassword)
assert.NotEmpty(got.PasswordHmac)
// Validate hmac
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase)
require.NoError(err)
hm, err := crypto.HmacSha256(ctx, tt.want.Password, databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
require.NoError(err)
assert.Equal([]byte(hm), got.PasswordHmac)
if tt.wantCount > 0 {
assert.NoError(db.TestVerifyOplog(t, rw, got.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
}
})
}
}
func TestRepository_UpdatePasswordCredentialKeyUpdate(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
@@ -3407,6 +4006,7 @@ func TestRepository_ListDeletedCredentialIds(t *testing.T) {
sshCreds := TestSshPrivateKeyCredentials(t, conn, wrapper, "username", TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId(), 2)
pwCreds := TestUsernamePasswordCredentials(t, conn, wrapper, "username", "testpassword", store.GetPublicId(), prj.GetPublicId(), 2)
updCreds := TestUsernamePasswordDomainCredentials(t, conn, wrapper, "username", "testpassword", "domain", store.GetPublicId(), prj.GetPublicId(), 2)
pCreds := TestPasswordCredentials(t, conn, wrapper, "testpassword", store.GetPublicId(), prj.GetPublicId(), 2)
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(err)
@@ -3490,6 +4090,23 @@ func TestRepository_ListDeletedCredentialIds(t *testing.T) {
require.True(time.Now().Before(ttime.Add(10 * time.Second)))
require.True(time.Now().After(ttime.Add(-10 * time.Second)))
// Delete a p credential
_, err = staticRepo.DeleteCredential(ctx, prj.GetPublicId(), pCreds[0].GetPublicId())
require.NoError(err)
// Expect five entries
deletedIds, ttime, err = repo.ListDeletedCredentialIds(ctx, time.Now().AddDate(-1, 0, 0))
require.NoError(err)
assert.Empty(
cmp.Diff(
[]string{jsonCreds[0].GetPublicId(), sshCreds[0].GetPublicId(), pwCreds[0].GetPublicId(), updCreds[0].GetPublicId(), pCreds[0].GetPublicId()},
deletedIds,
cmpopts.SortSlices(func(i, j string) bool { return i < j }),
),
)
require.True(time.Now().Before(ttime.Add(10 * time.Second)))
require.True(time.Now().After(ttime.Add(-10 * time.Second)))
// Try again with the time set to now, expect no entries
deletedIds, ttime, err = repo.ListDeletedCredentialIds(ctx, time.Now())
require.NoError(err)
@@ -3529,13 +4146,14 @@ func TestRepository_EstimatedCredentialCount(t *testing.T) {
sshCreds := TestSshPrivateKeyCredentials(t, conn, wrapper, "username", TestSshPrivateKeyPem, staticStore.GetPublicId(), prj.GetPublicId(), 2)
pwCreds := TestUsernamePasswordCredentials(t, conn, wrapper, "username", "testpassword", staticStore.GetPublicId(), prj.GetPublicId(), 2)
updCreds := TestUsernamePasswordDomainCredentials(t, conn, wrapper, "username", "testpassword", "domain", staticStore.GetPublicId(), prj.GetPublicId(), 2)
pCreds := TestPasswordCredentials(t, conn, wrapper, "testpassword", staticStore.GetPublicId(), prj.GetPublicId(), 2)
// Run analyze to update postgres meta tables
_, err = sqlDb.ExecContext(ctx, "analyze")
require.NoError(err)
numItems, err = repo.EstimatedCredentialCount(ctx)
require.NoError(err)
assert.Equal(8, numItems)
assert.Equal(10, numItems)
// Delete a json credential
_, err = staticRepo.DeleteCredential(ctx, prj.GetPublicId(), jsonCreds[0].GetPublicId())
@@ -3545,7 +4163,7 @@ func TestRepository_EstimatedCredentialCount(t *testing.T) {
numItems, err = repo.EstimatedCredentialCount(ctx)
require.NoError(err)
assert.Equal(7, numItems)
assert.Equal(9, numItems)
// Delete a ssh credential
_, err = staticRepo.DeleteCredential(ctx, prj.GetPublicId(), sshCreds[0].GetPublicId())
@@ -3555,7 +4173,7 @@ func TestRepository_EstimatedCredentialCount(t *testing.T) {
numItems, err = repo.EstimatedCredentialCount(ctx)
require.NoError(err)
assert.Equal(6, numItems)
assert.Equal(8, numItems)
// Delete a pw credential
_, err = staticRepo.DeleteCredential(ctx, prj.GetPublicId(), pwCreds[0].GetPublicId())
@@ -3565,7 +4183,7 @@ func TestRepository_EstimatedCredentialCount(t *testing.T) {
numItems, err = repo.EstimatedCredentialCount(ctx)
require.NoError(err)
assert.Equal(5, numItems)
assert.Equal(7, numItems)
// Delete a upd credential
_, err = staticRepo.DeleteCredential(ctx, prj.GetPublicId(), updCreds[0].GetPublicId())
@@ -3575,5 +4193,15 @@ func TestRepository_EstimatedCredentialCount(t *testing.T) {
numItems, err = repo.EstimatedCredentialCount(ctx)
require.NoError(err)
assert.Equal(4, numItems)
assert.Equal(6, numItems)
// Delete a p credential
_, err = staticRepo.DeleteCredential(ctx, prj.GetPublicId(), pCreds[0].GetPublicId())
require.NoError(err)
_, err = sqlDb.ExecContext(ctx, "analyze")
require.NoError(err)
numItems, err = repo.EstimatedCredentialCount(ctx)
require.NoError(err)
assert.Equal(5, numItems)
}
@@ -30,6 +30,11 @@ func (r *Repository) Retrieve(ctx context.Context, projectId string, ids []strin
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var pCreds []*PasswordCredential
err = r.reader.SearchWhere(ctx, &pCreds, "public_id in (?)", []any{ids})
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var spkCreds []*SshPrivateKeyCredential
err = r.reader.SearchWhere(ctx, &spkCreds, "public_id in (?)", []any{ids})
if err != nil {
@@ -41,9 +46,9 @@ func (r *Repository) Retrieve(ctx context.Context, projectId string, ids []strin
return nil, errors.Wrap(ctx, err, op)
}
if len(upCreds)+len(updCreds)+len(spkCreds)+len(jsonCreds) != len(ids) {
if len(upCreds)+len(updCreds)+len(pCreds)+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)+len(jsonCreds)))
fmt.Sprintf("mismatch between creds and number of ids requested, expected %d got %d", len(ids), len(upCreds)+len(updCreds)+len(pCreds)+len(spkCreds)+len(jsonCreds)))
}
out := make([]credential.Static, 0, len(ids))
@@ -73,6 +78,19 @@ func (r *Repository) Retrieve(ctx context.Context, projectId string, ids []strin
out = append(out, c)
}
for _, c := range pCreds {
// 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)
}
for _, c := range spkCreds {
// decrypt credential
databaseWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeDatabase)
@@ -39,6 +39,8 @@ func TestRepository_Retrieve(t *testing.T) {
upCred2 := TestUsernamePasswordCredential(t, conn, wrapper, "different user", "better password", staticStore.GetPublicId(), prj.GetPublicId())
updCred1 := TestUsernamePasswordDomainCredential(t, conn, wrapper, "user", "pass", "domain.com", staticStore.GetPublicId(), prj.GetPublicId())
updCred2 := TestUsernamePasswordDomainCredential(t, conn, wrapper, "different user", "better password", "new-domain.com", staticStore.GetPublicId(), prj.GetPublicId())
pCred1 := TestPasswordCredential(t, conn, wrapper, "better password", staticStore.GetPublicId(), prj.GetPublicId())
pCred2 := TestPasswordCredential(t, conn, wrapper, "another password", staticStore.GetPublicId(), prj.GetPublicId())
spkCred1 := TestSshPrivateKeyCredential(t, conn, wrapper, "final user", string(testdata.PEMBytes["ed25519"]), staticStore.GetPublicId(), prj.GetPublicId())
spkCred2 := TestSshPrivateKeyCredential(t, conn, wrapper, "last user", string(testdata.PEMBytes["rsa-openssh-format"]), staticStore.GetPublicId(), prj.GetPublicId())
spkCredWithPass := TestSshPrivateKeyCredential(t, conn, wrapper, "another last user",
@@ -125,6 +127,26 @@ func TestRepository_Retrieve(t *testing.T) {
updCred1, updCred2,
},
},
{
name: "valid-one-p-cred",
args: args{
projectId: prj.GetPublicId(),
credIds: []string{pCred1.GetPublicId()},
},
wantCreds: []credential.Static{
pCred1,
},
},
{
name: "valid-multiple-p-creds",
args: args{
projectId: prj.GetPublicId(),
credIds: []string{pCred1.GetPublicId(), pCred2.GetPublicId()},
},
wantCreds: []credential.Static{
pCred1, pCred2,
},
},
{
name: "valid-ssh-pk-cred",
args: args{
@@ -169,10 +191,10 @@ func TestRepository_Retrieve(t *testing.T) {
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(), updCred1.GetPublicId(), updCred2.GetPublicId()},
credIds: []string{upCred1.GetPublicId(), spkCred1.GetPublicId(), spkCredWithPass.GetPublicId(), spkCred2.GetPublicId(), upCred2.GetPublicId(), jsonCred1.GetPublicId(), jsonCred2.GetPublicId(), updCred1.GetPublicId(), updCred2.GetPublicId(), pCred1.GetPublicId(), pCred2.GetPublicId()},
},
wantCreds: []credential.Static{
upCred1, spkCred1, spkCredWithPass, spkCred2, upCred2, jsonCred1, jsonCred2, updCred1, updCred2,
upCred1, spkCred1, spkCredWithPass, spkCred2, upCred2, jsonCred1, jsonCred2, updCred1, updCred2, pCred1, pCred2,
},
},
}
@@ -193,6 +215,7 @@ func TestRepository_Retrieve(t *testing.T) {
cmpopts.IgnoreUnexported(
UsernamePasswordCredential{}, store.UsernamePasswordCredential{},
UsernamePasswordDomainCredential{}, store.UsernamePasswordDomainCredential{},
PasswordCredential{}, store.PasswordCredential{},
SshPrivateKeyCredential{}, store.SshPrivateKeyCredential{},
JsonCredential{}, store.JsonCredential{}),
cmpopts.IgnoreTypes(&timestamp.Timestamp{}),
+46
View File
@@ -17,6 +17,7 @@ func init() {
kms.RegisterTableRewrapFn("credential_static_ssh_private_key_credential", credStaticSshPrivKeyRewrapFn)
kms.RegisterTableRewrapFn("credential_static_json_credential", credStaticJsonRewrapFn)
kms.RegisterTableRewrapFn("credential_static_username_password_domain_credential", credStaticUsernamePasswordDomainRewrapFn)
kms.RegisterTableRewrapFn("credential_static_password_credential", credStaticPasswordRewrapFn)
}
func rewrapParameterChecks(ctx context.Context, dataKeyVersionId string, scopeId string, reader db.Reader, writer db.Writer, kmsRepo kms.GetWrapperer) string {
@@ -128,6 +129,51 @@ func credStaticUsernamePasswordDomainRewrapFn(ctx context.Context, dataKeyVersio
return nil
}
func credStaticPasswordRewrapFn(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kmsRepo kms.GetWrapperer) error {
const op = "static.credStaticPasswordRewrapFn"
if errStr := rewrapParameterChecks(ctx, dataKeyVersionId, scopeId, reader, writer, kmsRepo); errStr != "" {
return errors.New(ctx, errors.InvalidParameter, op, errStr)
}
var creds []*PasswordCredential
// Indexes exist on (store_id, etc), so we can query static stores via scope and refine with key id.
// This is the fastest query we can use without creating a new index on key_id.
rows, err := reader.Query(ctx, credStaticPasswordRewrapQuery, []any{scopeId, dataKeyVersionId})
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query sql for rows that need rewrapping"))
}
defer rows.Close()
for rows.Next() {
cred := allocPasswordCredential()
if err := rows.Scan(
&cred.PublicId,
&cred.CtPassword,
&cred.KeyId,
); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to failed to scan row"))
}
creds = append(creds, cred)
}
if err := rows.Err(); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to iterate over retrieved rows"))
}
wrapper, err := kmsRepo.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to fetch kms wrapper for rewrapping"))
}
for _, cred := range creds {
if err := cred.decrypt(ctx, wrapper); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to decrypt password credential"))
}
if err := cred.encrypt(ctx, wrapper); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to re-encrypt password credential"))
}
if _, err := writer.Update(ctx, cred, []string{"CtPassword", "KeyId"}, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to update password credential row with rewrapped fields"))
}
}
return nil
}
func credStaticSshPrivKeyRewrapFn(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kmsRepo kms.GetWrapperer) error {
const op = "static.credStaticSshPrivKeyRewrapFn"
if errStr := rewrapParameterChecks(ctx, dataKeyVersionId, scopeId, reader, writer, kmsRepo); errStr != "" {
@@ -321,3 +321,67 @@ func TestRewrap_credStaticUsernamePasswordDomainRewrapFn(t *testing.T) {
assert.Equal(t, cred.GetPasswordHmac(), got.GetPasswordHmac())
})
}
func TestRewrap_credStaticPasswordRewrapFn(t *testing.T) {
ctx := context.Background()
t.Run("errors-on-query-error", func(t *testing.T) {
conn, mock := db.TestSetupWithMock(t)
wrapper := db.TestWrapper(t)
mock.ExpectQuery(
`SELECT \* FROM "kms_schema_version" WHERE 1=1 ORDER BY "kms_schema_version"\."version" LIMIT \$1`,
).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now()))
mock.ExpectQuery(
`SELECT \* FROM "kms_oplog_schema_version" WHERE 1=1 ORDER BY "kms_oplog_schema_version"."version" LIMIT \$1`,
).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now()))
kmsCache := kms.TestKms(t, conn, wrapper)
rw := db.New(conn)
mock.ExpectQuery(
`select distinct pass\.public_id, pass\.password_encrypted, pass\.key_id from credential_static_password_credential pass inner join credential_static_store store on store\.public_id = pass\.store_id where store\.project_id = \$1 and pass\.key_id = \$2;`,
).WillReturnError(errors.New("Query error"))
err := credStaticPasswordRewrapFn(ctx, "some_id", "some_scope", rw, rw, kmsCache)
require.Error(t, err)
})
t.Run("success", func(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, wrapper)
rw := db.New(conn)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStore(t, conn, wrapper, prj.PublicId)
cred, err := NewPasswordCredential(cs.GetPublicId(), "password")
assert.NoError(t, err)
cred.PublicId, err = credential.NewPasswordCredentialId(ctx)
assert.NoError(t, err)
kmsWrapper, err := kmsCache.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase)
assert.NoError(t, err)
assert.NoError(t, cred.encrypt(ctx, kmsWrapper))
assert.NoError(t, rw.Create(context.Background(), cred))
// now things are stored in the db, we can rotate and rewrap
assert.NoError(t, kmsCache.RotateKeys(ctx, prj.PublicId))
assert.NoError(t, credStaticPasswordRewrapFn(ctx, cred.GetKeyId(), prj.PublicId, rw, rw, kmsCache))
// now we pull the credential back from the db, decrypt it with the new key, and ensure things match
got := allocPasswordCredential()
got.PublicId = cred.PublicId
assert.NoError(t, rw.LookupById(ctx, got))
kmsWrapper2, err := kmsCache.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase, kms.WithKeyId(got.GetKeyId()))
assert.NoError(t, err)
newKeyVersionId, err := kmsWrapper2.KeyId(ctx)
assert.NoError(t, err)
// decrypt with the new key version and check to make sure things match
assert.NoError(t, got.decrypt(ctx, kmsWrapper2))
assert.NotEmpty(t, got.GetKeyId())
assert.NotEqual(t, cred.GetKeyId(), got.GetKeyId())
assert.Equal(t, newKeyVersionId, got.GetKeyId())
assert.Equal(t, "password", string(got.GetPassword()))
assert.NotEmpty(t, got.GetPasswordHmac())
assert.Equal(t, cred.GetPasswordHmac(), got.GetPasswordHmac())
})
}
+209 -35
View File
@@ -136,6 +136,157 @@ func (x *CredentialStore) GetVersion() uint32 {
return 0
}
type PasswordCredential struct {
state protoimpl.MessageState `protogen:"open.v1"`
// 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 project_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"`
// password is the plain-text of the password associated with the credential. We are
// not storing this plain-text password in the database.
// @inject_tag: `gorm:"-" wrapping:"pt,password_data"`
Password []byte `protobuf:"bytes,8,opt,name=password,proto3" json:"password,omitempty" gorm:"-" wrapping:"pt,password_data"`
// ct_password is the ciphertext of the password. It
// is stored in the database.
// @inject_tag: `gorm:"column:password_encrypted;not_null" wrapping:"ct,password_data"`
CtPassword []byte `protobuf:"bytes,9,opt,name=ct_password,json=ctPassword,proto3" json:"ct_password,omitempty" gorm:"column:password_encrypted;not_null" wrapping:"ct,password_data"`
// password_hmac is a sha256-hmac of the unencrypted password. It is recalculated
// everytime the password is updated.
// @inject_tag: `gorm:"not_null"`
PasswordHmac []byte `protobuf:"bytes,10,opt,name=password_hmac,json=passwordHmac,proto3" json:"password_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"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PasswordCredential) Reset() {
*x = PasswordCredential{}
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PasswordCredential) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PasswordCredential) ProtoMessage() {}
func (x *PasswordCredential) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PasswordCredential.ProtoReflect.Descriptor instead.
func (*PasswordCredential) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_static_store_v1_static_proto_rawDescGZIP(), []int{1}
}
func (x *PasswordCredential) GetPublicId() string {
if x != nil {
return x.PublicId
}
return ""
}
func (x *PasswordCredential) GetCreateTime() *timestamp.Timestamp {
if x != nil {
return x.CreateTime
}
return nil
}
func (x *PasswordCredential) GetUpdateTime() *timestamp.Timestamp {
if x != nil {
return x.UpdateTime
}
return nil
}
func (x *PasswordCredential) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *PasswordCredential) GetDescription() string {
if x != nil {
return x.Description
}
return ""
}
func (x *PasswordCredential) GetStoreId() string {
if x != nil {
return x.StoreId
}
return ""
}
func (x *PasswordCredential) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
func (x *PasswordCredential) GetPassword() []byte {
if x != nil {
return x.Password
}
return nil
}
func (x *PasswordCredential) GetCtPassword() []byte {
if x != nil {
return x.CtPassword
}
return nil
}
func (x *PasswordCredential) GetPasswordHmac() []byte {
if x != nil {
return x.PasswordHmac
}
return nil
}
func (x *PasswordCredential) GetKeyId() string {
if x != nil {
return x.KeyId
}
return ""
}
type UsernamePasswordCredential struct {
state protoimpl.MessageState `protogen:"open.v1"`
// public_id is a surrogate key suitable for use in a public API.
@@ -186,7 +337,7 @@ type UsernamePasswordCredential struct {
func (x *UsernamePasswordCredential) Reset() {
*x = UsernamePasswordCredential{}
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[1]
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -198,7 +349,7 @@ func (x *UsernamePasswordCredential) String() string {
func (*UsernamePasswordCredential) ProtoMessage() {}
func (x *UsernamePasswordCredential) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[1]
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -211,7 +362,7 @@ func (x *UsernamePasswordCredential) ProtoReflect() protoreflect.Message {
// Deprecated: Use UsernamePasswordCredential.ProtoReflect.Descriptor instead.
func (*UsernamePasswordCredential) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_static_store_v1_static_proto_rawDescGZIP(), []int{1}
return file_controller_storage_credential_static_store_v1_static_proto_rawDescGZIP(), []int{2}
}
func (x *UsernamePasswordCredential) GetPublicId() string {
@@ -352,7 +503,7 @@ type UsernamePasswordDomainCredential struct {
func (x *UsernamePasswordDomainCredential) Reset() {
*x = UsernamePasswordDomainCredential{}
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[2]
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -364,7 +515,7 @@ func (x *UsernamePasswordDomainCredential) String() string {
func (*UsernamePasswordDomainCredential) ProtoMessage() {}
func (x *UsernamePasswordDomainCredential) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[2]
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -377,7 +528,7 @@ func (x *UsernamePasswordDomainCredential) ProtoReflect() protoreflect.Message {
// Deprecated: Use UsernamePasswordDomainCredential.ProtoReflect.Descriptor instead.
func (*UsernamePasswordDomainCredential) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_static_store_v1_static_proto_rawDescGZIP(), []int{2}
return file_controller_storage_credential_static_store_v1_static_proto_rawDescGZIP(), []int{3}
}
func (x *UsernamePasswordDomainCredential) GetPublicId() string {
@@ -534,7 +685,7 @@ type SshPrivateKeyCredential struct {
func (x *SshPrivateKeyCredential) Reset() {
*x = SshPrivateKeyCredential{}
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[3]
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -546,7 +697,7 @@ func (x *SshPrivateKeyCredential) String() string {
func (*SshPrivateKeyCredential) ProtoMessage() {}
func (x *SshPrivateKeyCredential) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[3]
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -559,7 +710,7 @@ func (x *SshPrivateKeyCredential) ProtoReflect() protoreflect.Message {
// Deprecated: Use SshPrivateKeyCredential.ProtoReflect.Descriptor instead.
func (*SshPrivateKeyCredential) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_static_store_v1_static_proto_rawDescGZIP(), []int{3}
return file_controller_storage_credential_static_store_v1_static_proto_rawDescGZIP(), []int{4}
}
func (x *SshPrivateKeyCredential) GetPublicId() string {
@@ -713,7 +864,7 @@ type JsonCredential struct {
func (x *JsonCredential) Reset() {
*x = JsonCredential{}
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[4]
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -725,7 +876,7 @@ func (x *JsonCredential) String() string {
func (*JsonCredential) ProtoMessage() {}
func (x *JsonCredential) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[4]
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -738,7 +889,7 @@ func (x *JsonCredential) ProtoReflect() protoreflect.Message {
// Deprecated: Use JsonCredential.ProtoReflect.Descriptor instead.
func (*JsonCredential) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_static_store_v1_static_proto_rawDescGZIP(), []int{4}
return file_controller_storage_credential_static_store_v1_static_proto_rawDescGZIP(), []int{5}
}
func (x *JsonCredential) GetPublicId() string {
@@ -835,7 +986,27 @@ const file_controller_storage_credential_static_store_v1_static_proto_rawDesc =
"\vDescription\x12\vdescriptionR\vdescription\x12\x1d\n" +
"\n" +
"project_id\x18\x06 \x01(\tR\tprojectId\x12\x18\n" +
"\aversion\x18\a \x01(\rR\aversion\"\xfd\x04\n" +
"\aversion\x18\a \x01(\rR\aversion\"\xb4\x04\n" +
"\x12PasswordCredential\x12\x1b\n" +
"\tpublic_id\x18\x01 \x01(\tR\bpublicId\x12K\n" +
"\vcreate_time\x18\x02 \x01(\v2*.controller.storage.timestamp.v1.TimestampR\n" +
"createTime\x12K\n" +
"\vupdate_time\x18\x03 \x01(\v2*.controller.storage.timestamp.v1.TimestampR\n" +
"updateTime\x12$\n" +
"\x04name\x18\x04 \x01(\tB\x10\xc2\xdd)\f\n" +
"\x04Name\x12\x04nameR\x04name\x12@\n" +
"\vdescription\x18\x05 \x01(\tB\x1e\xc2\xdd)\x1a\n" +
"\vDescription\x12\vdescriptionR\vdescription\x12\x19\n" +
"\bstore_id\x18\x06 \x01(\tR\astoreId\x12\x18\n" +
"\aversion\x18\a \x01(\rR\aversion\x12?\n" +
"\bpassword\x18\b \x01(\fB#\xc2\xdd)\x1f\n" +
"\bPassword\x12\x13attributes.passwordR\bpassword\x12\x1f\n" +
"\vct_password\x18\t \x01(\fR\n" +
"ctPassword\x12Q\n" +
"\rpassword_hmac\x18\n" +
" \x01(\fB,\xc2\xdd)(\n" +
"\fPasswordHmac\x12\x18attributes.password_hmacR\fpasswordHmac\x12\x15\n" +
"\x06key_id\x18\v \x01(\tR\x05keyId\"\xfd\x04\n" +
"\x1aUsernamePasswordCredential\x12\x1b\n" +
"\tpublic_id\x18\x01 \x01(\tR\bpublicId\x12K\n" +
"\vcreate_time\x18\x02 \x01(\v2*.controller.storage.timestamp.v1.TimestampR\n" +
@@ -944,31 +1115,34 @@ 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, 5)
var file_controller_storage_credential_static_store_v1_static_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_controller_storage_credential_static_store_v1_static_proto_goTypes = []any{
(*CredentialStore)(nil), // 0: controller.storage.credential.static.store.v1.CredentialStore
(*UsernamePasswordCredential)(nil), // 1: controller.storage.credential.static.store.v1.UsernamePasswordCredential
(*UsernamePasswordDomainCredential)(nil), // 2: controller.storage.credential.static.store.v1.UsernamePasswordDomainCredential
(*SshPrivateKeyCredential)(nil), // 3: controller.storage.credential.static.store.v1.SshPrivateKeyCredential
(*JsonCredential)(nil), // 4: controller.storage.credential.static.store.v1.JsonCredential
(*timestamp.Timestamp)(nil), // 5: controller.storage.timestamp.v1.Timestamp
(*PasswordCredential)(nil), // 1: controller.storage.credential.static.store.v1.PasswordCredential
(*UsernamePasswordCredential)(nil), // 2: controller.storage.credential.static.store.v1.UsernamePasswordCredential
(*UsernamePasswordDomainCredential)(nil), // 3: controller.storage.credential.static.store.v1.UsernamePasswordDomainCredential
(*SshPrivateKeyCredential)(nil), // 4: controller.storage.credential.static.store.v1.SshPrivateKeyCredential
(*JsonCredential)(nil), // 5: controller.storage.credential.static.store.v1.JsonCredential
(*timestamp.Timestamp)(nil), // 6: controller.storage.timestamp.v1.Timestamp
}
var file_controller_storage_credential_static_store_v1_static_proto_depIdxs = []int32{
5, // 0: controller.storage.credential.static.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 1: controller.storage.credential.static.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 2: controller.storage.credential.static.store.v1.UsernamePasswordCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 3: controller.storage.credential.static.store.v1.UsernamePasswordCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 4: controller.storage.credential.static.store.v1.UsernamePasswordDomainCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 5: controller.storage.credential.static.store.v1.UsernamePasswordDomainCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 6: controller.storage.credential.static.store.v1.SshPrivateKeyCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 7: controller.storage.credential.static.store.v1.SshPrivateKeyCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 8: controller.storage.credential.static.store.v1.JsonCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 9: controller.storage.credential.static.store.v1.JsonCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // [10:10] is the sub-list for method output_type
10, // [10:10] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension type_name
10, // [10:10] is the sub-list for extension extendee
0, // [0:10] is the sub-list for field type_name
6, // 0: controller.storage.credential.static.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 1: controller.storage.credential.static.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 2: controller.storage.credential.static.store.v1.PasswordCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 3: controller.storage.credential.static.store.v1.PasswordCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 4: controller.storage.credential.static.store.v1.UsernamePasswordCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 5: controller.storage.credential.static.store.v1.UsernamePasswordCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 6: controller.storage.credential.static.store.v1.UsernamePasswordDomainCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 7: controller.storage.credential.static.store.v1.UsernamePasswordDomainCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 8: controller.storage.credential.static.store.v1.SshPrivateKeyCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 9: controller.storage.credential.static.store.v1.SshPrivateKeyCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 10: controller.storage.credential.static.store.v1.JsonCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 11: controller.storage.credential.static.store.v1.JsonCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
12, // [12:12] is the sub-list for method output_type
12, // [12:12] is the sub-list for method input_type
12, // [12:12] is the sub-list for extension type_name
12, // [12:12] is the sub-list for extension extendee
0, // [0:12] is the sub-list for field type_name
}
func init() { file_controller_storage_credential_static_store_v1_static_proto_init() }
@@ -982,7 +1156,7 @@ func file_controller_storage_credential_static_store_v1_static_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_controller_storage_credential_static_store_v1_static_proto_rawDesc), len(file_controller_storage_credential_static_store_v1_static_proto_rawDesc)),
NumEnums: 0,
NumMessages: 5,
NumMessages: 6,
NumExtensions: 0,
NumServices: 0,
},
+71
View File
@@ -284,6 +284,77 @@ func TestUsernamePasswordDomainCredentials(
return creds
}
// TestPasswordCredential creates a password credential in the provided DB with
// the provided project id and any values passed in through.
// If any errors are encountered during the creation of the store, the test will fail.
func TestPasswordCredential(
t testing.TB,
conn *db.DB,
wrapper wrapping.Wrapper,
password, storeId, projectId string,
opts ...Option,
) *PasswordCredential {
t.Helper()
ctx := context.Background()
kmsCache := kms.TestKms(t, conn, wrapper)
w := db.New(conn)
opt := getOpts(opts...)
databaseWrapper, err := kmsCache.GetWrapper(ctx, projectId, kms.KeyPurposeDatabase)
assert.NoError(t, err)
require.NotNil(t, databaseWrapper)
cred, err := NewPasswordCredential(storeId, credential.Password(password), opts...)
require.NoError(t, err)
require.NotNil(t, cred)
id := opt.withPublicId
if id == "" {
id, err = credential.NewPasswordCredentialId(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
}
// TestPasswordCredentials creates count number of password credentials in
// the provided DB with the provided project id. If any errors are
// encountered during the creation of the credentials, the test will fail.
func TestPasswordCredentials(
t testing.TB,
conn *db.DB,
wrapper wrapping.Wrapper,
password, storeId, projectId string,
count int,
) []*PasswordCredential {
t.Helper()
ctx := context.Background()
kmsCache := kms.TestKms(t, conn, wrapper)
databaseWrapper, err := kmsCache.GetWrapper(ctx, projectId, kms.KeyPurposeDatabase)
assert.NoError(t, err)
require.NotNil(t, databaseWrapper)
creds := make([]*PasswordCredential, 0, count)
for i := 0; i < count; i++ {
creds = append(creds, TestPasswordCredential(t, conn, wrapper, password, storeId, projectId))
}
return creds
}
// TestSshPrivateKeyCredential creates an ssh private key credential in the
// provided DB with the provided project and any values passed in through. If any
// errors are encountered during the creation of the store, the test will fail.
@@ -0,0 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
// Package password provides access to the password
// stored in a Vault secret.
package password
@@ -0,0 +1,118 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package password
import (
"strings"
"github.com/mitchellh/pointerstructure"
)
type (
data map[string]any
// extractFunc attempts to extract the password
// from sd using the provided attribute names, using a known
// Vault data response format.
extractFunc func(sd data, passwordAttr string) string
)
// Extract attempts to extract the values of the password
// stored within the provided data using the given attribute names.
//
// Extract does not return partial results, i.e. if one of the attributes
// were extracted but not the other ("") will be returned.
func Extract(d data, passwordAttr string) string {
for _, f := range []extractFunc{
defaultExtract,
kv2Extract,
} {
password := f(d, passwordAttr)
if password != "" {
// got valid password from secret
return password
}
}
return ""
}
// defaultExtract looks for the passwordAttr in the data map
func defaultExtract(sd data, passwordAttr string) (password string) {
if sd == nil {
// nothing to do return early
return ""
}
var p any
switch {
case strings.HasPrefix(passwordAttr, "/"):
var err error
p, err = pointerstructure.Get(sd, passwordAttr)
if err != nil {
return ""
}
default:
p = sd[passwordAttr]
}
if p, ok := p.(string); ok {
password = p
}
return password
}
// kv2Extract looks for the the passwordAttr in the embedded
// 'data' field within the data map.
//
// Additionally it validates the data is in the expected KV-v2 format:
//
// {
// "data": {},
// "metadata: {}
// }
//
// If the format does not match, it returns (""). See:
// https://www.vaultproject.io/api/secret/kv/kv-v2#sample-response-1
func kv2Extract(sd data, passwordAttr string) (password string) {
if sd == nil {
// nothing to do return early
return ""
}
var data, metadata map[string]any
for k, v := range sd {
switch k {
case "data":
var ok bool
if data, ok = v.(map[string]any); !ok {
// data field should be of type map[string]interface{} in KV-v2
return ""
}
case "metadata":
var ok bool
if metadata, ok = v.(map[string]any); !ok {
// metadata field should be of type map[string]interface{} in KV-v2
return ""
}
default:
// secretData contains a non valid KV-v2 top level field
return ""
}
}
if data == nil || metadata == nil {
// missing required KV-v2 field
return ""
}
if p, ok := data[passwordAttr]; ok {
if p, ok := p.(string); ok {
password = p
}
}
return password
}
@@ -0,0 +1,176 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package password
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestBaseToPass(t *testing.T) {
t.Parallel()
type args struct {
s data
pAttr string
}
type pass struct {
pass string
}
tests := []struct {
name string
given args
want pass
}{
{
name: "nil-input",
want: pass{pass: ""},
},
{
name: "no-input",
given: args{},
want: pass{pass: ""},
},
{
name: "no-secret",
given: args{
pAttr: "password",
},
want: pass{pass: ""},
},
{
name: "no-match-password-secret",
given: args{
s: data{
"password-wrong": "pass",
},
pAttr: "password",
},
want: pass{pass: ""},
},
{
name: "valid-default",
given: args{
s: data{
"password": "pass",
},
pAttr: "password",
},
want: pass{pass: "pass"},
},
{
name: "no-match-password-secret-kv2",
given: args{
s: data{
"metadata": map[string]any{},
"data": map[string]any{
"password-wrong": "pass",
},
},
pAttr: "password",
},
want: pass{pass: ""},
},
{
name: "valid-kv2",
given: args{
s: data{
"metadata": map[string]any{},
"data": map[string]any{
"password": "pass",
},
},
pAttr: "password",
},
want: pass{pass: "pass"},
},
{
name: "no-metadata-kv2",
given: args{
s: data{
"data": map[string]any{
"password": "pass",
},
},
pAttr: "password",
},
want: pass{pass: ""},
},
{
name: "invalid-metadata-kv2",
given: args{
s: data{
"metadata": "string",
"data": map[string]any{
"password": "pass",
},
},
pAttr: "password",
},
want: pass{pass: ""},
},
{
name: "invalid-field-kv2",
given: args{
s: data{
"invalid": map[string]any{},
"metadata": map[string]any{},
"data": map[string]any{
"password": "pass",
},
},
pAttr: "password",
},
want: pass{pass: ""},
},
{
name: "valid-order-default-first",
given: args{
s: data{
"password": "default-pass",
"metadata": map[string]any{},
"data": map[string]any{
"password": "kv2-pass",
},
},
pAttr: "password",
},
want: pass{pass: "default-pass"},
},
{
name: "json-pointer-password",
given: args{
s: data{
"testing": map[string]any{
"my-password": "secret",
},
},
pAttr: "/testing/my-password",
},
want: pass{pass: "secret"},
},
{
name: "deep-json-pointer",
given: args{
s: data{
"first-path": map[string]any{
"deeper-path": map[string]any{
"my-password": "deeper-secret",
},
},
},
pAttr: "/first-path/deeper-path/my-password",
},
want: pass{pass: "deeper-secret"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert := assert.New(t)
pass := Extract(tt.given.s, tt.given.pAttr)
assert.Equal(tt.want.pass, pass)
})
}
}
@@ -21,6 +21,8 @@ func validMappingOverride(m MappingOverride, ct globals.CredentialType) bool {
return ct == globals.UsernamePasswordCredentialType
case *UsernamePasswordDomainOverride:
return ct == globals.UsernamePasswordDomainCredentialType
case *PasswordOverride:
return ct == globals.PasswordCredentialType
case *SshPrivateKeyOverride:
return ct == globals.SshPrivateKeyCredentialType
default:
@@ -171,6 +173,65 @@ func (o *UsernamePasswordDomainOverride) SetTableName(n string) {
o.tableName = n
}
// A PasswordOverride contains optional values for overriding the
// default mappings used to map a Vault secret to a Password credential
// type for the credential library that owns it.
type PasswordOverride struct {
*store.PasswordOverride
tableName string `gorm:"-"`
}
var _ MappingOverride = (*PasswordOverride)(nil)
// NewPasswordOverride creates a new in memory PasswordOverride.
// WithOverrideAttribute and WithOverridePasswordAttribute are the
// only valid options. All other options are ignored.
func NewPasswordOverride(opt ...Option) *PasswordOverride {
opts := getOpts(opt...)
o := &PasswordOverride{
PasswordOverride: &store.PasswordOverride{
PasswordAttribute: sanitize.String(opts.withOverridePasswordAttribute),
},
}
return o
}
func allocPasswordOverride() *PasswordOverride {
return &PasswordOverride{
PasswordOverride: &store.PasswordOverride{},
}
}
func (o *PasswordOverride) clone() MappingOverride {
cp := proto.Clone(o.PasswordOverride)
return &PasswordOverride{
PasswordOverride: cp.(*store.PasswordOverride),
}
}
func (o *PasswordOverride) setLibraryId(i string) {
o.LibraryId = i
}
func (o *PasswordOverride) sanitize() {
if sentinel.Is(o.PasswordAttribute) {
o.PasswordAttribute = ""
}
}
// TableName returns the table name.
func (o *PasswordOverride) TableName() string {
if o.tableName != "" {
return o.tableName
}
return "credential_vault_generic_library_password_mapping_override"
}
// SetTableName sets the table name.
func (o *PasswordOverride) SetTableName(n string) {
o.tableName = n
}
// A SshPrivateKeyOverride contains optional values for overriding the
// default mappings used to map a Vault secret to a SshPrivateKey credential
// type for the credential library that owns it.
@@ -45,6 +45,11 @@ func TestValidMappingOverrides(t *testing.T) {
ct: globals.UsernamePasswordDomainCredentialType,
want: true,
},
{
m: nil,
ct: globals.PasswordCredentialType,
want: true,
},
{
m: unknownMapper(1),
ct: globals.UnspecifiedCredentialType,
@@ -60,6 +65,11 @@ func TestValidMappingOverrides(t *testing.T) {
ct: globals.UsernamePasswordDomainCredentialType,
want: false,
},
{
m: unknownMapper(1),
ct: globals.PasswordCredentialType,
want: false,
},
{
m: allocUsernamePasswordOverride(),
ct: globals.UnspecifiedCredentialType,
@@ -80,6 +90,16 @@ func TestValidMappingOverrides(t *testing.T) {
ct: globals.UsernamePasswordDomainCredentialType,
want: true,
},
{
m: allocPasswordOverride(),
ct: globals.UnspecifiedCredentialType,
want: false,
},
{
m: allocPasswordOverride(),
ct: globals.PasswordCredentialType,
want: true,
},
{
m: allocSshPrivateKeyOverride(),
ct: globals.UnspecifiedCredentialType,
@@ -23,6 +23,7 @@ import (
ldapv3 "github.com/go-ldap/ldap/v3"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault/internal/password"
"github.com/hashicorp/boundary/internal/credential/vault/internal/sshprivatekey"
"github.com/hashicorp/boundary/internal/credential/vault/internal/usernamepassword"
"github.com/hashicorp/boundary/internal/credential/vault/internal/usernamepassworddomain"
@@ -66,6 +67,8 @@ func convert(ctx context.Context, bc *baseCred) (dynamicCred, error) {
return baseToSshPriKey(ctx, bc)
case globals.UsernamePasswordDomainCredentialType:
return baseToUsrPassDomain(ctx, bc)
case globals.PasswordCredentialType:
return baseToPass(ctx, bc)
}
return bc, nil
}
@@ -166,6 +169,45 @@ func baseToUsrPassDomain(ctx context.Context, bc *baseCred) (*usrPassDomainCred,
}, nil
}
var _ credential.PasswordOnly = (*passCred)(nil)
type passCred struct {
*baseCred
password credential.Password
}
func (c *passCred) Password() credential.Password { return c.password }
func baseToPass(ctx context.Context, bc *baseCred) (*passCred, error) {
switch {
case bc == nil:
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("nil baseCred"))
case bc.lib == nil:
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("nil baseCred.lib"))
case bc.Library().CredentialType() != globals.PasswordCredentialType:
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("invalid credential type"))
}
lib, ok := bc.lib.(*genericIssuingCredentialLibrary)
if !ok {
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("baseCred.lib is not of type genericIssuingCredentialLibrary"))
}
pAttr := lib.PasswordAttribute
if pAttr == "" {
pAttr = "password"
}
password := password.Extract(bc.secretData, pAttr)
if password == "" {
return nil, errors.E(ctx, errors.WithCode(errors.VaultInvalidCredentialMapping))
}
return &passCred{
baseCred: bc,
password: credential.Password(password),
}, nil
}
var _ credential.SshPrivateKey = (*sshPrivateKeyCred)(nil)
type sshPrivateKeyCred struct {
@@ -344,6 +344,37 @@ func TestRepository_getPrivateLibraries(t *testing.T) {
req := credential.Request{SourceId: lib.GetPublicId(), Purpose: credential.BrokeredPurpose}
requests = append(requests, req)
}
{
opts := []Option{
WithCredentialType(globals.PasswordCredentialType),
}
libIn, err := NewCredentialLibrary(origStore.GetPublicId(), "/vault/path", opts...)
assert.NoError(err)
require.NotNil(libIn)
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(err)
require.NotNil(lib)
libs[lib.GetPublicId()] = lib
req := credential.Request{SourceId: lib.GetPublicId(), Purpose: credential.BrokeredPurpose}
requests = append(requests, req)
}
{
opts := []Option{
WithCredentialType(globals.PasswordCredentialType),
WithMappingOverride(NewPasswordOverride(
WithOverridePasswordAttribute("test-password"),
)),
}
libIn, err := NewCredentialLibrary(origStore.GetPublicId(), "/vault/path", opts...)
assert.NoError(err)
require.NotNil(libIn)
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(err)
require.NotNil(lib)
libs[lib.GetPublicId()] = lib
req := credential.Request{SourceId: lib.GetPublicId(), Purpose: credential.BrokeredPurpose}
requests = append(requests, req)
}
{
opts := []Option{
WithCredentialType(globals.SshPrivateKeyCredentialType),
@@ -460,6 +491,8 @@ func TestRepository_getPrivateLibraries(t *testing.T) {
assert.Equal(w.UsernameAttribute, got.UsernameAttribute)
assert.Equal(w.PasswordAttribute, got.PasswordAttribute)
assert.Equal(w.DomainAttribute, got.DomainAttribute)
case *PasswordOverride:
assert.Equal(w.PasswordAttribute, got.PasswordAttribute)
case *SshPrivateKeyOverride:
assert.Equal(w.UsernameAttribute, got.UsernameAttribute)
assert.Equal(w.PrivateKeyAttribute, got.PrivateKeyAttribute)
@@ -1424,6 +1457,211 @@ func TestBaseToUsrPassDomain(t *testing.T) {
}
}
func TestBaseToPass(t *testing.T) {
t.Parallel()
tests := []struct {
name string
given *baseCred
want *passCred
wantErr errors.Code
}{
{
name: "nil-input",
wantErr: errors.InvalidParameter,
},
{
name: "nil-library",
given: &baseCred{},
wantErr: errors.InvalidParameter,
},
{
name: "library-not-password-type",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UnspecifiedCredentialType),
},
},
wantErr: errors.InvalidParameter,
},
{
name: "invalid-no-password-attribute",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
},
secretData: map[string]any{},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "valid-default-attributes",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
},
secretData: map[string]any{
"password": "my-password",
},
},
want: &passCred{
password: credential.Password("my-password"),
},
},
{
name: "valid-override-attributes",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
PasswordAttribute: "test-password",
},
secretData: map[string]any{
"password": "default-password",
"test-password": "override-password",
},
},
want: &passCred{
password: credential.Password("override-password"),
},
},
{
name: "invalid-password-override",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
PasswordAttribute: "missing-password",
},
secretData: map[string]any{
"password": "default-password",
"test-password": "override-password",
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-no-metadata-field",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
},
secretData: map[string]any{
"data": map[string]any{
"password": "my-password",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-no-data-field",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
},
secretData: map[string]any{
"metadata": map[string]any{},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
},
secretData: map[string]any{
"metadata": "hello",
"data": map[string]any{
"password": "my-password",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
},
secretData: map[string]any{
"metadata": map[string]any{},
"data": "hello",
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-additional-field",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
},
secretData: map[string]any{
"bad-field": "hello",
"metadata": map[string]any{},
"data": map[string]any{
"password": "my-password",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "valid-kv2-default-password-attribute",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
},
secretData: map[string]any{
"metadata": map[string]any{},
"data": map[string]any{
"password": "my-password",
},
},
},
want: &passCred{
password: credential.Password("my-password"),
},
},
{
name: "valid-kv2-override-password-attribute",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
PasswordAttribute: "test-password",
},
secretData: map[string]any{
"metadata": map[string]any{},
"data": map[string]any{
"password": "default-password",
"test-password": "override-password",
},
},
},
want: &passCred{
password: credential.Password("override-password"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := baseToPass(context.Background(), tt.given)
if tt.wantErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err)
assert.Nil(got)
return
}
require.NoError(err)
want := tt.want
want.baseCred = tt.given
assert.Equal(want, got)
})
}
}
func TestBaseToSshPriKey(t *testing.T) {
t.Parallel()
@@ -406,6 +406,14 @@ func (pl *listLookupLibrary) toCredentialLibrary() *CredentialLibrary {
upd.sanitize()
cl.MappingOverride = upd
}
case string(globals.PasswordCredentialType):
if pl.PasswordAttribute != "" {
p := allocPasswordOverride()
p.LibraryId = pl.PublicId
p.PasswordAttribute = pl.PasswordAttribute
p.sanitize()
cl.MappingOverride = p
}
case string(globals.SshPrivateKeyCredentialType):
if pl.UsernameAttribute != "" || pl.PrivateKeyAttribute != "" || pl.PrivateKeyPassphraseAttribute != "" {
pk := allocSshPrivateKeyOverride()
@@ -293,6 +293,76 @@ func TestRepository_CreateCredentialLibrary(t *testing.T) {
},
},
},
{
name: "valid-password-credential-type",
in: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(globals.PasswordCredentialType),
},
},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(globals.PasswordCredentialType),
},
},
},
{
name: "unknown-password-mapping-override-type",
in: &CredentialLibrary{
MappingOverride: unknownMapper(1),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(globals.PasswordCredentialType),
},
},
wantErr: errors.VaultInvalidMappingOverride,
},
{
name: "invalid-password-mapping-override-type",
in: &CredentialLibrary{
MappingOverride: NewUsernamePasswordOverride(WithOverrideUsernameAttribute("test")),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(globals.PasswordCredentialType),
},
},
wantErr: errors.VaultInvalidMappingOverride,
},
{
name: "valid-password-credential-type-with-password-override",
in: &CredentialLibrary{
MappingOverride: NewPasswordOverride(
WithOverridePasswordAttribute("ptest"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(globals.PasswordCredentialType),
},
},
want: &CredentialLibrary{
MappingOverride: NewPasswordOverride(
WithOverridePasswordAttribute("ptest"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(globals.PasswordCredentialType),
},
},
},
{
name: "valid-ssh-private-key-credential-type",
in: &CredentialLibrary{
@@ -339,7 +409,7 @@ func TestRepository_CreateCredentialLibrary(t *testing.T) {
wantErr: errors.VaultInvalidMappingOverride,
},
{
name: "valid-ssh-prviate-key-credential-type-with-username-override",
name: "valid-ssh-private-key-credential-type-with-username-override",
in: &CredentialLibrary{
MappingOverride: NewSshPrivateKeyOverride(
WithOverrideUsernameAttribute("utest"),
@@ -486,6 +556,15 @@ func TestRepository_CreateCredentialLibrary(t *testing.T) {
override := allocUsernamePasswordOverride()
assert.NoError(rw.LookupWhere(ctx, &override, "library_id = ?", []any{got.GetPublicId()}))
case *PasswordOverride:
g, ok := got.MappingOverride.(*PasswordOverride)
require.True(ok)
assert.Equal(w.PasswordAttribute, g.PasswordAttribute)
// verify it was persisted in the database
override := allocPasswordOverride()
assert.NoError(rw.LookupWhere(ctx, &override, "library_id = ?", []any{got.GetPublicId()}))
case *SshPrivateKeyOverride:
g, ok := got.MappingOverride.(*SshPrivateKeyOverride)
require.True(ok)
@@ -1273,6 +1352,94 @@ func TestRepository_UpdateCredentialLibrary(t *testing.T) {
masks: []string{"MappingOverride"},
wantErr: errors.VaultInvalidMappingOverride,
},
{
name: "password-attribute-change-password-attribute",
orig: &CredentialLibrary{
MappingOverride: NewPasswordOverride(
WithOverridePasswordAttribute("orig-password"),
),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-password-repo",
CredentialType: string(globals.PasswordCredentialType),
},
},
chgFn: changeMappingOverride(
NewPasswordOverride(
WithOverridePasswordAttribute("changed-password"),
),
),
masks: []string{"MappingOverride"},
want: &CredentialLibrary{
MappingOverride: NewPasswordOverride(
WithOverridePasswordAttribute("changed-password"),
),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-password-repo",
CredentialType: string(globals.PasswordCredentialType),
},
},
wantCount: 1,
},
{
name: "no-mapping-override-change-password-attributes",
orig: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-password-repo",
CredentialType: string(globals.PasswordCredentialType),
},
},
chgFn: changeMappingOverride(
NewPasswordOverride(
WithOverridePasswordAttribute("changed-password"),
),
),
masks: []string{"MappingOverride"},
want: &CredentialLibrary{
MappingOverride: NewPasswordOverride(
WithOverridePasswordAttribute("changed-password"),
),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-password-repo",
CredentialType: string(globals.PasswordCredentialType),
},
},
wantCount: 1,
},
{
name: "password-attributes-delete-mapping-override",
orig: &CredentialLibrary{
MappingOverride: NewPasswordOverride(
WithOverridePasswordAttribute("orig-password"),
WithOverridePrivateKeyAttribute("orig-private-key"),
WithOverridePrivateKeyPassphraseAttribute("orig-passphrase"),
),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-password-repo",
CredentialType: string(globals.PasswordCredentialType),
},
},
chgFn: changeMappingOverride(nil),
masks: []string{"MappingOverride"},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-password-repo",
CredentialType: string(globals.PasswordCredentialType),
},
},
wantCount: 1,
},
{
name: "ssh-private-key-attributes-change-username-attribute",
orig: &CredentialLibrary{
@@ -1535,6 +1702,10 @@ func TestRepository_UpdateCredentialLibrary(t *testing.T) {
require.True(ok)
assert.Equal(w.UsernameAttribute, g.UsernameAttribute)
assert.Equal(w.PasswordAttribute, g.PasswordAttribute)
case *PasswordOverride:
g, ok := got.MappingOverride.(*PasswordOverride)
require.True(ok)
assert.Equal(w.PasswordAttribute, g.PasswordAttribute)
case *SshPrivateKeyOverride:
g, ok := got.MappingOverride.(*SshPrivateKeyOverride)
require.True(ok)
@@ -1800,6 +1971,31 @@ func TestRepository_LookupCredentialLibrary(t *testing.T) {
},
},
},
{
name: "valid-password-credential-type",
in: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(globals.PasswordCredentialType),
},
},
},
{
name: "valid-password-credential-type-with-password-override",
in: &CredentialLibrary{
MappingOverride: NewPasswordOverride(
WithOverridePasswordAttribute("ptest"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(globals.PasswordCredentialType),
},
},
},
{
name: "valid-ssh-private-key-credential-type",
in: &CredentialLibrary{
@@ -1901,6 +2097,10 @@ func TestRepository_LookupCredentialLibrary(t *testing.T) {
require.True(ok)
assert.Equal(w.UsernameAttribute, g.UsernameAttribute)
assert.Equal(w.PasswordAttribute, g.PasswordAttribute)
case *PasswordOverride:
g, ok := got.MappingOverride.(*PasswordOverride)
require.True(ok)
assert.Equal(w.PasswordAttribute, g.PasswordAttribute)
case *SshPrivateKeyOverride:
g, ok := got.MappingOverride.(*SshPrivateKeyOverride)
require.True(ok)
+96 -29
View File
@@ -1174,6 +1174,68 @@ func (x *UsernamePasswordDomainOverride) GetDomainAttribute() string {
return ""
}
type PasswordOverride struct {
state protoimpl.MessageState `protogen:"open.v1"`
// library_id of the owning vault credential library.
// @inject_tag: `gorm:"primary_key"`
LibraryId string `protobuf:"bytes,1,opt,name=library_id,json=libraryId,proto3" json:"library_id,omitempty" gorm:"primary_key"`
// password_attribute is the name of the attribute in the Data field of a
// Vault api.Secret that maps to a password.
// If set, it overrides any default attribute names the system uses to
// find a password attribute.
//
// See https://github.com/hashicorp/vault/blob/5e505ec039177e8212cbbab74ccb644c46e62e63/api/secret.go#L25
//
// @inject_tag: `gorm:"default:null"`
PasswordAttribute string `protobuf:"bytes,2,opt,name=password_attribute,json=passwordAttribute,proto3" json:"password_attribute,omitempty" gorm:"default:null"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PasswordOverride) Reset() {
*x = PasswordOverride{}
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PasswordOverride) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PasswordOverride) ProtoMessage() {}
func (x *PasswordOverride) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PasswordOverride.ProtoReflect.Descriptor instead.
func (*PasswordOverride) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{8}
}
func (x *PasswordOverride) GetLibraryId() string {
if x != nil {
return x.LibraryId
}
return ""
}
func (x *PasswordOverride) GetPasswordAttribute() string {
if x != nil {
return x.PasswordAttribute
}
return ""
}
type SshPrivateKeyOverride struct {
state protoimpl.MessageState `protogen:"open.v1"`
// library_id of the owning vault credential library.
@@ -1213,7 +1275,7 @@ type SshPrivateKeyOverride struct {
func (x *SshPrivateKeyOverride) Reset() {
*x = SshPrivateKeyOverride{}
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[8]
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1225,7 +1287,7 @@ func (x *SshPrivateKeyOverride) String() string {
func (*SshPrivateKeyOverride) ProtoMessage() {}
func (x *SshPrivateKeyOverride) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[8]
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1238,7 +1300,7 @@ func (x *SshPrivateKeyOverride) ProtoReflect() protoreflect.Message {
// Deprecated: Use SshPrivateKeyOverride.ProtoReflect.Descriptor instead.
func (*SshPrivateKeyOverride) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{8}
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{9}
}
func (x *SshPrivateKeyOverride) GetLibraryId() string {
@@ -1306,7 +1368,7 @@ type LdapCredentialLibrary struct {
func (x *LdapCredentialLibrary) Reset() {
*x = LdapCredentialLibrary{}
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[9]
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1318,7 +1380,7 @@ func (x *LdapCredentialLibrary) String() string {
func (*LdapCredentialLibrary) ProtoMessage() {}
func (x *LdapCredentialLibrary) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[9]
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1331,7 +1393,7 @@ func (x *LdapCredentialLibrary) ProtoReflect() protoreflect.Message {
// Deprecated: Use LdapCredentialLibrary.ProtoReflect.Descriptor instead.
func (*LdapCredentialLibrary) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{9}
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{10}
}
func (x *LdapCredentialLibrary) GetPublicId() string {
@@ -1546,7 +1608,11 @@ const file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc = ""
"library_id\x18\x01 \x01(\tR\tlibraryId\x12-\n" +
"\x12username_attribute\x18\x02 \x01(\tR\x11usernameAttribute\x12-\n" +
"\x12password_attribute\x18\x03 \x01(\tR\x11passwordAttribute\x12)\n" +
"\x10domain_attribute\x18\x04 \x01(\tR\x0fdomainAttribute\"\xe2\x01\n" +
"\x10domain_attribute\x18\x04 \x01(\tR\x0fdomainAttribute\"`\n" +
"\x10PasswordOverride\x12\x1d\n" +
"\n" +
"library_id\x18\x01 \x01(\tR\tlibraryId\x12-\n" +
"\x12password_attribute\x18\x02 \x01(\tR\x11passwordAttribute\"\xe2\x01\n" +
"\x15SshPrivateKeyOverride\x12\x1d\n" +
"\n" +
"library_id\x18\x01 \x01(\tR\tlibraryId\x12-\n" +
@@ -1582,7 +1648,7 @@ func file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP()
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescData
}
var file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
var file_controller_storage_credential_vault_store_v1_vault_proto_goTypes = []any{
(*CredentialStore)(nil), // 0: controller.storage.credential.vault.store.v1.CredentialStore
(*Token)(nil), // 1: controller.storage.credential.vault.store.v1.Token
@@ -1592,28 +1658,29 @@ var file_controller_storage_credential_vault_store_v1_vault_proto_goTypes = []an
(*Credential)(nil), // 5: controller.storage.credential.vault.store.v1.Credential
(*UsernamePasswordOverride)(nil), // 6: controller.storage.credential.vault.store.v1.UsernamePasswordOverride
(*UsernamePasswordDomainOverride)(nil), // 7: controller.storage.credential.vault.store.v1.UsernamePasswordDomainOverride
(*SshPrivateKeyOverride)(nil), // 8: controller.storage.credential.vault.store.v1.SshPrivateKeyOverride
(*LdapCredentialLibrary)(nil), // 9: controller.storage.credential.vault.store.v1.LdapCredentialLibrary
(*timestamp.Timestamp)(nil), // 10: controller.storage.timestamp.v1.Timestamp
(*PasswordOverride)(nil), // 8: controller.storage.credential.vault.store.v1.PasswordOverride
(*SshPrivateKeyOverride)(nil), // 9: controller.storage.credential.vault.store.v1.SshPrivateKeyOverride
(*LdapCredentialLibrary)(nil), // 10: controller.storage.credential.vault.store.v1.LdapCredentialLibrary
(*timestamp.Timestamp)(nil), // 11: controller.storage.timestamp.v1.Timestamp
}
var file_controller_storage_credential_vault_store_v1_vault_proto_depIdxs = []int32{
10, // 0: controller.storage.credential.vault.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 1: controller.storage.credential.vault.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 2: controller.storage.credential.vault.store.v1.CredentialStore.delete_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 3: controller.storage.credential.vault.store.v1.Token.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 4: controller.storage.credential.vault.store.v1.Token.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 5: controller.storage.credential.vault.store.v1.Token.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 6: controller.storage.credential.vault.store.v1.Token.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 7: controller.storage.credential.vault.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 8: controller.storage.credential.vault.store.v1.CredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 9: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 10: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 11: controller.storage.credential.vault.store.v1.Credential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 12: controller.storage.credential.vault.store.v1.Credential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 13: controller.storage.credential.vault.store.v1.Credential.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 14: controller.storage.credential.vault.store.v1.Credential.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 15: controller.storage.credential.vault.store.v1.LdapCredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 16: controller.storage.credential.vault.store.v1.LdapCredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 0: controller.storage.credential.vault.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 1: controller.storage.credential.vault.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 2: controller.storage.credential.vault.store.v1.CredentialStore.delete_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 3: controller.storage.credential.vault.store.v1.Token.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 4: controller.storage.credential.vault.store.v1.Token.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 5: controller.storage.credential.vault.store.v1.Token.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 6: controller.storage.credential.vault.store.v1.Token.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 7: controller.storage.credential.vault.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 8: controller.storage.credential.vault.store.v1.CredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 9: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 10: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 11: controller.storage.credential.vault.store.v1.Credential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 12: controller.storage.credential.vault.store.v1.Credential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 13: controller.storage.credential.vault.store.v1.Credential.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 14: controller.storage.credential.vault.store.v1.Credential.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 15: controller.storage.credential.vault.store.v1.LdapCredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 16: controller.storage.credential.vault.store.v1.LdapCredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
17, // [17:17] is the sub-list for method output_type
17, // [17:17] is the sub-list for method input_type
17, // [17:17] is the sub-list for extension type_name
@@ -1632,7 +1699,7 @@ func file_controller_storage_credential_vault_store_v1_vault_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc), len(file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc)),
NumEnums: 0,
NumMessages: 10,
NumMessages: 11,
NumExtensions: 0,
NumServices: 0,
},
@@ -83,6 +83,7 @@ var (
globals.SshPrivateKeyCredentialType,
globals.UnspecifiedCredentialType,
globals.UsernamePasswordDomainCredentialType,
globals.PasswordCredentialType,
}
validKeyTypes = []string{
@@ -851,6 +852,11 @@ func toProto(ctx context.Context, in credential.Library, opt ...handlers.Option)
m[domainAttribute] = mapping.DomainAttribute
}
case *vault.PasswordOverride:
if mapping.PasswordAttribute != "" {
m[passwordAttribute] = mapping.PasswordAttribute
}
case *vault.SshPrivateKeyOverride:
if mapping.UsernameAttribute != "" {
m[usernameAttribute] = mapping.UsernameAttribute
@@ -1006,6 +1012,17 @@ func toStorageVaultLibrary(ctx context.Context, storeId string, in *pb.Credentia
opts = append(opts, vault.WithMappingOverride(vault.NewUsernamePasswordDomainOverride(mapOpts...)))
}
case globals.PasswordCredentialType:
opts = append(opts, vault.WithCredentialType(credentialType))
overrides := in.CredentialMappingOverrides.AsMap()
var mapOpts []vault.Option
if password := overrides[passwordAttribute]; password != nil {
mapOpts = append(mapOpts, vault.WithOverridePasswordAttribute(password.(string)))
}
if len(mapOpts) > 0 {
opts = append(opts, vault.WithMappingOverride(vault.NewPasswordOverride(mapOpts...)))
}
case globals.SshPrivateKeyCredentialType:
opts = append(opts, vault.WithCredentialType(credentialType))
overrides := in.CredentialMappingOverrides.AsMap()
@@ -1340,6 +1357,8 @@ func validateMapping(badFields map[string]string, credentialType globals.Credent
validFields[usernameAttribute] = true
validFields[passwordAttribute] = true
validFields[domainAttribute] = true
case globals.PasswordCredentialType:
validFields[passwordAttribute] = true
default:
badFields[globals.CredentialTypeField] = fmt.Sprintf("Unknown credential type %q", credentialType)
return
@@ -1457,6 +1476,18 @@ func getMappingUpdates(credentialType globals.CredentialType, current vault.Mapp
default:
ret[domainAttribute] = currentDomain
}
case globals.PasswordCredentialType:
var currentPass any
if overrides, ok := current.(*vault.PasswordOverride); ok {
currentPass = overrides.PasswordAttribute
}
switch {
case masks[passwordAttribute]:
ret[passwordAttribute] = new[passwordAttribute]
default:
ret[passwordAttribute] = currentPass
}
case globals.SshPrivateKeyCredentialType:
var currentUser, currentpPass, currentPk any
if overrides, ok := current.(*vault.SshPrivateKeyOverride); ok {
@@ -14,7 +14,6 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth/password"
"github.com/hashicorp/boundary/internal/authtoken"
@@ -87,25 +86,6 @@ func sshCredentialLibraryToProto(credLib *vault.SSHCertificateCredentialLibrary,
}
}
func ldapCredentialLibraryToProto(credLib *vault.LdapCredentialLibrary, project *iam.Scope) *pb.CredentialLibrary {
return &pb.CredentialLibrary{
Id: credLib.GetPublicId(),
CredentialStoreId: credLib.GetStoreId(),
CredentialType: credLib.GetCredentialType(),
Scope: &scopepb.ScopeInfo{Id: project.GetPublicId(), Type: scope.Project.String(), ParentScopeId: project.GetParentId()},
CreatedTime: credLib.GetCreateTime().GetTimestamp(),
UpdatedTime: credLib.GetUpdateTime().GetTimestamp(),
Version: credLib.GetVersion(),
Type: vault.LdapCredentialLibrarySubtype.String(),
AuthorizedActions: testAuthorizedActions,
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String(credLib.GetVaultPath()),
},
},
}
}
func TestList(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
@@ -134,9 +114,6 @@ func TestList(t *testing.T) {
for _, l := range vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 10) {
wantLibraries = append(wantLibraries, sshCredentialLibraryToProto(l, prj))
}
for _, l := range vault.TestLdapCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 10) {
wantLibraries = append(wantLibraries, ldapCredentialLibraryToProto(l, prj))
}
cases := []struct {
name string
@@ -154,7 +131,7 @@ func TestList(t *testing.T) {
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 30,
EstItemCount: 20,
},
anonRes: &pbs.ListCredentialLibrariesResponse{
Items: wantLibraries,
@@ -162,7 +139,7 @@ func TestList(t *testing.T) {
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 30,
EstItemCount: 20,
},
},
{
@@ -287,20 +264,16 @@ func TestList_Attributes(t *testing.T) {
_, prj := iam.TestScopes(t, iamRepo)
ts := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 3)
storeGeneric, storeSSHCertificate, storeLdap := ts[0], ts[1], ts[2]
ts := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2)
storeGeneric, storeSSHCertificate := ts[0], ts[1]
var wantLibrariesGeneric []*pb.CredentialLibrary
var wantLibrariesSSHCertificate []*pb.CredentialLibrary
var wantLibrariesLdap []*pb.CredentialLibrary
for _, l := range vault.TestCredentialLibraries(t, conn, wrapper, storeGeneric.GetPublicId(), globals.UnspecifiedCredentialType, 5) {
wantLibrariesGeneric = append(wantLibrariesGeneric, vaultCredentialLibraryToProto(l, prj))
}
for _, l := range vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, storeSSHCertificate.GetPublicId(), 10) {
wantLibrariesSSHCertificate = append(wantLibrariesSSHCertificate, sshCredentialLibraryToProto(l, prj))
}
for _, l := range vault.TestLdapCredentialLibraries(t, conn, wrapper, storeLdap.GetPublicId(), 10) {
wantLibrariesLdap = append(wantLibrariesLdap, ldapCredentialLibraryToProto(l, prj))
}
cases := []struct {
name string
@@ -345,24 +318,6 @@ func TestList_Attributes(t *testing.T) {
RemovedIds: nil,
}, // anonymous user does not have access to attributes
},
{
name: "Filter on Attribute Ldap Library",
req: &pbs.ListCredentialLibrariesRequest{CredentialStoreId: storeLdap.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/path"==%q`, wantLibrariesLdap[2].GetVaultLdapCredentialLibraryAttributes().GetPath().Value)},
res: &pbs.ListCredentialLibrariesResponse{
Items: wantLibrariesLdap[2:3],
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 1,
},
anonRes: &pbs.ListCredentialLibrariesResponse{
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
}, // anonymous user does not have access to attributes
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
@@ -632,47 +587,26 @@ func TestCreate(t *testing.T) {
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "No Vault LDAP path",
req: &pbs.CreateCredentialLibraryRequest{
Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.LdapCredentialLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String(""),
},
name: "Invalid password mapping",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Invalid Vault LDAP credential type",
req: &pbs.CreateCredentialLibraryRequest{
Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.LdapCredentialLibrarySubtype.String(),
CredentialType: string(globals.SshPrivateKeyCredentialType),
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String("ldap/invalid/path"),
},
},
},
},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Invalid credential library type",
req: &pbs.CreateCredentialLibraryRequest{
Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: "bla-invalid",
CredentialType: "bla-invalid",
},
},
CredentialType: string(globals.PasswordCredentialType),
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
passwordAttribute: "password-test",
"invalid": "invalid-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
@@ -1073,6 +1007,90 @@ func TestCreate(t *testing.T) {
},
},
},
{
name: "Create a valid vault CredentialLibrary password type",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
CredentialType: string(globals.PasswordCredentialType),
}},
idPrefix: globals.VaultCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
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: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: string(globals.PasswordCredentialType),
AuthorizedActions: testAuthorizedActions,
},
},
},
{
name: "Create a valid vault CredentialLibrary password type with password mapping",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
passwordAttribute: "password-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
CredentialType: string(globals.PasswordCredentialType),
}},
idPrefix: globals.VaultCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
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: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: string(globals.PasswordCredentialType),
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
passwordAttribute: "password-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
AuthorizedActions: testAuthorizedActions,
},
},
},
{
name: "Create a valid vault CredentialLibrary ssh_private_key type",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
@@ -1193,40 +1211,6 @@ func TestCreate(t *testing.T) {
},
},
},
{
name: "Create a valid Vault LDAP Credential library",
req: &pbs.CreateCredentialLibraryRequest{
Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.LdapCredentialLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String("ldap/creds/hi"),
},
},
},
},
idPrefix: globals.VaultLdapCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultLdapCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
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: vault.LdapCredentialLibrarySubtype.String(),
CredentialType: string(globals.UsernamePasswordDomainCredentialType),
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String("ldap/creds/hi"),
},
},
AuthorizedActions: testAuthorizedActions,
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
@@ -1324,6 +1308,17 @@ func TestGet(t *testing.T) {
userPassDomainLib, err := repo.CreateCredentialLibrary(context.Background(), prj.GetPublicId(), libDomain)
require.NoError(t, err)
libPassword, err := vault.NewCredentialLibrary(store.GetPublicId(), "vault/path",
vault.WithCredentialType("password"),
vault.WithMappingOverride(
vault.NewPasswordOverride(
vault.WithOverridePasswordAttribute("pass"),
)))
require.NoError(t, err)
passwordLib, err := repo.CreateCredentialLibrary(context.Background(), prj.GetPublicId(), libPassword)
require.NoError(t, err)
lib1, err := vault.NewCredentialLibrary(store.GetPublicId(), "vault/path/ssh",
vault.WithCredentialType("ssh_private_key"),
vault.WithMappingOverride(
@@ -1341,13 +1336,6 @@ func TestGet(t *testing.T) {
sshCertLib, err := repo.CreateSSHCertificateCredentialLibrary(context.Background(), prj.GetPublicId(), lib2)
require.NoError(t, err)
libLdap1, err := vault.NewLdapCredentialLibrary(store.GetPublicId(), "ldap/creds/test")
require.NoError(t, err)
require.NotNil(t, libLdap1)
libLdap1, err = repo.CreateLdapCredentialLibrary(t.Context(), prj.GetPublicId(), libLdap1)
require.NoError(t, err)
require.NotNil(t, libLdap1)
cases := []struct {
name string
id string
@@ -1441,6 +1429,37 @@ func TestGet(t *testing.T) {
},
},
},
{
name: "success-password",
id: passwordLib.GetPublicId(),
res: &pbs.GetCredentialLibraryResponse{
Item: &pb.CredentialLibrary{
Id: passwordLib.GetPublicId(),
CredentialStoreId: passwordLib.GetStoreId(),
Scope: &scopepb.ScopeInfo{Id: store.GetProjectId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
Type: vault.GenericLibrarySubtype.String(),
AuthorizedActions: testAuthorizedActions,
CreatedTime: passwordLib.CreateTime.GetTimestamp(),
UpdatedTime: passwordLib.UpdateTime.GetTimestamp(),
Version: 1,
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(passwordLib.GetVaultPath()),
HttpMethod: wrapperspb.String(passwordLib.GetHttpMethod()),
},
},
CredentialType: "password",
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
passwordAttribute: "pass",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
},
{
name: "success-ssh-private-key",
id: sshPkLib.GetPublicId(),
@@ -1498,28 +1517,6 @@ func TestGet(t *testing.T) {
},
},
},
{
name: "success-ldap",
id: libLdap1.GetPublicId(),
res: &pbs.GetCredentialLibraryResponse{
Item: &pb.CredentialLibrary{
Id: libLdap1.GetPublicId(),
CredentialStoreId: libLdap1.GetStoreId(),
CredentialType: libLdap1.GetCredentialType(),
Scope: &scopepb.ScopeInfo{Id: store.GetProjectId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
Type: vault.LdapCredentialLibrarySubtype.String(),
AuthorizedActions: testAuthorizedActions,
CreatedTime: libLdap1.CreateTime.GetTimestamp(),
UpdatedTime: libLdap1.UpdateTime.GetTimestamp(),
Version: 1,
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String(libLdap1.GetVaultPath()),
},
},
},
},
},
{
name: "not found error",
id: fmt.Sprintf("%s_1234567890", globals.VaultCredentialLibraryPrefix),
@@ -1582,7 +1579,6 @@ func TestDelete(t *testing.T) {
store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
vl := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), globals.UnspecifiedCredentialType, 1)[0]
vl2 := vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0]
vl3 := vault.TestLdapCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0]
s, err := NewService(ctx, iamRepoFn, repoFn, 1000)
require.NoError(t, err)
cases := []struct {
@@ -1599,10 +1595,6 @@ func TestDelete(t *testing.T) {
name: "success-ssh-cert",
id: vl2.GetPublicId(),
},
{
name: "success-ldap",
id: vl3.GetPublicId(),
},
{
name: "not found error",
id: fmt.Sprintf("%s_1234567890", globals.VaultCredentialLibraryPrefix),
@@ -2118,6 +2110,84 @@ func TestUpdate(t *testing.T) {
return out
},
},
{
name: "password-attribute-change-password-attribute",
opts: []vault.Option{
vault.WithCredentialType("password"),
vault.WithMappingOverride(
vault.NewPasswordOverride(
vault.WithOverridePasswordAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passwordAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
passwordAttribute: "changed-pass",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides.Fields[passwordAttribute] = structpb.NewStringValue("changed-pass")
return out
},
},
{
name: "password-no-mapping-override-change-password-attribute",
opts: []vault.Option{
vault.WithCredentialType("password"),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passwordAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
passwordAttribute: "new-pass",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
v := map[string]any{
passwordAttribute: "new-pass",
}
var err error
out.CredentialMappingOverrides, err = structpb.NewStruct(v)
require.NoError(t, err)
return out
},
},
{
name: "password-attribute-delete-mapping-override",
opts: []vault.Option{
vault.WithCredentialType("password"),
vault.WithMappingOverride(
vault.NewPasswordOverride(
vault.WithOverridePasswordAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(credentialMappingPathField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: nil,
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides = nil
return out
},
},
{
name: "no-mapping-override-delete-mapping-override",
opts: []vault.Option{
@@ -2166,7 +2236,7 @@ func TestUpdate(t *testing.T) {
},
},
{
name: "username-password-attributes-delete-mapping-override-field-specific",
name: "username-password-domain-attributes-delete-mapping-override-field-specific",
opts: []vault.Option{
vault.WithCredentialType("username_password_domain"),
vault.WithMappingOverride(
@@ -2248,6 +2318,75 @@ func TestUpdate(t *testing.T) {
return out
},
},
{
name: "password-no-mapping-override-delete-mapping-override",
opts: []vault.Option{
vault.WithCredentialType("password"),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(credentialMappingPathField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: nil,
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides = nil
return out
},
},
{
name: "password-no-mapping-override-delete-mapping-override-field-specific",
opts: []vault.Option{
vault.WithCredentialType("password"),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passwordAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
passwordAttribute: nil,
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides = nil
return out
},
},
{
name: "password-delete-mapping-override-field-specific",
opts: []vault.Option{
vault.WithCredentialType("password"),
vault.WithMappingOverride(
vault.NewPasswordOverride(
vault.WithOverridePasswordAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passwordAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
passwordAttribute: nil,
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides = nil
return out
},
},
{
name: "ssh-private-key-attributes-change-username-attribute",
opts: []vault.Option{
@@ -3402,287 +3541,6 @@ func TestUpdate_SSHCertificateCredentialLibrary(t *testing.T) {
}
}
func TestUpdate_LdapCredentialLibrary(t *testing.T) {
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
rw := db.New(conn)
iamRepo := iam.TestRepo(t, conn, wrapper)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
repoFn := func() (*vault.Repository, error) {
return vault.NewRepository(testCtx, rw, rw, kms, sche)
}
_, prj := iam.TestScopes(t, iamRepo)
ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId())
s, err := NewService(testCtx, iamRepoFn, repoFn, 1000)
require.NoError(t, err)
cs := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)
store := cs[0]
freshLibrary := func(opt ...vault.Option) (*vault.LdapCredentialLibrary, func()) {
repo, err := repoFn()
require.NoError(t, err)
lib, err := vault.NewLdapCredentialLibrary(store.GetPublicId(), "ldap/creds/foo", opt...)
require.NoError(t, err)
vl, err := repo.CreateLdapCredentialLibrary(ctx, prj.GetPublicId(), lib)
require.NoError(t, err)
clean := func() {
_, err := s.DeleteCredentialLibrary(ctx, &pbs.DeleteCredentialLibraryRequest{Id: vl.GetPublicId()})
require.NoError(t, err)
}
return vl, clean
}
tests := []struct {
name string
req *pbs.UpdateCredentialLibraryRequest
res func(*pb.CredentialLibrary) *pb.CredentialLibrary
wantErr bool
wantErrStatus int
wantErrStr string
}{
{
name: "updateNonExistentField",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime.
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"doesntExist"}},
Item: &pb.CredentialLibrary{
Name: wrapperspb.String("updateNonExistentField_test"),
},
},
wantErr: true,
wantErrStatus: runtime.HTTPStatusFromCode(codes.InvalidArgument),
wantErrStr: "No valid fields included in the update mask.",
},
{
name: "updateImmutableField",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime.
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"storeId"}},
Item: &pb.CredentialLibrary{
CredentialStoreId: "immutable",
},
},
wantErr: true,
wantErrStatus: runtime.HTTPStatusFromCode(codes.InvalidArgument),
wantErrStr: "No valid fields included in the update mask.",
},
{
name: "updateValidFieldWithInvalidData",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime.
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"attributes.path"}},
Item: &pb.CredentialLibrary{
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String("invalid_path"),
},
},
},
},
wantErr: true,
wantErrStr: "vault_path_must_have_staticcred_or_creds constraint failed",
},
{
name: "updateImmutableCredentialTypeField",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime.
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"credentialType"}},
Item: &pb.CredentialLibrary{
CredentialType: string(globals.SshPrivateKeyCredentialType),
},
},
wantErr: true,
wantErrStatus: runtime.HTTPStatusFromCode(codes.InvalidArgument),
wantErrStr: "Cannot modify credential type.",
},
{
name: "updateImmutableTypeField",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime.
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"type"}},
Item: &pb.CredentialLibrary{
Type: string(globals.SshCertificateCredentialType),
},
},
wantErr: true,
wantErrStatus: runtime.HTTPStatusFromCode(codes.InvalidArgument),
wantErrStr: "Cannot modify resource type.",
},
{
name: "unsetVaultPath",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime.
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"attributes.path"}},
Item: &pb.CredentialLibrary{
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String(""),
},
},
},
},
wantErr: true,
wantErrStatus: runtime.HTTPStatusFromCode(codes.InvalidArgument),
wantErrStr: "attributes.path\", desc: \"This is a required field and cannot be set to empty.",
},
{
name: "updateName",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"name"}},
Item: &pb.CredentialLibrary{
Name: wrapperspb.String("updated_name"),
},
},
res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary {
cl.Name = wrapperspb.String("updated_name")
return cl
},
},
{
name: "updateDescription",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"description"}},
Item: &pb.CredentialLibrary{
Description: wrapperspb.String("updated_description"),
},
},
res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary {
cl.Description = wrapperspb.String("updated_description")
return cl
},
},
{
name: "updateValidVaultPath",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"attributes.path"}},
Item: &pb.CredentialLibrary{
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String("ldap/static-cred/updated/path"),
},
},
},
},
res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary {
cl.GetVaultLdapCredentialLibraryAttributes().Path = wrapperspb.String("ldap/static-cred/updated/path")
return cl
},
},
{
name: "updateMultipleOneNonExistent",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime.
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"name", "doesntExist"}},
Item: &pb.CredentialLibrary{
Name: wrapperspb.String("updated_name"),
},
},
res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary {
cl.Name = wrapperspb.String("updated_name")
return cl
},
},
{
name: "updateMultipleOneImmutable",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime.
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"name", "credentialStoreId"}},
Item: &pb.CredentialLibrary{
Name: wrapperspb.String("updated_name"),
CredentialStoreId: "immutable",
},
},
res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary {
cl.Name = wrapperspb.String("updated_name")
return cl
},
},
{
name: "updateMultiple",
req: &pbs.UpdateCredentialLibraryRequest{
Id: "", // Set at test runtime
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"name", "attributes.path"}},
Item: &pb.CredentialLibrary{
Name: wrapperspb.String("updated_name"),
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String("ldap/creds/updated/path"),
},
},
},
},
res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary {
cl.Name = wrapperspb.String("updated_name")
cl.GetVaultLdapCredentialLibraryAttributes().Path = wrapperspb.String("ldap/creds/updated/path")
return cl
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
st, cleanup := freshLibrary()
defer cleanup()
if tc.req.Item.GetVersion() == 0 {
tc.req.Item.Version = 1
}
if tc.req.GetId() == "" {
tc.req.Id = st.GetPublicId()
}
resToChange, err := s.GetCredentialLibrary(ctx, &pbs.GetCredentialLibraryRequest{Id: st.GetPublicId()})
require.NoError(err)
got, gErr := s.UpdateCredentialLibrary(ctx, tc.req)
if tc.wantErr {
require.ErrorContains(gErr, tc.wantErrStr)
gApiErr, ok := gErr.(*handlers.ApiError)
if ok {
// We don't always return status.
require.NotNil(gApiErr)
require.EqualValues(tc.wantErrStatus, gApiErr.Status)
}
require.Nil(got)
return
}
require.NoError(gErr)
require.NotNil(got)
want := &pbs.UpdateCredentialLibraryResponse{Item: tc.res(resToChange.GetItem())}
gotUpdateTime := got.GetItem().GetUpdatedTime()
created := st.GetCreateTime().GetTimestamp()
assert.True(gotUpdateTime.AsTime().After(created.AsTime()), "Should have been updated after it's creation. Was updated %v, which is after %v", gotUpdateTime, created)
want.Item.UpdatedTime = got.Item.UpdatedTime
assert.EqualValues(2, got.Item.Version)
want.Item.Version = 2
assert.Empty(cmp.Diff(
got,
want,
protocmp.Transform(),
cmpopts.SortSlices(func(a, b string) bool {
return a < b
}),
))
})
}
}
func TestListPagination(t *testing.T) {
// Set database read timeout to avoid duplicates in response
oldReadTimeout := globals.RefreshReadLookbackDuration
@@ -3731,9 +3589,6 @@ func TestListPagination(t *testing.T) {
for _, l := range vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, credStore.GetPublicId(), 5) {
allCredentialLibraries = append(allCredentialLibraries, sshCredentialLibraryToProto(l, prj))
}
for _, l := range vault.TestLdapCredentialLibraries(t, conn, wrapper, credStore.GetPublicId(), 5) {
allCredentialLibraries = append(allCredentialLibraries, ldapCredentialLibraryToProto(l, prj))
}
// Reverse as we return items sorted by create_time desceding (newest first)
slices.Reverse(allCredentialLibraries)
@@ -3787,7 +3642,7 @@ func TestListPagination(t *testing.T) {
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 15,
EstItemCount: 10,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
@@ -3812,7 +3667,7 @@ func TestListPagination(t *testing.T) {
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 15,
EstItemCount: 10,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
@@ -3824,10 +3679,10 @@ func TestListPagination(t *testing.T) {
// Request rest of results
req.ListToken = got.ListToken
req.PageSize = 15
req.PageSize = 10
got, err = s.ListCredentialLibraries(ctx, req)
require.NoError(err)
require.Len(got.GetItems(), 11)
require.Len(got.GetItems(), 6)
// Compare without comparing the list token
assert.Empty(
cmp.Diff(
@@ -3838,7 +3693,7 @@ func TestListPagination(t *testing.T) {
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 15,
EstItemCount: 10,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
@@ -3863,14 +3718,14 @@ func TestListPagination(t *testing.T) {
// Update one of the other stores
allCredentialLibraries[1].Name = wrapperspb.String("new-name")
allCredentialLibraries[1].Version = 2
updatedLibrary := &vault.LdapCredentialLibrary{
LdapCredentialLibrary: &store.LdapCredentialLibrary{
updatedLibrary := &vault.SSHCertificateCredentialLibrary{
SSHCertificateCredentialLibrary: &store.SSHCertificateCredentialLibrary{
PublicId: allCredentialLibraries[1].GetId(),
StoreId: allCredentialLibraries[1].GetCredentialStoreId(),
Name: allCredentialLibraries[1].Name.Value,
},
}
cred, _, err := vaultRepo.UpdateLdapCredentialLibrary(ctx, prj.PublicId, updatedLibrary, 1, []string{"name"})
cred, _, err := vaultRepo.UpdateSSHCertificateCredentialLibrary(ctx, prj.PublicId, updatedLibrary, 1, []string{"name"})
require.NoError(err)
allCredentialLibraries[1].UpdatedTime = cred.UpdateTime.GetTimestamp()
allCredentialLibraries[1].Version = cred.Version
@@ -3904,7 +3759,7 @@ func TestListPagination(t *testing.T) {
SortDir: "desc",
// Should contain the deleted library
RemovedIds: []string{deletedCredLib.Id},
EstItemCount: 15,
EstItemCount: 10,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
@@ -3929,7 +3784,7 @@ func TestListPagination(t *testing.T) {
SortBy: "updated_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 15,
EstItemCount: 10,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
@@ -3957,7 +3812,7 @@ func TestListPagination(t *testing.T) {
SortDir: "desc",
// Should be empty again
RemovedIds: nil,
EstItemCount: 15,
EstItemCount: 10,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
@@ -3980,7 +3835,7 @@ func TestListPagination(t *testing.T) {
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 15,
EstItemCount: 10,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
@@ -49,6 +49,7 @@ var (
upMaskManager handlers.MaskManager
spkMaskManager handlers.MaskManager
jsonMaskManager handlers.MaskManager
pMaskmanager handlers.MaskManager
// IdActions contains the set of actions that can be performed on
// individual resources
@@ -97,6 +98,13 @@ func init() {
); err != nil {
panic(err)
}
if pMaskmanager, err = handlers.NewMaskManager(
context.Background(),
handlers.MaskDestination{&store.PasswordCredential{}},
handlers.MaskSource{&pb.Credential{}, &pb.PasswordAttributes{}},
); err != nil {
panic(err)
}
// TODO: refactor to remove IdActionsMap and CollectionActions package variables
action.RegisterResource(resource.Credential, IdActions, CollectionActions)
@@ -454,6 +462,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.PasswordSubtype.String():
cred, err := toPasswordStorageCredential(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.CreatePasswordCredential(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
case credential.SshPrivateKeySubtype.String():
cred, err := toSshPrivateKeyStorageCredential(ctx, item.GetCredentialStoreId(), item)
if err != nil {
@@ -551,7 +576,29 @@ func (s Service) updateInRepo(
return nil, handlers.NotFoundErrorf("Credential %q doesn't exist or incorrect version provided.", id)
}
return out, nil
case credential.PasswordSubtype:
dbMasks = append(dbMasks, pMaskmanager.Translate(masks)...)
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 := toPasswordStorageCredential(ctx, storeId, in)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to convert to password storage credential"))
}
cred.PublicId = id
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
out, rowsUpdated, err := repo.UpdatePasswordCredential(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
case credential.SshPrivateKeySubtype:
dbMasks = append(dbMasks, spkMaskManager.Translate(masks)...)
if len(dbMasks) == 0 {
@@ -585,7 +632,6 @@ func (s Service) updateInRepo(
return nil, handlers.NotFoundErrorf("Credential %q doesn't exist or incorrect version provided.", id)
}
return out, nil
case credential.JsonSubtype:
dbMasks = append(dbMasks, jsonMaskManager.Translate(masks, "attributes", "object")...)
if len(dbMasks) == 0 {
@@ -609,7 +655,6 @@ func (s Service) updateInRepo(
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()))
@@ -731,6 +776,8 @@ func toProto(in credential.Static, opt ...handlers.Option) (*pb.Credential, erro
out.Type = credential.UsernamePasswordSubtype.String()
case *static.UsernamePasswordDomainCredential:
out.Type = credential.UsernamePasswordDomainSubtype.String()
case *static.PasswordCredential:
out.Type = credential.PasswordSubtype.String()
case *static.SshPrivateKeyCredential:
out.Type = credential.SshPrivateKeySubtype.String()
case *static.JsonCredential:
@@ -779,6 +826,14 @@ func toProto(in credential.Static, opt ...handlers.Option) (*pb.Credential, erro
},
}
}
case *static.PasswordCredential:
if outputFields.Has(globals.AttributesField) {
out.Attrs = &pb.Credential_PasswordAttributes{
PasswordAttributes: &pb.PasswordAttributes{
PasswordHmac: base64.RawURLEncoding.EncodeToString(cred.GetPasswordHmac()),
},
}
}
case *static.SshPrivateKeyCredential:
if outputFields.Has(globals.AttributesField) {
out.Attrs = &pb.Credential_SshPrivateKeyAttributes{
@@ -848,6 +903,28 @@ func toUsernamePasswordDomainStorageCredential(ctx context.Context, storeId stri
return cs, err
}
func toPasswordStorageCredential(ctx context.Context, storeId string, in *pb.Credential) (out *static.PasswordCredential, err error) {
const op = "credentials.toPasswordStorageCredential"
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.GetPasswordAttributes()
cs, err := static.NewPasswordCredential(
storeId,
credential.Password(attrs.GetPassword().GetValue()),
opts...)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to build credential"))
}
return cs, err
}
func toSshPrivateKeyStorageCredential(ctx context.Context, storeId string, in *pb.Credential) (out *static.SshPrivateKeyCredential, err error) {
const op = "credentials.toSshPrivateKeyStorageCredential"
var opts []static.Option
@@ -919,6 +996,7 @@ func validateGetRequest(req *pbs.GetCredentialRequest) error {
globals.UsernamePasswordCredentialPrefix,
globals.UsernamePasswordCredentialPreviousPrefix,
globals.UsernamePasswordDomainCredentialPrefix,
globals.PasswordCredentialPrefix,
globals.SshPrivateKeyCredentialPrefix,
globals.JsonCredentialPrefix,
)
@@ -949,6 +1027,10 @@ func validateCreateRequest(req *pbs.CreateCredentialRequest) error {
if req.Item.GetUsernamePasswordDomainAttributes().GetDomain().GetValue() == "" {
badFields[domainField] = "Field required for creating a username-password-domain credential."
}
case credential.PasswordSubtype.String():
if req.Item.GetPasswordAttributes().GetPassword().GetValue() == "" {
badFields[passwordField] = "Field required for creating a password credential."
}
case credential.SshPrivateKeySubtype.String():
if req.Item.GetSshPrivateKeyAttributes().GetUsername().GetValue() == "" {
badFields[usernameField] = "Field required for creating an SSH private key credential."
@@ -977,7 +1059,6 @@ func validateCreateRequest(req *pbs.CreateCredentialRequest) error {
}
}
}
case credential.JsonSubtype.String():
object := req.GetItem().GetJsonAttributes().GetObject()
if object == nil || len(object.AsMap()) <= 0 {
@@ -985,7 +1066,6 @@ func validateCreateRequest(req *pbs.CreateCredentialRequest) error {
} else if _, err := json.Marshal(object); err != nil {
badFields[objectField] = "Unable to parse given json value"
}
default:
badFields[globals.TypeField] = fmt.Sprintf("Unsupported credential type %q", req.Item.GetType())
}
@@ -1018,7 +1098,11 @@ func validateUpdateRequest(req *pbs.UpdateCredentialRequest) error {
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), domainField) && attrs.GetDomain().GetValue() == "" {
badFields[domainField] = "This is a required field and cannot be set to empty."
}
case credential.PasswordSubtype:
attrs := req.GetItem().GetPasswordAttributes()
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), passwordField) && attrs.GetPassword().GetValue() == "" {
badFields[passwordField] = "This is a required field and cannot be set to empty."
}
case credential.SshPrivateKeySubtype:
attrs := req.GetItem().GetSshPrivateKeyAttributes()
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), usernameField) && attrs.GetUsername().GetValue() == "" {
@@ -1050,7 +1134,6 @@ func validateUpdateRequest(req *pbs.UpdateCredentialRequest) error {
}
}
}
case credential.JsonSubtype:
if handlers.MaskContainsPrefix(req.GetUpdateMask().GetPaths(), objectField) {
object := req.GetItem().GetJsonAttributes().GetObject()
@@ -1060,7 +1143,6 @@ func validateUpdateRequest(req *pbs.UpdateCredentialRequest) error {
badFields[objectField] = "Unable to parse given json value"
}
}
default:
badFields[globals.IdField] = "Unknown credential type."
}
@@ -1070,6 +1152,7 @@ func validateUpdateRequest(req *pbs.UpdateCredentialRequest) error {
globals.UsernamePasswordCredentialPrefix,
globals.UsernamePasswordCredentialPreviousPrefix,
globals.UsernamePasswordDomainCredentialPrefix,
globals.PasswordCredentialPrefix,
globals.SshPrivateKeyCredentialPrefix,
globals.JsonCredentialPrefix,
)
@@ -1082,6 +1165,7 @@ func validateDeleteRequest(req *pbs.DeleteCredentialRequest) error {
globals.UsernamePasswordCredentialPrefix,
globals.UsernamePasswordCredentialPreviousPrefix,
globals.UsernamePasswordDomainCredentialPrefix,
globals.PasswordCredentialPrefix,
globals.SshPrivateKeyCredentialPrefix,
globals.JsonCredentialPrefix,
)
@@ -68,7 +68,7 @@ func staticJsonCredentialToProto(cred *static.JsonCredential, prj *iam.Scope, hm
}
}
func staticPasswordCredentialToProto(cred *static.UsernamePasswordCredential, prj *iam.Scope, hmac string) *pb.Credential {
func staticUsernamePasswordCredentialToProto(cred *static.UsernamePasswordCredential, prj *iam.Scope, hmac string) *pb.Credential {
return &pb.Credential{
Id: cred.GetPublicId(),
CredentialStoreId: cred.GetStoreId(),
@@ -87,7 +87,25 @@ func staticPasswordCredentialToProto(cred *static.UsernamePasswordCredential, pr
}
}
func staticPasswordDomainCredentialToProto(cred *static.UsernamePasswordDomainCredential, prj *iam.Scope, hmac string) *pb.Credential {
func staticPasswordCredentialToProto(cred *static.PasswordCredential, prj *iam.Scope, hmac string) *pb.Credential {
return &pb.Credential{
Id: cred.GetPublicId(),
CredentialStoreId: cred.GetStoreId(),
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
CreatedTime: cred.GetCreateTime().GetTimestamp(),
UpdatedTime: cred.GetUpdateTime().GetTimestamp(),
Version: cred.GetVersion(),
Type: credential.PasswordSubtype.String(),
AuthorizedActions: testAuthorizedActions,
Attrs: &pb.Credential_PasswordAttributes{
PasswordAttributes: &pb.PasswordAttributes{
PasswordHmac: base64.RawURLEncoding.EncodeToString([]byte(hmac)),
},
},
}
}
func staticUsernamePasswordDomainCredentialToProto(cred *static.UsernamePasswordDomainCredential, prj *iam.Scope, hmac string) *pb.Credential {
return &pb.Credential{
Id: cred.GetPublicId(),
CredentialStoreId: cred.GetStoreId(),
@@ -155,13 +173,18 @@ func TestList(t *testing.T) {
c := static.TestUsernamePasswordCredential(t, conn, wrapper, user, pass, store.GetPublicId(), prj.GetPublicId())
hm, err := crypto.HmacSha256(ctx, []byte(pass), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
require.NoError(t, err)
wantCreds = append(wantCreds, staticPasswordCredentialToProto(c, prj, hm))
wantCreds = append(wantCreds, staticUsernamePasswordCredentialToProto(c, prj, hm))
domain := fmt.Sprintf("domain-%d", i)
upd := static.TestUsernamePasswordDomainCredential(t, conn, wrapper, user, pass, domain, store.GetPublicId(), prj.GetPublicId())
hm, err = crypto.HmacSha256(ctx, []byte(pass), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
require.NoError(t, err)
wantCreds = append(wantCreds, staticPasswordDomainCredentialToProto(upd, prj, hm))
wantCreds = append(wantCreds, staticUsernamePasswordDomainCredentialToProto(upd, prj, hm))
p := static.TestPasswordCredential(t, conn, wrapper, pass, store.GetPublicId(), prj.GetPublicId())
hm, err = crypto.HmacSha256(ctx, []byte(pass), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
require.NoError(t, err)
wantCreds = append(wantCreds, staticPasswordCredentialToProto(p, prj, hm))
spk := static.TestSshPrivateKeyCredential(t, conn, wrapper, user, static.TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId())
hm, err = crypto.HmacSha256(ctx, []byte(static.TestSshPrivateKeyPem), databaseWrapper, []byte(store.GetPublicId()), nil)
@@ -193,7 +216,7 @@ func TestList(t *testing.T) {
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 40,
EstItemCount: 50,
},
anonRes: &pbs.ListCredentialsResponse{
Items: wantCreds,
@@ -247,9 +270,9 @@ func TestList(t *testing.T) {
},
{
name: "Filter on Attribute",
req: &pbs.ListCredentialsRequest{CredentialStoreId: store.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/username"==%q`, wantCreds[4].GetUsernamePasswordAttributes().GetUsername().Value)},
req: &pbs.ListCredentialsRequest{CredentialStoreId: store.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/username"==%q`, wantCreds[0].GetUsernamePasswordAttributes().GetUsername().Value)},
res: &pbs.ListCredentialsResponse{
Items: wantCreds[4:7],
Items: []*pb.Credential{wantCreds[0], wantCreds[1], wantCreds[3]},
ResponseType: "complete",
ListToken: "",
SortBy: "created_time",
@@ -363,6 +386,10 @@ func TestGet(t *testing.T) {
updHm, err := crypto.HmacSha256(context.Background(), []byte("pass"), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
require.NoError(t, err)
pCred := static.TestPasswordCredential(t, conn, wrapper, "pass", store.GetPublicId(), prj.GetPublicId())
pHm, err := crypto.HmacSha256(context.Background(), []byte("pass"), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
require.NoError(t, err)
spkCred := static.TestSshPrivateKeyCredential(t, conn, wrapper, "user", static.TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId())
spkHm, err := crypto.HmacSha256(context.Background(), []byte(static.TestSshPrivateKeyPem), databaseWrapper, []byte(store.GetPublicId()), nil)
require.NoError(t, err)
@@ -455,7 +482,27 @@ func TestGet(t *testing.T) {
},
},
},
{
name: "success-password-only-credential",
id: pCred.GetPublicId(),
res: &pbs.GetCredentialResponse{
Item: &pb.Credential{
Id: pCred.GetPublicId(),
CredentialStoreId: pCred.GetStoreId(),
Scope: &scopepb.ScopeInfo{Id: store.GetProjectId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
Type: credential.PasswordSubtype.String(),
AuthorizedActions: testAuthorizedActions,
CreatedTime: pCred.CreateTime.GetTimestamp(),
UpdatedTime: pCred.UpdateTime.GetTimestamp(),
Version: 1,
Attrs: &pb.Credential_PasswordAttributes{
PasswordAttributes: &pb.PasswordAttributes{
PasswordHmac: base64.RawURLEncoding.EncodeToString([]byte(pHm)),
},
},
},
},
},
{
name: "success-spk",
id: spkCred.GetPublicId(),
@@ -589,6 +636,7 @@ func TestDelete(t *testing.T) {
upCred := static.TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId())
updCred := static.TestUsernamePasswordDomainCredential(t, conn, wrapper, "user", "pass", "domain", store.GetPublicId(), prj.GetPublicId())
pCred := static.TestPasswordCredential(t, conn, wrapper, "pass", store.GetPublicId(), prj.GetPublicId())
spkCred := static.TestSshPrivateKeyCredential(t, conn, wrapper, "user", static.TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId())
obj, _ := static.TestJsonObject(t)
@@ -609,6 +657,10 @@ func TestDelete(t *testing.T) {
name: "success-upd",
id: updCred.GetPublicId(),
},
{
name: "success-p",
id: pCred.GetPublicId(),
},
{
name: "success-spk",
id: spkCred.GetPublicId(),
@@ -906,6 +958,92 @@ func TestCreate(t *testing.T) {
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Can't specify Id P",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
CredentialStoreId: store.GetPublicId(),
Id: globals.PasswordCredentialPrefix + "_notallowed",
Type: credential.PasswordSubtype.String(),
Attrs: &pb.Credential_PasswordAttributes{
PasswordAttributes: &pb.PasswordAttributes{
Password: wrapperspb.String("password"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Invalid Credential Store Id P",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
CredentialStoreId: "p_invalidid",
Type: credential.PasswordSubtype.String(),
Attrs: &pb.Credential_PasswordAttributes{
PasswordAttributes: &pb.PasswordAttributes{
Password: wrapperspb.String("password"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Can't specify Created Time",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
CredentialStoreId: store.GetPublicId(),
CreatedTime: timestamppb.Now(),
Type: credential.PasswordSubtype.String(),
Attrs: &pb.Credential_PasswordAttributes{
PasswordAttributes: &pb.PasswordAttributes{
Password: wrapperspb.String("password"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Can't specify Updated Time",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
CredentialStoreId: store.GetPublicId(),
UpdatedTime: timestamppb.Now(),
Type: credential.PasswordSubtype.String(),
Attrs: &pb.Credential_PasswordAttributes{
PasswordAttributes: &pb.PasswordAttributes{
Password: wrapperspb.String("password"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Must provide type",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
CredentialStoreId: store.GetPublicId(),
Attrs: &pb.Credential_PasswordAttributes{
PasswordAttributes: &pb.PasswordAttributes{
Password: wrapperspb.String("password"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Must provide password",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
CredentialStoreId: store.GetPublicId(),
Type: credential.PasswordSubtype.String(),
Attrs: &pb.Credential_PasswordAttributes{
PasswordAttributes: &pb.PasswordAttributes{
// Empty password
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Must provide private key",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
@@ -959,6 +1097,32 @@ func TestCreate(t *testing.T) {
},
},
},
{
name: "valid-p",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
CredentialStoreId: store.GetPublicId(),
Type: credential.PasswordSubtype.String(),
Attrs: &pb.Credential_PasswordAttributes{
PasswordAttributes: &pb.PasswordAttributes{
Password: wrapperspb.String("password"),
},
},
}},
idPrefix: globals.PasswordCredentialPrefix + "_",
res: &pbs.CreateCredentialResponse{
Uri: fmt.Sprintf("credentials/%s_", globals.PasswordCredentialPrefix),
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.PasswordSubtype.String(),
AuthorizedActions: testAuthorizedActions,
},
},
},
{
name: "valid-spk",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
@@ -1086,6 +1250,15 @@ func TestCreate(t *testing.T) {
assert.Equal(base64.RawURLEncoding.EncodeToString([]byte(hm)), got.GetItem().GetUsernamePasswordAttributes().GetPasswordHmac())
assert.Empty(got.GetItem().GetUsernamePasswordAttributes().GetPassword())
case credential.PasswordSubtype.String():
password := tc.req.GetItem().GetPasswordAttributes().GetPassword().GetValue()
hm, err := crypto.HmacSha256(ctx, []byte(password), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
require.NoError(err)
// Validate attributes equal
assert.Equal(base64.RawURLEncoding.EncodeToString([]byte(hm)), got.GetItem().GetPasswordAttributes().GetPasswordHmac())
assert.Empty(got.GetItem().GetPasswordAttributes().GetPassword())
case credential.SshPrivateKeySubtype.String():
pk := tc.req.GetItem().GetSshPrivateKeyAttributes().GetPrivateKey().GetValue()
hm, err := crypto.HmacSha256(ctx, []byte(pk), databaseWrapper, []byte(store.GetPublicId()), nil)
@@ -1193,6 +1366,16 @@ func TestUpdate(t *testing.T) {
return cred, clean
}
freshCredP := func(pass string) (*static.PasswordCredential, func()) {
t.Helper()
cred := static.TestPasswordCredential(t, conn, wrapper, pass, store.GetPublicId(), prj.GetPublicId())
clean := func() {
_, err := s.DeleteCredential(ctx, &pbs.DeleteCredentialRequest{Id: cred.GetPublicId()})
require.NoError(t, err)
}
return cred, clean
}
freshCredSpk := func(user string) (*static.SshPrivateKeyCredential, func()) {
t.Helper()
cred := static.TestSshPrivateKeyCredential(t, conn, wrapper, user, static.TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId())
@@ -1405,6 +1588,26 @@ func TestUpdate(t *testing.T) {
return out
},
},
{
name: "update-password-attributes-passwordOnly",
req: &pbs.UpdateCredentialRequest{
UpdateMask: fieldmask("attributes.password"),
Item: &pb.Credential{
Attrs: &pb.Credential_PasswordAttributes{
PasswordAttributes: &pb.PasswordAttributes{
Password: wrapperspb.String("new-password"),
},
},
},
},
res: func(in *pb.Credential) *pb.Credential {
out := proto.Clone(in).(*pb.Credential)
hm, err := crypto.HmacSha256(context.Background(), []byte("new-password"), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
require.NoError(t, err)
out.GetPasswordAttributes().PasswordHmac = base64.RawURLEncoding.EncodeToString([]byte(hm))
return out
},
},
{
name: "update-spk",
req: &pbs.UpdateCredentialRequest{
@@ -1663,6 +1866,8 @@ func TestUpdate(t *testing.T) {
cred, cleanup = freshCredJson()
} else if strings.Contains(tc.name, "domainupd") {
cred, cleanup = freshCredUpd("user", "pass", "domain")
} else if strings.Contains(tc.name, "passwordOnly") {
cred, cleanup = freshCredP("pass")
} else {
cred, cleanup = freshCredUp("user", "pass")
}
@@ -1716,6 +1921,14 @@ func TestUpdate(t *testing.T) {
credUp, cleanUp := freshCredUp("user", "pass")
defer cleanUp()
// cant update read only fields
credUpd, cleanUpd := freshCredUpd("user", "pass", "domain")
defer cleanUpd()
// cant update read only fields
credP, cleanP := freshCredP("pass")
defer cleanP()
newStore := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
roCases := []struct {
@@ -1781,6 +1994,42 @@ func TestUpdate(t *testing.T) {
}
matcher(t, gErr)
assert.Nil(t, got)
req = &pbs.UpdateCredentialRequest{
Id: credUpd.GetPublicId(),
Item: tc.item,
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{tc.path}},
}
req.Item.Version = credUpd.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)
req = &pbs.UpdateCredentialRequest{
Id: credP.GetPublicId(),
Item: tc.item,
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{tc.path}},
}
req.Item.Version = credP.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)
})
}
}
@@ -1841,12 +2090,12 @@ func TestListPagination(t *testing.T) {
for _, l := range static.TestUsernamePasswordDomainCredentials(t, conn, wrapper, "username", "password", "domain", credStore.GetPublicId(), prj.PublicId, 5) {
hm, err := crypto.HmacSha256(ctx, []byte("password"), databaseWrapper, []byte(credStore.GetPublicId()), nil, crypto.WithEd25519())
require.NoError(err)
allCredentials = append(allCredentials, staticPasswordDomainCredentialToProto(l, prj, hm))
allCredentials = append(allCredentials, staticUsernamePasswordDomainCredentialToProto(l, prj, hm))
}
for _, l := range static.TestUsernamePasswordCredentials(t, conn, wrapper, "username", "password", credStore.GetPublicId(), prj.PublicId, 5) {
hm, err := crypto.HmacSha256(ctx, []byte("password"), databaseWrapper, []byte(credStore.GetPublicId()), nil, crypto.WithEd25519())
require.NoError(err)
allCredentials = append(allCredentials, staticPasswordCredentialToProto(l, prj, hm))
allCredentials = append(allCredentials, staticUsernamePasswordCredentialToProto(l, prj, hm))
}
// Reverse slices since response is ordered by created_time descending (newest first)
@@ -137,6 +137,17 @@ func dynamicToSessionCredential(ctx context.Context, cred credential.Dynamic) (*
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for credential"))
}
case credential.PasswordOnly:
credData, err = handlers.ProtoToStruct(
ctx,
&pb.PasswordCredential{
Password: string(c.Password()),
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for credential"))
}
case credential.SshPrivateKey:
credData, err = handlers.ProtoToStruct(
ctx,
@@ -264,6 +275,21 @@ func staticToSessionCredential(ctx context.Context, cred credential.Static) (*pb
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for username password domain credential"))
}
case *credstatic.PasswordCredential:
var err error
credType = string(globals.PasswordCredentialType)
credData, err = handlers.ProtoToStruct(
ctx,
&pb.PasswordCredential{
Password: string(c.GetPassword()),
},
)
secret = map[string]any{
"password": string(c.GetPassword()),
}
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for password credential"))
}
case *credstatic.SshPrivateKeyCredential:
var err error
credType = string(globals.SshPrivateKeyCredentialType)
@@ -285,7 +311,6 @@ func staticToSessionCredential(ctx context.Context, cred credential.Static) (*pb
if err != nil {
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(globals.JsonCredentialType)
@@ -2060,6 +2060,7 @@ func validateAddCredentialSourcesRequest(req *pbs.AddTargetCredentialSourcesRequ
globals.UsernamePasswordCredentialPrefix,
globals.UsernamePasswordCredentialPreviousPrefix,
globals.UsernamePasswordDomainCredentialPrefix,
globals.PasswordCredentialPrefix,
globals.SshPrivateKeyCredentialPrefix,
globals.JsonCredentialPrefix) {
badFields[globals.BrokeredCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
@@ -2100,6 +2101,7 @@ func validateSetCredentialSourcesRequest(req *pbs.SetTargetCredentialSourcesRequ
globals.UsernamePasswordCredentialPrefix,
globals.UsernamePasswordCredentialPreviousPrefix,
globals.UsernamePasswordDomainCredentialPrefix,
globals.PasswordCredentialPrefix,
globals.SshPrivateKeyCredentialPrefix,
globals.JsonCredentialPrefix) {
badFields[globals.BrokeredCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
@@ -2144,6 +2146,7 @@ func validateRemoveCredentialSourcesRequest(req *pbs.RemoveTargetCredentialSourc
globals.UsernamePasswordCredentialPrefix,
globals.UsernamePasswordCredentialPreviousPrefix,
globals.UsernamePasswordDomainCredentialPrefix,
globals.PasswordCredentialPrefix,
globals.SshPrivateKeyCredentialPrefix,
globals.JsonCredentialPrefix) {
badFields[globals.BrokeredCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
@@ -3085,6 +3085,7 @@ func TestAddTargetCredentialSources(t *testing.T) {
storeStatic := credstatic.TestCredentialStore(t, conn, wrapper, proj.GetPublicId())
creds := credstatic.TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", storeStatic.GetPublicId(), proj.GetPublicId(), 2)
updCreds := credstatic.TestUsernamePasswordDomainCredentials(t, conn, wrapper, "user", "pass", "domain", storeStatic.GetPublicId(), proj.GetPublicId(), 2)
pCreds := credstatic.TestPasswordCredentials(t, conn, wrapper, "pass", storeStatic.GetPublicId(), proj.GetPublicId(), 2)
addCases := []struct {
name string
@@ -3110,6 +3111,12 @@ func TestAddTargetCredentialSources(t *testing.T) {
addSources: []string{updCreds[1].GetPublicId()},
resultSourceIds: []string{updCreds[1].GetPublicId()},
},
{
name: "Add static p cred on empty target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty for static_p sources"),
addSources: []string{pCreds[1].GetPublicId()},
resultSourceIds: []string{pCreds[1].GetPublicId()},
},
{
name: "Add library on library populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for lib-lib sources", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose, string(cls[0].CredentialType()))})),
@@ -3128,6 +3135,12 @@ func TestAddTargetCredentialSources(t *testing.T) {
addSources: []string{cls[1].GetPublicId()},
resultSourceIds: []string{updCreds[0].GetPublicId(), cls[1].GetPublicId()},
},
{
name: "Add library on static p cred populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for lib-static_p sources", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose)})),
addSources: []string{cls[1].GetPublicId()},
resultSourceIds: []string{pCreds[0].GetPublicId(), cls[1].GetPublicId()},
},
{
name: "Add static cred on library populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for static-lib sources", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose, string(cls[0].CredentialType()))})),
@@ -3140,6 +3153,12 @@ func TestAddTargetCredentialSources(t *testing.T) {
addSources: []string{updCreds[1].GetPublicId()},
resultSourceIds: []string{cls[0].GetPublicId(), updCreds[1].GetPublicId()},
},
{
name: "Add p static cred on library populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for static_p-lib sources", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose, string(cls[0].CredentialType()))})),
addSources: []string{pCreds[1].GetPublicId()},
resultSourceIds: []string{cls[0].GetPublicId(), pCreds[1].GetPublicId()},
},
{
name: "Add static cred on static cred populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for static-static sources", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})),
@@ -3152,6 +3171,12 @@ func TestAddTargetCredentialSources(t *testing.T) {
addSources: []string{updCreds[1].GetPublicId()},
resultSourceIds: []string{creds[0].GetPublicId(), updCreds[1].GetPublicId()},
},
{
name: "Add static p cred on static cred populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for static_p-static sources", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})),
addSources: []string{pCreds[1].GetPublicId()},
resultSourceIds: []string{creds[0].GetPublicId(), pCreds[1].GetPublicId()},
},
{
name: "Add duplicated sources on library populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicated for lib sources", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose, string(cls[0].CredentialType()))})),
@@ -3170,6 +3195,12 @@ func TestAddTargetCredentialSources(t *testing.T) {
addSources: []string{cls[1].GetPublicId(), cls[1].GetPublicId(), updCreds[1].GetPublicId(), updCreds[1].GetPublicId()},
resultSourceIds: []string{updCreds[0].GetPublicId(), cls[1].GetPublicId(), updCreds[1].GetPublicId()},
},
{
name: "Add duplicated sources on static p cred populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicated for static p sources", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose)})),
addSources: []string{cls[1].GetPublicId(), cls[1].GetPublicId(), pCreds[1].GetPublicId(), pCreds[1].GetPublicId()},
resultSourceIds: []string{pCreds[0].GetPublicId(), cls[1].GetPublicId(), pCreds[1].GetPublicId()},
},
}
for _, tc := range addCases {
@@ -3296,6 +3327,7 @@ func TestSetTargetCredentialSources(t *testing.T) {
storeStatic := credstatic.TestCredentialStore(t, conn, wrapper, proj.GetPublicId())
creds := credstatic.TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", storeStatic.GetPublicId(), proj.GetPublicId(), 2)
updCreds := credstatic.TestUsernamePasswordDomainCredentials(t, conn, wrapper, "user", "pass", "domain", storeStatic.GetPublicId(), proj.GetPublicId(), 2)
pCreds := credstatic.TestPasswordCredentials(t, conn, wrapper, "pass", storeStatic.GetPublicId(), proj.GetPublicId(), 2)
setCases := []struct {
name string
@@ -3321,6 +3353,12 @@ func TestSetTargetCredentialSources(t *testing.T) {
setCredentialSources: []string{updCreds[1].GetPublicId()},
resultCredentialSourceIds: []string{updCreds[1].GetPublicId()},
},
{
name: "Set static_p on empty target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty static_p"),
setCredentialSources: []string{pCreds[1].GetPublicId()},
resultCredentialSourceIds: []string{pCreds[1].GetPublicId()},
},
{
name: "Set library on library populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated library-library", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose, string(cls[0].CredentialType()))})),
@@ -3339,6 +3377,12 @@ func TestSetTargetCredentialSources(t *testing.T) {
setCredentialSources: []string{updCreds[1].GetPublicId()},
resultCredentialSourceIds: []string{updCreds[1].GetPublicId()},
},
{
name: "Set static_p on library populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated static_p-library", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose, string(cls[0].CredentialType()))})),
setCredentialSources: []string{pCreds[1].GetPublicId()},
resultCredentialSourceIds: []string{pCreds[1].GetPublicId()},
},
{
name: "Set library on static populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated library-static", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})),
@@ -3351,6 +3395,12 @@ func TestSetTargetCredentialSources(t *testing.T) {
setCredentialSources: []string{cls[1].GetPublicId()},
resultCredentialSourceIds: []string{cls[1].GetPublicId()},
},
{
name: "Set library on static_p populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated library-static_p", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose)})),
setCredentialSources: []string{cls[1].GetPublicId()},
resultCredentialSourceIds: []string{cls[1].GetPublicId()},
},
{
name: "Set static on static populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated static-static", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})),
@@ -3363,12 +3413,24 @@ func TestSetTargetCredentialSources(t *testing.T) {
setCredentialSources: []string{updCreds[1].GetPublicId()},
resultCredentialSourceIds: []string{updCreds[1].GetPublicId()},
},
{
name: "Set static_p on static populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated static_p-static", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})),
setCredentialSources: []string{pCreds[1].GetPublicId()},
resultCredentialSourceIds: []string{pCreds[1].GetPublicId()},
},
{
name: "Set static on static_upd populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated static-static_upd", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", updCreds[0].GetPublicId(), credential.BrokeredPurpose)})),
setCredentialSources: []string{creds[1].GetPublicId()},
resultCredentialSourceIds: []string{creds[1].GetPublicId()},
},
{
name: "Set static on static_p populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated static-static_p", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose)})),
setCredentialSources: []string{creds[1].GetPublicId()},
resultCredentialSourceIds: []string{creds[1].GetPublicId()},
},
{
name: "Set duplicate library on populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicate library", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose, string(cls[0].CredentialType()))})),
@@ -3387,12 +3449,19 @@ func TestSetTargetCredentialSources(t *testing.T) {
setCredentialSources: []string{updCreds[1].GetPublicId(), updCreds[1].GetPublicId()},
resultCredentialSourceIds: []string{updCreds[1].GetPublicId()},
},
{
name: "Set duplicate static_p on populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicate static_p", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose)})),
setCredentialSources: []string{pCreds[1].GetPublicId(), pCreds[1].GetPublicId()},
resultCredentialSourceIds: []string{pCreds[1].GetPublicId()},
},
{
name: "Set empty on populated target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "another populated",
target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose, string(cls[0].CredentialType()))}),
target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)}),
target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", updCreds[0].GetPublicId(), credential.BrokeredPurpose)}),
target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose)}),
),
setCredentialSources: []string{},
resultCredentialSourceIds: nil,
@@ -3509,6 +3578,7 @@ func TestRemoveTargetCredentialSources(t *testing.T) {
csStatic := credstatic.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0]
creds := credstatic.TestUsernamePasswordCredentials(t, conn, wrapper, "u", "p", csStatic.GetPublicId(), proj.GetPublicId(), 2)
updCreds := credstatic.TestUsernamePasswordDomainCredentials(t, conn, wrapper, "user", "pass", "domain", csStatic.GetPublicId(), proj.GetPublicId(), 2)
pCreds := credstatic.TestPasswordCredentials(t, conn, wrapper, "pass", csStatic.GetPublicId(), proj.GetPublicId(), 2)
removeCases := []struct {
name string
@@ -3535,6 +3605,12 @@ func TestRemoveTargetCredentialSources(t *testing.T) {
removeCredentialSources: []string{updCreds[1].GetPublicId()},
wantErr: true,
},
{
name: "Remove static_p from empty",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty static_p"),
removeCredentialSources: []string{pCreds[1].GetPublicId()},
wantErr: true,
},
{
name: "Remove 1 of 2 libraries",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove partial lib",
@@ -3565,6 +3641,16 @@ func TestRemoveTargetCredentialSources(t *testing.T) {
removeCredentialSources: []string{updCreds[1].GetPublicId()},
resultCredentialSourceIds: []string{updCreds[0].GetPublicId()},
},
{
name: "Remove 1 of 2 static_p credentials",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove partial static_p",
target.WithStaticCredentials([]*target.StaticCredential{
target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose),
target.TestNewStaticCredential("", pCreds[1].GetPublicId(), credential.BrokeredPurpose),
})),
removeCredentialSources: []string{pCreds[1].GetPublicId()},
resultCredentialSourceIds: []string{pCreds[0].GetPublicId()},
},
{
name: "Remove 1 duplicate set of 2 libraries",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove duplicate lib",
@@ -3601,6 +3687,19 @@ func TestRemoveTargetCredentialSources(t *testing.T) {
},
resultCredentialSourceIds: []string{updCreds[0].GetPublicId()},
},
{
name: "Remove 1 duplicate set of 2 static_p credentials",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove duplicate static_p",
target.WithStaticCredentials([]*target.StaticCredential{
target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose),
target.TestNewStaticCredential("", pCreds[1].GetPublicId(), credential.BrokeredPurpose),
})),
removeCredentialSources: []string{
pCreds[1].GetPublicId(), pCreds[1].GetPublicId(),
},
resultCredentialSourceIds: []string{pCreds[0].GetPublicId()},
},
{
name: "Remove mixed sources from target",
tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove mixed",
@@ -3613,12 +3712,14 @@ func TestRemoveTargetCredentialSources(t *testing.T) {
target.TestNewStaticCredential("", creds[1].GetPublicId(), credential.BrokeredPurpose),
target.TestNewStaticCredential("", updCreds[0].GetPublicId(), credential.BrokeredPurpose),
target.TestNewStaticCredential("", updCreds[1].GetPublicId(), credential.BrokeredPurpose),
target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose),
target.TestNewStaticCredential("", pCreds[1].GetPublicId(), credential.BrokeredPurpose),
})),
removeCredentialSources: []string{
cls[1].GetPublicId(), creds[0].GetPublicId(), updCreds[0].GetPublicId(),
cls[1].GetPublicId(), creds[0].GetPublicId(), updCreds[0].GetPublicId(), pCreds[0].GetPublicId(),
},
resultCredentialSourceIds: []string{
cls[0].GetPublicId(), creds[1].GetPublicId(), updCreds[1].GetPublicId(),
cls[0].GetPublicId(), creds[1].GetPublicId(), updCreds[1].GetPublicId(), pCreds[1].GetPublicId(),
},
},
{
@@ -3633,11 +3734,14 @@ func TestRemoveTargetCredentialSources(t *testing.T) {
target.TestNewStaticCredential("", creds[1].GetPublicId(), credential.BrokeredPurpose),
target.TestNewStaticCredential("", updCreds[0].GetPublicId(), credential.BrokeredPurpose),
target.TestNewStaticCredential("", updCreds[1].GetPublicId(), credential.BrokeredPurpose),
target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose),
target.TestNewStaticCredential("", pCreds[1].GetPublicId(), credential.BrokeredPurpose),
})),
removeCredentialSources: []string{
cls[0].GetPublicId(), cls[1].GetPublicId(),
creds[0].GetPublicId(), creds[1].GetPublicId(),
updCreds[0].GetPublicId(), updCreds[1].GetPublicId(),
pCreds[0].GetPublicId(), pCreds[1].GetPublicId(),
},
resultCredentialSourceIds: []string{},
},
@@ -4170,7 +4274,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: "username_password",
CredentialType: string(globals.UsernamePasswordCredentialType),
}})
require.NoError(t, err)
@@ -4203,7 +4307,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: "username_password",
CredentialType: string(globals.UsernamePasswordCredentialType),
CredentialMappingOverrides: &structpb.Struct{Fields: map[string]*structpb.Value{
"username_attribute": structpb.NewStringValue("non-default-user"),
"password_attribute": structpb.NewStringValue("non-default-pass"),
@@ -4211,6 +4315,44 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
}})
require.NoError(t, err)
defaultPassword := v.CreateKVSecret(t, "default-password", []byte(`{"data": {"password": "my-default-password"}}`))
require.NotNil(t, defaultPassword)
nonDefaultPassword := v.CreateKVSecret(t, "non-default-password", []byte(`{"data": {"non-default-password": "my-non-default-password"}}`))
require.NotNil(t, nonDefaultPassword)
clsRespPassword, err := credLibService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{
CredentialStoreId: vaultStore.GetPublicId(),
Name: wrapperspb.String("Password Library"),
Description: wrapperspb.String("Password Library Description"),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{
VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(path.Join("secret", "data", "default-password")),
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: string(globals.PasswordCredentialType),
}})
require.NoError(t, err)
clsRespPasswordWithMapping, err := credLibService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{
CredentialStoreId: vaultStore.GetPublicId(),
Name: wrapperspb.String("Password Mapping Library"),
Description: wrapperspb.String("Password Mapping Library Description"),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{
VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(path.Join("secret", "data", "non-default-password")),
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: string(globals.PasswordCredentialType),
CredentialMappingOverrides: &structpb.Struct{Fields: map[string]*structpb.Value{
"password_attribute": structpb.NewStringValue("non-default-password"),
}},
}})
require.NoError(t, err)
staticStore := credstatic.TestCredentialStore(t, conn, wrapper, proj.GetPublicId())
credService, err := credentials.NewService(ctx, iamRepoFn, staticCredRepoFn, 1000)
require.NoError(t, err)
@@ -4245,6 +4387,20 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, updCredResp)
pCredResp, err := credService.CreateCredential(ctx, &pbs.CreateCredentialRequest{Item: &credpb.Credential{
CredentialStoreId: staticStore.GetPublicId(),
Type: credential.PasswordSubtype.String(),
Name: wrapperspb.String("P Cred Name"),
Description: wrapperspb.String("P Cred Description"),
Attrs: &credpb.Credential_PasswordAttributes{
PasswordAttributes: &credpb.PasswordAttributes{
Password: wrapperspb.String("static-password"),
},
},
}})
require.NoError(t, err)
require.NotNil(t, pCredResp)
sshPkCredResp, err := credService.CreateCredential(ctx, &pbs.CreateCredentialRequest{Item: &credpb.Credential{
CredentialStoreId: staticStore.GetPublicId(),
Type: credential.SshPrivateKeySubtype.String(),
@@ -4291,7 +4447,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: "ssh_private_key",
CredentialType: string(globals.SshPrivateKeyCredentialType),
}})
require.NoError(t, err)
@@ -4310,7 +4466,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: "ssh_private_key",
CredentialType: string(globals.SshPrivateKeyCredentialType),
CredentialMappingOverrides: &structpb.Struct{Fields: map[string]*structpb.Value{
"username_attribute": structpb.NewStringValue("non-default-user"),
"private_key_attribute": structpb.NewStringValue("non-default-pk"),
@@ -4333,7 +4489,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: "ssh_private_key",
CredentialType: string(globals.SshPrivateKeyCredentialType),
}})
require.NoError(t, err)
require.NotNil(t, clsRespSshPrivateKeyWithPass)
@@ -4353,7 +4509,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: "ssh_private_key",
CredentialType: string(globals.SshPrivateKeyCredentialType),
CredentialMappingOverrides: &structpb.Struct{Fields: map[string]*structpb.Value{
"username_attribute": structpb.NewStringValue("/data/non-default-user"),
"private_key_attribute": structpb.NewStringValue("/data/non-default-pk"),
@@ -4443,6 +4599,58 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
},
wantedConnectionLimit: 100,
},
{
name: "vault-password",
hostSourceId: shs.GetPublicId(),
credSourceId: clsRespPassword.GetItem().GetId(),
wantedHostId: h.GetPublicId(),
wantedEndpoint: h.GetAddress(),
wantedCred: &pb.SessionCredential{
CredentialSource: &pb.CredentialSource{
Id: clsRespPassword.GetItem().GetId(),
Name: clsRespPassword.GetItem().GetName().GetValue(),
Description: clsRespPassword.GetItem().GetDescription().GetValue(),
CredentialStoreId: vaultStore.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
CredentialType: string(globals.PasswordCredentialType),
},
Credential: func() *structpb.Struct {
data := map[string]any{
"password": "my-default-password",
}
st, err := structpb.NewStruct(data)
require.NoError(t, err)
return st
}(),
},
wantedConnectionLimit: 10,
},
{
name: "vault-password-with-mapping",
hostSourceId: shs.GetPublicId(),
credSourceId: clsRespPasswordWithMapping.GetItem().GetId(),
wantedHostId: h.GetPublicId(),
wantedEndpoint: h.GetAddress(),
wantedCred: &pb.SessionCredential{
CredentialSource: &pb.CredentialSource{
Id: clsRespPasswordWithMapping.GetItem().GetId(),
Name: clsRespPasswordWithMapping.GetItem().GetName().GetValue(),
Description: clsRespPasswordWithMapping.GetItem().GetDescription().GetValue(),
CredentialStoreId: vaultStore.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
CredentialType: string(globals.PasswordCredentialType),
},
Credential: func() *structpb.Struct {
data := map[string]any{
"password": "my-non-default-password",
}
st, err := structpb.NewStruct(data)
require.NoError(t, err)
return st
}(),
},
wantedConnectionLimit: 100,
},
{
name: "static-UsernamePassword",
hostSourceId: shs.GetPublicId(),
@@ -4498,6 +4706,32 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
},
wantedConnectionLimit: 1000,
},
{
name: "static-Password",
hostSourceId: shs.GetPublicId(),
credSourceId: pCredResp.GetItem().GetId(),
wantedHostId: h.GetPublicId(),
wantedEndpoint: h.GetAddress(),
wantedCred: &pb.SessionCredential{
CredentialSource: &pb.CredentialSource{
Id: pCredResp.GetItem().GetId(),
Name: pCredResp.GetItem().GetName().GetValue(),
Description: pCredResp.GetItem().GetDescription().GetValue(),
CredentialStoreId: staticStore.GetPublicId(),
Type: credstatic.Subtype.String(),
CredentialType: string(globals.PasswordCredentialType),
},
Credential: func() *structpb.Struct {
data := map[string]any{
"password": "static-password",
}
st, err := structpb.NewStruct(data)
require.NoError(t, err)
return st
}(),
},
wantedConnectionLimit: 1000,
},
{
name: "static-ssh-private-key",
hostSourceId: shs.GetPublicId(),
@@ -79,6 +79,7 @@ begin;
for each row execute procedure default_vault_ldap_credential_type();
-- Replaces view from 99/01_credential_vault_library_refactor.up.sql
-- Replaced in 101/02_credential_vault_password_library.up.sql
drop view credential_vault_library_issue_credentials;
create view credential_vault_library_issue_credentials as
with
@@ -0,0 +1,212 @@
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
begin;
create table credential_static_password_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,
name wt_name null,
description wt_description,
create_time wt_timestamp,
update_time wt_timestamp,
version wt_version,
password_encrypted bytea not null,
constraint password_encrypted_must_not_be_empty
check(
length(password_encrypted) > 0
),
password_hmac bytea not null,
constraint password_hmac_must_not_be_empty
check(
length(password_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,
project_id wt_public_id not null,
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_password_credential_store_id_name_uq
unique (store_id, name),
constraint credential_static_password_store_id_public_id_uq
unique (store_id, public_id)
);
comment on table credential_static_password_credential is
'credential_static_password_credential is a table where each row is a resource that represents a static password credential. '
'It is a credential_static subtype and an aggregate root.';
create trigger default_create_time_column before insert on credential_static_password_credential
for each row execute procedure default_create_time();
create trigger insert_credential_static_subtype before insert on credential_static_password_credential
for each row execute procedure insert_credential_static_subtype();
create trigger immutable_columns before update on credential_static_password_credential
for each row execute procedure immutable_columns('public_id', 'store_id', 'project_id', 'create_time');
create trigger update_credential_static_table_update_time before update on credential_static_password_credential
for each row execute procedure update_credential_static_table_update_time();
create trigger update_time_column before update on credential_static_password_credential
for each row execute procedure update_time_column();
create trigger update_version_column after update on credential_static_password_credential
for each row execute procedure update_version_column();
create trigger delete_credential_static_subtype after delete on credential_static_password_credential
for each row execute procedure delete_credential_static_subtype();
create table credential_static_password_credential_hst (
public_id wt_public_id not null,
name wt_name,
description wt_description,
project_id wt_public_id not null,
store_id wt_public_id not null,
password_hmac bytea not null,
history_id wt_url_safe_id default wt_url_safe_id() primary key
constraint credential_static_history_base_fkey
references credential_static_history_base (history_id)
on delete cascade
on update cascade,
valid_range tstzrange not null default tstzrange (current_timestamp, null),
constraint credential_static_password_credential_hst_valid_range_excl
exclude using gist (public_id with =, valid_range with &&)
);
comment on table credential_static_password_credential_hst is
'credential_static_password_credential_hst is a history table where each row contains the values from a row '
'in the credential_static_password_credential table during the time range in the valid_range column.';
create trigger insert_credential_static_history_subtype before insert on credential_static_password_credential_hst
for each row execute function insert_credential_static_history_subtype();
create trigger hst_on_insert after insert on credential_static_password_credential
for each row execute function hst_on_insert();
create trigger hst_on_update after update on credential_static_password_credential
for each row execute function hst_on_update();
create trigger hst_on_delete after delete on credential_static_password_credential
for each row execute function hst_on_delete();
create trigger delete_credential_static_history_subtype after delete on credential_static_password_credential_hst
for each row execute function delete_credential_static_history_subtype();
create table credential_static_password_credential_deleted (
public_id wt_public_id primary key,
delete_time wt_timestamp not null
);
comment on table credential_static_password_credential_deleted is
'credential_static_password_credential_deleted holds the ID and delete_time '
'of every deleted static password credential. '
'It is automatically trimmed of records older than 30 days by a job.';
create trigger insert_deleted_id after delete on credential_static_password_credential
for each row execute procedure insert_deleted_id('credential_static_password_credential_deleted');
create index credential_static_password_credential_deleted_delete_time_idx on credential_static_password_credential_deleted (delete_time);
create view credential_static_password_credential_hst_aggregate as
select
rsc.recording_id,
spc.public_id,
spc.name,
spc.description,
spc.password_hmac,
css.public_id as store_public_id,
css.project_id as store_project_id,
css.name as store_name,
css.description as store_description,
string_agg(distinct rsc.credential_purpose, '|') as purposes
from
credential_static_password_credential_hst as spc
left join recording_static_credential as rsc on spc.history_id = rsc.credential_static_hst_id
join credential_static_store_hst as css on rsc.credential_static_store_hst_id = css.history_id
group by spc.history_id, rsc.recording_id, css.history_id;
comment on view credential_static_password_credential_hst_aggregate is
'credential_static_password_credential_hst_aggregate contains the password credential history data along with its store and purpose data.';
-- This constraint replaces the previous constraint created in 98/01_credential_static_username_password_domain_credential.up.sql
alter table credential_type_enm
drop constraint only_predefined_credential_types_allowed;
alter table credential_type_enm
add constraint only_predefined_credential_types_allowed
check (
name in (
'unspecified',
'username_password',
'ssh_private_key',
'ssh_certificate',
'username_password_domain',
'password'
)
);
insert into credential_type_enm (name)
values ('password');
-- This function replaces the previous function created in 98/01_credential_static_username_password_domain_credential.up.sql
create or replace function insert_recording_static_credentials() returns trigger
as $$
begin
with
session_recording(session_id, recording_id) as (
select session_id, public_id
from recording_session
where session_id = new.session_id
),
session_static_creds(cred_id, purpose, recording_id) as (
select credential_static_id, credential_purpose, recording_id
from session_credential_static
join session_recording using (session_id)
),
static_cred_history(public_id, store_id, cred_hst_id, valid_range) as (
select public_id, store_id, history_id, valid_range
from credential_static_json_credential_hst
union
select public_id, store_id, history_id, valid_range
from credential_static_ssh_private_key_credential_hst
union
select public_id, store_id, history_id, valid_range
from credential_static_username_password_credential_hst
union
select public_id, store_id, history_id, valid_range
from credential_static_username_password_domain_credential_hst
union
select public_id, store_id, history_id, valid_range
from credential_static_password_credential_hst
),
final(recording_id, cred_id, store_id, cred_hst_id, store_hst_id, cred_purpose) as (
select ssc.recording_id, sc.public_id, sc.store_id, sc.cred_hst_id, store_hst.history_id, ssc.purpose
from static_cred_history as sc
join credential_static_store_hst as store_hst on sc.store_id = store_hst.public_id
and store_hst.valid_range @> current_timestamp
join session_static_creds as ssc on sc.public_id = ssc.cred_id
where sc.public_id in (select cred_id from session_static_creds)
and sc.valid_range @> current_timestamp
)
insert into recording_static_credential
(recording_id, credential_static_store_hst_id, credential_static_hst_id, credential_purpose)
select recording_id, store_hst_id, cred_hst_id, cred_purpose
from final;
return new;
end;
$$ language plpgsql;
comment on function insert_recording_static_credentials is
'insert_recording_static_credentials is an after insert trigger for the recording_session table.';
insert into oplog_ticket (name, version)
values ('credential_static_password_credential', 1);
commit;
@@ -0,0 +1,267 @@
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
begin;
create table credential_vault_generic_library_password_mapping_override (
library_id wt_public_id primary key
constraint credential_vault_generic_library_fkey
references credential_vault_generic_library (public_id)
on delete cascade
on update cascade
constraint credential_vault_generic_library_mapping_override_fkey
references credential_vault_generic_library_mapping_override (library_id)
on delete cascade
on update cascade,
password_attribute wt_sentinel
default wt_to_sentinel('no override')
not null
);
comment on table credential_vault_generic_library_password_mapping_override is
'credential_vault_generic_library_password_mapping_override is a table '
'where each row represents a mapping that overrides the default mapping '
'from a generic vault secret to a password credential type '
'for a vault credential library.';
create trigger insert_credential_vault_generic_library_mapping_override_subtyp before insert on credential_vault_generic_library_password_mapping_override
for each row execute procedure insert_credential_vault_generic_library_mapping_override_subtyp();
create trigger delete_credential_vault_generic_library_mapping_override_subtyp after delete on credential_vault_generic_library_password_mapping_override
for each row execute procedure delete_credential_vault_generic_library_mapping_override_subtyp();
-- Replaces view from 100/01_credential_vault_ldap_library.up.sql
drop view credential_vault_library_issue_credentials;
create view credential_vault_library_issue_credentials as
with
username_password_override (library_id, username_attribute, password_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(password_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_username_password_mapping_ovrd
),
ssh_private_key_override (library_id, username_attribute, private_key_attribute, private_key_passphrase_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(private_key_attribute, wt_to_sentinel('no override')),
nullif(private_key_passphrase_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_ssh_private_key_mapping_ovrd
),
username_password_domain_override (library_id, username_attribute, password_attribute, domain_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(password_attribute, wt_to_sentinel('no override')),
nullif(domain_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_usern_pass_domain_mapping_ovrd
),
password_override (library_id, password_attribute) as (
select library_id,
nullif(password_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_password_mapping_override
)
select library.public_id as public_id,
library.store_id as store_id,
library.name as name,
library.description as description,
library.create_time as create_time,
library.update_time as update_time,
library.version as version,
library.vault_path as vault_path,
library.http_method as http_method,
library.http_request_body as http_request_body,
library.credential_type as credential_type,
null as key_type,
null as key_bits,
null as username,
null as ttl,
null as key_id,
null as critical_options,
null as extensions,
store.project_id as project_id,
store.vault_address as vault_address,
store.namespace as namespace,
store.ca_cert as ca_cert,
store.tls_server_name as tls_server_name,
store.tls_skip_verify as tls_skip_verify,
store.worker_filter as worker_filter,
store.ct_token as ct_token, -- encrypted
store.token_hmac as token_hmac,
store.token_status as token_status,
store.token_key_id as token_key_id,
store.client_cert as client_cert,
store.ct_client_key as ct_client_key, -- encrypted
store.client_key_id as client_key_id,
coalesce(upasso.username_attribute, sshpk.username_attribute, pd.username_attribute)
as username_attribute,
coalesce(upasso.password_attribute, pd.password_attribute, po.password_attribute)
as password_attribute,
pd.domain_attribute as domain_attribute,
sshpk.private_key_attribute as private_key_attribute,
sshpk.private_key_passphrase_attribute as private_key_passphrase_attribute,
'generic' as cred_lib_type, -- used to switch on
null as additional_valid_principals
from credential_vault_generic_library library
join credential_vault_store_client store
on library.store_id = store.public_id
left join username_password_override upasso
on library.public_id = upasso.library_id
left join ssh_private_key_override sshpk
on library.public_id = sshpk.library_id
left join username_password_domain_override pd
on library.public_id = pd.library_id
left join password_override po
on library.public_id = po.library_id
union
select library.public_id as public_id,
library.store_id as store_id,
library.name as name,
library.description as description,
library.create_time as create_time,
library.update_time as update_time,
library.version as version,
library.vault_path as vault_path,
null as http_method,
null as http_request_body,
library.credential_type as credential_type,
library.key_type as key_type,
library.key_bits as key_bits,
library.username as username,
library.ttl as ttl,
library.key_id as key_id,
library.critical_options as critical_options,
library.extensions as extensions,
store.project_id as project_id,
store.vault_address as vault_address,
store.namespace as namespace,
store.ca_cert as ca_cert,
store.tls_server_name as tls_server_name,
store.tls_skip_verify as tls_skip_verify,
store.worker_filter as worker_filter,
store.ct_token as ct_token, -- encrypted
store.token_hmac as token_hmac,
store.token_status as token_status,
store.token_key_id as token_key_id,
store.client_cert as client_cert,
store.ct_client_key as ct_client_key, -- encrypted
store.client_key_id as client_key_id,
null as username_attribute,
null as password_attribute,
null as domain_attribute,
null as private_key_attribute,
null as private_key_passphrase_attribute,
'ssh-signed-cert' as cred_lib_type, -- used to switch on
additional_valid_principals as additional_valid_principals
from credential_vault_ssh_cert_library library
join credential_vault_store_client store
on library.store_id = store.public_id
union
select library.public_id as public_id,
library.store_id as store_id,
library.name as name,
library.description as description,
library.create_time as create_time,
library.update_time as update_time,
library.version as version,
library.vault_path as vault_path,
null as http_method,
null as http_request_body,
library.credential_type as credential_type,
null as key_type,
null as key_bits,
null as username,
null as ttl,
null as key_id,
null as critical_options,
null as extensions,
store.project_id as project_id,
store.vault_address as vault_address,
store.namespace as namespace,
store.ca_cert as ca_cert,
store.tls_server_name as tls_server_name,
store.tls_skip_verify as tls_skip_verify,
store.worker_filter as worker_filter,
store.ct_token as ct_token, -- encrypted
store.token_hmac as token_hmac,
store.token_status as token_status,
store.token_key_id as token_key_id,
store.client_cert as client_cert,
store.ct_client_key as ct_client_key, -- encrypted
store.client_key_id as client_key_id,
pd.username_attribute as username_attribute,
pd.password_attribute as password_attribute,
pd.domain_attribute as domain_attribute,
null as private_key_attribute,
null as private_key_passphrase_attribute,
'ldap' as cred_lib_type, -- used to switch on
null as additional_valid_principals
from credential_vault_ldap_library library
join credential_vault_store_client store
on library.store_id = store.public_id
left join username_password_domain_override pd
on library.public_id = pd.library_id;
comment on view credential_vault_library_issue_credentials is
'credential_vault_library_issue_credentials is a view where each row contains a credential library and the credential library''s data needed to connect to Vault. '
'This view should only be used when issuing credentials from a Vault credential library. Each row may contain encrypted data. '
'This view should not be used to retrieve data which will be returned external to boundary.';
-- Replaces view created in 99/01_credential_vault_library_refactor.up.sql
drop view credential_vault_generic_library_list_lookup;
create view credential_vault_generic_library_list_lookup as
with
username_password_override (library_id, username_attribute, password_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(password_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_username_password_mapping_ovrd
),
ssh_private_key_override (library_id, username_attribute, private_key_attribute, private_key_passphrase_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(private_key_attribute, wt_to_sentinel('no override')),
nullif(private_key_passphrase_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_ssh_private_key_mapping_ovrd
),
username_password_domain_override (library_id, username_attribute, password_attribute, domain_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(password_attribute, wt_to_sentinel('no override')),
nullif(domain_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_usern_pass_domain_mapping_ovrd
),
password_override (library_id, password_attribute) as (
select library_id,
nullif(password_attribute, wt_to_sentinel('no override'))
from credential_vault_generic_library_password_mapping_override
)
select library.public_id as public_id,
library.store_id as store_id,
library.name as name,
library.description as description,
library.create_time as create_time,
library.update_time as update_time,
library.version as version,
library.vault_path as vault_path,
library.http_method as http_method,
library.http_request_body as http_request_body,
library.credential_type as credential_type,
coalesce(upasso.username_attribute, sshpk.username_attribute, pd.username_attribute)
as username_attribute,
coalesce(upasso.password_attribute, pd.password_attribute, po.password_attribute)
as password_attribute,
sshpk.private_key_attribute as private_key_attribute,
sshpk.private_key_passphrase_attribute as private_key_passphrase_attribute,
pd.domain_attribute as domain_attribute
from credential_vault_generic_library library
left join username_password_override upasso
on library.public_id = upasso.library_id
left join ssh_private_key_override sshpk
on library.public_id = sshpk.library_id
left join username_password_domain_override pd
on library.public_id = pd.library_id
left join password_override po
on library.public_id = po.library_id;
comment on view credential_vault_generic_library_list_lookup is
'credential_vault_generic_library_list_lookup is a view where each row contains a credential library and any of library''s credential mapping overrides. '
'No encrypted data is returned. This view can be used to retrieve data which will be returned external to boundary.';
commit;
@@ -153,8 +153,8 @@ begin;
comment on view credential_static_username_password_domain_credential_hst_agg is
'credential_static_username_password_domain_credential_hst_aggregate contains the username password credential history data along with its store and purpose data.';
-- This constraint replaces the previous constraint created in 63/01_credential_vault_ssh_cert_library.up.sql
-- This constraint is replaced in 101/01_credential_static_password_credential.up.sql
alter table credential_type_enm
drop constraint only_predefined_credential_types_allowed;
@@ -173,8 +173,8 @@ begin;
insert into credential_type_enm (name)
values ('username_password_domain');
-- This function replaces the previous function created in 71/14_recording_static_credential.up.sql
-- This function is replaced in 101/01_credential_static_password_credential.up.sql
create or replace function insert_recording_static_credentials() returns trigger
as $$
begin
@@ -37,9 +37,8 @@ begin;
create trigger delete_credential_vault_library_mapping_override_subtype after delete on credential_vault_library_username_password_domain_mapping_ovrd
for each row execute procedure delete_credential_vault_library_mapping_override_subtype();
-- Replaces view from 78/01_ssh_signed_certs_additional_valid_principals.up.sql
-- Replaced in 99/01_credential_vault_library_refactor.up.sql
-- Replaces view from 78/01_ssh_signed_certs_additional_valid_principals.up.sql
-- Replaced in 99/01_credential_vault_library_refactor.up.sql
drop view credential_vault_library_issue_credentials;
create view credential_vault_library_issue_credentials as
with
@@ -161,10 +160,9 @@ begin;
'This view should only be used when issuing credentials from a Vault credential library. Each row may contain encrypted data. '
'This view should not be used to retrieve data which will be returned external to boundary.';
-- Replaces view created in 49/01_vault_credentials.up.sql
-- Replaced in 99/01_credential_vault_library_refactor.up.sql where this
-- view's name changed to credential_vault_generic_library_list_lookup.
-- Replaces view created in 49/01_vault_credentials.up.sql
-- Replaced in 99/01_credential_vault_library_refactor.up.sql where this
-- view's name changed to credential_vault_generic_library_list_lookup.
drop view credential_vault_library_list_lookup;
create view credential_vault_library_list_lookup as
with
@@ -503,6 +503,7 @@ begin;
'This view should not be used to retrieve data which will be returned external to boundary.';
-- Replaces and renames view defined in 98/02_username_password_domain_vault.up.sql.
-- Replaced in 101/02_credential_vault_password_library.up.sql
alter view credential_vault_library_list_lookup
rename to credential_vault_generic_library_list_lookup;
drop view credential_vault_generic_library_list_lookup;
@@ -82,6 +82,11 @@ message Credential {
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.subtype) = "username_password_domain"
];
PasswordAttributes password_attributes = 105 [
(google.api.field_visibility).restriction = "INTERNAL",
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.subtype) = "password"
];
}
// Output only. The available actions on this resource for this user.
@@ -157,6 +162,28 @@ message UsernamePasswordDomainAttributes {
]; // @gotags: `class:"public"`
}
// The attributes of a Password Credential.
message PasswordAttributes {
// Input only. The password associated with the credential.
google.protobuf.StringValue password = 10 [
json_name = "password",
(custom_options.v1.generate_sdk_option) = true,
(custom_options.v1.mask_mapping) = {
this: "attributes.password"
that: "Password"
}
]; // @gotags: `class:"secret"`
// Output only. The hmac value of the password.
string password_hmac = 20 [
json_name = "password_hmac",
(custom_options.v1.mask_mapping) = {
this: "attributes.password_hmac"
that: "PasswordHmac"
}
]; // @gotags: `class:"public"`
}
// The attributes of a SshPrivateKey Credential.
message SshPrivateKeyAttributes {
// The username associated with the credential.
@@ -308,6 +308,10 @@ message Credential {
(google.api.field_visibility).restriction = "INTERNAL",
(custom_options.v1.subtype) = "json"
];
PasswordCredentialAttributes password_attributes = 11 [
(google.api.field_visibility).restriction = "INTERNAL",
(custom_options.v1.subtype) = "password"
];
}
}
@@ -338,6 +342,12 @@ message JsonCredentialAttributes {
string object_hmac = 1; // @gotags: class:"public"
}
// The attributes of a Password Credential.
message PasswordCredentialAttributes {
// The hmac value of the password.
string password_hmac = 1; // @gotags: class:"public"
}
// CredentialLibrary contains all fields related to an Credential Library resource
message CredentialLibrary {
// The ID of the Credential Library.
@@ -475,6 +475,12 @@ message UsernamePasswordDomainCredential {
string domain = 3; // @gotags: `class:"public"`
}
// The layout of the struct for "credential" field in SessionCredential for a password credential type.
message PasswordCredential {
// Password of the credential
string password = 1; // @gotags: `class:"secret"`
}
// The layout of the struct for "credential" field in SessionCredential for a ssh_private_key credential type.
message SshPrivateKeyCredential {
// Username of the credential
@@ -49,6 +49,69 @@ message CredentialStore {
uint32 version = 7;
}
message PasswordCredential {
// 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 project_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;
// password is the plain-text of the password associated with the credential. We are
// not storing this plain-text password in the database.
// @inject_tag: `gorm:"-" wrapping:"pt,password_data"`
bytes password = 8 [(custom_options.v1.mask_mapping) = {
this: "Password"
that: "attributes.password"
}];
// ct_password is the ciphertext of the password. It
// is stored in the database.
// @inject_tag: `gorm:"column:password_encrypted;not_null" wrapping:"ct,password_data"`
bytes ct_password = 9;
// password_hmac is a sha256-hmac of the unencrypted password. It is recalculated
// everytime the password is updated.
// @inject_tag: `gorm:"not_null"`
bytes password_hmac = 10 [(custom_options.v1.mask_mapping) = {
this: "PasswordHmac"
that: "attributes.password_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;
}
message UsernamePasswordCredential {
// public_id is a surrogate key suitable for use in a public API.
// @inject_tag: `gorm:"primary_key"`
@@ -499,6 +499,22 @@ message UsernamePasswordDomainOverride {
string domain_attribute = 4;
}
message PasswordOverride {
// library_id of the owning vault credential library.
// @inject_tag: `gorm:"primary_key"`
string library_id = 1;
// password_attribute is the name of the attribute in the Data field of a
// Vault api.Secret that maps to a password.
// If set, it overrides any default attribute names the system uses to
// find a password attribute.
//
// See https://github.com/hashicorp/vault/blob/5e505ec039177e8212cbbab74ccb644c46e62e63/api/secret.go#L25
//
// @inject_tag: `gorm:"default:null"`
string password_attribute = 2;
}
message SshPrivateKeyOverride {
// library_id of the owning vault credential library.
// @inject_tag: `gorm:"primary_key"`
@@ -63,6 +63,19 @@ function create_json_credential() {
$args
}
function create_password_credential() {
local name=$1
local sid=$2
local pass=$3
export BP="${pass}"
boundary credentials create password \
-name $name \
-description 'test password credential' \
-credential-store-id $sid \
-password env://BP
}
function read_credential() {
boundary credentials read -id $1 -format json
}
@@ -12,6 +12,7 @@ export NEW_UPD_AT_CREDENTIAL='test-at-user-domain-pass'
export NEW_UPD_SLASH_CREDENTIAL='test-slash-domain-user-pass'
export NEW_UPD_CREDENTIAL_DOMAIN='test-domain-user-plus-domain'
export NEW_JSON_CREDENTIAL='test-json'
export NEW_PASSWORD_CREDENTIAL='test-pass'
@test "boundary/login: can login as default user" {
run login $DEFAULT_LOGIN
@@ -68,6 +69,20 @@ export NEW_JSON_CREDENTIAL='test-json'
}
@test "boundary/credentials: can create $NEW_PASSWORD_CREDENTIAL credential in $NEW_STORE store" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run create_password_credential $NEW_PASSWORD_CREDENTIAL $csid 'password'
echo "$output"
[ "$status" -eq 0 ]
}
@test "boundary/credentials: can not create already created $NEW_PASSWORD_CREDENTIAL credential" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run create_password_credential $NEW_PASSWORD_CREDENTIAL $csid 'password'
echo "$output"
[ "$status" -eq 1 ]
}
@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'
@@ -142,6 +157,14 @@ export NEW_JSON_CREDENTIAL='test-json'
[ "$status" -eq 0 ]
}
@test "boundary/credentials: can read $NEW_PASSWORD_CREDENTIAL credential" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local cid=$(credential_id $NEW_PASSWORD_CREDENTIAL $csid)
run read_credential $cid
echo "$output"
[ "$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)
@@ -186,6 +209,15 @@ export NEW_JSON_CREDENTIAL='test-json'
[ "$status" -eq 0 ]
}
@test "boundary/credentials: can delete $NEW_PASSWORD_CREDENTIAL credential" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local cid=$(credential_id $NEW_PASSWORD_CREDENTIAL $csid)
run delete_credential $cid
echo "$output"
run has_status_code "$output" "204"
[ "$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)
@@ -255,6 +287,14 @@ export NEW_JSON_CREDENTIAL='test-json'
[ "$status" -eq 1 ]
}
@test "boundary/credential-stores: can not read deleted $NEW_PASSWORD_CREDENTIAL credential" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
local cid=$(credential_id $NEW_PASSWORD_CREDENTIAL $csid)
run read_credential $cid
echo "$output"
[ "$status" -eq 1 ]
}
@test "boundary/credential-stores: can delete $NEW_STORE static store" {
local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID)
run delete_credential_store $csid
@@ -59,6 +59,7 @@ type Credential struct {
// *Credential_SshPrivateKeyAttributes
// *Credential_JsonAttributes
// *Credential_UsernamePasswordDomainAttributes
// *Credential_PasswordAttributes
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"`
@@ -211,6 +212,15 @@ func (x *Credential) GetUsernamePasswordDomainAttributes() *UsernamePasswordDoma
return nil
}
func (x *Credential) GetPasswordAttributes() *PasswordAttributes {
if x != nil {
if x, ok := x.Attrs.(*Credential_PasswordAttributes); ok {
return x.PasswordAttributes
}
}
return nil
}
func (x *Credential) GetAuthorizedActions() []string {
if x != nil {
return x.AuthorizedActions
@@ -243,6 +253,10 @@ type Credential_UsernamePasswordDomainAttributes struct {
UsernamePasswordDomainAttributes *UsernamePasswordDomainAttributes `protobuf:"bytes,104,opt,name=username_password_domain_attributes,json=usernamePasswordDomainAttributes,proto3,oneof"`
}
type Credential_PasswordAttributes struct {
PasswordAttributes *PasswordAttributes `protobuf:"bytes,105,opt,name=password_attributes,json=passwordAttributes,proto3,oneof"`
}
func (*Credential_Attributes) isCredential_Attrs() {}
func (*Credential_UsernamePasswordAttributes) isCredential_Attrs() {}
@@ -253,6 +267,8 @@ func (*Credential_JsonAttributes) isCredential_Attrs() {}
func (*Credential_UsernamePasswordDomainAttributes) isCredential_Attrs() {}
func (*Credential_PasswordAttributes) isCredential_Attrs() {}
// The attributes of a UsernamePassword Credential.
type UsernamePasswordAttributes struct {
state protoimpl.MessageState `protogen:"open.v1"`
@@ -388,6 +404,61 @@ func (x *UsernamePasswordDomainAttributes) GetDomain() *wrapperspb.StringValue {
return nil
}
// The attributes of a Password Credential.
type PasswordAttributes struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Input only. The password associated with the credential.
Password *wrapperspb.StringValue `protobuf:"bytes,10,opt,name=password,proto3" json:"password,omitempty" class:"secret"` // @gotags: `class:"secret"`
// Output only. The hmac value of the password.
PasswordHmac string `protobuf:"bytes,20,opt,name=password_hmac,proto3" json:"password_hmac,omitempty" class:"public"` // @gotags: `class:"public"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PasswordAttributes) Reset() {
*x = PasswordAttributes{}
mi := &file_controller_api_resources_credentials_v1_credential_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PasswordAttributes) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PasswordAttributes) ProtoMessage() {}
func (x *PasswordAttributes) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_resources_credentials_v1_credential_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PasswordAttributes.ProtoReflect.Descriptor instead.
func (*PasswordAttributes) Descriptor() ([]byte, []int) {
return file_controller_api_resources_credentials_v1_credential_proto_rawDescGZIP(), []int{3}
}
func (x *PasswordAttributes) GetPassword() *wrapperspb.StringValue {
if x != nil {
return x.Password
}
return nil
}
func (x *PasswordAttributes) GetPasswordHmac() string {
if x != nil {
return x.PasswordHmac
}
return ""
}
// The attributes of a SshPrivateKey Credential.
type SshPrivateKeyAttributes struct {
state protoimpl.MessageState `protogen:"open.v1"`
@@ -407,7 +478,7 @@ type SshPrivateKeyAttributes struct {
func (x *SshPrivateKeyAttributes) Reset() {
*x = SshPrivateKeyAttributes{}
mi := &file_controller_api_resources_credentials_v1_credential_proto_msgTypes[3]
mi := &file_controller_api_resources_credentials_v1_credential_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -419,7 +490,7 @@ func (x *SshPrivateKeyAttributes) String() string {
func (*SshPrivateKeyAttributes) ProtoMessage() {}
func (x *SshPrivateKeyAttributes) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_resources_credentials_v1_credential_proto_msgTypes[3]
mi := &file_controller_api_resources_credentials_v1_credential_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -432,7 +503,7 @@ func (x *SshPrivateKeyAttributes) ProtoReflect() protoreflect.Message {
// Deprecated: Use SshPrivateKeyAttributes.ProtoReflect.Descriptor instead.
func (*SshPrivateKeyAttributes) Descriptor() ([]byte, []int) {
return file_controller_api_resources_credentials_v1_credential_proto_rawDescGZIP(), []int{3}
return file_controller_api_resources_credentials_v1_credential_proto_rawDescGZIP(), []int{4}
}
func (x *SshPrivateKeyAttributes) GetUsername() *wrapperspb.StringValue {
@@ -483,7 +554,7 @@ type JsonAttributes struct {
func (x *JsonAttributes) Reset() {
*x = JsonAttributes{}
mi := &file_controller_api_resources_credentials_v1_credential_proto_msgTypes[4]
mi := &file_controller_api_resources_credentials_v1_credential_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -495,7 +566,7 @@ func (x *JsonAttributes) String() string {
func (*JsonAttributes) ProtoMessage() {}
func (x *JsonAttributes) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_resources_credentials_v1_credential_proto_msgTypes[4]
mi := &file_controller_api_resources_credentials_v1_credential_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -508,7 +579,7 @@ func (x *JsonAttributes) ProtoReflect() protoreflect.Message {
// Deprecated: Use JsonAttributes.ProtoReflect.Descriptor instead.
func (*JsonAttributes) Descriptor() ([]byte, []int) {
return file_controller_api_resources_credentials_v1_credential_proto_rawDescGZIP(), []int{4}
return file_controller_api_resources_credentials_v1_credential_proto_rawDescGZIP(), []int{5}
}
func (x *JsonAttributes) GetObject() *structpb.Struct {
@@ -529,8 +600,7 @@ var File_controller_api_resources_credentials_v1_credential_proto protoreflect.F
const file_controller_api_resources_credentials_v1_credential_proto_rawDesc = "" +
"\n" +
"8controller/api/resources/credentials/v1/credential.proto\x12'controller.api.resources.credentials.v1\x1a.controller/api/resources/scopes/v1/scope.proto\x1a*controller/custom_options/v1/options.proto\x1a\x1bgoogle/api/visibility.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\xa5\n" +
"\n" +
"8controller/api/resources/credentials/v1/credential.proto\x12'controller.api.resources.credentials.v1\x1a.controller/api/resources/scopes/v1/scope.proto\x1a*controller/custom_options/v1/options.proto\x1a\x1bgoogle/api/visibility.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\xb8\v\n" +
"\n" +
"Credential\x12\x0e\n" +
"\x02id\x18\n" +
@@ -555,7 +625,9 @@ const file_controller_api_resources_credentials_v1_credential_proto_rawDesc = ""
"\x0fjson_attributes\x18g \x01(\v27.controller.api.resources.credentials.v1.JsonAttributesB\x1c\xa0\xda)\x01\x9a\xe3)\x04json\xfa\xd2\xe4\x93\x02\n" +
"\x12\bINTERNALH\x00R\x0ejsonAttributes\x12\xcc\x01\n" +
"#username_password_domain_attributes\x18h \x01(\v2I.controller.api.resources.credentials.v1.UsernamePasswordDomainAttributesB0\xa0\xda)\x01\x9a\xe3)\x18username_password_domain\xfa\xd2\xe4\x93\x02\n" +
"\x12\bINTERNALH\x00R usernamePasswordDomainAttributes\x12/\n" +
"\x12\bINTERNALH\x00R usernamePasswordDomainAttributes\x12\x90\x01\n" +
"\x13password_attributes\x18i \x01(\v2;.controller.api.resources.credentials.v1.PasswordAttributesB \xa0\xda)\x01\x9a\xe3)\bpassword\xfa\xd2\xe4\x93\x02\n" +
"\x12\bINTERNALH\x00R\x12passwordAttributes\x12/\n" +
"\x12authorized_actions\x18\xac\x02 \x03(\tR\x12authorized_actionsB\a\n" +
"\x05attrs\"\xb6\x02\n" +
"\x1aUsernamePasswordAttributes\x12a\n" +
@@ -575,7 +647,13 @@ const file_controller_api_resources_credentials_v1_credential_proto_rawDesc = ""
"\rpassword_hmac\x18\x1e \x01(\tB,\xc2\xdd)(\n" +
"\x18attributes.password_hmac\x12\fPasswordHmacR\rpassword_hmac\x12Y\n" +
"\x06domain\x18( \x01(\v2\x1c.google.protobuf.StringValueB#\xa0\xda)\x01\xc2\xdd)\x1b\n" +
"\x11attributes.domain\x12\x06DomainR\x06domain\"\xee\x04\n" +
"\x11attributes.domain\x12\x06DomainR\x06domain\"\xcb\x01\n" +
"\x12PasswordAttributes\x12a\n" +
"\bpassword\x18\n" +
" \x01(\v2\x1c.google.protobuf.StringValueB'\xa0\xda)\x01\xc2\xdd)\x1f\n" +
"\x13attributes.password\x12\bPasswordR\bpassword\x12R\n" +
"\rpassword_hmac\x18\x14 \x01(\tB,\xc2\xdd)(\n" +
"\x18attributes.password_hmac\x12\fPasswordHmacR\rpassword_hmac\"\xee\x04\n" +
"\x17SshPrivateKeyAttributes\x12a\n" +
"\busername\x18\n" +
" \x01(\v2\x1c.google.protobuf.StringValueB'\xa0\xda)\x01\xc2\xdd)\x1f\n" +
@@ -609,43 +687,46 @@ 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, 5)
var file_controller_api_resources_credentials_v1_credential_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_controller_api_resources_credentials_v1_credential_proto_goTypes = []any{
(*Credential)(nil), // 0: controller.api.resources.credentials.v1.Credential
(*UsernamePasswordAttributes)(nil), // 1: controller.api.resources.credentials.v1.UsernamePasswordAttributes
(*UsernamePasswordDomainAttributes)(nil), // 2: controller.api.resources.credentials.v1.UsernamePasswordDomainAttributes
(*SshPrivateKeyAttributes)(nil), // 3: controller.api.resources.credentials.v1.SshPrivateKeyAttributes
(*JsonAttributes)(nil), // 4: controller.api.resources.credentials.v1.JsonAttributes
(*scopes.ScopeInfo)(nil), // 5: controller.api.resources.scopes.v1.ScopeInfo
(*wrapperspb.StringValue)(nil), // 6: google.protobuf.StringValue
(*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp
(*structpb.Struct)(nil), // 8: google.protobuf.Struct
(*PasswordAttributes)(nil), // 3: controller.api.resources.credentials.v1.PasswordAttributes
(*SshPrivateKeyAttributes)(nil), // 4: controller.api.resources.credentials.v1.SshPrivateKeyAttributes
(*JsonAttributes)(nil), // 5: controller.api.resources.credentials.v1.JsonAttributes
(*scopes.ScopeInfo)(nil), // 6: controller.api.resources.scopes.v1.ScopeInfo
(*wrapperspb.StringValue)(nil), // 7: google.protobuf.StringValue
(*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp
(*structpb.Struct)(nil), // 9: google.protobuf.Struct
}
var file_controller_api_resources_credentials_v1_credential_proto_depIdxs = []int32{
5, // 0: controller.api.resources.credentials.v1.Credential.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
6, // 1: controller.api.resources.credentials.v1.Credential.name:type_name -> google.protobuf.StringValue
6, // 2: controller.api.resources.credentials.v1.Credential.description:type_name -> google.protobuf.StringValue
7, // 3: controller.api.resources.credentials.v1.Credential.created_time:type_name -> google.protobuf.Timestamp
7, // 4: controller.api.resources.credentials.v1.Credential.updated_time:type_name -> google.protobuf.Timestamp
8, // 5: controller.api.resources.credentials.v1.Credential.attributes:type_name -> google.protobuf.Struct
6, // 0: controller.api.resources.credentials.v1.Credential.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
7, // 1: controller.api.resources.credentials.v1.Credential.name:type_name -> google.protobuf.StringValue
7, // 2: controller.api.resources.credentials.v1.Credential.description:type_name -> google.protobuf.StringValue
8, // 3: controller.api.resources.credentials.v1.Credential.created_time:type_name -> google.protobuf.Timestamp
8, // 4: controller.api.resources.credentials.v1.Credential.updated_time:type_name -> google.protobuf.Timestamp
9, // 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
3, // 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.Credential.json_attributes:type_name -> controller.api.resources.credentials.v1.JsonAttributes
4, // 7: controller.api.resources.credentials.v1.Credential.ssh_private_key_attributes:type_name -> controller.api.resources.credentials.v1.SshPrivateKeyAttributes
5, // 8: controller.api.resources.credentials.v1.Credential.json_attributes:type_name -> controller.api.resources.credentials.v1.JsonAttributes
2, // 9: controller.api.resources.credentials.v1.Credential.username_password_domain_attributes:type_name -> controller.api.resources.credentials.v1.UsernamePasswordDomainAttributes
6, // 10: controller.api.resources.credentials.v1.UsernamePasswordAttributes.username:type_name -> google.protobuf.StringValue
6, // 11: controller.api.resources.credentials.v1.UsernamePasswordAttributes.password:type_name -> google.protobuf.StringValue
6, // 12: controller.api.resources.credentials.v1.UsernamePasswordDomainAttributes.username:type_name -> google.protobuf.StringValue
6, // 13: controller.api.resources.credentials.v1.UsernamePasswordDomainAttributes.password:type_name -> google.protobuf.StringValue
6, // 14: controller.api.resources.credentials.v1.UsernamePasswordDomainAttributes.domain:type_name -> google.protobuf.StringValue
6, // 15: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.username:type_name -> google.protobuf.StringValue
6, // 16: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.private_key:type_name -> google.protobuf.StringValue
6, // 17: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.private_key_passphrase:type_name -> google.protobuf.StringValue
8, // 18: controller.api.resources.credentials.v1.JsonAttributes.object:type_name -> google.protobuf.Struct
19, // [19:19] is the sub-list for method output_type
19, // [19:19] is the sub-list for method input_type
19, // [19:19] is the sub-list for extension type_name
19, // [19:19] is the sub-list for extension extendee
0, // [0:19] is the sub-list for field type_name
3, // 10: controller.api.resources.credentials.v1.Credential.password_attributes:type_name -> controller.api.resources.credentials.v1.PasswordAttributes
7, // 11: controller.api.resources.credentials.v1.UsernamePasswordAttributes.username:type_name -> google.protobuf.StringValue
7, // 12: controller.api.resources.credentials.v1.UsernamePasswordAttributes.password:type_name -> google.protobuf.StringValue
7, // 13: controller.api.resources.credentials.v1.UsernamePasswordDomainAttributes.username:type_name -> google.protobuf.StringValue
7, // 14: controller.api.resources.credentials.v1.UsernamePasswordDomainAttributes.password:type_name -> google.protobuf.StringValue
7, // 15: controller.api.resources.credentials.v1.UsernamePasswordDomainAttributes.domain:type_name -> google.protobuf.StringValue
7, // 16: controller.api.resources.credentials.v1.PasswordAttributes.password:type_name -> google.protobuf.StringValue
7, // 17: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.username:type_name -> google.protobuf.StringValue
7, // 18: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.private_key:type_name -> google.protobuf.StringValue
7, // 19: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.private_key_passphrase:type_name -> google.protobuf.StringValue
9, // 20: controller.api.resources.credentials.v1.JsonAttributes.object:type_name -> google.protobuf.Struct
21, // [21:21] is the sub-list for method output_type
21, // [21:21] is the sub-list for method input_type
21, // [21:21] is the sub-list for extension type_name
21, // [21:21] is the sub-list for extension extendee
0, // [0:21] is the sub-list for field type_name
}
func init() { file_controller_api_resources_credentials_v1_credential_proto_init() }
@@ -659,6 +740,7 @@ func file_controller_api_resources_credentials_v1_credential_proto_init() {
(*Credential_SshPrivateKeyAttributes)(nil),
(*Credential_JsonAttributes)(nil),
(*Credential_UsernamePasswordDomainAttributes)(nil),
(*Credential_PasswordAttributes)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -666,7 +748,7 @@ func file_controller_api_resources_credentials_v1_credential_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_controller_api_resources_credentials_v1_credential_proto_rawDesc), len(file_controller_api_resources_credentials_v1_credential_proto_rawDesc)),
NumEnums: 0,
NumMessages: 5,
NumMessages: 6,
NumExtensions: 0,
NumServices: 0,
},
@@ -1132,6 +1132,7 @@ type Credential struct {
// *Credential_UsernamePasswordAttributes
// *Credential_SshPrivateKeyAttributes
// *Credential_JsonAttributes
// *Credential_PasswordAttributes
Attrs isCredential_Attrs `protobuf_oneof:"attrs"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
@@ -1252,6 +1253,15 @@ func (x *Credential) GetJsonAttributes() *JsonCredentialAttributes {
return nil
}
func (x *Credential) GetPasswordAttributes() *PasswordCredentialAttributes {
if x != nil {
if x, ok := x.Attrs.(*Credential_PasswordAttributes); ok {
return x.PasswordAttributes
}
}
return nil
}
type isCredential_Attrs interface {
isCredential_Attrs()
}
@@ -1273,6 +1283,10 @@ type Credential_JsonAttributes struct {
JsonAttributes *JsonCredentialAttributes `protobuf:"bytes,10,opt,name=json_attributes,json=jsonAttributes,proto3,oneof"`
}
type Credential_PasswordAttributes struct {
PasswordAttributes *PasswordCredentialAttributes `protobuf:"bytes,11,opt,name=password_attributes,json=passwordAttributes,proto3,oneof"`
}
func (*Credential_Attributes) isCredential_Attrs() {}
func (*Credential_UsernamePasswordAttributes) isCredential_Attrs() {}
@@ -1281,6 +1295,8 @@ func (*Credential_SshPrivateKeyAttributes) isCredential_Attrs() {}
func (*Credential_JsonAttributes) isCredential_Attrs() {}
func (*Credential_PasswordAttributes) isCredential_Attrs() {}
// The attributes of a UsernamePassword Credential.
type UsernamePasswordCredentialAttributes struct {
state protoimpl.MessageState `protogen:"open.v1"`
@@ -1446,6 +1462,52 @@ func (x *JsonCredentialAttributes) GetObjectHmac() string {
return ""
}
// The attributes of a Password Credential.
type PasswordCredentialAttributes struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The hmac value of the password.
PasswordHmac string `protobuf:"bytes,1,opt,name=password_hmac,json=passwordHmac,proto3" json:"password_hmac,omitempty" class:"public"` // @gotags: class:"public"
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PasswordCredentialAttributes) Reset() {
*x = PasswordCredentialAttributes{}
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PasswordCredentialAttributes) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PasswordCredentialAttributes) ProtoMessage() {}
func (x *PasswordCredentialAttributes) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[14]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PasswordCredentialAttributes.ProtoReflect.Descriptor instead.
func (*PasswordCredentialAttributes) Descriptor() ([]byte, []int) {
return file_controller_api_resources_sessionrecordings_v1_session_recording_proto_rawDescGZIP(), []int{14}
}
func (x *PasswordCredentialAttributes) GetPasswordHmac() string {
if x != nil {
return x.PasswordHmac
}
return ""
}
// CredentialLibrary contains all fields related to an Credential Library resource
type CredentialLibrary struct {
state protoimpl.MessageState `protogen:"open.v1"`
@@ -1474,7 +1536,7 @@ type CredentialLibrary struct {
func (x *CredentialLibrary) Reset() {
*x = CredentialLibrary{}
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[14]
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1486,7 +1548,7 @@ func (x *CredentialLibrary) String() string {
func (*CredentialLibrary) ProtoMessage() {}
func (x *CredentialLibrary) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[14]
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[15]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1499,7 +1561,7 @@ func (x *CredentialLibrary) ProtoReflect() protoreflect.Message {
// Deprecated: Use CredentialLibrary.ProtoReflect.Descriptor instead.
func (*CredentialLibrary) Descriptor() ([]byte, []int) {
return file_controller_api_resources_sessionrecordings_v1_session_recording_proto_rawDescGZIP(), []int{14}
return file_controller_api_resources_sessionrecordings_v1_session_recording_proto_rawDescGZIP(), []int{15}
}
func (x *CredentialLibrary) GetId() string {
@@ -1632,7 +1694,7 @@ type VaultCredentialLibraryAttributes struct {
func (x *VaultCredentialLibraryAttributes) Reset() {
*x = VaultCredentialLibraryAttributes{}
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[15]
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1644,7 +1706,7 @@ func (x *VaultCredentialLibraryAttributes) String() string {
func (*VaultCredentialLibraryAttributes) ProtoMessage() {}
func (x *VaultCredentialLibraryAttributes) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[15]
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[16]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1657,7 +1719,7 @@ func (x *VaultCredentialLibraryAttributes) ProtoReflect() protoreflect.Message {
// Deprecated: Use VaultCredentialLibraryAttributes.ProtoReflect.Descriptor instead.
func (*VaultCredentialLibraryAttributes) Descriptor() ([]byte, []int) {
return file_controller_api_resources_sessionrecordings_v1_session_recording_proto_rawDescGZIP(), []int{15}
return file_controller_api_resources_sessionrecordings_v1_session_recording_proto_rawDescGZIP(), []int{16}
}
func (x *VaultCredentialLibraryAttributes) GetPath() string {
@@ -1706,7 +1768,7 @@ type VaultSSHCertificateCredentialLibraryAttributes struct {
func (x *VaultSSHCertificateCredentialLibraryAttributes) Reset() {
*x = VaultSSHCertificateCredentialLibraryAttributes{}
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[16]
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1718,7 +1780,7 @@ func (x *VaultSSHCertificateCredentialLibraryAttributes) String() string {
func (*VaultSSHCertificateCredentialLibraryAttributes) ProtoMessage() {}
func (x *VaultSSHCertificateCredentialLibraryAttributes) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[16]
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[17]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1731,7 +1793,7 @@ func (x *VaultSSHCertificateCredentialLibraryAttributes) ProtoReflect() protoref
// Deprecated: Use VaultSSHCertificateCredentialLibraryAttributes.ProtoReflect.Descriptor instead.
func (*VaultSSHCertificateCredentialLibraryAttributes) Descriptor() ([]byte, []int) {
return file_controller_api_resources_sessionrecordings_v1_session_recording_proto_rawDescGZIP(), []int{16}
return file_controller_api_resources_sessionrecordings_v1_session_recording_proto_rawDescGZIP(), []int{17}
}
func (x *VaultSSHCertificateCredentialLibraryAttributes) GetPath() string {
@@ -1810,7 +1872,7 @@ type ValuesAtTime struct {
func (x *ValuesAtTime) Reset() {
*x = ValuesAtTime{}
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[17]
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1822,7 +1884,7 @@ func (x *ValuesAtTime) String() string {
func (*ValuesAtTime) ProtoMessage() {}
func (x *ValuesAtTime) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[17]
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[18]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1835,7 +1897,7 @@ func (x *ValuesAtTime) ProtoReflect() protoreflect.Message {
// Deprecated: Use ValuesAtTime.ProtoReflect.Descriptor instead.
func (*ValuesAtTime) Descriptor() ([]byte, []int) {
return file_controller_api_resources_sessionrecordings_v1_session_recording_proto_rawDescGZIP(), []int{17}
return file_controller_api_resources_sessionrecordings_v1_session_recording_proto_rawDescGZIP(), []int{18}
}
func (x *ValuesAtTime) GetUser() *User {
@@ -1936,7 +1998,7 @@ type SessionRecording struct {
func (x *SessionRecording) Reset() {
*x = SessionRecording{}
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[18]
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1948,7 +2010,7 @@ func (x *SessionRecording) String() string {
func (*SessionRecording) ProtoMessage() {}
func (x *SessionRecording) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[18]
mi := &file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[19]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1961,7 +2023,7 @@ func (x *SessionRecording) ProtoReflect() protoreflect.Message {
// Deprecated: Use SessionRecording.ProtoReflect.Descriptor instead.
func (*SessionRecording) Descriptor() ([]byte, []int) {
return file_controller_api_resources_sessionrecordings_v1_session_recording_proto_rawDescGZIP(), []int{18}
return file_controller_api_resources_sessionrecordings_v1_session_recording_proto_rawDescGZIP(), []int{19}
}
func (x *SessionRecording) GetId() string {
@@ -2220,7 +2282,7 @@ const file_controller_api_resources_sessionrecordings_v1_session_recording_proto
"\tnamespace\x18\x02 \x01(\tR\tnamespace\x12(\n" +
"\x0ftls_server_name\x18\x03 \x01(\tR\x0ftls_server_name\x12(\n" +
"\x0ftls_skip_verify\x18\x04 \x01(\bR\x0ftls_skip_verify\x12$\n" +
"\rworker_filter\x18\x05 \x01(\tR\rworker_filter\"\xc6\x06\n" +
"\rworker_filter\x18\x05 \x01(\tR\rworker_filter\"\xe5\a\n" +
"\n" +
"Credential\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\x12j\n" +
@@ -2238,7 +2300,9 @@ const file_controller_api_resources_sessionrecordings_v1_session_recording_proto
"\x12\bINTERNALH\x00R\x17sshPrivateKeyAttributes\x12\x8c\x01\n" +
"\x0fjson_attributes\x18\n" +
" \x01(\v2G.controller.api.resources.sessionrecordings.v1.JsonCredentialAttributesB\x18\x9a\xe3)\x04json\xfa\xd2\xe4\x93\x02\n" +
"\x12\bINTERNALH\x00R\x0ejsonAttributesB\a\n" +
"\x12\bINTERNALH\x00R\x0ejsonAttributes\x12\x9c\x01\n" +
"\x13password_attributes\x18\v \x01(\v2K.controller.api.resources.sessionrecordings.v1.PasswordCredentialAttributesB\x1c\x9a\xe3)\bpassword\xfa\xd2\xe4\x93\x02\n" +
"\x12\bINTERNALH\x00R\x12passwordAttributesB\a\n" +
"\x05attrs\"g\n" +
"$UsernamePasswordCredentialAttributes\x12\x1a\n" +
"\busername\x18\x01 \x01(\tR\busername\x12#\n" +
@@ -2249,7 +2313,9 @@ const file_controller_api_resources_sessionrecordings_v1_session_recording_proto
"\x1bprivate_key_passphrase_hmac\x18\x03 \x01(\tR\x18privateKeyPassphraseHmac\";\n" +
"\x18JsonCredentialAttributes\x12\x1f\n" +
"\vobject_hmac\x18\x01 \x01(\tR\n" +
"objectHmac\"\xd3\a\n" +
"objectHmac\"C\n" +
"\x1cPasswordCredentialAttributes\x12#\n" +
"\rpassword_hmac\x18\x01 \x01(\tR\fpasswordHmac\"\xd3\a\n" +
"\x11CredentialLibrary\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\x12j\n" +
"\x10credential_store\x18\x02 \x01(\v2>.controller.api.resources.sessionrecordings.v1.CredentialStoreR\x10credential_store\x12\x12\n" +
@@ -2340,7 +2406,7 @@ func file_controller_api_resources_sessionrecordings_v1_session_recording_proto_
return file_controller_api_resources_sessionrecordings_v1_session_recording_proto_rawDescData
}
var file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes = make([]protoimpl.MessageInfo, 21)
var file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes = make([]protoimpl.MessageInfo, 22)
var file_controller_api_resources_sessionrecordings_v1_session_recording_proto_goTypes = []any{
(*ChannelRecording)(nil), // 0: controller.api.resources.sessionrecordings.v1.ChannelRecording
(*ConnectionRecording)(nil), // 1: controller.api.resources.sessionrecordings.v1.ConnectionRecording
@@ -2356,73 +2422,75 @@ var file_controller_api_resources_sessionrecordings_v1_session_recording_proto_g
(*UsernamePasswordCredentialAttributes)(nil), // 11: controller.api.resources.sessionrecordings.v1.UsernamePasswordCredentialAttributes
(*SshPrivateKeyCredentialAttributes)(nil), // 12: controller.api.resources.sessionrecordings.v1.SshPrivateKeyCredentialAttributes
(*JsonCredentialAttributes)(nil), // 13: controller.api.resources.sessionrecordings.v1.JsonCredentialAttributes
(*CredentialLibrary)(nil), // 14: controller.api.resources.sessionrecordings.v1.CredentialLibrary
(*VaultCredentialLibraryAttributes)(nil), // 15: controller.api.resources.sessionrecordings.v1.VaultCredentialLibraryAttributes
(*VaultSSHCertificateCredentialLibraryAttributes)(nil), // 16: controller.api.resources.sessionrecordings.v1.VaultSSHCertificateCredentialLibraryAttributes
(*ValuesAtTime)(nil), // 17: controller.api.resources.sessionrecordings.v1.ValuesAtTime
(*SessionRecording)(nil), // 18: controller.api.resources.sessionrecordings.v1.SessionRecording
nil, // 19: controller.api.resources.sessionrecordings.v1.VaultSSHCertificateCredentialLibraryAttributes.CriticalOptionsEntry
nil, // 20: controller.api.resources.sessionrecordings.v1.VaultSSHCertificateCredentialLibraryAttributes.ExtensionsEntry
(*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp
(*durationpb.Duration)(nil), // 22: google.protobuf.Duration
(*scopes.ScopeInfo)(nil), // 23: controller.api.resources.scopes.v1.ScopeInfo
(*structpb.Struct)(nil), // 24: google.protobuf.Struct
(*PasswordCredentialAttributes)(nil), // 14: controller.api.resources.sessionrecordings.v1.PasswordCredentialAttributes
(*CredentialLibrary)(nil), // 15: controller.api.resources.sessionrecordings.v1.CredentialLibrary
(*VaultCredentialLibraryAttributes)(nil), // 16: controller.api.resources.sessionrecordings.v1.VaultCredentialLibraryAttributes
(*VaultSSHCertificateCredentialLibraryAttributes)(nil), // 17: controller.api.resources.sessionrecordings.v1.VaultSSHCertificateCredentialLibraryAttributes
(*ValuesAtTime)(nil), // 18: controller.api.resources.sessionrecordings.v1.ValuesAtTime
(*SessionRecording)(nil), // 19: controller.api.resources.sessionrecordings.v1.SessionRecording
nil, // 20: controller.api.resources.sessionrecordings.v1.VaultSSHCertificateCredentialLibraryAttributes.CriticalOptionsEntry
nil, // 21: controller.api.resources.sessionrecordings.v1.VaultSSHCertificateCredentialLibraryAttributes.ExtensionsEntry
(*timestamppb.Timestamp)(nil), // 22: google.protobuf.Timestamp
(*durationpb.Duration)(nil), // 23: google.protobuf.Duration
(*scopes.ScopeInfo)(nil), // 24: controller.api.resources.scopes.v1.ScopeInfo
(*structpb.Struct)(nil), // 25: google.protobuf.Struct
}
var file_controller_api_resources_sessionrecordings_v1_session_recording_proto_depIdxs = []int32{
21, // 0: controller.api.resources.sessionrecordings.v1.ChannelRecording.created_time:type_name -> google.protobuf.Timestamp
21, // 1: controller.api.resources.sessionrecordings.v1.ChannelRecording.updated_time:type_name -> google.protobuf.Timestamp
21, // 2: controller.api.resources.sessionrecordings.v1.ChannelRecording.start_time:type_name -> google.protobuf.Timestamp
21, // 3: controller.api.resources.sessionrecordings.v1.ChannelRecording.end_time:type_name -> google.protobuf.Timestamp
22, // 4: controller.api.resources.sessionrecordings.v1.ChannelRecording.duration:type_name -> google.protobuf.Duration
21, // 5: controller.api.resources.sessionrecordings.v1.ConnectionRecording.created_time:type_name -> google.protobuf.Timestamp
21, // 6: controller.api.resources.sessionrecordings.v1.ConnectionRecording.updated_time:type_name -> google.protobuf.Timestamp
21, // 7: controller.api.resources.sessionrecordings.v1.ConnectionRecording.start_time:type_name -> google.protobuf.Timestamp
21, // 8: controller.api.resources.sessionrecordings.v1.ConnectionRecording.end_time:type_name -> google.protobuf.Timestamp
22, // 9: controller.api.resources.sessionrecordings.v1.ConnectionRecording.duration:type_name -> google.protobuf.Duration
22, // 0: controller.api.resources.sessionrecordings.v1.ChannelRecording.created_time:type_name -> google.protobuf.Timestamp
22, // 1: controller.api.resources.sessionrecordings.v1.ChannelRecording.updated_time:type_name -> google.protobuf.Timestamp
22, // 2: controller.api.resources.sessionrecordings.v1.ChannelRecording.start_time:type_name -> google.protobuf.Timestamp
22, // 3: controller.api.resources.sessionrecordings.v1.ChannelRecording.end_time:type_name -> google.protobuf.Timestamp
23, // 4: controller.api.resources.sessionrecordings.v1.ChannelRecording.duration:type_name -> google.protobuf.Duration
22, // 5: controller.api.resources.sessionrecordings.v1.ConnectionRecording.created_time:type_name -> google.protobuf.Timestamp
22, // 6: controller.api.resources.sessionrecordings.v1.ConnectionRecording.updated_time:type_name -> google.protobuf.Timestamp
22, // 7: controller.api.resources.sessionrecordings.v1.ConnectionRecording.start_time:type_name -> google.protobuf.Timestamp
22, // 8: controller.api.resources.sessionrecordings.v1.ConnectionRecording.end_time:type_name -> google.protobuf.Timestamp
23, // 9: controller.api.resources.sessionrecordings.v1.ConnectionRecording.duration:type_name -> google.protobuf.Duration
0, // 10: controller.api.resources.sessionrecordings.v1.ConnectionRecording.channel_recordings:type_name -> controller.api.resources.sessionrecordings.v1.ChannelRecording
23, // 11: controller.api.resources.sessionrecordings.v1.User.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
23, // 12: controller.api.resources.sessionrecordings.v1.HostCatalog.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
24, // 13: controller.api.resources.sessionrecordings.v1.HostCatalog.attributes:type_name -> google.protobuf.Struct
24, // 11: controller.api.resources.sessionrecordings.v1.User.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
24, // 12: controller.api.resources.sessionrecordings.v1.HostCatalog.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
25, // 13: controller.api.resources.sessionrecordings.v1.HostCatalog.attributes:type_name -> google.protobuf.Struct
3, // 14: controller.api.resources.sessionrecordings.v1.Host.host_catalog:type_name -> controller.api.resources.sessionrecordings.v1.HostCatalog
24, // 15: controller.api.resources.sessionrecordings.v1.Host.attributes:type_name -> google.protobuf.Struct
25, // 15: controller.api.resources.sessionrecordings.v1.Host.attributes:type_name -> google.protobuf.Struct
5, // 16: controller.api.resources.sessionrecordings.v1.Host.static_host_attributes:type_name -> controller.api.resources.sessionrecordings.v1.StaticHostAttributes
23, // 17: controller.api.resources.sessionrecordings.v1.Target.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
24, // 18: controller.api.resources.sessionrecordings.v1.Target.attributes:type_name -> google.protobuf.Struct
24, // 17: controller.api.resources.sessionrecordings.v1.Target.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
25, // 18: controller.api.resources.sessionrecordings.v1.Target.attributes:type_name -> google.protobuf.Struct
7, // 19: controller.api.resources.sessionrecordings.v1.Target.ssh_target_attributes:type_name -> controller.api.resources.sessionrecordings.v1.SshTargetAttributes
24, // 20: controller.api.resources.sessionrecordings.v1.CredentialStore.attributes:type_name -> google.protobuf.Struct
25, // 20: controller.api.resources.sessionrecordings.v1.CredentialStore.attributes:type_name -> google.protobuf.Struct
9, // 21: controller.api.resources.sessionrecordings.v1.CredentialStore.vault_credential_store_attributes:type_name -> controller.api.resources.sessionrecordings.v1.VaultCredentialStoreAttributes
8, // 22: controller.api.resources.sessionrecordings.v1.Credential.credential_store:type_name -> controller.api.resources.sessionrecordings.v1.CredentialStore
24, // 23: controller.api.resources.sessionrecordings.v1.Credential.attributes:type_name -> google.protobuf.Struct
25, // 23: controller.api.resources.sessionrecordings.v1.Credential.attributes:type_name -> google.protobuf.Struct
11, // 24: controller.api.resources.sessionrecordings.v1.Credential.username_password_attributes:type_name -> controller.api.resources.sessionrecordings.v1.UsernamePasswordCredentialAttributes
12, // 25: controller.api.resources.sessionrecordings.v1.Credential.ssh_private_key_attributes:type_name -> controller.api.resources.sessionrecordings.v1.SshPrivateKeyCredentialAttributes
13, // 26: controller.api.resources.sessionrecordings.v1.Credential.json_attributes:type_name -> controller.api.resources.sessionrecordings.v1.JsonCredentialAttributes
8, // 27: controller.api.resources.sessionrecordings.v1.CredentialLibrary.credential_store:type_name -> controller.api.resources.sessionrecordings.v1.CredentialStore
24, // 28: controller.api.resources.sessionrecordings.v1.CredentialLibrary.attributes:type_name -> google.protobuf.Struct
15, // 29: controller.api.resources.sessionrecordings.v1.CredentialLibrary.vault_credential_library_attributes:type_name -> controller.api.resources.sessionrecordings.v1.VaultCredentialLibraryAttributes
15, // 30: controller.api.resources.sessionrecordings.v1.CredentialLibrary.vault_generic_credential_library_attributes:type_name -> controller.api.resources.sessionrecordings.v1.VaultCredentialLibraryAttributes
16, // 31: controller.api.resources.sessionrecordings.v1.CredentialLibrary.vault_ssh_certificate_credential_library_attributes:type_name -> controller.api.resources.sessionrecordings.v1.VaultSSHCertificateCredentialLibraryAttributes
19, // 32: controller.api.resources.sessionrecordings.v1.VaultSSHCertificateCredentialLibraryAttributes.critical_options:type_name -> controller.api.resources.sessionrecordings.v1.VaultSSHCertificateCredentialLibraryAttributes.CriticalOptionsEntry
20, // 33: controller.api.resources.sessionrecordings.v1.VaultSSHCertificateCredentialLibraryAttributes.extensions:type_name -> controller.api.resources.sessionrecordings.v1.VaultSSHCertificateCredentialLibraryAttributes.ExtensionsEntry
2, // 34: controller.api.resources.sessionrecordings.v1.ValuesAtTime.user:type_name -> controller.api.resources.sessionrecordings.v1.User
6, // 35: controller.api.resources.sessionrecordings.v1.ValuesAtTime.target:type_name -> controller.api.resources.sessionrecordings.v1.Target
4, // 36: controller.api.resources.sessionrecordings.v1.ValuesAtTime.host:type_name -> controller.api.resources.sessionrecordings.v1.Host
10, // 37: controller.api.resources.sessionrecordings.v1.ValuesAtTime.credentials:type_name -> controller.api.resources.sessionrecordings.v1.Credential
14, // 38: controller.api.resources.sessionrecordings.v1.ValuesAtTime.credential_libraries:type_name -> controller.api.resources.sessionrecordings.v1.CredentialLibrary
23, // 39: controller.api.resources.sessionrecordings.v1.SessionRecording.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
21, // 40: controller.api.resources.sessionrecordings.v1.SessionRecording.created_time:type_name -> google.protobuf.Timestamp
21, // 41: controller.api.resources.sessionrecordings.v1.SessionRecording.updated_time:type_name -> google.protobuf.Timestamp
21, // 42: controller.api.resources.sessionrecordings.v1.SessionRecording.start_time:type_name -> google.protobuf.Timestamp
21, // 43: controller.api.resources.sessionrecordings.v1.SessionRecording.end_time:type_name -> google.protobuf.Timestamp
22, // 44: controller.api.resources.sessionrecordings.v1.SessionRecording.duration:type_name -> google.protobuf.Duration
1, // 45: controller.api.resources.sessionrecordings.v1.SessionRecording.connection_recordings:type_name -> controller.api.resources.sessionrecordings.v1.ConnectionRecording
17, // 46: controller.api.resources.sessionrecordings.v1.SessionRecording.create_time_values:type_name -> controller.api.resources.sessionrecordings.v1.ValuesAtTime
21, // 47: controller.api.resources.sessionrecordings.v1.SessionRecording.retain_until:type_name -> google.protobuf.Timestamp
21, // 48: controller.api.resources.sessionrecordings.v1.SessionRecording.delete_after:type_name -> google.protobuf.Timestamp
49, // [49:49] is the sub-list for method output_type
49, // [49:49] is the sub-list for method input_type
49, // [49:49] is the sub-list for extension type_name
49, // [49:49] is the sub-list for extension extendee
0, // [0:49] is the sub-list for field type_name
14, // 27: controller.api.resources.sessionrecordings.v1.Credential.password_attributes:type_name -> controller.api.resources.sessionrecordings.v1.PasswordCredentialAttributes
8, // 28: controller.api.resources.sessionrecordings.v1.CredentialLibrary.credential_store:type_name -> controller.api.resources.sessionrecordings.v1.CredentialStore
25, // 29: controller.api.resources.sessionrecordings.v1.CredentialLibrary.attributes:type_name -> google.protobuf.Struct
16, // 30: controller.api.resources.sessionrecordings.v1.CredentialLibrary.vault_credential_library_attributes:type_name -> controller.api.resources.sessionrecordings.v1.VaultCredentialLibraryAttributes
16, // 31: controller.api.resources.sessionrecordings.v1.CredentialLibrary.vault_generic_credential_library_attributes:type_name -> controller.api.resources.sessionrecordings.v1.VaultCredentialLibraryAttributes
17, // 32: controller.api.resources.sessionrecordings.v1.CredentialLibrary.vault_ssh_certificate_credential_library_attributes:type_name -> controller.api.resources.sessionrecordings.v1.VaultSSHCertificateCredentialLibraryAttributes
20, // 33: controller.api.resources.sessionrecordings.v1.VaultSSHCertificateCredentialLibraryAttributes.critical_options:type_name -> controller.api.resources.sessionrecordings.v1.VaultSSHCertificateCredentialLibraryAttributes.CriticalOptionsEntry
21, // 34: controller.api.resources.sessionrecordings.v1.VaultSSHCertificateCredentialLibraryAttributes.extensions:type_name -> controller.api.resources.sessionrecordings.v1.VaultSSHCertificateCredentialLibraryAttributes.ExtensionsEntry
2, // 35: controller.api.resources.sessionrecordings.v1.ValuesAtTime.user:type_name -> controller.api.resources.sessionrecordings.v1.User
6, // 36: controller.api.resources.sessionrecordings.v1.ValuesAtTime.target:type_name -> controller.api.resources.sessionrecordings.v1.Target
4, // 37: controller.api.resources.sessionrecordings.v1.ValuesAtTime.host:type_name -> controller.api.resources.sessionrecordings.v1.Host
10, // 38: controller.api.resources.sessionrecordings.v1.ValuesAtTime.credentials:type_name -> controller.api.resources.sessionrecordings.v1.Credential
15, // 39: controller.api.resources.sessionrecordings.v1.ValuesAtTime.credential_libraries:type_name -> controller.api.resources.sessionrecordings.v1.CredentialLibrary
24, // 40: controller.api.resources.sessionrecordings.v1.SessionRecording.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
22, // 41: controller.api.resources.sessionrecordings.v1.SessionRecording.created_time:type_name -> google.protobuf.Timestamp
22, // 42: controller.api.resources.sessionrecordings.v1.SessionRecording.updated_time:type_name -> google.protobuf.Timestamp
22, // 43: controller.api.resources.sessionrecordings.v1.SessionRecording.start_time:type_name -> google.protobuf.Timestamp
22, // 44: controller.api.resources.sessionrecordings.v1.SessionRecording.end_time:type_name -> google.protobuf.Timestamp
23, // 45: controller.api.resources.sessionrecordings.v1.SessionRecording.duration:type_name -> google.protobuf.Duration
1, // 46: controller.api.resources.sessionrecordings.v1.SessionRecording.connection_recordings:type_name -> controller.api.resources.sessionrecordings.v1.ConnectionRecording
18, // 47: controller.api.resources.sessionrecordings.v1.SessionRecording.create_time_values:type_name -> controller.api.resources.sessionrecordings.v1.ValuesAtTime
22, // 48: controller.api.resources.sessionrecordings.v1.SessionRecording.retain_until:type_name -> google.protobuf.Timestamp
22, // 49: controller.api.resources.sessionrecordings.v1.SessionRecording.delete_after:type_name -> google.protobuf.Timestamp
50, // [50:50] is the sub-list for method output_type
50, // [50:50] is the sub-list for method input_type
50, // [50:50] is the sub-list for extension type_name
50, // [50:50] is the sub-list for extension extendee
0, // [0:50] is the sub-list for field type_name
}
func init() { file_controller_api_resources_sessionrecordings_v1_session_recording_proto_init() }
@@ -2450,8 +2518,9 @@ func file_controller_api_resources_sessionrecordings_v1_session_recording_proto_
(*Credential_UsernamePasswordAttributes)(nil),
(*Credential_SshPrivateKeyAttributes)(nil),
(*Credential_JsonAttributes)(nil),
(*Credential_PasswordAttributes)(nil),
}
file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[14].OneofWrappers = []any{
file_controller_api_resources_sessionrecordings_v1_session_recording_proto_msgTypes[15].OneofWrappers = []any{
(*CredentialLibrary_Attributes)(nil),
(*CredentialLibrary_VaultCredentialLibraryAttributes)(nil),
(*CredentialLibrary_VaultGenericCredentialLibraryAttributes)(nil),
@@ -2463,7 +2532,7 @@ func file_controller_api_resources_sessionrecordings_v1_session_recording_proto_
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_controller_api_resources_sessionrecordings_v1_session_recording_proto_rawDesc), len(file_controller_api_resources_sessionrecordings_v1_session_recording_proto_rawDesc)),
NumEnums: 0,
NumMessages: 21,
NumMessages: 22,
NumExtensions: 0,
NumServices: 0,
},
@@ -1487,6 +1487,52 @@ func (x *UsernamePasswordDomainCredential) GetDomain() string {
return ""
}
// The layout of the struct for "credential" field in SessionCredential for a password credential type.
type PasswordCredential struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Password of the credential
Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty" class:"secret"` // @gotags: `class:"secret"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PasswordCredential) Reset() {
*x = PasswordCredential{}
mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PasswordCredential) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PasswordCredential) ProtoMessage() {}
func (x *PasswordCredential) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[16]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PasswordCredential.ProtoReflect.Descriptor instead.
func (*PasswordCredential) Descriptor() ([]byte, []int) {
return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{16}
}
func (x *PasswordCredential) GetPassword() string {
if x != nil {
return x.Password
}
return ""
}
// The layout of the struct for "credential" field in SessionCredential for a ssh_private_key credential type.
type SshPrivateKeyCredential struct {
state protoimpl.MessageState `protogen:"open.v1"`
@@ -1502,7 +1548,7 @@ type SshPrivateKeyCredential struct {
func (x *SshPrivateKeyCredential) Reset() {
*x = SshPrivateKeyCredential{}
mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[16]
mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1514,7 +1560,7 @@ func (x *SshPrivateKeyCredential) String() string {
func (*SshPrivateKeyCredential) ProtoMessage() {}
func (x *SshPrivateKeyCredential) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[16]
mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[17]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1527,7 +1573,7 @@ func (x *SshPrivateKeyCredential) ProtoReflect() protoreflect.Message {
// Deprecated: Use SshPrivateKeyCredential.ProtoReflect.Descriptor instead.
func (*SshPrivateKeyCredential) Descriptor() ([]byte, []int) {
return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{16}
return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{17}
}
func (x *SshPrivateKeyCredential) GetUsername() string {
@@ -1709,7 +1755,9 @@ const file_controller_api_resources_targets_v1_target_proto_rawDesc = "" +
" UsernamePasswordDomainCredential\x12\x1a\n" +
"\busername\x18\x01 \x01(\tR\busername\x12\x1a\n" +
"\bpassword\x18\x02 \x01(\tR\bpassword\x12\x16\n" +
"\x06domain\x18\x03 \x01(\tR\x06domain\"\x8c\x01\n" +
"\x06domain\x18\x03 \x01(\tR\x06domain\"0\n" +
"\x12PasswordCredential\x12\x1a\n" +
"\bpassword\x18\x01 \x01(\tR\bpassword\"\x8c\x01\n" +
"\x17SshPrivateKeyCredential\x12\x1a\n" +
"\busername\x18\x01 \x01(\tR\busername\x12\x1f\n" +
"\vprivate_key\x18\x02 \x01(\tR\n" +
@@ -1728,7 +1776,7 @@ func file_controller_api_resources_targets_v1_target_proto_rawDescGZIP() []byte
return file_controller_api_resources_targets_v1_target_proto_rawDescData
}
var file_controller_api_resources_targets_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 17)
var file_controller_api_resources_targets_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
var file_controller_api_resources_targets_v1_target_proto_goTypes = []any{
(*Alias)(nil), // 0: controller.api.resources.targets.v1.Alias
(*TargetAliasAttributes)(nil), // 1: controller.api.resources.targets.v1.TargetAliasAttributes
@@ -1746,57 +1794,58 @@ var file_controller_api_resources_targets_v1_target_proto_goTypes = []any{
(*SessionAuthorization)(nil), // 13: controller.api.resources.targets.v1.SessionAuthorization
(*UsernamePasswordCredential)(nil), // 14: controller.api.resources.targets.v1.UsernamePasswordCredential
(*UsernamePasswordDomainCredential)(nil), // 15: controller.api.resources.targets.v1.UsernamePasswordDomainCredential
(*SshPrivateKeyCredential)(nil), // 16: controller.api.resources.targets.v1.SshPrivateKeyCredential
(*structpb.Struct)(nil), // 17: google.protobuf.Struct
(*scopes.ScopeInfo)(nil), // 18: controller.api.resources.scopes.v1.ScopeInfo
(*wrapperspb.StringValue)(nil), // 19: google.protobuf.StringValue
(*timestamppb.Timestamp)(nil), // 20: google.protobuf.Timestamp
(*wrapperspb.UInt32Value)(nil), // 21: google.protobuf.UInt32Value
(*wrapperspb.Int32Value)(nil), // 22: google.protobuf.Int32Value
(*wrapperspb.BoolValue)(nil), // 23: google.protobuf.BoolValue
(*PasswordCredential)(nil), // 16: controller.api.resources.targets.v1.PasswordCredential
(*SshPrivateKeyCredential)(nil), // 17: controller.api.resources.targets.v1.SshPrivateKeyCredential
(*structpb.Struct)(nil), // 18: google.protobuf.Struct
(*scopes.ScopeInfo)(nil), // 19: controller.api.resources.scopes.v1.ScopeInfo
(*wrapperspb.StringValue)(nil), // 20: google.protobuf.StringValue
(*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp
(*wrapperspb.UInt32Value)(nil), // 22: google.protobuf.UInt32Value
(*wrapperspb.Int32Value)(nil), // 23: google.protobuf.Int32Value
(*wrapperspb.BoolValue)(nil), // 24: google.protobuf.BoolValue
}
var file_controller_api_resources_targets_v1_target_proto_depIdxs = []int32{
1, // 0: controller.api.resources.targets.v1.Alias.attributes:type_name -> controller.api.resources.targets.v1.TargetAliasAttributes
2, // 1: controller.api.resources.targets.v1.TargetAliasAttributes.authorize_session_arguments:type_name -> controller.api.resources.targets.v1.AuthorizeSessionArguments
17, // 2: controller.api.resources.targets.v1.SessionSecret.decoded:type_name -> google.protobuf.Struct
18, // 2: controller.api.resources.targets.v1.SessionSecret.decoded:type_name -> google.protobuf.Struct
4, // 3: controller.api.resources.targets.v1.SessionCredential.credential_source:type_name -> controller.api.resources.targets.v1.CredentialSource
5, // 4: controller.api.resources.targets.v1.SessionCredential.secret:type_name -> controller.api.resources.targets.v1.SessionSecret
17, // 5: controller.api.resources.targets.v1.SessionCredential.credential:type_name -> google.protobuf.Struct
18, // 6: controller.api.resources.targets.v1.Target.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
19, // 7: controller.api.resources.targets.v1.Target.name:type_name -> google.protobuf.StringValue
19, // 8: controller.api.resources.targets.v1.Target.description:type_name -> google.protobuf.StringValue
20, // 9: controller.api.resources.targets.v1.Target.created_time:type_name -> google.protobuf.Timestamp
20, // 10: controller.api.resources.targets.v1.Target.updated_time:type_name -> google.protobuf.Timestamp
18, // 5: controller.api.resources.targets.v1.SessionCredential.credential:type_name -> google.protobuf.Struct
19, // 6: controller.api.resources.targets.v1.Target.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
20, // 7: controller.api.resources.targets.v1.Target.name:type_name -> google.protobuf.StringValue
20, // 8: controller.api.resources.targets.v1.Target.description:type_name -> google.protobuf.StringValue
21, // 9: controller.api.resources.targets.v1.Target.created_time:type_name -> google.protobuf.Timestamp
21, // 10: controller.api.resources.targets.v1.Target.updated_time:type_name -> google.protobuf.Timestamp
3, // 11: controller.api.resources.targets.v1.Target.host_sources:type_name -> controller.api.resources.targets.v1.HostSource
21, // 12: controller.api.resources.targets.v1.Target.session_max_seconds:type_name -> google.protobuf.UInt32Value
22, // 13: controller.api.resources.targets.v1.Target.session_connection_limit:type_name -> google.protobuf.Int32Value
19, // 14: controller.api.resources.targets.v1.Target.worker_filter:type_name -> google.protobuf.StringValue
19, // 15: controller.api.resources.targets.v1.Target.egress_worker_filter:type_name -> google.protobuf.StringValue
19, // 16: controller.api.resources.targets.v1.Target.ingress_worker_filter:type_name -> google.protobuf.StringValue
22, // 12: controller.api.resources.targets.v1.Target.session_max_seconds:type_name -> google.protobuf.UInt32Value
23, // 13: controller.api.resources.targets.v1.Target.session_connection_limit:type_name -> google.protobuf.Int32Value
20, // 14: controller.api.resources.targets.v1.Target.worker_filter:type_name -> google.protobuf.StringValue
20, // 15: controller.api.resources.targets.v1.Target.egress_worker_filter:type_name -> google.protobuf.StringValue
20, // 16: controller.api.resources.targets.v1.Target.ingress_worker_filter:type_name -> google.protobuf.StringValue
4, // 17: controller.api.resources.targets.v1.Target.brokered_credential_sources:type_name -> controller.api.resources.targets.v1.CredentialSource
4, // 18: controller.api.resources.targets.v1.Target.injected_application_credential_sources:type_name -> controller.api.resources.targets.v1.CredentialSource
17, // 19: controller.api.resources.targets.v1.Target.attributes:type_name -> google.protobuf.Struct
18, // 19: controller.api.resources.targets.v1.Target.attributes:type_name -> google.protobuf.Struct
8, // 20: controller.api.resources.targets.v1.Target.tcp_target_attributes:type_name -> controller.api.resources.targets.v1.TcpTargetAttributes
9, // 21: controller.api.resources.targets.v1.Target.ssh_target_attributes:type_name -> controller.api.resources.targets.v1.SshTargetAttributes
10, // 22: controller.api.resources.targets.v1.Target.rdp_target_attributes:type_name -> controller.api.resources.targets.v1.RdpTargetAttributes
19, // 23: controller.api.resources.targets.v1.Target.address:type_name -> google.protobuf.StringValue
20, // 23: controller.api.resources.targets.v1.Target.address:type_name -> google.protobuf.StringValue
0, // 24: controller.api.resources.targets.v1.Target.aliases:type_name -> controller.api.resources.targets.v1.Alias
0, // 25: controller.api.resources.targets.v1.Target.with_aliases:type_name -> controller.api.resources.targets.v1.Alias
21, // 26: controller.api.resources.targets.v1.TcpTargetAttributes.default_port:type_name -> google.protobuf.UInt32Value
21, // 27: controller.api.resources.targets.v1.TcpTargetAttributes.default_client_port:type_name -> google.protobuf.UInt32Value
21, // 28: controller.api.resources.targets.v1.SshTargetAttributes.default_port:type_name -> google.protobuf.UInt32Value
21, // 29: controller.api.resources.targets.v1.SshTargetAttributes.default_client_port:type_name -> google.protobuf.UInt32Value
19, // 30: controller.api.resources.targets.v1.SshTargetAttributes.storage_bucket_id:type_name -> google.protobuf.StringValue
23, // 31: controller.api.resources.targets.v1.SshTargetAttributes.enable_session_recording:type_name -> google.protobuf.BoolValue
21, // 32: controller.api.resources.targets.v1.RdpTargetAttributes.default_port:type_name -> google.protobuf.UInt32Value
21, // 33: controller.api.resources.targets.v1.RdpTargetAttributes.default_client_port:type_name -> google.protobuf.UInt32Value
18, // 34: controller.api.resources.targets.v1.SessionAuthorizationData.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
20, // 35: controller.api.resources.targets.v1.SessionAuthorizationData.created_time:type_name -> google.protobuf.Timestamp
20, // 36: controller.api.resources.targets.v1.SessionAuthorizationData.expiration:type_name -> google.protobuf.Timestamp
22, // 26: controller.api.resources.targets.v1.TcpTargetAttributes.default_port:type_name -> google.protobuf.UInt32Value
22, // 27: controller.api.resources.targets.v1.TcpTargetAttributes.default_client_port:type_name -> google.protobuf.UInt32Value
22, // 28: controller.api.resources.targets.v1.SshTargetAttributes.default_port:type_name -> google.protobuf.UInt32Value
22, // 29: controller.api.resources.targets.v1.SshTargetAttributes.default_client_port:type_name -> google.protobuf.UInt32Value
20, // 30: controller.api.resources.targets.v1.SshTargetAttributes.storage_bucket_id:type_name -> google.protobuf.StringValue
24, // 31: controller.api.resources.targets.v1.SshTargetAttributes.enable_session_recording:type_name -> google.protobuf.BoolValue
22, // 32: controller.api.resources.targets.v1.RdpTargetAttributes.default_port:type_name -> google.protobuf.UInt32Value
22, // 33: controller.api.resources.targets.v1.RdpTargetAttributes.default_client_port:type_name -> google.protobuf.UInt32Value
19, // 34: controller.api.resources.targets.v1.SessionAuthorizationData.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
21, // 35: controller.api.resources.targets.v1.SessionAuthorizationData.created_time:type_name -> google.protobuf.Timestamp
21, // 36: controller.api.resources.targets.v1.SessionAuthorizationData.expiration:type_name -> google.protobuf.Timestamp
11, // 37: controller.api.resources.targets.v1.SessionAuthorizationData.worker_info:type_name -> controller.api.resources.targets.v1.WorkerInfo
18, // 38: controller.api.resources.targets.v1.SessionAuthorization.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
20, // 39: controller.api.resources.targets.v1.SessionAuthorization.created_time:type_name -> google.protobuf.Timestamp
20, // 40: controller.api.resources.targets.v1.SessionAuthorization.expiration:type_name -> google.protobuf.Timestamp
19, // 38: controller.api.resources.targets.v1.SessionAuthorization.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
21, // 39: controller.api.resources.targets.v1.SessionAuthorization.created_time:type_name -> google.protobuf.Timestamp
21, // 40: controller.api.resources.targets.v1.SessionAuthorization.expiration:type_name -> google.protobuf.Timestamp
6, // 41: controller.api.resources.targets.v1.SessionAuthorization.credentials:type_name -> controller.api.resources.targets.v1.SessionCredential
42, // [42:42] is the sub-list for method output_type
42, // [42:42] is the sub-list for method input_type
@@ -1822,7 +1871,7 @@ func file_controller_api_resources_targets_v1_target_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_controller_api_resources_targets_v1_target_proto_rawDesc), len(file_controller_api_resources_targets_v1_target_proto_rawDesc)),
NumEnums: 0,
NumMessages: 17,
NumMessages: 18,
NumExtensions: 0,
NumServices: 0,
},
@@ -262,6 +262,41 @@ func CreateStaticCredentialUsernamePasswordCli(t testing.TB, ctx context.Context
return credentialId, nil
}
// CreateStaticCredentialPasswordCli uses the cli to create a new password credential in the
// provided static credential store.
// Returns the id of the new credential
func CreateStaticCredentialPasswordCli(t testing.TB, ctx context.Context, credentialStoreId string, password string) (string, error) {
name, err := base62.Random(16)
if err != nil {
return "", err
}
output := e2e.RunCommand(ctx, "boundary",
e2e.WithArgs(
"credentials", "create", "password",
"-credential-store-id", credentialStoreId,
"-password", "env://E2E_CREDENTIALS_PASSWORD",
"-name", fmt.Sprintf("e2e Credential %s", name),
"-description", "e2e",
"-format", "json",
),
e2e.WithEnv("E2E_CREDENTIALS_PASSWORD", password),
)
if output.Err != nil {
return "", fmt.Errorf("%w: %s", output.Err, string(output.Stderr))
}
var createCredentialsResult credentials.CredentialCreateResult
err = json.Unmarshal(output.Stdout, &createCredentialsResult)
if err != nil {
return "", err
}
credentialId := createCredentialsResult.Item.Id
t.Logf("Created Password Credential: %s", credentialId)
return credentialId, nil
}
// CreateStaticCredentialUsernamePasswordDomainCli uses the cli to create a new username password domain credential in the
// provided static credential store.
// Returns the id of the new credential
-79
View File
@@ -35,12 +35,6 @@ type cassandraConfig struct {
NetworkAlias string
}
type redisConfig struct {
User string
Password string
NetworkAlias string
}
// StartBoundaryDatabase spins up a postgres database in a docker container.
// Returns information about the container
func StartBoundaryDatabase(t testing.TB, pool *dockertest.Pool, network *dockertest.Network, repository, tag string) *Container {
@@ -478,63 +472,6 @@ func StartCassandra(t testing.TB, pool *dockertest.Pool, network *dockertest.Net
}
}
// StartRedis starts a Redis database in a docker container.
// Returns information about the container
func StartRedis(t testing.TB, pool *dockertest.Pool, network *dockertest.Network, repository, tag string) *Container {
t.Log("Starting Redis database...")
c, err := LoadConfig()
require.NoError(t, err)
err = pool.Client.PullImage(docker.PullImageOptions{
Repository: fmt.Sprintf("%s/%s", c.DockerMirror, repository),
Tag: tag,
}, docker.AuthConfiguration{})
require.NoError(t, err)
config := redisConfig{
User: "e2eboundary",
Password: "e2eboundary",
NetworkAlias: "e2eredis",
}
resource, err := pool.RunWithOptions(&dockertest.RunOptions{
Repository: fmt.Sprintf("%s/%s", c.DockerMirror, repository),
Tag: tag,
ExposedPorts: []string{"6379/tcp"},
Name: config.NetworkAlias,
Networks: []*dockertest.Network{network},
})
require.NoError(t, err)
err = pool.Retry(func() error {
cmd := exec.Command("docker", "exec", config.NetworkAlias, "redis-cli", "PING")
output, cmdErr := cmd.CombinedOutput()
if cmdErr != nil {
return fmt.Errorf("failed to connect to Redis container '%s': %v\nOutput: %s", config.NetworkAlias, cmdErr, string(output))
}
return nil
})
require.NoError(t, err, "Redis container did not start in time or is not healthy")
err = setupRedisAuthAndUser(t, resource, pool, &config)
require.NoError(t, err)
return &Container{
Resource: resource,
UriLocalhost: fmt.Sprintf(
"redis://%s:%s@localhost:6379",
config.User,
config.Password,
),
UriNetwork: fmt.Sprintf(
"redis://%s:%s@%s:6379",
config.User,
config.Password,
config.NetworkAlias,
),
}
}
// setupCassandraAuthAndUser enables authentication on a Cassandra container and creates a user with permissions.
func setupCassandraAuthAndUser(t testing.TB, resource *dockertest.Resource, pool *dockertest.Pool, config *cassandraConfig) error {
t.Helper()
@@ -593,19 +530,3 @@ func setupCassandraAuthAndUser(t testing.TB, resource *dockertest.Resource, pool
}
return nil
}
// setupRedisAuthAndUser configures a Redis container by creating a user with permissions.
func setupRedisAuthAndUser(t testing.TB, resource *dockertest.Resource, pool *dockertest.Pool, config *redisConfig) error {
t.Helper()
t.Log("Configuring Redis authentication and user permissions...")
err := exec.Command(
"docker", "exec", config.NetworkAlias, "redis-cli",
"ACL", "SETUSER", config.User, "on", fmt.Sprintf(">%s", config.Password), "+@read", "+@write", "allkeys",
).Run()
if err != nil {
return err
}
return nil
}
+146
View File
@@ -0,0 +1,146 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package infra
import (
"fmt"
"io"
"net/url"
"os/exec"
"strings"
"testing"
"github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
"github.com/stretchr/testify/require"
)
type RedisContainerInfo struct {
Hostname string
Port string
Username string
Password string
}
type redisConfig struct {
User string
Password string
NetworkAlias string
}
// SetupRedisContainer starts a Redis container and returns its connection info
func SetupRedisContainer(t *testing.T) *RedisContainerInfo {
pool, err := dockertest.NewPool("")
require.NoError(t, err)
network, err := pool.NetworksByName("e2e_cluster")
require.NoError(t, err, "Failed to get e2e_cluster network")
c := startRedis(t, pool, &network[0], "redis", "latest")
require.NotNil(t, c, "Redis container should not be nil")
t.Cleanup(func() {
if err := pool.Purge(c.Resource); err != nil {
t.Logf("Failed to purge Redis container: %v", err)
}
})
u, err := url.Parse(c.UriNetwork)
t.Log(u)
require.NoError(t, err, "Failed to parse Redis URL")
user, hostname, port := u.User.Username(), u.Hostname(), u.Port()
pw, pwSet := u.User.Password()
t.Logf("Redis info: user=%s, host=%s, port=%s, password-set:%t",
user, hostname, port, pwSet)
return &RedisContainerInfo{
Hostname: hostname,
Port: port,
Username: user,
Password: pw,
}
}
// startRedis starts a Redis database in a docker container.
// Returns information about the container
func startRedis(t testing.TB, pool *dockertest.Pool, network *dockertest.Network, repository, tag string) *Container {
t.Log("Starting Redis database...")
c, err := LoadConfig()
require.NoError(t, err)
err = pool.Client.PullImage(docker.PullImageOptions{
Repository: fmt.Sprintf("%s/%s", c.DockerMirror, repository),
Tag: tag,
}, docker.AuthConfiguration{})
require.NoError(t, err)
config := redisConfig{
User: "e2eboundary",
Password: "e2eboundary",
NetworkAlias: "e2eredis",
}
resource, err := pool.RunWithOptions(&dockertest.RunOptions{
Repository: fmt.Sprintf("%s/%s", c.DockerMirror, repository),
Tag: tag,
ExposedPorts: []string{"6379/tcp"},
Name: config.NetworkAlias,
Networks: []*dockertest.Network{network},
})
require.NoError(t, err)
err = pool.Retry(func() error {
cmd := exec.Command("docker", "exec", config.NetworkAlias, "redis-cli", "PING")
output, cmdErr := cmd.CombinedOutput()
if cmdErr != nil {
return fmt.Errorf("failed to connect to Redis container '%s': %v\nOutput: %s", config.NetworkAlias, cmdErr, string(output))
}
return nil
})
require.NoError(t, err, "Redis container did not start in time or is not healthy")
// configure redis users
// e2e user
err = exec.Command(
"docker", "exec", config.NetworkAlias, "redis-cli",
"ACL", "SETUSER", config.User, "on", fmt.Sprintf(">%s", config.Password), "+@all", "allkeys",
).Run()
require.NoError(t, err)
// default user
err = exec.Command(
"docker", "exec", config.NetworkAlias, "redis-cli",
"ACL", "SETUSER", "default", fmt.Sprintf(">%s", config.Password),
).Run()
require.NoError(t, err)
return &Container{
Resource: resource,
UriLocalhost: fmt.Sprintf(
"redis://%s:%s@localhost:6379",
config.User,
config.Password,
),
UriNetwork: fmt.Sprintf(
"redis://%s:%s@%s:6379",
config.User,
config.Password,
config.NetworkAlias,
),
}
}
func SendRedisCommand(stdin io.WriteCloser, stdout io.ReadCloser, cmdStr string) (string, error) {
_, err := stdin.Write([]byte(cmdStr))
if err != nil {
return "", err
}
buf := make([]byte, 1024)
n, err := stdout.Read(buf)
if err != nil {
return "", err
}
return strings.TrimSpace(string(buf[:n])), nil
}
@@ -0,0 +1,96 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package base_test
import (
"context"
"io"
"os/exec"
"testing"
"github.com/hashicorp/boundary/internal/target"
"github.com/hashicorp/boundary/testing/internal/e2e"
"github.com/hashicorp/boundary/testing/internal/e2e/boundary"
"github.com/hashicorp/boundary/testing/internal/e2e/infra"
"github.com/stretchr/testify/require"
)
// TestCliTcpTargetConnectRedisPassword uses the boundary cli to connect to a target
// using `connect redis` and a password credential.
func TestCliTcpTargetConnectRedisPassword(t *testing.T) {
e2e.MaybeSkipTest(t)
// Setup
ctx := context.Background()
redisInfo := infra.SetupRedisContainer(t)
boundary.AuthenticateAdminCli(t, ctx)
// Create Boundary resources
orgId, err := boundary.CreateOrgCli(t, ctx)
require.NoError(t, err)
t.Cleanup(func() {
ctx := context.Background()
boundary.AuthenticateAdminCli(t, ctx)
output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs("scopes", "delete", "-id", orgId))
require.NoError(t, output.Err, string(output.Stderr))
})
projectId, err := boundary.CreateProjectCli(t, ctx, orgId)
require.NoError(t, err)
targetId, err := boundary.CreateTargetCli(
t,
ctx,
projectId,
redisInfo.Port,
[]target.Option{target.WithAddress(redisInfo.Hostname)},
)
require.NoError(t, err)
storeId, err := boundary.CreateCredentialStoreStaticCli(t, ctx, projectId)
require.NoError(t, err)
credentialId, err := boundary.CreateStaticCredentialPasswordCli(
t,
ctx,
storeId,
redisInfo.Password,
)
require.NoError(t, err)
err = boundary.AddBrokeredCredentialSourceToTargetCli(t, ctx, targetId, credentialId)
require.NoError(t, err)
// Validate redis connect
cmd := exec.CommandContext(ctx,
"boundary",
"connect", "redis",
"-target-id", targetId,
)
stdin, err := cmd.StdinPipe()
require.NoError(t, err)
stdout, err := cmd.StdoutPipe()
require.NoError(t, err)
require.NoError(t, cmd.Start())
output, err := infra.SendRedisCommand(stdin, stdout, "ACL WHOAMI\r\n")
require.NoError(t, err)
require.Equal(t, "default", output) // redis default user
output, err = infra.SendRedisCommand(stdin, stdout, "SET e2etestkey e2etestvalue\r\n")
require.NoError(t, err)
require.Equal(t, "OK", output)
output, err = infra.SendRedisCommand(stdin, stdout, "GET e2etestkey\r\n")
require.NoError(t, err)
require.Equal(t, "e2etestvalue", output)
output, err = infra.SendRedisCommand(stdin, stdout, "QUIT\r\n")
require.Equal(t, io.EOF, err)
require.Empty(t, output)
// Confirm that boundary connect has closed
err = cmd.Wait()
require.NoError(t, err)
}
@@ -1,141 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package base_test
import (
"context"
"io"
"net/url"
"os/exec"
"strings"
"testing"
"github.com/hashicorp/boundary/internal/target"
"github.com/hashicorp/boundary/testing/internal/e2e"
"github.com/hashicorp/boundary/testing/internal/e2e/boundary"
"github.com/hashicorp/boundary/testing/internal/e2e/infra"
"github.com/ory/dockertest/v3"
"github.com/stretchr/testify/require"
)
// TestCliTcpTargetConnectRedis uses the boundary cli to connect to a target using `connect redis`
func TestCliTcpTargetConnectRedis(t *testing.T) {
e2e.MaybeSkipTest(t)
pool, err := dockertest.NewPool("")
require.NoError(t, err)
ctx := context.Background()
network, err := pool.NetworksByName("e2e_cluster")
require.NoError(t, err, "Failed to get e2e_cluster network")
c := infra.StartRedis(t, pool, &network[0], "redis", "latest")
require.NotNil(t, c, "Redis container should not be nil")
t.Cleanup(func() {
if err := pool.Purge(c.Resource); err != nil {
t.Logf("Failed to purge Redis container: %v", err)
}
})
u, err := url.Parse(c.UriNetwork)
t.Log(u)
require.NoError(t, err, "Failed to parse Redis URL")
user, hostname, port := u.User.Username(), u.Hostname(), u.Port()
pw, pwSet := u.User.Password()
t.Logf("Redis info: user=%s, host=%s, port=%s, password-set:%t",
user, hostname, port, pwSet)
// Wait for Redis to be ready
err = pool.Retry(func() error {
out, e := exec.CommandContext(ctx, "docker", "exec", hostname,
"redis-cli", "-h", hostname, "-p", port, "PING").CombinedOutput()
t.Logf("Redis PING output: %s", out)
return e
})
require.NoError(t, err, "Redis container failed to start")
boundary.AuthenticateAdminCli(t, ctx)
orgId, err := boundary.CreateOrgCli(t, ctx)
require.NoError(t, err)
t.Cleanup(func() {
ctx := context.Background()
boundary.AuthenticateAdminCli(t, ctx)
output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs("scopes", "delete", "-id", orgId))
require.NoError(t, output.Err, string(output.Stderr))
})
projectId, err := boundary.CreateProjectCli(t, ctx, orgId)
require.NoError(t, err)
targetId, err := boundary.CreateTargetCli(
t,
ctx,
projectId,
port,
[]target.Option{target.WithAddress(hostname)},
)
require.NoError(t, err)
storeId, err := boundary.CreateCredentialStoreStaticCli(t, ctx, projectId)
require.NoError(t, err)
credentialId, err := boundary.CreateStaticCredentialUsernamePasswordCli(
t,
ctx,
storeId,
user,
pw,
)
require.NoError(t, err)
err = boundary.AddBrokeredCredentialSourceToTargetCli(t, ctx, targetId, credentialId)
require.NoError(t, err)
t.Logf("Attempting to connect to Redis target %s", targetId)
cmd := exec.CommandContext(ctx,
"boundary",
"connect", "redis",
"-target-id", targetId,
)
stdin, err := cmd.StdinPipe()
require.NoError(t, err)
stdout, err := cmd.StdoutPipe()
require.NoError(t, err)
require.NoError(t, cmd.Start())
output, err := sendRedisCommand(stdin, stdout, "SET e2etestkey e2etestvalue\r\n")
require.NoError(t, err)
require.Equal(t, "OK", output)
output, err = sendRedisCommand(stdin, stdout, "GET e2etestkey\r\n")
require.NoError(t, err)
require.Equal(t, "e2etestvalue", output)
output, err = sendRedisCommand(stdin, stdout, "QUIT\r\n")
require.Equal(t, io.EOF, err)
require.Empty(t, output)
// Confirm that boundary connect has closed
err = cmd.Wait()
require.NoError(t, err)
}
func sendRedisCommand(stdin io.WriteCloser, stdout io.ReadCloser, cmdStr string) (string, error) {
_, err := stdin.Write([]byte(cmdStr))
if err != nil {
return "", err
}
buf := make([]byte, 1024)
n, err := stdout.Read(buf)
if err != nil {
return "", err
}
return strings.TrimSpace(string(buf[:n])), nil
}
@@ -0,0 +1,97 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package base_test
import (
"context"
"io"
"os/exec"
"testing"
"github.com/hashicorp/boundary/internal/target"
"github.com/hashicorp/boundary/testing/internal/e2e"
"github.com/hashicorp/boundary/testing/internal/e2e/boundary"
"github.com/hashicorp/boundary/testing/internal/e2e/infra"
"github.com/stretchr/testify/require"
)
// TestCliTcpTargetConnectRedisUsernamePassword uses the boundary cli to connect to a target
// using `connect redis` and a username-password credential.
func TestCliTcpTargetConnectRedisUsernamePassword(t *testing.T) {
e2e.MaybeSkipTest(t)
// Setup
ctx := context.Background()
redisInfo := infra.SetupRedisContainer(t)
boundary.AuthenticateAdminCli(t, ctx)
// Create Boundary resources
orgId, err := boundary.CreateOrgCli(t, ctx)
require.NoError(t, err)
t.Cleanup(func() {
ctx := context.Background()
boundary.AuthenticateAdminCli(t, ctx)
output := e2e.RunCommand(ctx, "boundary", e2e.WithArgs("scopes", "delete", "-id", orgId))
require.NoError(t, output.Err, string(output.Stderr))
})
projectId, err := boundary.CreateProjectCli(t, ctx, orgId)
require.NoError(t, err)
targetId, err := boundary.CreateTargetCli(
t,
ctx,
projectId,
redisInfo.Port,
[]target.Option{target.WithAddress(redisInfo.Hostname)},
)
require.NoError(t, err)
storeId, err := boundary.CreateCredentialStoreStaticCli(t, ctx, projectId)
require.NoError(t, err)
credentialId, err := boundary.CreateStaticCredentialUsernamePasswordCli(
t,
ctx,
storeId,
redisInfo.Username,
redisInfo.Password,
)
require.NoError(t, err)
err = boundary.AddBrokeredCredentialSourceToTargetCli(t, ctx, targetId, credentialId)
require.NoError(t, err)
// Validate redis connect
cmd := exec.CommandContext(ctx,
"boundary",
"connect", "redis",
"-target-id", targetId,
)
stdin, err := cmd.StdinPipe()
require.NoError(t, err)
stdout, err := cmd.StdoutPipe()
require.NoError(t, err)
require.NoError(t, cmd.Start())
output, err := infra.SendRedisCommand(stdin, stdout, "ACL WHOAMI\r\n")
require.NoError(t, err)
require.Equal(t, redisInfo.Username, output)
output, err = infra.SendRedisCommand(stdin, stdout, "SET e2etestkey e2etestvalue\r\n")
require.NoError(t, err)
require.Equal(t, "OK", output)
output, err = infra.SendRedisCommand(stdin, stdout, "GET e2etestkey\r\n")
require.NoError(t, err)
require.Equal(t, "e2etestvalue", output)
output, err = infra.SendRedisCommand(stdin, stdout, "QUIT\r\n")
require.Equal(t, io.EOF, err)
require.Empty(t, output)
// Confirm that boundary connect has closed
err = cmd.Wait()
require.NoError(t, err)
}