Merge pull request #1796 from hashicorp/llb-credential-type-mapping

This allows users to set the type of credential that a credential
library will provide.

A User Password credential type and an Unspecified credential type are included
with the expectation that additional credential types will be added in the
future. This change also adds the credential type attribute to existing
credential libraries and sets it to Unspecified. The credential type can
only be set when a credential library is created.

The vault credential library includes functionality for converting a
Vault Secret into a User Password Credential. This mapping is enabled by
default, however, the user can override the attributes used for this
mapping.
This commit is contained in:
Timothy Messier authored and GitHub committed 2021-12-17 14:03:58 -05:00
commit 27ebfa1125
47 files changed
+3850 -518

No files matched your search

@@ -13,17 +13,19 @@ import (
)
type CredentialLibrary struct {
Id string `json:"id,omitempty"`
CredentialStoreId string `json:"credential_store_id,omitempty"`
Scope *scopes.ScopeInfo `json:"scope,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
CreatedTime time.Time `json:"created_time,omitempty"`
UpdatedTime time.Time `json:"updated_time,omitempty"`
Version uint32 `json:"version,omitempty"`
Type string `json:"type,omitempty"`
Attributes map[string]interface{} `json:"attributes,omitempty"`
AuthorizedActions []string `json:"authorized_actions,omitempty"`
Id string `json:"id,omitempty"`
CredentialStoreId string `json:"credential_store_id,omitempty"`
Scope *scopes.ScopeInfo `json:"scope,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
CreatedTime time.Time `json:"created_time,omitempty"`
UpdatedTime time.Time `json:"updated_time,omitempty"`
Version uint32 `json:"version,omitempty"`
Type string `json:"type,omitempty"`
Attributes map[string]interface{} `json:"attributes,omitempty"`
AuthorizedActions []string `json:"authorized_actions,omitempty"`
CredentialType string `json:"credential_type,omitempty"`
CredentialMappingOverrides map[string]interface{} `json:"credential_mapping_overrides,omitempty"`
response *api.Response
}
+24
View File
@@ -86,6 +86,30 @@ func DefaultAttributes() Option {
}
}
func WithCredentialMappingOverrides(inCredentialMappingOverrides map[string]interface{}) Option {
return func(o *options) {
o.postMap["credential_mapping_overrides"] = inCredentialMappingOverrides
}
}
func DefaultCredentialMappingOverrides() Option {
return func(o *options) {
o.postMap["credential_mapping_overrides"] = nil
}
}
func WithCredentialType(inCredentialType string) Option {
return func(o *options) {
o.postMap["credential_type"] = inCredentialType
}
}
func DefaultCredentialType() Option {
return func(o *options) {
o.postMap["credential_type"] = nil
}
}
func WithDescription(inDescription string) Option {
return func(o *options) {
o.postMap["description"] = inDescription
+2
View File
@@ -70,4 +70,6 @@ const (
EgressCredentialSourceIdsField = "egress_credential_source_ids"
EgressCredentialSourcesField = "egress_credential_sources"
ConnectionsField = "connections"
CredentialTypeField = "credential_type"
CredentialMappingOverridesField = "credential_mapping_overrides"
)
@@ -87,6 +87,11 @@ func (c *Command) printListTable(items []*credentiallibraries.CredentialLibrary)
fmt.Sprintf(" Type: %s", m.Type),
)
}
if m.CredentialType != "" {
output = append(output,
fmt.Sprintf(" Credential Type: %s", m.CredentialType),
)
}
if m.Name != "" {
output = append(output,
fmt.Sprintf(" Name: %s", m.Name),
@@ -168,6 +173,20 @@ func printItemTable(result api.GenericResult) string {
)
}
if item.CredentialType != "" {
ret = append(ret,
"",
" Credential Type:",
fmt.Sprintf(" %v", item.CredentialType),
)
if len(item.CredentialMappingOverrides) > 0 {
ret = append(ret,
" Credential Mapping Overrides:",
base.WrapMap(4, maxLength, item.CredentialMappingOverrides),
)
}
}
return base.WrapForHelpText(ret)
}
@@ -13,15 +13,19 @@ func init() {
}
const (
pathFlagName = "vault-path"
httpMethodFlagName = "vault-http-method"
httpRequestBodyFlagName = "vault-http-request-body"
pathFlagName = "vault-path"
httpMethodFlagName = "vault-http-method"
httpRequestBodyFlagName = "vault-http-request-body"
credentialTypeFlagName = "credential-type"
credentialMappingFlagName = "credential-mapping-override"
)
type extraVaultCmdVars struct {
flagPath string
flagHttpMethod string
flagHttpRequestBody string
flagPath string
flagHttpMethod string
flagHttpRequestBody string
flagCredentialType string
flagCredentialMapping []base.CombinedSliceFlagValue
}
func extraVaultActionsFlagsMapFuncImpl() map[string][]string {
@@ -30,9 +34,16 @@ func extraVaultActionsFlagsMapFuncImpl() map[string][]string {
pathFlagName,
httpMethodFlagName,
httpRequestBodyFlagName,
credentialTypeFlagName,
credentialMappingFlagName,
},
"update": {
pathFlagName,
httpMethodFlagName,
httpRequestBodyFlagName,
credentialMappingFlagName,
},
}
flags["update"] = flags["create"]
return flags
}
@@ -59,11 +70,24 @@ func extraVaultFlagsFuncImpl(c *VaultCommand, set *base.FlagSets, _ *base.FlagSe
Target: &c.flagHttpRequestBody,
Usage: "The http request body the library uses to communicate with vault. This can be the value itself, refer to a file on disk (file://) from which the value will be read, or an env var (env://) from which the value will be read.",
})
case credentialTypeFlagName:
f.StringVar(&base.StringVar{
Name: credentialTypeFlagName,
Target: &c.flagCredentialType,
Usage: "The type of credential this library will issue, defaults to Unspecified.",
})
case credentialMappingFlagName:
f.CombinationSliceVar(&base.CombinationSliceVar{
Name: credentialMappingFlagName,
Target: &c.flagCredentialMapping,
KvSplit: true,
Usage: "The credential mapping override.",
})
}
}
}
func extraVaultFlagHandlingFuncImpl(c *VaultCommand, f *base.FlagSets, opts *[]credentiallibraries.Option) bool {
func extraVaultFlagHandlingFuncImpl(c *VaultCommand, _ *base.FlagSets, opts *[]credentiallibraries.Option) bool {
switch c.flagPath {
case "":
default:
@@ -84,11 +108,44 @@ func extraVaultFlagHandlingFuncImpl(c *VaultCommand, f *base.FlagSets, opts *[]c
rb, _ := parseutil.ParsePath(c.flagHttpRequestBody)
*opts = append(*opts, credentiallibraries.WithVaultCredentialLibraryHttpRequestBody(rb))
}
switch c.flagCredentialType {
case "":
case "null":
*opts = append(*opts, credentiallibraries.DefaultCredentialType())
default:
*opts = append(*opts, credentiallibraries.WithCredentialType(c.flagCredentialType))
}
switch len(c.flagCredentialMapping) {
case 0:
case 1:
if len(c.flagCredentialMapping[0].Keys) == 0 && c.flagCredentialMapping[0].Value == "null" {
*opts = append(*opts, credentiallibraries.DefaultCredentialMappingOverrides())
break
}
fallthrough
default:
mappings := make(map[string]interface{}, len(c.flagCredentialMapping))
for _, mapping := range c.flagCredentialMapping {
switch {
case len(mapping.Keys) != 1 || mapping.Keys[0] == "" || mapping.Value == "":
// mapping override does not support key segments (e.g. 'x.y=z')
c.UI.Error("Credential mapping override must be in the format 'key=value', 'key=null' to clear field or 'null' to clear all.")
return false
case mapping.Value == "null":
// user provided 'key=null' indicating the field specific override should
// be cleared, set map value to nil
mappings[mapping.Keys[0]] = nil
default:
mappings[mapping.Keys[0]] = mapping.Value
}
}
*opts = append(*opts, credentiallibraries.WithCredentialMappingOverrides(mappings))
}
return true
}
func (c *VaultCommand) extraVaultHelpFunc(helpMap map[string]func() string) string {
func (c *VaultCommand) extraVaultHelpFunc(_ map[string]func() string) string {
var helpStr string
switch c.Func {
case "create":
+10
View File
@@ -18,11 +18,21 @@ type Store interface {
GetScopeId() string
}
// Type is the type of credential provided by a library.
type Type string
// Credential type values.
const (
UnspecifiedType Type = "unspecified"
UserPasswordType Type = "user_password"
)
// A Library is a resource that provides credentials that are of the same
// type and access level from a single store.
type Library interface {
boundary.Resource
GetStoreId() string
CredentialType() Type
}
// Purpose is the purpose of the credential.
+30 -17
View File
@@ -92,6 +92,10 @@ type Library struct {
// The store_id of the owning store and must be set.
// @inject_tag: `gorm:"not_null"`
StoreId string `protobuf:"bytes,2,opt,name=store_id,json=storeId,proto3" json:"store_id,omitempty" gorm:"not_null"`
// credential_type is optional. If set, it indicates the type of
// credential the library returns.
// @inject_tag: `gorm:"default:null"`
CredentialType string `protobuf:"bytes,3,opt,name=credential_type,json=credentialType,proto3" json:"credential_type,omitempty" gorm:"default:null"`
}
func (x *Library) Reset() {
@@ -140,6 +144,13 @@ func (x *Library) GetStoreId() string {
return ""
}
func (x *Library) GetCredentialType() string {
if x != nil {
return x.CredentialType
}
return ""
}
type Credential struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -320,27 +331,29 @@ var file_controller_storage_credential_store_v1_credential_proto_rawDesc = []byt
0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70,
0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65,
0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65,
0x49, 0x64, 0x22, 0x41, 0x0a, 0x07, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x1b, 0x0a,
0x49, 0x64, 0x22, 0x6a, 0x0a, 0x07, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x1b, 0x0a,
0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74,
0x6f, 0x72, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x74,
0x6f, 0x72, 0x65, 0x49, 0x64, 0x22, 0x29, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74,
0x69, 0x61, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64,
0x6f, 0x72, 0x65, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74,
0x69, 0x61, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e,
0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x22, 0x29,
0x0a, 0x0a, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x1b, 0x0a, 0x09,
0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x22, 0x40, 0x0a, 0x06, 0x53, 0x74, 0x61,
0x74, 0x69, 0x63, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64,
0x22, 0x40, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75,
0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70,
0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x6f, 0x72, 0x65,
0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x65,
0x49, 0x64, 0x22, 0x45, 0x0a, 0x07, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x12, 0x1b, 0x0a,
0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x69,
0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x49, 0x64, 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72,
0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72,
0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x73,
0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x49, 0x64, 0x22, 0x45, 0x0a, 0x07, 0x44,
0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69,
0x63, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69,
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79,
0x49, 0x64, 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64,
0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x72, 0x65,
0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74,
0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -1,8 +1,11 @@
package vault
import (
"context"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/oplog"
"google.golang.org/protobuf/proto"
)
@@ -21,17 +24,20 @@ const (
type CredentialLibrary struct {
*store.CredentialLibrary
tableName string `gorm:"-"`
MappingOverride MappingOverride `gorm:"-"`
}
// NewCredentialLibrary creates a new in memory CredentialLibrary
// for a Vault backend at vaultPath assigned to storeId.
// Name, description, method, and request body are the only valid options.
// All other options are ignored.
// Name, description, method, request body, credential type, and mapping
// override are the only valid options. All other options are ignored.
func NewCredentialLibrary(storeId string, vaultPath string, opt ...Option) (*CredentialLibrary, error) {
const op = "vault.NewCredentialLibrary"
opts := getOpts(opt...)
l := &CredentialLibrary{
MappingOverride: opts.withMappingOverride,
CredentialLibrary: &store.CredentialLibrary{
StoreId: storeId,
Name: opts.withName,
@@ -39,12 +45,21 @@ func NewCredentialLibrary(storeId string, vaultPath string, opt ...Option) (*Cre
VaultPath: vaultPath,
HttpRequestBody: opts.withRequestBody,
HttpMethod: string(opts.withMethod),
CredentialType: string(opts.withCredentialType),
},
}
return l, nil
}
func (l *CredentialLibrary) validate(ctx context.Context, caller errors.Op) error {
switch {
case !validMappingOverride(l.MappingOverride, l.CredentialType()):
return errors.New(ctx, errors.VaultInvalidMappingOverride, caller, "invalid credential type for mapping override")
}
return nil
}
func allocCredentialLibrary() *CredentialLibrary {
return &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{},
@@ -52,12 +67,25 @@ func allocCredentialLibrary() *CredentialLibrary {
}
func (l *CredentialLibrary) clone() *CredentialLibrary {
var m MappingOverride
if l.MappingOverride != nil {
m = l.MappingOverride.clone()
}
cp := proto.Clone(l.CredentialLibrary)
return &CredentialLibrary{
MappingOverride: m,
CredentialLibrary: cp.(*store.CredentialLibrary),
}
}
func (l *CredentialLibrary) setId(i string) {
l.PublicId = i
if l.MappingOverride != nil {
l.MappingOverride.setLibraryId(i)
}
}
// TableName returns the table name.
func (l *CredentialLibrary) TableName() string {
if l.tableName != "" {
@@ -83,4 +111,14 @@ func (l *CredentialLibrary) oplog(op oplog.OpType) oplog.Metadata {
return metadata
}
// CredentialType returns the type of credential the library retrieves.
func (l *CredentialLibrary) CredentialType() credential.Type {
switch ct := l.GetCredentialType(); ct {
case "":
return credential.UnspecifiedType
default:
return credential.Type(ct)
}
}
var _ credential.Library = (*CredentialLibrary)(nil)
@@ -4,6 +4,7 @@ import (
"context"
"testing"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/iam"
@@ -37,7 +38,6 @@ func TestCredentialLibrary_New(t *testing.T) {
name string
args args
want *CredentialLibrary
wantErr bool
wantCreateErr bool
}{
{
@@ -190,6 +190,63 @@ func TestCredentialLibrary_New(t *testing.T) {
},
wantCreateErr: true,
},
{
name: "minimum-actually-valid",
args: args{
storeId: cs.PublicId,
vaultPath: "vault/path",
opts: []Option{
WithMethod(MethodGet),
},
},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.PublicId,
VaultPath: "vault/path",
HttpMethod: string(MethodGet),
},
},
},
{
name: "credential-type",
args: args{
storeId: cs.PublicId,
vaultPath: "vault/path",
opts: []Option{
WithMethod(MethodGet),
WithCredentialType(credential.UserPasswordType),
},
},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.PublicId,
VaultPath: "vault/path",
HttpMethod: string(MethodGet),
CredentialType: string(credential.UserPasswordType),
},
},
},
{
name: "credential-type-with-userpass-mapping",
args: args{
storeId: cs.PublicId,
vaultPath: "vault/path",
opts: []Option{
WithMethod(MethodGet),
WithCredentialType(credential.UserPasswordType),
WithMappingOverride(NewUserPasswordOverride(WithOverrideUsernameAttribute("test"))),
},
},
want: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(WithOverrideUsernameAttribute("test")),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.PublicId,
VaultPath: "vault/path",
HttpMethod: string(MethodGet),
CredentialType: string(credential.UserPasswordType),
},
},
},
}
for _, tt := range tests {
@@ -198,17 +255,21 @@ func TestCredentialLibrary_New(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
got, err := NewCredentialLibrary(tt.args.storeId, tt.args.vaultPath, tt.args.opts...)
if tt.wantErr {
assert.Error(err)
require.Nil(got)
return
}
require.NoError(err)
require.NotNil(got)
assert.Emptyf(got.PublicId, "PublicId set")
assert.Equal(tt.want, got)
switch ct := tt.want.GetCredentialType(); ct {
case string(credential.UserPasswordType):
assert.Equal(credential.UserPasswordType, got.CredentialType())
case string(credential.UnspecifiedType), "":
assert.Equal(credential.UnspecifiedType, got.CredentialType())
default:
assert.Failf("Unknown credential type", "%s", ct)
}
id, err := newCredentialLibraryId()
assert.NoError(err)
+4
View File
@@ -17,4 +17,8 @@ const (
tlsServerNameField = "TlsServerName"
tlsSkipVerifyField = "TlsSkipVerify"
tokenField = "Token"
// MappingOverrideField represents the field mask indicating a mapping override
// update has been requested.
MappingOverrideField = "MappingOverride"
)
@@ -0,0 +1,3 @@
// Package userpassword provides access to the username and password
// stored in a Vault secret.
package userpassword
@@ -0,0 +1,96 @@
package userpassword
type (
data map[string]interface{}
// extractFunc attempts to extract the username and password
// from sd using the provided attribute names, using a known
// Vault data response format.
extractFunc func(sd data, usernameAttr, passwordAttr string) (string, string)
)
// Extract attempts to extract the values of the username and 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, usernameAttr, passwordAttr string) (string, string) {
for _, f := range []extractFunc{
defaultExtract,
kv2Extract,
} {
username, password := f(d, usernameAttr, passwordAttr)
if username != "" && password != "" {
// got valid username and password from secret
return username, password
}
}
return "", ""
}
// defaultExtract looks for the usernameAttr and passwordAttr in the data map
func defaultExtract(sd data, usernameAttr, passwordAttr string) (username string, password string) {
if u, ok := sd[usernameAttr]; ok {
if u, ok := u.(string); ok {
username = u
}
}
if p, ok := sd[passwordAttr]; ok {
if p, ok := p.(string); ok {
password = p
}
}
return username, password
}
// kv2Extract looks for the the usernameAttr and passwordAttr in the embedded
// 'data' field within the data map.
//
// Additionaly 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(d data, usernameAttr, passwordAttr string) (username string, password string) {
var data, metadata map[string]interface{}
for k, v := range d {
switch k {
case "data":
var ok bool
if data, ok = v.(map[string]interface{}); !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]interface{}); !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 u, ok := data[usernameAttr]; ok {
if u, ok := u.(string); ok {
username = u
}
}
if p, ok := data[passwordAttr]; ok {
if p, ok := p.(string); ok {
password = p
}
}
return username, password
}
@@ -0,0 +1,196 @@
package userpassword
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestBaseToUsrPass(t *testing.T) {
t.Parallel()
type args struct {
s data
uAttr string
pAttr string
}
type usrPass struct {
user string
pass string
}
tests := []struct {
name string
given args
want usrPass
}{
{
name: "nil-input",
want: usrPass{user: "", pass: ""},
},
{
name: "no-input",
given: args{},
want: usrPass{user: "", pass: ""},
},
{
name: "no-secret",
given: args{
uAttr: "username",
pAttr: "password",
},
want: usrPass{user: "", pass: ""},
},
{
name: "no-match-username-secret",
given: args{
s: data{
"username-wrong": "user",
"password": "pass",
},
uAttr: "username",
pAttr: "password",
},
want: usrPass{user: "", pass: ""},
},
{
name: "no-match-username-secret",
given: args{
s: data{
"username": "user",
"password-wrong": "pass",
},
uAttr: "username",
pAttr: "password",
},
want: usrPass{user: "", pass: ""},
},
{
name: "valid-default",
given: args{
s: data{
"username": "user",
"password": "pass",
},
uAttr: "username",
pAttr: "password",
},
want: usrPass{user: "user", pass: "pass"},
},
{
name: "no-match-username-secret-kv2",
given: args{
s: data{
"metadata": map[string]interface{}{},
"data": map[string]interface{}{
"username-wrong": "user",
"password": "pass",
},
},
uAttr: "username",
pAttr: "password",
},
want: usrPass{user: "", pass: ""},
},
{
name: "no-match-password-secret-kv2",
given: args{
s: data{
"metadata": map[string]interface{}{},
"data": map[string]interface{}{
"username": "user",
"password-wrong": "pass",
},
},
uAttr: "username",
pAttr: "password",
},
want: usrPass{user: "", pass: ""},
},
{
name: "valid-kv2",
given: args{
s: data{
"metadata": map[string]interface{}{},
"data": map[string]interface{}{
"username": "user",
"password": "pass",
},
},
uAttr: "username",
pAttr: "password",
},
want: usrPass{user: "user", pass: "pass"},
},
{
name: "no-metadata-kv2",
given: args{
s: data{
"data": map[string]interface{}{
"username": "user",
"password": "pass",
},
},
uAttr: "username",
pAttr: "password",
},
want: usrPass{user: "", pass: ""},
},
{
name: "invalid-metadata-kv2",
given: args{
s: data{
"metadata": "string",
"data": map[string]interface{}{
"username": "user",
"password": "pass",
},
},
uAttr: "username",
pAttr: "password",
},
want: usrPass{user: "", pass: ""},
},
{
name: "invalid-field-kv2",
given: args{
s: data{
"invalid": map[string]interface{}{},
"metadata": map[string]interface{}{},
"data": map[string]interface{}{
"username": "user",
"password": "pass",
},
},
uAttr: "username",
pAttr: "password",
},
want: usrPass{user: "", pass: ""},
},
{
name: "valid-order-default-first",
given: args{
s: data{
"username": "default-user",
"password": "default-pass",
"metadata": map[string]interface{}{},
"data": map[string]interface{}{
"username": "kv2-user",
"password": "kv2-pass",
},
},
uAttr: "username",
pAttr: "password",
},
want: usrPass{user: "default-user", pass: "default-pass"},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert := assert.New(t)
user, pass := Extract(tt.given.s, tt.given.uAttr, tt.given.pAttr)
assert.Equal(tt.want.user, user)
assert.Equal(tt.want.pass, pass)
})
}
}
@@ -0,0 +1,98 @@
package vault
import (
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/db/sanitize"
"github.com/hashicorp/boundary/internal/db/sentinel"
"google.golang.org/protobuf/proto"
)
// validMappingOverride reports whether the given mapping override is valid
// for the given credential type.
func validMappingOverride(m MappingOverride, ct credential.Type) bool {
switch m.(type) {
case nil:
return true // it is always valid to not specify a mapping override
case *UserPasswordOverride:
return ct == credential.UserPasswordType
default:
return false // an unknown mapping override type is never valid
}
}
// A MappingOverride is an interface holding one of the mapping override
// types: UserPasswordOverride.
type MappingOverride interface {
clone() MappingOverride
setLibraryId(i string)
// sanitize replaces all sentinel values in the MappingOverride with
// zero values. It is called before a MappingOverride is returned from
// the domain layer when the MappingOverride has been loaded from the
// database.
sanitize()
}
// A UserPasswordOverride contains optional values for overriding the
// default mappings used to map a Vault secret to a UserPassword credential
// type for the credential library that owns it.
type UserPasswordOverride struct {
*store.UserPasswordOverride
tableName string `gorm:"-"`
}
var _ MappingOverride = (*UserPasswordOverride)(nil)
// NewUserPasswordOverride creates a new in memory UserPasswordOverride.
// WithOverrideUsernameAttribute and WithOverridePasswordAttribute are the
// only valid options. All other options are ignored.
func NewUserPasswordOverride(opt ...Option) *UserPasswordOverride {
opts := getOpts(opt...)
o := &UserPasswordOverride{
UserPasswordOverride: &store.UserPasswordOverride{
UsernameAttribute: sanitize.String(opts.withOverrideUsernameAttribute),
PasswordAttribute: sanitize.String(opts.withOverridePasswordAttribute),
},
}
return o
}
func allocUserPasswordOverride() *UserPasswordOverride {
return &UserPasswordOverride{
UserPasswordOverride: &store.UserPasswordOverride{},
}
}
func (o *UserPasswordOverride) clone() MappingOverride {
cp := proto.Clone(o.UserPasswordOverride)
return &UserPasswordOverride{
UserPasswordOverride: cp.(*store.UserPasswordOverride),
}
}
func (o *UserPasswordOverride) setLibraryId(i string) {
o.LibraryId = i
}
func (o *UserPasswordOverride) sanitize() {
if sentinel.Is(o.UsernameAttribute) {
o.UsernameAttribute = ""
}
if sentinel.Is(o.PasswordAttribute) {
o.PasswordAttribute = ""
}
}
// TableName returns the table name.
func (o *UserPasswordOverride) TableName() string {
if o.tableName != "" {
return o.tableName
}
return "credential_vault_library_user_password_mapping_override"
}
// SetTableName sets the table name.
func (o *UserPasswordOverride) SetTableName(n string) {
o.tableName = n
}
@@ -0,0 +1,68 @@
package vault
import (
"fmt"
"testing"
"github.com/hashicorp/boundary/internal/credential"
"github.com/stretchr/testify/assert"
)
type unknownMapper int
func (u unknownMapper) clone() MappingOverride { return u }
func (u unknownMapper) setLibraryId(_ string) {}
func (u unknownMapper) sanitize() {}
var _ MappingOverride = unknownMapper(0)
func TestValidMappingOverrides(t *testing.T) {
tests := []struct {
m MappingOverride
ct credential.Type
want bool
}{
{
m: nil,
ct: "invalid",
want: true,
},
{
m: nil,
ct: credential.UnspecifiedType,
want: true,
},
{
m: nil,
ct: credential.UserPasswordType,
want: true,
},
{
m: unknownMapper(1),
ct: credential.UnspecifiedType,
want: false,
},
{
m: unknownMapper(1),
ct: credential.UserPasswordType,
want: false,
},
{
m: allocUserPasswordOverride(),
ct: credential.UnspecifiedType,
want: false,
},
{
m: allocUserPasswordOverride(),
ct: credential.UserPasswordType,
want: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(fmt.Sprintf("%T-%s", tt.m, tt.ct), func(t *testing.T) {
got := validMappingOverride(tt.m, tt.ct)
assert.Equal(t, tt.want, got)
})
}
}
+49 -10
View File
@@ -1,5 +1,7 @@
package vault
import "github.com/hashicorp/boundary/internal/credential"
// getOpts - iterate the inbound Options and return a struct
func getOpts(opt ...Option) options {
opts := getDefaultOptions()
@@ -14,16 +16,21 @@ type Option func(*options)
// options = how options are represented
type options struct {
withName string
withDescription string
withLimit int
withCACert []byte
withNamespace string
withTlsServerName string
withTlsSkipVerify bool
withClientCert *ClientCertificate
withMethod Method
withRequestBody []byte
withName string
withDescription string
withLimit int
withCACert []byte
withNamespace string
withTlsServerName string
withTlsSkipVerify bool
withClientCert *ClientCertificate
withMethod Method
withRequestBody []byte
withCredentialType credential.Type
withOverrideUsernameAttribute string
withOverridePasswordAttribute string
withMappingOverride MappingOverride
}
func getDefaultOptions() options {
@@ -109,3 +116,35 @@ func WithRequestBody(b []byte) Option {
o.withRequestBody = b
}
}
// WithCredentialType provides an optional credential type to associate
// with a credential library.
func WithCredentialType(t credential.Type) Option {
return func(o *options) {
o.withCredentialType = t
}
}
// WithOverrideUsernameAttribute provides the name of an attribute in the
// Data field of a Vault api.Secret that maps to a username value.
func WithOverrideUsernameAttribute(s string) Option {
return func(o *options) {
o.withOverrideUsernameAttribute = s
}
}
// WithOverridePasswordAttribute provides the name of an attribute in the
// Data field of a Vault api.Secret that maps to a password value.
func WithOverridePasswordAttribute(s string) Option {
return func(o *options) {
o.withOverridePasswordAttribute = s
}
}
// WithMappingOverride provides an optional mapping override to use for
// mapping the Data fields of a Vault api.Secret to a credential.
func WithMappingOverride(m MappingOverride) Option {
return func(o *options) {
o.withMappingOverride = m
}
}
+25
View File
@@ -3,6 +3,7 @@ package vault
import (
"testing"
"github.com/hashicorp/boundary/internal/credential"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -83,4 +84,28 @@ func Test_GetOpts(t *testing.T) {
testOpts.withRequestBody = []byte("body")
assert.Equal(t, opts, testOpts)
})
t.Run("WithCredentialType", func(t *testing.T) {
opts := getOpts(WithCredentialType(credential.UserPasswordType))
testOpts := getDefaultOptions()
testOpts.withCredentialType = credential.UserPasswordType
assert.Equal(t, opts, testOpts)
})
t.Run("WithOverrideUsernameAttribute", func(t *testing.T) {
opts := getOpts(WithOverrideUsernameAttribute("test"))
testOpts := getDefaultOptions()
testOpts.withOverrideUsernameAttribute = "test"
assert.Equal(t, opts, testOpts)
})
t.Run("WithOverridePasswordAttribute", func(t *testing.T) {
opts := getOpts(WithOverridePasswordAttribute("test"))
testOpts := getDefaultOptions()
testOpts.withOverridePasswordAttribute = "test"
assert.Equal(t, opts, testOpts)
})
t.Run("WithMappingOverride", func(t *testing.T) {
opts := getOpts(WithMappingOverride(unknownMapper(1)))
testOpts := getDefaultOptions()
testOpts.withMappingOverride = unknownMapper(1)
assert.Equal(t, opts, testOpts)
})
}
+187 -62
View File
@@ -5,91 +5,150 @@ import (
"database/sql"
"fmt"
"strings"
"time"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault/internal/userpassword"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/go-kms-wrapping/structwrapping"
vault "github.com/hashicorp/vault/api"
"google.golang.org/protobuf/proto"
)
var _ credential.Dynamic = (*actualCredential)(nil)
var _ credential.UserPassword = (*usrPassCred)(nil)
type actualCredential struct {
id string
sessionId string
lib *privateLibrary
secretData map[string]interface{}
purpose credential.Purpose
type usrPassCred struct {
*baseCred
username string
password credential.Password
}
func (ac *actualCredential) GetPublicId() string { return ac.id }
func (ac *actualCredential) GetSessionId() string { return ac.sessionId }
func (ac *actualCredential) Secret() credential.SecretData { return ac.secretData }
func (ac *actualCredential) Library() credential.Library { return ac.lib }
func (ac *actualCredential) Purpose() credential.Purpose { return ac.purpose }
func (c *usrPassCred) Username() string { return c.username }
func (c *usrPassCred) Password() credential.Password { return c.password }
var _ credential.Dynamic = (*baseCred)(nil)
type baseCred struct {
*Credential
lib *privateLibrary
secretData map[string]interface{}
}
func (bc *baseCred) Secret() credential.SecretData { return bc.secretData }
func (bc *baseCred) Library() credential.Library { return bc.lib }
func (bc *baseCred) Purpose() credential.Purpose { return bc.lib.Purpose }
func (bc *baseCred) getExpiration() time.Duration { return bc.expiration }
// convert converts bc to a specific credential type if bc is not
// UnspecifiedType.
func convert(ctx context.Context, bc *baseCred) (dynamicCred, error) {
switch bc.Library().CredentialType() {
case credential.UserPasswordType:
return baseToUsrPass(ctx, bc)
}
return bc, nil
}
func baseToUsrPass(ctx context.Context, bc *baseCred) (*usrPassCred, 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() != credential.UserPasswordType:
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("invalid credential type"))
}
uAttr, pAttr := bc.lib.UsernameAttribute, bc.lib.PasswordAttribute
if uAttr == "" {
uAttr = "username"
}
if pAttr == "" {
pAttr = "password"
}
username, password := userpassword.Extract(bc.secretData, uAttr, pAttr)
if username == "" || password == "" {
return nil, errors.E(ctx, errors.WithCode(errors.VaultInvalidCredentialMapping))
}
return &usrPassCred{
baseCred: bc,
username: username,
password: credential.Password(password),
}, nil
}
var _ credential.Library = (*privateLibrary)(nil)
// A privateLibrary contains all the values needed to connect to Vault and
// retrieve credentials.
type privateLibrary struct {
PublicId string `gorm:"primary_key"`
StoreId string
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
Version uint32
ScopeId string
VaultPath string
HttpMethod string
HttpRequestBody []byte
VaultAddress string
Namespace string
CaCert []byte
TlsServerName string
TlsSkipVerify bool
TokenHmac []byte
Token TokenSecret
CtToken []byte
TokenKeyId string
ClientCert []byte
ClientKey KeySecret
CtClientKey []byte
ClientKeyId string
Purpose credential.Purpose `gorm:"-"`
PublicId string `gorm:"primary_key"`
StoreId string
CredType string `gorm:"column:credential_type"`
UsernameAttribute string
PasswordAttribute string
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
Version uint32
ScopeId string
VaultPath string
HttpMethod string
HttpRequestBody []byte
VaultAddress string
Namespace string
CaCert []byte
TlsServerName string
TlsSkipVerify bool
TokenHmac []byte
Token TokenSecret
CtToken []byte
TokenKeyId string
ClientCert []byte
ClientKey KeySecret
CtClientKey []byte
ClientKeyId string
Purpose credential.Purpose `gorm:"-"`
}
func (pl *privateLibrary) clone() *privateLibrary {
// The 'append(a[:0:0], a...)' comes from
// https://github.com/go101/go101/wiki/How-to-perfectly-clone-a-slice%3F
return &privateLibrary{
PublicId: pl.PublicId,
StoreId: pl.StoreId,
Name: pl.Name,
Description: pl.Description,
CreateTime: proto.Clone(pl.CreateTime).(*timestamp.Timestamp),
UpdateTime: proto.Clone(pl.UpdateTime).(*timestamp.Timestamp),
Version: pl.Version,
ScopeId: pl.ScopeId,
VaultPath: pl.VaultPath,
HttpMethod: pl.HttpMethod,
HttpRequestBody: append(pl.HttpRequestBody[:0:0], pl.HttpRequestBody...),
VaultAddress: pl.VaultAddress,
Namespace: pl.Namespace,
CaCert: append(pl.CaCert[:0:0], pl.CaCert...),
TlsServerName: pl.TlsServerName,
TlsSkipVerify: pl.TlsSkipVerify,
TokenHmac: append(pl.TokenHmac[:0:0], pl.TokenHmac...),
Token: append(pl.Token[:0:0], pl.Token...),
CtToken: append(pl.CtToken[:0:0], pl.CtToken...),
TokenKeyId: pl.TokenKeyId,
ClientCert: append(pl.ClientCert[:0:0], pl.ClientCert...),
ClientKey: append(pl.ClientKey[:0:0], pl.ClientKey...),
CtClientKey: append(pl.CtClientKey[:0:0], pl.CtClientKey...),
ClientKeyId: pl.ClientKeyId,
Purpose: pl.Purpose,
PublicId: pl.PublicId,
StoreId: pl.StoreId,
CredType: pl.CredType,
UsernameAttribute: pl.UsernameAttribute,
PasswordAttribute: pl.PasswordAttribute,
Name: pl.Name,
Description: pl.Description,
CreateTime: proto.Clone(pl.CreateTime).(*timestamp.Timestamp),
UpdateTime: proto.Clone(pl.UpdateTime).(*timestamp.Timestamp),
Version: pl.Version,
ScopeId: pl.ScopeId,
VaultPath: pl.VaultPath,
HttpMethod: pl.HttpMethod,
HttpRequestBody: append(pl.HttpRequestBody[:0:0], pl.HttpRequestBody...),
VaultAddress: pl.VaultAddress,
Namespace: pl.Namespace,
CaCert: append(pl.CaCert[:0:0], pl.CaCert...),
TlsServerName: pl.TlsServerName,
TlsSkipVerify: pl.TlsSkipVerify,
TokenHmac: append(pl.TokenHmac[:0:0], pl.TokenHmac...),
Token: append(pl.Token[:0:0], pl.Token...),
CtToken: append(pl.CtToken[:0:0], pl.CtToken...),
TokenKeyId: pl.TokenKeyId,
ClientCert: append(pl.ClientCert[:0:0], pl.ClientCert...),
ClientKey: append(pl.ClientKey[:0:0], pl.ClientKey...),
CtClientKey: append(pl.CtClientKey[:0:0], pl.CtClientKey...),
ClientKeyId: pl.ClientKeyId,
Purpose: pl.Purpose,
}
}
@@ -101,6 +160,15 @@ func (pl *privateLibrary) GetVersion() uint32 { return pl.Versi
func (pl *privateLibrary) GetCreateTime() *timestamp.Timestamp { return pl.CreateTime }
func (pl *privateLibrary) GetUpdateTime() *timestamp.Timestamp { return pl.UpdateTime }
func (pl *privateLibrary) CredentialType() credential.Type {
switch ct := pl.CredType; ct {
case "":
return credential.UnspecifiedType
default:
return credential.Type(ct)
}
}
func (pl *privateLibrary) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "vault.(privateLibrary).decrypt"
@@ -157,6 +225,63 @@ func (pl *privateLibrary) client() (*client, error) {
return client, nil
}
type dynamicCred interface {
credential.Dynamic
getExpiration() time.Duration
insertQuery() (query string, queryValues []interface{})
updateSessionQuery(purpose credential.Purpose) (query string, queryValues []interface{})
}
// retrieveCredential retrieves a dynamic credential from Vault for the
// given sessionId.
func (pl *privateLibrary) retrieveCredential(ctx context.Context, op errors.Op, sessionId string) (dynamicCred, error) {
// Get the credential ID early. No need to get a secret from Vault
// if there is no way to save it in the database.
credId, err := newCredentialId()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
client, err := pl.client()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var secret *vault.Secret
switch Method(pl.HttpMethod) {
case MethodGet:
secret, err = client.get(pl.VaultPath)
case MethodPost:
secret, err = client.post(pl.VaultPath, pl.HttpRequestBody)
default:
return nil, errors.New(ctx, errors.Internal, op, fmt.Sprintf("unknown http method: library: %s", pl.PublicId))
}
if err != nil {
// TODO(mgaffney) 05/2021: detect if the error is because of an
// expired or invalid token
return nil, errors.Wrap(ctx, err, op)
}
if secret == nil {
return nil, errors.E(ctx, errors.WithCode(errors.VaultEmptySecret), errors.WithOp(op))
}
leaseDuration := time.Duration(secret.LeaseDuration) * time.Second
cred, err := newCredential(pl.GetPublicId(), sessionId, secret.LeaseID, pl.TokenHmac, leaseDuration)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cred.PublicId = credId
cred.IsRenewable = secret.Renewable
dCred := &baseCred{
Credential: cred,
lib: pl,
secretData: secret.Data,
}
return convert(ctx, dCred)
}
// TableName returns the table name for gorm.
func (pl *privateLibrary) TableName() string {
return "credential_vault_library_private"
@@ -6,6 +6,7 @@ import (
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/scheduler"
@@ -77,7 +78,7 @@ func TestRepository_getPrivateLibraries(t *testing.T) {
assert.NotNil(origLookup.Token())
assert.Equal(origStore.GetPublicId(), origLookup.GetPublicId())
libs := make(map[string]*CredentialLibrary, 3)
libs := make(map[string]*CredentialLibrary)
var requests []credential.Request
{
libIn, err := NewCredentialLibrary(origStore.GetPublicId(), "/vault/path")
@@ -112,6 +113,72 @@ func TestRepository_getPrivateLibraries(t *testing.T) {
req := credential.Request{SourceId: lib.GetPublicId(), Purpose: credential.ApplicationPurpose}
requests = append(requests, req)
}
{
opts := []Option{
WithCredentialType(credential.UserPasswordType),
}
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.ApplicationPurpose}
requests = append(requests, req)
}
{
opts := []Option{
WithCredentialType(credential.UserPasswordType),
WithMappingOverride(NewUserPasswordOverride(
WithOverrideUsernameAttribute("test-username"),
)),
}
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.ApplicationPurpose}
requests = append(requests, req)
}
{
opts := []Option{
WithCredentialType(credential.UserPasswordType),
WithMappingOverride(NewUserPasswordOverride(
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.ApplicationPurpose}
requests = append(requests, req)
}
{
opts := []Option{
WithCredentialType(credential.UserPasswordType),
WithMappingOverride(NewUserPasswordOverride(
WithOverrideUsernameAttribute("test-username"),
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.ApplicationPurpose}
requests = append(requests, req)
}
gotLibs, err := repo.getPrivateLibraries(ctx, requests)
assert.NoError(err)
@@ -131,6 +198,16 @@ func TestRepository_getPrivateLibraries(t *testing.T) {
assert.Equal(want.VaultPath, got.VaultPath)
assert.Equal(want.HttpMethod, got.HttpMethod)
assert.Equal(want.HttpRequestBody, got.HttpRequestBody)
assert.Equal(want.CredentialType(), got.CredentialType())
if mo := want.MappingOverride; mo != nil {
switch w := mo.(type) {
case *UserPasswordOverride:
assert.Equal(w.UsernameAttribute, got.UsernameAttribute)
assert.Equal(w.PasswordAttribute, got.PasswordAttribute)
default:
assert.Fail("unknown mapping override type")
}
}
}
})
}
@@ -227,3 +304,368 @@ func TestRequestMap(t *testing.T) {
})
}
}
func TestBaseToUsrPass(t *testing.T) {
t.Parallel()
tests := []struct {
name string
given *baseCred
want *usrPassCred
wantErr errors.Code
}{
{
name: "nil-input",
wantErr: errors.InvalidParameter,
},
{
name: "nil-library",
given: &baseCred{},
wantErr: errors.InvalidParameter,
},
{
name: "library-not-username-password-type",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UnspecifiedType),
},
},
wantErr: errors.InvalidParameter,
},
{
name: "invalid-no-username-default-password-attribute",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
},
secretData: map[string]interface{}{
"password": "my-password",
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-no-password-default-username-attribute",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
},
secretData: map[string]interface{}{
"username": "my-username",
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "valid-default-attributes",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
},
secretData: map[string]interface{}{
"username": "my-username",
"password": "my-password",
},
},
want: &usrPassCred{
username: "my-username",
password: credential.Password("my-password"),
},
},
{
name: "valid-override-attributes",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
UsernameAttribute: "test-username",
PasswordAttribute: "test-password",
},
secretData: map[string]interface{}{
"username": "default-username",
"password": "default-password",
"test-username": "override-username",
"test-password": "override-password",
},
},
want: &usrPassCred{
username: "override-username",
password: credential.Password("override-password"),
},
},
{
name: "valid-default-username-override-password",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
PasswordAttribute: "test-password",
},
secretData: map[string]interface{}{
"username": "default-username",
"password": "default-password",
"test-username": "override-username",
"test-password": "override-password",
},
},
want: &usrPassCred{
username: "default-username",
password: credential.Password("override-password"),
},
},
{
name: "valid-override-username-default-password",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
UsernameAttribute: "test-username",
},
secretData: map[string]interface{}{
"username": "default-username",
"password": "default-password",
"test-username": "override-username",
"test-password": "override-password",
},
},
want: &usrPassCred{
username: "override-username",
password: credential.Password("default-password"),
},
},
{
name: "invalid-username-override",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
UsernameAttribute: "missing-username",
},
secretData: map[string]interface{}{
"username": "default-username",
"password": "default-password",
"test-username": "override-username",
"test-password": "override-password",
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-password-override",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
UsernameAttribute: "missing-password",
},
secretData: map[string]interface{}{
"username": "default-username",
"password": "default-password",
"test-username": "override-username",
"test-password": "override-password",
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-no-metadata-field",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
},
secretData: map[string]interface{}{
"data": map[string]interface{}{
"username": "my-username",
"password": "my-password",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-no-data-field",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
},
secretData: map[string]interface{}{
"metadata": map[string]interface{}{},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-no-username-default-password-attribute",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
},
secretData: map[string]interface{}{
"metadata": map[string]interface{}{},
"data": map[string]interface{}{
"password": "my-password",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-no-passsword-default-username-attribute",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
},
secretData: map[string]interface{}{
"metadata": map[string]interface{}{},
"data": map[string]interface{}{
"username": "my-username",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
},
secretData: map[string]interface{}{
"metadata": "hello",
"data": map[string]interface{}{
"username": "my-username",
"password": "my-password",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
},
secretData: map[string]interface{}{
"metadata": map[string]interface{}{},
"data": "hello",
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-additional-field",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
},
secretData: map[string]interface{}{
"bad-field": "hello",
"metadata": map[string]interface{}{},
"data": map[string]interface{}{
"username": "my-username",
"password": "my-password",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "valid-kv2-default-attributes",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
},
secretData: map[string]interface{}{
"metadata": map[string]interface{}{},
"data": map[string]interface{}{
"username": "my-username",
"password": "my-password",
},
},
},
want: &usrPassCred{
username: "my-username",
password: credential.Password("my-password"),
},
},
{
name: "valid-kv2-override-attributes",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
UsernameAttribute: "test-username",
PasswordAttribute: "test-password",
},
secretData: map[string]interface{}{
"metadata": map[string]interface{}{},
"data": map[string]interface{}{
"username": "default-username",
"password": "default-password",
"test-username": "override-username",
"test-password": "override-password",
},
},
},
want: &usrPassCred{
username: "override-username",
password: credential.Password("override-password"),
},
},
{
name: "valid-kv2-default-username-override-password",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
PasswordAttribute: "test-password",
},
secretData: map[string]interface{}{
"metadata": map[string]interface{}{},
"data": map[string]interface{}{
"username": "default-username",
"password": "default-password",
"test-username": "override-username",
"test-password": "override-password",
},
},
},
want: &usrPassCred{
username: "default-username",
password: credential.Password("override-password"),
},
},
{
name: "valid-kv2-override-username-default-password",
given: &baseCred{
lib: &privateLibrary{
CredType: string(credential.UserPasswordType),
UsernameAttribute: "test-username",
},
secretData: map[string]interface{}{
"metadata": map[string]interface{}{},
"data": map[string]interface{}{
"username": "default-username",
"password": "default-password",
"test-username": "override-username",
"test-password": "override-password",
},
},
},
want: &usrPassCred{
username: "override-username",
password: credential.Password("default-password"),
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := baseToUsrPass(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)
})
}
}
@@ -7,6 +7,7 @@ import (
"github.com/hashicorp/boundary/internal/db"
dbcommon "github.com/hashicorp/boundary/internal/db/common"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
@@ -48,11 +49,15 @@ func (r *Repository) CreateCredentialLibrary(ctx context.Context, scopeId string
l.HttpMethod = string(MethodGet)
}
if err := l.validate(ctx, op); err != nil {
return nil, err // intentionally not wrapped.
}
id, err := newCredentialLibraryId()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
l.PublicId = id
l.setId(id)
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
if err != nil {
@@ -62,11 +67,34 @@ func (r *Repository) CreateCredentialLibrary(ctx context.Context, scopeId string
var newCredentialLibrary *CredentialLibrary
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
newCredentialLibrary = l.clone()
err := w.Create(ctx, newCredentialLibrary, db.WithOplog(oplogWrapper, l.oplog(oplog.OpType_OP_TYPE_CREATE)))
var msgs []*oplog.Message
ticket, err := w.GetTicket(l)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
}
// insert credential library
newCredentialLibrary = l.clone()
var lOplogMsg oplog.Message
if err := w.Create(ctx, newCredentialLibrary, db.NewOplogMsg(&lOplogMsg)); err != nil {
return errors.Wrap(ctx, err, op)
}
msgs = append(msgs, &lOplogMsg)
// insert mapper override (if exists)
if l.MappingOverride != nil {
var msg oplog.Message
if err := w.Create(ctx, newCredentialLibrary.MappingOverride, db.NewOplogMsg(&msg)); err != nil {
return errors.Wrap(ctx, err, op)
}
newCredentialLibrary.MappingOverride.sanitize()
msgs = append(msgs, &msg)
}
metadata := l.oplog(oplog.OpType_OP_TYPE_CREATE)
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
}
return nil
},
)
@@ -86,8 +114,8 @@ func (r *Repository) CreateCredentialLibrary(ctx context.Context, scopeId string
// number of records updated. l is not changed.
//
// l must contain a valid PublicId. Only Name, Description, VaultPath,
// HttpMethod, and HttpRequestBody can be updated. If l.Name is set to a
// non-empty string, it must be unique within l.StoreId.
// HttpMethod, HttpRequestBody, and MappingOverride can be updated. If
// l.Name is set to a non-empty string, it must be unique within l.StoreId.
//
// An attribute of l will be set to NULL in the database if the attribute
// in l is the zero value and it is included in fieldMaskPaths except for
@@ -113,6 +141,7 @@ func (r *Repository) UpdateCredentialLibrary(ctx context.Context, scopeId string
}
l = l.clone()
var updateMappingOverride bool
for _, f := range fieldMaskPaths {
switch {
case strings.EqualFold(nameField, f):
@@ -120,6 +149,8 @@ func (r *Repository) UpdateCredentialLibrary(ctx context.Context, scopeId string
case strings.EqualFold(vaultPathField, f):
case strings.EqualFold(httpMethodField, f):
case strings.EqualFold(httpRequestBodyField, f):
case strings.EqualFold(MappingOverrideField, f):
updateMappingOverride = true
default:
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, f)
}
@@ -132,12 +163,19 @@ func (r *Repository) UpdateCredentialLibrary(ctx context.Context, scopeId string
vaultPathField: l.VaultPath,
httpMethodField: l.HttpMethod,
httpRequestBodyField: l.HttpRequestBody,
MappingOverrideField: l.MappingOverride,
},
fieldMaskPaths,
nil,
)
if strutil.StrListContains(nullFields, httpMethodField) {
// GET is the default value for the HttpMethod field in
// CredentialLibrary. The http_method column in the database does
// not allow NULL values but it also does not define a default
// value. Therefore, if the httpMethodField is in nullFields:
// remove it from nullFields then add it to dbMask and set the
// value to GET.
dbMask = append(dbMask, httpMethodField)
nullFields = strutil.StrListDelete(nullFields, httpMethodField)
l.HttpMethod = string(MethodGet)
@@ -147,6 +185,32 @@ func (r *Repository) UpdateCredentialLibrary(ctx context.Context, scopeId string
return nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "missing field mask")
}
origLib, err := r.LookupCredentialLibrary(ctx, l.PublicId)
switch {
case err != nil:
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op)
case origLib == nil:
return nil, db.NoRowsAffected, errors.New(ctx, errors.RecordNotFound, op, fmt.Sprintf("credential library %s", l.PublicId))
case updateMappingOverride && !validMappingOverride(l.MappingOverride, origLib.CredentialType()):
return nil, db.NoRowsAffected, errors.New(ctx, errors.VaultInvalidMappingOverride, op, "invalid mapping override for credential type")
}
var filteredDbMask, filteredNullFields []string
for _, f := range dbMask {
switch {
case strings.EqualFold(MappingOverrideField, f):
default:
filteredDbMask = append(filteredDbMask, f)
}
}
for _, f := range nullFields {
switch {
case strings.EqualFold(MappingOverrideField, f):
default:
filteredNullFields = append(filteredNullFields, f)
}
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt),
@@ -156,16 +220,94 @@ func (r *Repository) UpdateCredentialLibrary(ctx context.Context, scopeId string
var rowsUpdated int
var returnedCredentialLibrary *CredentialLibrary
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
returnedCredentialLibrary = l.clone()
var err error
rowsUpdated, err = w.Update(ctx, returnedCredentialLibrary, dbMask, nullFields,
db.WithOplog(oplogWrapper, l.oplog(oplog.OpType_OP_TYPE_UPDATE)),
db.WithVersion(&version))
if err == nil && rowsUpdated > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been updated")
func(rr db.Reader, w db.Writer) error {
var msgs []*oplog.Message
ticket, err := w.GetTicket(l)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
}
return err
l := l.clone()
var lOplogMsg oplog.Message
// Update the credential library table
switch {
case len(filteredDbMask) == 0 && len(filteredNullFields) == 0:
// the credential library's fields are not being updated,
// just one of it's child objects, so we just need to
// update the library's version.
l.Version = version + 1
rowsUpdated, err = w.Update(ctx, l, []string{"Version"}, nil, db.NewOplogMsg(&lOplogMsg), db.WithVersion(&version))
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential library version"))
}
switch rowsUpdated {
case 1:
case 0:
return nil
default:
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated credential library version and %d rows updated", rowsUpdated))
}
default:
rowsUpdated, err = w.Update(ctx, l, filteredDbMask, filteredNullFields, db.NewOplogMsg(&lOplogMsg), db.WithVersion(&version))
if err != nil {
if errors.IsUniqueError(err) {
return errors.New(ctx, errors.NotUnique, op,
fmt.Sprintf("name %s already exists: %s", l.Name, l.PublicId))
}
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential library"))
}
switch rowsUpdated {
case 1:
case 0:
return nil
default:
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated credential library and %d rows updated", rowsUpdated))
}
}
msgs = append(msgs, &lOplogMsg)
// Update a credential mapping override table if applicable
if updateMappingOverride {
// delete the current mapping override if it exists
if origLib.MappingOverride != nil {
var msg oplog.Message
rowsDeleted, err := w.Delete(ctx, origLib.MappingOverride, db.NewOplogMsg(&msg))
switch {
case err != nil:
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete mapping override"))
default:
switch rowsDeleted {
case 0, 1:
default:
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("delete mapping override and %d rows deleted", rowsDeleted))
}
}
msgs = append(msgs, &msg)
}
// insert a new mapping override if specified
if l.MappingOverride != nil {
var msg oplog.Message
l.MappingOverride.setLibraryId(l.PublicId)
if err := w.Create(ctx, l.MappingOverride, db.NewOplogMsg(&msg)); err != nil {
return errors.Wrap(ctx, err, op)
}
msgs = append(msgs, &msg)
}
}
metadata := l.oplog(oplog.OpType_OP_TYPE_UPDATE)
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
}
pl := allocPublicLibrary()
pl.PublicId = l.PublicId
if err := rr.LookupByPublicId(ctx, pl); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to retrieve updated credential library"))
}
returnedCredentialLibrary = pl.toCredentialLibrary()
return nil
},
)
@@ -187,7 +329,7 @@ func (r *Repository) LookupCredentialLibrary(ctx context.Context, publicId strin
if publicId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id")
}
l := allocCredentialLibrary()
l := allocPublicLibrary()
l.PublicId = publicId
if err := r.reader.LookupByPublicId(ctx, l); err != nil {
if errors.IsNotFoundError(err) {
@@ -195,9 +337,63 @@ func (r *Repository) LookupCredentialLibrary(ctx context.Context, publicId strin
}
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", publicId)))
}
return l, nil
return l.toCredentialLibrary(), nil
}
// publicLibrary is a credential library and any of library's credential
// mapping overrides. It does not include encrypted data and is safe to
// return external to boundary.
type publicLibrary struct {
PublicId string `gorm:"primary_key"`
StoreId string
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
Version uint32
VaultPath string
HttpMethod string
HttpRequestBody []byte
CredentialType string
UsernameAttribute string
PasswordAttribute string
}
func allocPublicLibrary() *publicLibrary {
return &publicLibrary{}
}
func (pl *publicLibrary) toCredentialLibrary() *CredentialLibrary {
cl := allocCredentialLibrary()
cl.PublicId = pl.PublicId
cl.StoreId = pl.StoreId
cl.Name = pl.Name
cl.Description = pl.Description
cl.CreateTime = pl.CreateTime
cl.UpdateTime = pl.UpdateTime
cl.Version = pl.Version
cl.VaultPath = pl.VaultPath
cl.HttpMethod = pl.HttpMethod
cl.HttpRequestBody = pl.HttpRequestBody
cl.CredentialLibrary.CredentialType = pl.CredentialType
if pl.UsernameAttribute != "" || pl.PasswordAttribute != "" {
up := allocUserPasswordOverride()
up.LibraryId = pl.PublicId
up.UsernameAttribute = pl.UsernameAttribute
up.PasswordAttribute = pl.PasswordAttribute
up.sanitize()
cl.MappingOverride = up
}
return cl
}
// TableName returns the table name for gorm.
func (_ *publicLibrary) TableName() string { return "credential_vault_library_public" }
// GetPublicId returns the public id.
func (pl *publicLibrary) GetPublicId() string { return pl.PublicId }
// DeleteCredentialLibrary deletes publicId from the repository and returns
// the number of records deleted.
func (r *Repository) DeleteCredentialLibrary(ctx context.Context, scopeId string, publicId string, _ ...Option) (int, error) {
@@ -5,8 +5,7 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/db"
dbassert "github.com/hashicorp/boundary/internal/db/assert"
@@ -17,7 +16,6 @@ import (
"github.com/hashicorp/boundary/internal/scheduler"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/testing/protocmp"
)
func TestRepository_CreateCredentialLibrary(t *testing.T) {
@@ -166,6 +164,127 @@ func TestRepository_CreateCredentialLibrary(t *testing.T) {
},
},
},
{
name: "valid-user-password-credential-type",
in: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(credential.UserPasswordType),
},
},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(credential.UserPasswordType),
},
},
},
{
name: "unknown-mapping-override-type",
in: &CredentialLibrary{
MappingOverride: unknownMapper(1),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(credential.UserPasswordType),
},
},
wantErr: errors.VaultInvalidMappingOverride,
},
{
name: "invalid-mapping-override-type",
in: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(WithOverrideUsernameAttribute("test")),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
},
},
wantErr: errors.VaultInvalidMappingOverride,
},
{
name: "valid-user-password-credential-type-with-username-override",
in: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverrideUsernameAttribute("utest"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(credential.UserPasswordType),
},
},
want: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverrideUsernameAttribute("utest"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(credential.UserPasswordType),
},
},
},
{
name: "valid-user-password-credential-type-with-password-override",
in: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverridePasswordAttribute("ptest"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(credential.UserPasswordType),
},
},
want: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverridePasswordAttribute("ptest"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(credential.UserPasswordType),
},
},
},
{
name: "valid-user-password-credential-type-with-username-and-password-override",
in: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverrideUsernameAttribute("utest"),
WithOverridePasswordAttribute("ptest"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(credential.UserPasswordType),
},
},
want: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverrideUsernameAttribute("utest"),
WithOverridePasswordAttribute("ptest"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(credential.UserPasswordType),
},
},
},
}
for _, tt := range tests {
@@ -191,8 +310,28 @@ func TestRepository_CreateCredentialLibrary(t *testing.T) {
assert.NotSame(tt.in, got)
assert.Equal(tt.want.Name, got.Name)
assert.Equal(tt.want.Description, got.Description)
assert.Equal(tt.want.CredentialType(), got.CredentialType())
assert.Equal(got.CreateTime, got.UpdateTime)
assert.NoError(db.TestVerifyOplog(t, rw, got.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)))
if tt.in.MappingOverride != nil {
require.NotNil(got.MappingOverride)
assert.IsType(tt.want.MappingOverride, got.MappingOverride)
switch w := tt.want.MappingOverride.(type) {
case *UserPasswordOverride:
g, ok := got.MappingOverride.(*UserPasswordOverride)
require.True(ok)
assert.Equal(w.UsernameAttribute, g.UsernameAttribute)
assert.Equal(w.PasswordAttribute, g.PasswordAttribute)
default:
assert.Fail("Unknown mapping override")
}
// verify it was persisted in the database
override := allocUserPasswordOverride()
assert.NoError(rw.LookupWhere(ctx, &override, "library_id = ?", got.GetPublicId()))
}
})
}
@@ -322,6 +461,20 @@ func TestRepository_UpdateCredentialLibrary(t *testing.T) {
}
}
changeCredentialType := func(t credential.Type) func(*CredentialLibrary) *CredentialLibrary {
return func(l *CredentialLibrary) *CredentialLibrary {
l.CredentialLibrary.CredentialType = string(t)
return l
}
}
changeMappingOverride := func(m MappingOverride) func(*CredentialLibrary) *CredentialLibrary {
return func(l *CredentialLibrary) *CredentialLibrary {
l.MappingOverride = m
return l
}
}
makeNil := func() func(*CredentialLibrary) *CredentialLibrary {
return func(l *CredentialLibrary) *CredentialLibrary {
return nil
@@ -366,6 +519,7 @@ func TestRepository_UpdateCredentialLibrary(t *testing.T) {
wantCount int
wantErr errors.Code
}{
{
name: "nil-credential-library",
orig: &CredentialLibrary{
@@ -604,17 +758,21 @@ func TestRepository_UpdateCredentialLibrary(t *testing.T) {
{
name: "change-vault-path",
orig: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(WithOverrideUsernameAttribute("orig-username")),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/old/path",
HttpMethod: "GET",
VaultPath: "/old/path",
CredentialType: string(credential.UserPasswordType),
},
},
chgFn: changeVaultPath("/new/path"),
masks: []string{vaultPathField},
want: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(WithOverrideUsernameAttribute("orig-username")),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/new/path",
HttpMethod: "GET",
VaultPath: "/new/path",
CredentialType: string(credential.UserPasswordType),
},
},
wantCount: 1,
@@ -757,6 +915,196 @@ func TestRepository_UpdateCredentialLibrary(t *testing.T) {
},
wantCount: 1,
},
{
name: "read-only-credential-type-in-field-mask",
orig: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-name-repo",
CredentialType: string(credential.UserPasswordType),
},
},
chgFn: changeCredentialType(credential.UnspecifiedType),
masks: []string{"PublicId", "CreateTime", "UpdateTime", "StoreId", "CredentialType"},
wantErr: errors.InvalidFieldMask,
},
{
name: "user-password-attributes-change-username-attribute",
orig: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverrideUsernameAttribute("orig-username"),
WithOverridePasswordAttribute("orig-password"),
),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-name-repo",
CredentialType: string(credential.UserPasswordType),
},
},
chgFn: changeMappingOverride(
NewUserPasswordOverride(
WithOverrideUsernameAttribute("changed-username"),
),
),
masks: []string{"MappingOverride"},
want: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverrideUsernameAttribute("changed-username"),
),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-name-repo",
CredentialType: string(credential.UserPasswordType),
},
},
wantCount: 1,
},
{
name: "user-password-attributes-change-password-attribute",
orig: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverrideUsernameAttribute("orig-username"),
WithOverridePasswordAttribute("orig-password"),
),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-name-repo",
CredentialType: string(credential.UserPasswordType),
},
},
chgFn: changeMappingOverride(
NewUserPasswordOverride(
WithOverridePasswordAttribute("changed-password"),
),
),
masks: []string{"MappingOverride"},
want: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverridePasswordAttribute("changed-password"),
),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-name-repo",
CredentialType: string(credential.UserPasswordType),
},
},
wantCount: 1,
},
{
name: "user-password-attributes-change-username-and-password-attributes",
orig: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverrideUsernameAttribute("orig-username"),
WithOverridePasswordAttribute("orig-password"),
),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-name-repo",
CredentialType: string(credential.UserPasswordType),
},
},
chgFn: changeMappingOverride(
NewUserPasswordOverride(
WithOverrideUsernameAttribute("changed-username"),
WithOverridePasswordAttribute("changed-password"),
),
),
masks: []string{"MappingOverride"},
want: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverrideUsernameAttribute("changed-username"),
WithOverridePasswordAttribute("changed-password"),
),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-name-repo",
CredentialType: string(credential.UserPasswordType),
},
},
wantCount: 1,
},
{
name: "no-mapping-override-change-username-and-password-attributes",
orig: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-name-repo",
CredentialType: string(credential.UserPasswordType),
},
},
chgFn: changeMappingOverride(
NewUserPasswordOverride(
WithOverrideUsernameAttribute("changed-username"),
WithOverridePasswordAttribute("changed-password"),
),
),
masks: []string{"MappingOverride"},
want: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverrideUsernameAttribute("changed-username"),
WithOverridePasswordAttribute("changed-password"),
),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-name-repo",
CredentialType: string(credential.UserPasswordType),
},
},
wantCount: 1,
},
{
name: "user-password-attributes-delete-mapping-override",
orig: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverrideUsernameAttribute("orig-username"),
WithOverridePasswordAttribute("orig-password"),
),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-name-repo",
CredentialType: string(credential.UserPasswordType),
},
},
chgFn: changeMappingOverride(nil),
masks: []string{"MappingOverride"},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-name-repo",
CredentialType: string(credential.UserPasswordType),
},
},
wantCount: 1,
},
{
name: "set-mapping-override-on-unspecified-credential-type",
orig: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-name-repo",
},
},
chgFn: changeMappingOverride(
NewUserPasswordOverride(
WithOverrideUsernameAttribute("changed-username"),
WithOverridePasswordAttribute("changed-password"),
),
),
masks: []string{"MappingOverride"},
wantErr: errors.VaultInvalidMappingOverride,
},
}
for _, tt := range tests {
@@ -798,19 +1146,36 @@ func TestRepository_UpdateCredentialLibrary(t *testing.T) {
underlyingDB, err := conn.SqlDB(ctx)
require.NoError(err)
dbassert := dbassert.New(t, underlyingDB)
if tt.want.Name == "" {
switch tt.want.Name {
case "":
dbassert.IsNull(got, "name")
return
default:
assert.Equal(tt.want.Name, got.Name)
}
assert.Equal(tt.want.Name, got.Name)
if tt.want.Description == "" {
switch tt.want.Description {
case "":
dbassert.IsNull(got, "description")
return
default:
assert.Equal(tt.want.Description, got.Description)
}
assert.Equal(tt.want.Description, got.Description)
if tt.wantCount > 0 {
assert.NoError(db.TestVerifyOplog(t, rw, got.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
}
switch w := tt.want.MappingOverride.(type) {
case nil:
assert.Nil(got.MappingOverride)
case *UserPasswordOverride:
g, ok := got.MappingOverride.(*UserPasswordOverride)
require.True(ok)
assert.Equal(w.UsernameAttribute, g.UsernameAttribute)
assert.Equal(w.PasswordAttribute, g.PasswordAttribute)
default:
assert.Fail("Unknown mapping override")
}
})
}
@@ -935,64 +1300,151 @@ func TestRepository_LookupCredentialLibrary(t *testing.T) {
rw := db.New(conn)
wrapper := db.TestWrapper(t)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
l := TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 1)[0]
{
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
badId, err := newCredentialLibraryId()
require.NoError(t, err)
require.NotNil(t, badId)
tests := []struct {
name string
in *CredentialLibrary
}{
{
name: "valid-no-options",
in: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
},
},
},
{
name: "valid-user-password-credential-type",
in: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(credential.UserPasswordType),
},
},
},
{
name: "valid-user-password-credential-type-with-username-override",
in: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverrideUsernameAttribute("utest"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(credential.UserPasswordType),
},
},
},
{
name: "valid-user-password-credential-type-with-password-override",
in: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverridePasswordAttribute("ptest"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(credential.UserPasswordType),
},
},
},
{
name: "valid-user-password-credential-type-with-username-and-password-override",
in: &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverrideUsernameAttribute("utest"),
WithOverridePasswordAttribute("ptest"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(credential.UserPasswordType),
},
},
},
}
tests := []struct {
name string
in string
want *CredentialLibrary
wantErr errors.Code
}{
{
name: "valid",
in: l.GetPublicId(),
want: l,
},
{
name: "empty-public-id",
in: "",
wantErr: errors.InvalidParameter,
},
{
name: "not-found",
in: badId,
},
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
// setup
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
repo, err := NewRepository(rw, rw, kms, sche)
assert.NoError(err)
require.NotNil(repo)
orig, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), tt.in)
assert.NoError(err)
require.NotEmpty(orig)
// test
got, err := repo.LookupCredentialLibrary(ctx, orig.GetPublicId())
assert.NoError(err)
require.NotEmpty(got)
assert.Equal(orig.Name, got.Name)
assert.Equal(orig.Description, got.Description)
assert.Equal(orig.CredentialType(), got.CredentialType())
if tt.in.MappingOverride != nil {
require.NotNil(got.MappingOverride)
assert.IsType(orig.MappingOverride, got.MappingOverride)
switch w := orig.MappingOverride.(type) {
case *UserPasswordOverride:
g, ok := got.MappingOverride.(*UserPasswordOverride)
require.True(ok)
assert.Equal(w.UsernameAttribute, g.UsernameAttribute)
assert.Equal(w.PasswordAttribute, g.PasswordAttribute)
default:
assert.Fail("Unknown mapping override")
}
}
})
}
}
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()
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
repo, err := NewRepository(rw, rw, kms, sche)
assert.NoError(err)
require.NotNil(repo)
t.Run("empty-public-id", func(t *testing.T) {
// setup
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
repo, err := NewRepository(rw, rw, kms, sche)
assert.NoError(err)
require.NotNil(repo)
// test
got, err := repo.LookupCredentialLibrary(ctx, "")
wantErr := errors.InvalidParameter
assert.Truef(errors.Match(errors.T(wantErr), err), "want err: %q got: %q", wantErr, err)
assert.Nil(got)
})
got, err := repo.LookupCredentialLibrary(ctx, tt.in)
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)
switch {
case tt.want == nil:
assert.Nil(got)
case tt.want != nil:
assert.NotNil(got)
assert.Equal(got, tt.want)
}
})
}
t.Run("not-found", func(t *testing.T) {
// setup
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
repo, err := NewRepository(rw, rw, kms, sche)
assert.NoError(err)
require.NotNil(repo)
badId, err := newCredentialLibraryId()
assert.NoError(err)
require.NotNil(badId)
// test
got, err := repo.LookupCredentialLibrary(ctx, badId)
assert.NoError(err)
assert.Empty(got)
})
}
func TestRepository_DeleteCredentialLibrary(t *testing.T) {
@@ -1001,56 +1453,95 @@ func TestRepository_DeleteCredentialLibrary(t *testing.T) {
rw := db.New(conn)
wrapper := db.TestWrapper(t)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
l := TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 1)[0]
{
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
l := TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 1)[0]
badId, err := newCredentialLibraryId()
require.NoError(t, err)
require.NotNil(t, badId)
badId, err := newCredentialLibraryId()
require.NoError(t, err)
require.NotNil(t, badId)
tests := []struct {
name string
in string
want int
wantErr errors.Code
}{
{
name: "found",
in: l.GetPublicId(),
want: 1,
},
{
name: "not-found",
in: badId,
},
{
name: "empty-public-id",
in: "",
wantErr: errors.InvalidParameter,
},
tests := []struct {
name string
in string
want int
wantErr errors.Code
}{
{
name: "found",
in: l.GetPublicId(),
want: 1,
},
{
name: "not-found",
in: badId,
},
{
name: "empty-public-id",
in: "",
wantErr: errors.InvalidParameter,
},
}
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()
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
repo, err := NewRepository(rw, rw, kms, sche)
assert.NoError(err)
require.NotNil(repo)
got, err := repo.DeleteCredentialLibrary(ctx, prj.GetPublicId(), tt.in)
if tt.wantErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err)
return
}
assert.NoError(err)
assert.Equal(tt.want, got, "row count")
})
}
}
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()
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
repo, err := NewRepository(rw, rw, kms, sche)
assert.NoError(err)
require.NotNil(repo)
t.Run("library-with-mapping-overrides", func(t *testing.T) {
// setup
assert, require := assert.New(t), require.New(t)
got, err := repo.DeleteCredentialLibrary(ctx, prj.GetPublicId(), tt.in)
if tt.wantErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err)
return
}
assert.NoError(err)
assert.Equal(tt.want, got, "row count")
})
}
ctx := context.Background()
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
repo, err := NewRepository(rw, rw, kms, sche)
assert.NoError(err)
require.NotNil(repo)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
lib := &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverrideUsernameAttribute("orig-username"),
WithOverridePasswordAttribute("orig-password"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-name-repo",
CredentialType: string(credential.UserPasswordType),
},
}
orig, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), lib)
assert.NoError(err)
require.NotNil(orig)
// test
got, err := repo.DeleteCredentialLibrary(ctx, prj.GetPublicId(), orig.GetPublicId())
assert.NoError(err)
assert.Equal(1, got)
})
}
func TestRepository_ListCredentialLibraries(t *testing.T) {
@@ -1059,59 +1550,83 @@ func TestRepository_ListCredentialLibraries(t *testing.T) {
rw := db.New(conn)
wrapper := db.TestWrapper(t)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
css := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2)
csA, csB := css[0], css[1]
t.Run("CredentialStore-with-a-library", func(t *testing.T) {
// setup
assert, require := assert.New(t), require.New(t)
libs := TestCredentialLibraries(t, conn, wrapper, csA.GetPublicId(), 3)
ctx := context.Background()
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
repo, err := NewRepository(rw, rw, kms, sche)
assert.NoError(err)
require.NotNil(repo)
tests := []struct {
name string
in string
opts []Option
want []*CredentialLibrary
wantErr errors.Code
}{
{
name: "with-no-credential-store-id",
wantErr: errors.InvalidParameter,
},
{
name: "CredentialStore-with-no-libraries",
in: csB.GetPublicId(),
want: []*CredentialLibrary{},
},
{
name: "CredentialStore-with-libraries",
in: csA.GetPublicId(),
want: libs,
},
}
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2)[0]
lib := &CredentialLibrary{
MappingOverride: NewUserPasswordOverride(
WithOverrideUsernameAttribute("orig-username"),
WithOverridePasswordAttribute("orig-password"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-name-repo",
CredentialType: string(credential.UserPasswordType),
},
}
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()
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
repo, err := NewRepository(rw, rw, kms, sche)
assert.NoError(err)
require.NotNil(repo)
got, err := repo.ListCredentialLibraries(ctx, tt.in, tt.opts...)
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)
opts := []cmp.Option{
cmpopts.SortSlices(func(x, y *CredentialLibrary) bool { return x.PublicId < y.PublicId }),
protocmp.Transform(),
}
assert.Empty(cmp.Diff(tt.want, got, opts...))
})
}
orig, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), lib)
assert.NoError(err)
require.NotNil(orig)
// test
got, err := repo.ListCredentialLibraries(ctx, cs.GetPublicId())
assert.NoError(err)
require.Len(got, 1)
got1 := got[0]
assert.Equal(orig.GetPublicId(), got1.GetPublicId())
assert.Equal(orig.GetStoreId(), got1.GetStoreId())
assert.Equal(orig.GetHttpMethod(), got1.GetHttpMethod())
assert.Equal(orig.GetVaultPath(), got1.GetVaultPath())
assert.Equal(orig.GetName(), got1.GetName())
assert.Equal(orig.GetCredentialType(), got1.GetCredentialType())
assert.Empty(got1.MappingOverride)
})
t.Run("with-no-credential-store-id", func(t *testing.T) {
// setup
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
repo, err := NewRepository(rw, rw, kms, sche)
assert.NoError(err)
require.NotNil(repo)
// test
got, err := repo.ListCredentialLibraries(ctx, "")
wantErr := errors.InvalidParameter
assert.Truef(errors.Match(errors.T(wantErr), err), "want err: %q got: %q", wantErr, err)
assert.Nil(got)
})
t.Run("CredentialStore-with-no-libraries", func(t *testing.T) {
// setup
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
repo, err := NewRepository(rw, rw, kms, sche)
assert.NoError(err)
require.NotNil(repo)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
// test
got, err := repo.ListCredentialLibraries(ctx, cs.GetPublicId())
assert.NoError(err)
assert.Empty(got)
})
}
func TestRepository_ListCredentialLibraries_Limits(t *testing.T) {
@@ -2,13 +2,11 @@ package vault
import (
"context"
"fmt"
"time"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
vault "github.com/hashicorp/vault/api"
)
var _ credential.Issuer = (*Repository)(nil)
@@ -36,48 +34,13 @@ func (r *Repository) Issue(ctx context.Context, sessionId string, requests []cre
var creds []credential.Dynamic
var minLease time.Duration
for _, lib := range libs {
// Get the credential ID early. No need to get a secret from Vault
// if there is no way to save it in the database.
credId, err := newCredentialId()
cred, err := lib.retrieveCredential(ctx, op, sessionId)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
return nil, err
}
client, err := lib.client()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
if minLease > cred.getExpiration() {
minLease = cred.getExpiration()
}
var secret *vault.Secret
switch Method(lib.HttpMethod) {
case MethodGet:
secret, err = client.get(lib.VaultPath)
case MethodPost:
secret, err = client.post(lib.VaultPath, lib.HttpRequestBody)
default:
return nil, errors.New(ctx, errors.Internal, op, fmt.Sprintf("unknown http method: library: %s", lib.PublicId))
}
if err != nil {
// TODO(mgaffney) 05/2021: detect if the error is because of an
// expired or invalid token
return nil, errors.Wrap(ctx, err, op)
}
if secret == nil {
return nil, errors.E(ctx, errors.WithCode(errors.VaultEmptySecret), errors.WithOp(op))
}
leaseDuration := time.Duration(secret.LeaseDuration) * time.Second
if minLease > leaseDuration {
minLease = leaseDuration
}
cred, err := newCredential(lib.GetPublicId(), sessionId, secret.LeaseID, lib.TokenHmac, leaseDuration)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cred.PublicId = credId
cred.IsRenewable = secret.Renewable
insertQuery, insertQueryValues := cred.insertQuery()
updateQuery, updateQueryValues := cred.updateSessionQuery(lib.Purpose)
if _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
@@ -105,13 +68,7 @@ func (r *Repository) Issue(ctx context.Context, sessionId string, requests []cre
return nil, errors.Wrap(ctx, err, op)
}
creds = append(creds, &actualCredential{
id: cred.PublicId,
sessionId: cred.SessionId,
lib: lib,
secretData: secret.Data,
purpose: lib.Purpose,
})
creds = append(creds, cred)
}
// Best effort update next run time of credential renewal job, but an error should not
@@ -36,94 +36,138 @@ func TestRepository_IssueCredentials(t *testing.T) {
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
kms := kms.TestKms(t, conn, wrapper)
assert, require := assert.New(t), require.New(t)
sche := scheduler.TestScheduler(t, conn, wrapper)
repo, err := vault.NewRepository(rw, rw, kms, sche)
require.NoError(err)
require.NotNil(repo)
require.NoError(t, err)
require.NotNil(t, repo)
err = vault.RegisterJobs(ctx, sche, rw, rw, kms)
require.NoError(err)
require.NoError(t, err)
_, token := v.CreateToken(t, vault.WithPolicies([]string{"default", "boundary-controller", "database", "pki", "secret"}))
// Create valid KV secret
// Create valid user password KV secret
v.CreateKVSecret(t, "my-secret", []byte(`{"data":{"username":"user","password":"pass"}}`))
var opts []vault.Option
opts = append(opts, vault.WithCACert(v.CaCert))
clientCert, err := vault.NewClientCertificate(v.ClientCert, v.ClientKey)
require.NoError(err)
require.NoError(t, err)
opts = append(opts, vault.WithClientCert(clientCert))
credStoreIn, err := vault.NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token), opts...)
assert.NoError(err)
require.NotNil(credStoreIn)
assert.NoError(t, err)
require.NotNil(t, credStoreIn)
origStore, err := repo.CreateCredentialStore(ctx, credStoreIn)
assert.NoError(err)
require.NotNil(origStore)
assert.NoError(t, err)
require.NotNil(t, origStore)
type libT int
const (
libDB libT = iota
libUsrPassDB
libErrUsrPassDB
libPKI
libErrPKI
libKV
libErrKV
libUsrPassKV
)
libs := make(map[libT]string)
{
libPath := path.Join("database", "creds", "opened")
libIn, err := vault.NewCredentialLibrary(origStore.GetPublicId(), libPath)
assert.NoError(err)
require.NotNil(libIn)
assert.NoError(t, err)
require.NotNil(t, libIn)
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(err)
require.NotNil(lib)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libDB] = lib.GetPublicId()
}
{
libPath := path.Join("pki", "issue", "boundary")
libIn, err := vault.NewCredentialLibrary(origStore.GetPublicId(), libPath, vault.WithMethod(vault.MethodPost), vault.WithRequestBody([]byte(`{"common_name":"boundary.com"}`)))
assert.NoError(err)
require.NotNil(libIn)
assert.NoError(t, err)
require.NotNil(t, libIn)
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(err)
require.NotNil(lib)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libPKI] = lib.GetPublicId()
}
{
libPath := path.Join("pki", "issue", "boundary")
libIn, err := vault.NewCredentialLibrary(origStore.GetPublicId(), libPath, vault.WithMethod(vault.MethodPost))
assert.NoError(err)
require.NotNil(libIn)
assert.NoError(t, err)
require.NotNil(t, libIn)
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(err)
require.NotNil(lib)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libErrPKI] = lib.GetPublicId()
}
{
libPath := path.Join("secret", "data", "my-secret")
libIn, err := vault.NewCredentialLibrary(origStore.GetPublicId(), libPath, opts...)
assert.NoError(err)
require.NotNil(libIn)
assert.NoError(t, err)
require.NotNil(t, libIn)
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(err)
require.NotNil(lib)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libKV] = lib.GetPublicId()
}
{
libPath := path.Join("secret", "data", "fake-secret")
libIn, err := vault.NewCredentialLibrary(origStore.GetPublicId(), libPath, opts...)
assert.NoError(err)
require.NotNil(libIn)
assert.NoError(t, err)
require.NotNil(t, libIn)
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(err)
require.NotNil(lib)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libErrKV] = lib.GetPublicId()
}
{
libPath := path.Join("database", "creds", "opened")
opts := []vault.Option{
vault.WithCredentialType(credential.UserPasswordType),
}
libIn, err := vault.NewCredentialLibrary(origStore.GetPublicId(), libPath, opts...)
assert.NoError(t, err)
require.NotNil(t, libIn)
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libUsrPassDB] = lib.GetPublicId()
}
{
libPath := path.Join("database", "creds", "opened")
opts := []vault.Option{
vault.WithCredentialType(credential.UserPasswordType),
vault.WithMappingOverride(vault.NewUserPasswordOverride(
vault.WithOverrideUsernameAttribute("test-username"),
vault.WithOverridePasswordAttribute("test-password"),
)),
}
libIn, err := vault.NewCredentialLibrary(origStore.GetPublicId(), libPath, opts...)
assert.NoError(t, err)
require.NotNil(t, libIn)
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libErrUsrPassDB] = lib.GetPublicId()
}
{
libPath := path.Join("secret", "data", "my-secret")
opts := []vault.Option{
vault.WithCredentialType(credential.UserPasswordType),
}
libIn, err := vault.NewCredentialLibrary(origStore.GetPublicId(), libPath, opts...)
assert.NoError(t, err)
require.NotNil(t, libIn)
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libUsrPassKV] = lib.GetPublicId()
}
at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
uId := at.GetIamUserId()
@@ -214,6 +258,16 @@ func TestRepository_IssueCredentials(t *testing.T) {
},
},
},
{
name: "one-db-valid-username-password-library",
convertFn: rc2dc,
requests: []credential.Request{
{
SourceId: libs[libUsrPassDB],
Purpose: credential.ApplicationPurpose,
},
},
},
{
name: "invalid-kv-does-not-exist",
convertFn: rc2dc,
@@ -225,10 +279,32 @@ func TestRepository_IssueCredentials(t *testing.T) {
},
wantErr: errors.VaultEmptySecret,
},
{
name: "invalid-username-password-library",
convertFn: rc2dc,
requests: []credential.Request{
{
SourceId: libs[libErrUsrPassDB],
Purpose: credential.ApplicationPurpose,
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "valid-kv-username-password-library",
convertFn: rc2dc,
requests: []credential.Request{
{
SourceId: libs[libUsrPassKV],
Purpose: credential.ApplicationPurpose,
},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
UserId: uId,
HostId: h.GetPublicId(),
@@ -246,7 +322,23 @@ func TestRepository_IssueCredentials(t *testing.T) {
return
}
assert.Len(got, len(tt.requests))
assert.NoError(err)
require.NoError(err)
assert.NotZero(len(got))
for _, dc := range got {
switch dc.Library().CredentialType() {
case credential.UserPasswordType:
if upc, ok := dc.(credential.UserPassword); ok {
assert.NotEmpty(upc.Username())
assert.NotEmpty(upc.Password())
break
}
assert.Fail("want UserPassword credential from library with credential type UserPassword")
case credential.UnspecifiedType:
if _, ok := dc.(credential.UserPassword); ok {
assert.Fail("do not want UserPassword credential from library with credential type Unspecified")
}
}
}
})
}
}
@@ -20,6 +20,8 @@ import (
"github.com/hashicorp/boundary/internal/servers/worker"
"github.com/hashicorp/go-hclog"
"github.com/stretchr/testify/require"
_ "github.com/hashicorp/boundary/internal/servers/controller/handlers/targets/tcp"
)
func TestSetupSleepyDevEnvironment(t *testing.T) {
+181 -64
View File
@@ -502,6 +502,10 @@ type CredentialLibrary struct {
// Can only be set if http_method is POST.
// @inject_tag: `gorm:"default:null"`
HttpRequestBody []byte `protobuf:"bytes,10,opt,name=http_request_body,json=httpRequestBody,proto3" json:"http_request_body,omitempty" gorm:"default:null"`
// credential_type is optional. If set, it indicates the type of
// credential the library returns.
// @inject_tag: `gorm:"default:null"`
CredentialType string `protobuf:"bytes,11,opt,name=credential_type,json=credentialType,proto3" json:"credential_type,omitempty" gorm:"default:null"`
}
func (x *CredentialLibrary) Reset() {
@@ -606,6 +610,13 @@ func (x *CredentialLibrary) GetHttpRequestBody() []byte {
return nil
}
func (x *CredentialLibrary) GetCredentialType() string {
if x != nil {
return x.CredentialType
}
return ""
}
type Credential struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -776,6 +787,87 @@ func (x *Credential) GetStatus() string {
return ""
}
type UserPasswordOverride struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// 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"`
// username_attribute is the name of the attribute in the Data field of a
// Vault api.Secret that maps to a username.
// If set, it overrides any default attribute names the system uses to
// find a username attribute.
//
// See https://github.com/hashicorp/vault/blob/5e505ec039177e8212cbbab74ccb644c46e62e63/api/secret.go#L25
//
// @inject_tag: `gorm:"default:null"`
UsernameAttribute string `protobuf:"bytes,2,opt,name=username_attribute,json=usernameAttribute,proto3" json:"username_attribute,omitempty" gorm:"default:null"`
// 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,3,opt,name=password_attribute,json=passwordAttribute,proto3" json:"password_attribute,omitempty" gorm:"default:null"`
}
func (x *UserPasswordOverride) Reset() {
*x = UserPasswordOverride{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UserPasswordOverride) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UserPasswordOverride) ProtoMessage() {}
func (x *UserPasswordOverride) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UserPasswordOverride.ProtoReflect.Descriptor instead.
func (*UserPasswordOverride) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{5}
}
func (x *UserPasswordOverride) GetLibraryId() string {
if x != nil {
return x.LibraryId
}
return ""
}
func (x *UserPasswordOverride) GetUsernameAttribute() string {
if x != nil {
return x.UsernameAttribute
}
return ""
}
func (x *UserPasswordOverride) GetPasswordAttribute() string {
if x != nil {
return x.PasswordAttribute
}
return ""
}
var File_controller_storage_credential_vault_store_v1_vault_proto protoreflect.FileDescriptor
var file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc = []byte{
@@ -897,7 +989,7 @@ var file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc = []by
0x18, 0x82, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
0x63, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x48, 0x6d, 0x61, 0x63, 0x12, 0x15, 0x0a, 0x06, 0x6b,
0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79,
0x49, 0x64, 0x22, 0xd4, 0x04, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61,
0x49, 0x64, 0x22, 0xfd, 0x04, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61,
0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c,
0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62,
0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f,
@@ -934,48 +1026,60 @@ var file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc = []by
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x1c, 0x61, 0x74, 0x74, 0x72,
0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x52, 0x0f, 0x68, 0x74, 0x74, 0x70, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x22, 0xc3, 0x04, 0x0a, 0x0a, 0x43, 0x72,
0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c,
0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62,
0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79,
0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x69, 0x62, 0x72, 0x61,
0x72, 0x79, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f,
0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f,
0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x68, 0x6d, 0x61,
0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x48, 0x6d,
0x61, 0x63, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d,
0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d,
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12,
0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e,
0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74,
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x56, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f,
0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f,
0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12,
0x53, 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69,
0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x6e, 0x65, 0x77,
0x61, 0x62, 0x6c, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x52, 0x65,
0x6e, 0x65, 0x77, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42,
0x45, 0x5a, 0x43, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61,
0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79,
0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e,
0x74, 0x69, 0x61, 0x6c, 0x2f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65,
0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x72, 0x65,
0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0b, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x54, 0x79,
0x70, 0x65, 0x22, 0xc3, 0x04, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61,
0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x1d,
0x0a, 0x0a, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x09, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x49, 0x64, 0x12, 0x1d, 0x0a,
0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a,
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x48, 0x6d, 0x61, 0x63, 0x12, 0x4b, 0x0a, 0x0b, 0x63,
0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74,
0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e,
0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72,
0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61,
0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61,
0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74,
0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12,
0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x08,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64,
0x12, 0x56, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c,
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x6e,
0x65, 0x77, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x53, 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x69,
0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73,
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65,
0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a,
0x0c, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x0b, 0x20,
0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x62, 0x6c, 0x65,
0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09,
0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x93, 0x01, 0x0a, 0x14, 0x55, 0x73, 0x65,
0x72, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x49, 0x64,
0x12, 0x2d, 0x0a, 0x12, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x61, 0x74, 0x74,
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x73,
0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12,
0x2d, 0x0a, 0x12, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x61, 0x74, 0x74, 0x72,
0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x70, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x42, 0x45,
0x5a, 0x43, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73,
0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74,
0x69, 0x61, 0x6c, 0x2f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b,
0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -990,29 +1094,30 @@ 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, 5)
var file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_controller_storage_credential_vault_store_v1_vault_proto_goTypes = []interface{}{
(*CredentialStore)(nil), // 0: controller.storage.credential.vault.store.v1.CredentialStore
(*Token)(nil), // 1: controller.storage.credential.vault.store.v1.Token
(*ClientCertificate)(nil), // 2: controller.storage.credential.vault.store.v1.ClientCertificate
(*CredentialLibrary)(nil), // 3: controller.storage.credential.vault.store.v1.CredentialLibrary
(*Credential)(nil), // 4: controller.storage.credential.vault.store.v1.Credential
(*timestamp.Timestamp)(nil), // 5: controller.storage.timestamp.v1.Timestamp
(*CredentialStore)(nil), // 0: controller.storage.credential.vault.store.v1.CredentialStore
(*Token)(nil), // 1: controller.storage.credential.vault.store.v1.Token
(*ClientCertificate)(nil), // 2: controller.storage.credential.vault.store.v1.ClientCertificate
(*CredentialLibrary)(nil), // 3: controller.storage.credential.vault.store.v1.CredentialLibrary
(*Credential)(nil), // 4: controller.storage.credential.vault.store.v1.Credential
(*UserPasswordOverride)(nil), // 5: controller.storage.credential.vault.store.v1.UserPasswordOverride
(*timestamp.Timestamp)(nil), // 6: controller.storage.timestamp.v1.Timestamp
}
var file_controller_storage_credential_vault_store_v1_vault_proto_depIdxs = []int32{
5, // 0: controller.storage.credential.vault.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 1: controller.storage.credential.vault.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 2: controller.storage.credential.vault.store.v1.CredentialStore.delete_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 3: controller.storage.credential.vault.store.v1.Token.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 4: controller.storage.credential.vault.store.v1.Token.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 5: controller.storage.credential.vault.store.v1.Token.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 6: controller.storage.credential.vault.store.v1.Token.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 7: controller.storage.credential.vault.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 8: controller.storage.credential.vault.store.v1.CredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 9: controller.storage.credential.vault.store.v1.Credential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 10: controller.storage.credential.vault.store.v1.Credential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 11: controller.storage.credential.vault.store.v1.Credential.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 12: controller.storage.credential.vault.store.v1.Credential.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 0: controller.storage.credential.vault.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 1: controller.storage.credential.vault.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 2: controller.storage.credential.vault.store.v1.CredentialStore.delete_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 3: controller.storage.credential.vault.store.v1.Token.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 4: controller.storage.credential.vault.store.v1.Token.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 5: controller.storage.credential.vault.store.v1.Token.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 6: controller.storage.credential.vault.store.v1.Token.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 7: controller.storage.credential.vault.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 8: controller.storage.credential.vault.store.v1.CredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 9: controller.storage.credential.vault.store.v1.Credential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 10: controller.storage.credential.vault.store.v1.Credential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 11: controller.storage.credential.vault.store.v1.Credential.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // 12: controller.storage.credential.vault.store.v1.Credential.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
13, // [13:13] is the sub-list for method output_type
13, // [13:13] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
@@ -1086,6 +1191,18 @@ func file_controller_storage_credential_vault_store_v1_vault_proto_init() {
return nil
}
}
file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UserPasswordOverride); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -1093,7 +1210,7 @@ func file_controller_storage_credential_vault_store_v1_vault_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc,
NumEnums: 0,
NumMessages: 5,
NumMessages: 6,
NumExtensions: 0,
NumServices: 0,
},
@@ -577,6 +577,7 @@ begin;
'credential_vault_store_public is a view where each row contains a credential store. '
'No encrypted data is returned. This view can be used to retrieve data which will be returned external to boundary.';
-- Replaced in 21/05_vault_private_library.up.sql
create view credential_vault_library_private as
select library.public_id as public_id,
library.store_id as store_id,
@@ -0,0 +1,17 @@
begin;
create function wt_to_sentinel(string text)
returns text
as $$
select concat(
u&'\fffe',
trim(trailing u&'\ffff' from trim(leading u&'\fffe ' from string)),
u&'\ffff'
);
$$ language sql
immutable
returns null on null input;
comment on function wt_to_sentinel is
'wt_to_sentinel takes a string and returns it as a wt_sentinel';
commit;
@@ -0,0 +1,77 @@
begin;
create table credential_type_enm (
name text primary key
constraint only_predefined_credential_types_allowed
check (
name in (
'unspecified',
'user_password'
)
)
);
comment on table credential_type_enm is
'credential_type_enm is an enumeration table for the types of credentials a credential library can provide.';
insert into credential_type_enm (name)
values
('unspecified'),
('user_password');
-- Add a credential_type column to the base credential_library table and the
-- credential_vault_library subtype table.
alter table credential_library
add column credential_type text not null default 'unspecified'
constraint credential_type_enm_fkey
references credential_type_enm (name)
on delete restrict
on update cascade,
add constraint credential_library_store_id_public_id_credential_type_uq
unique(store_id, public_id, credential_type)
;
alter table credential_vault_library
add column credential_type text not null default 'unspecified'
constraint credential_type_enm_fkey
references credential_type_enm (name)
on delete restrict
on update cascade,
drop constraint credential_library_fkey,
add constraint credential_library_fkey
foreign key (store_id, public_id, credential_type)
references credential_library (store_id, public_id, credential_type)
on delete cascade
on update cascade
;
-- This drops a constraint that is no longer needed.
-- It could not be dropped until after the credential_library_fkey constraint was
-- dropped and then recreated.
alter table credential_library
drop constraint credential_library_store_id_public_id_uq
;
-- Drop and recreate the insert trigger function for credential_library subtypes.
drop trigger insert_credential_library_subtype on credential_vault_library;
drop function insert_credential_library_subtype();
-- insert_credential_library_subtype() is a before insert trigger
-- function for subtypes of credential_library
create function insert_credential_library_subtype()
returns trigger
as $$
begin
insert into credential_library
(public_id, store_id, credential_type)
values
(new.public_id, new.store_id, new.credential_type);
return new;
end;
$$ language plpgsql;
create trigger insert_credential_library_subtype before insert on credential_vault_library
for each row execute procedure insert_credential_library_subtype();
commit;
@@ -0,0 +1,40 @@
begin;
create table credential_vault_library_mapping_override (
library_id wt_public_id primary key
constraint credential_vault_library_fkey
references credential_vault_library (public_id)
on delete cascade
on update cascade
);
comment on table credential_vault_library_mapping_override is
'credential_vault_library_mapping_override is a base table for the vault library mapping override type. '
'Each row is owned by a single vault library and maps 1-to-1 to a row in one of the vault library mapping override subtype tables.';
-- insert_credential_vault_library_mapping_override_subtype() is a before insert trigger
-- function for subtypes of credential_vault_library_mapping_override
create function insert_credential_vault_library_mapping_override_subtype()
returns trigger
as $$
begin
insert into credential_vault_library_mapping_override
(library_id)
values
(new.library_id);
return new;
end;
$$ language plpgsql;
-- delete_credential_vault_library_mapping_override_subtype() is an after delete trigger
-- function for subtypes of credential_vault_library_mapping_override
create function delete_credential_vault_library_mapping_override_subtype()
returns trigger
as $$
begin
delete from credential_vault_library_mapping_override
where library_id = old.library_id;
return null; -- result is ignored since this is an after trigger
end;
$$ language plpgsql;
commit;
@@ -0,0 +1,32 @@
begin;
create table credential_vault_library_user_password_mapping_override (
library_id wt_public_id primary key
constraint credential_vault_library_fkey
references credential_vault_library (public_id)
on delete cascade
on update cascade
constraint credential_vault_library_mapping_override_fkey
references credential_vault_library_mapping_override (library_id)
on delete cascade
on update cascade,
username_attribute wt_sentinel
default wt_to_sentinel('no override')
not null,
password_attribute wt_sentinel
default wt_to_sentinel('no override')
not null
);
comment on table credential_vault_library_user_password_mapping_override is
'credential_vault_library_user_password_mapping_override is a table '
'where each row represents a mapping that overrides the default mapping '
'from a generic vault secret to a user password credential type '
'for a vault credential library.';
create trigger insert_credential_vault_library_mapping_override_subtype before insert on credential_vault_library_user_password_mapping_override
for each row execute procedure insert_credential_vault_library_mapping_override_subtype();
create trigger delete_credential_vault_library_mapping_override_subtype after delete on credential_vault_library_user_password_mapping_override
for each row execute procedure delete_credential_vault_library_mapping_override_subtype();
commit;
@@ -0,0 +1,68 @@
begin;
-- Replaces view from 10/04_vault_credential.up.sql
drop view credential_vault_library_private;
create view credential_vault_library_private as
with
password_override (library_id, username_attribute, password_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(password_attribute, wt_to_sentinel('no override'))
from credential_vault_library_user_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,
store.scope_id as scope_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.token_hmac as token_hmac,
store.ct_token as ct_token, -- encrypted
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,
upasso.username_attribute as username_attribute,
upasso.password_attribute as password_attribute
from credential_vault_library library
join credential_vault_store_private store
on library.store_id = store.public_id
left join password_override upasso
on library.public_id = upasso.library_id
and store.token_status = 'current';
comment on view credential_vault_library_private is
'credential_vault_library_private is a view where each row contains a credential library and the credential library''s data needed to connect to Vault. '
'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.';
create view credential_vault_library_public as
select public_id,
store_id,
name,
description,
create_time,
update_time,
version,
vault_path,
http_method,
http_request_body,
credential_type,
username_attribute,
password_attribute
from credential_vault_library_private;
comment on view credential_vault_library_public is
'credential_vault_library_public 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;
+3 -1
View File
@@ -18,7 +18,9 @@ endif
PROVE_OPTS ?=
TESTS ?= tests/setup/*.sql \
tests/wh/*/*.sql
tests/wh/*/*.sql \
tests/sentinel/*.sql \
tests/credential/*/*.sql
POSTGRES_DOCKER_IMAGE_BASE ?= postgres
@@ -313,12 +313,41 @@ begin;
values
('p____bwidget', 'vs_______wvs', 'widget vault store', 'None', 'https://vault.widget', 'default');
insert into credential_vault_library
(store_id, public_id, name, description, vault_path, http_method)
insert into credential_vault_token
(store_id, key_id, status, token_hmac, token, last_renewal_time, expiration_time)
values
('vs_______wvs', 'vl______wvl1', 'widget vault library', 'None', '/secrets', 'GET'),
('vs_______wvs', 'vl______wvl2', 'widget vault ssh', 'None', '/secrets/ssh/admin', 'GET'),
('vs_______wvs', 'vl______wvl3', 'widget vault kv', 'None', '/secrets/kv', 'GET');
('vs_______wvs', 'kdkv___widget', 'current', 'hmac-value', 'token-value', now(), now() + interval '1 hour');
insert into credential_vault_library
(store_id, public_id, name, description, vault_path, http_method, credential_type)
values
('vs_______wvs', 'vl______wvl1', 'widget vault library', 'None', '/secrets', 'GET', 'unspecified'),
('vs_______wvs', 'vl______wvl2', 'widget vault ssh', 'None', '/secrets/ssh/admin', 'GET', 'unspecified'),
('vs_______wvs', 'vl______wvl3', 'widget vault kv one', 'None', '/secrets/kv/one', 'GET', 'user_password'),
('vs_______wvs', 'vl______wvl4', 'widget vault kv two', 'None', '/secrets/kv/two', 'GET', 'user_password'),
('vs_______wvs', 'vl______wvl5', 'widget vault kv three', 'None', '/secrets/kv/three', 'GET', 'user_password'),
('vs_______wvs', 'vl______wvl6', 'widget vault kv four', 'None', '/secrets/kv/four', 'GET', 'user_password'),
('vs_______wvs', 'vl______wvl7', 'widget vault kv five', 'None', '/secrets/kv/five', 'GET', 'user_password');
insert into credential_vault_library_user_password_mapping_override
(library_id)
values
('vl______wvl4');
insert into credential_vault_library_user_password_mapping_override
(library_id, username_attribute)
values
('vl______wvl5', 'my_username');
insert into credential_vault_library_user_password_mapping_override
(library_id, password_attribute)
values
('vl______wvl6', 'my_password');
insert into credential_vault_library_user_password_mapping_override
(library_id, username_attribute, password_attribute)
values
('vl______wvl7', 'my_username', 'my_password');
insert into target_credential_library
(target_id, credential_library_id, credential_purpose)
@@ -0,0 +1,39 @@
-- credential_vault_library tests:
-- the following triggers
-- insert_credential_library_subtype
-- delete_credential_library_subtype
begin;
select plan(10);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
-- validate the setup data
select is(count(*), 1::bigint) from credential_vault_library where public_id = 'vl______wvl1' and credential_type = 'unspecified';
select is(count(*), 1::bigint) from credential_library where public_id = 'vl______wvl1' and credential_type = 'unspecified';
select is(count(*), 1::bigint) from credential_vault_library where public_id = 'vl______wvl3' and credential_type = 'user_password';
select is(count(*), 1::bigint) from credential_library where public_id = 'vl______wvl3' and credential_type = 'user_password';
-- validate the insert triggers
prepare insert_vault_library as
insert into credential_vault_library
(store_id, public_id, vault_path, http_method, credential_type)
values
('vs_______wvs', 'vl_______tt1', '/secrets/kv', 'GET', 'user_password');
select lives_ok('insert_vault_library');
select is(count(*), 1::bigint) from credential_vault_library where public_id = 'vl_______tt1' and credential_type = 'user_password';
select is(count(*), 1::bigint) from credential_library where public_id = 'vl_______tt1' and credential_type = 'user_password';
-- validate the delete triggers
prepare delete_vault_library as
delete
from credential_vault_library
where public_id = 'vl_______tt1';
select lives_ok('delete_vault_library');
select is(count(*), 0::bigint) from credential_vault_library where public_id = 'vl_______tt1';
select is(count(*), 0::bigint) from credential_library where public_id = 'vl_______tt1';
select * from finish();
rollback;
@@ -0,0 +1,61 @@
-- credential_vault_library_user_password_mapping_override tests:
-- the following triggers
-- insert_credential_vault_library_mapping_override_subtype
-- delete_credential_vault_library_mapping_override_subtype
begin;
select plan(11);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
-- validate the setup data
select is(count(*), 4::bigint)
from credential_vault_library_user_password_mapping_override
where library_id in ('vl______wvl4', 'vl______wvl5', 'vl______wvl6', 'vl______wvl7');
select is(count(*), 4::bigint)
from credential_vault_library_mapping_override
where library_id in ('vl______wvl4', 'vl______wvl5', 'vl______wvl6', 'vl______wvl7');
prepare select_private_libraries as
select public_id::text, credential_type::text, username_attribute::text, password_attribute::text
from credential_vault_library_private
where public_id in ('vl______wvl2', 'vl______wvl3', 'vl______wvl4', 'vl______wvl5', 'vl______wvl6', 'vl______wvl7')
order by public_id;
select results_eq(
'select_private_libraries',
$$VALUES
('vl______wvl2', 'unspecified', null, null),
('vl______wvl3', 'user_password', null, null),
('vl______wvl4', 'user_password', null, null),
('vl______wvl5', 'user_password', 'my_username', null),
('vl______wvl6', 'user_password', null, 'my_password'),
('vl______wvl7', 'user_password', 'my_username', 'my_password')$$
);
-- validate the insert triggers
select is(count(*), 0::bigint) from credential_vault_library_user_password_mapping_override where library_id = 'vl______wvl3';
select is(count(*), 0::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl3';
prepare insert_credential_vault_library_user_password_mapping_override as
insert into credential_vault_library_user_password_mapping_override
(library_id, username_attribute, password_attribute)
values
('vl______wvl3', 'my_username', 'my_password');
select lives_ok('insert_credential_vault_library_user_password_mapping_override');
select is(count(*), 1::bigint) from credential_vault_library_user_password_mapping_override where library_id = 'vl______wvl3';
select is(count(*), 1::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl3';
-- validate the delete triggers
prepare delete_credential_vault_library_user_password_mapping_override as
delete
from credential_vault_library_user_password_mapping_override
where library_id = 'vl______wvl3';
select lives_ok('delete_credential_vault_library_user_password_mapping_override');
select is(count(*), 0::bigint) from credential_vault_library_user_password_mapping_override where library_id = 'vl______wvl3';
select is(count(*), 0::bigint) from credential_vault_library_mapping_override where library_id = 'vl______wvl3';
select * from finish();
rollback;
@@ -0,0 +1,21 @@
-- is_sentinel tests:
-- wt_is_sentinel function
begin;
select plan(10);
select has_domain('wt_sentinel');
select has_function('wt_is_sentinel', array['text']);
select ok(wt_is_sentinel(u&'\fffe' || 'default' || u&'\ffff'), 'basic sentinel');
select ok(wt_is_sentinel(u&'\fffe' || u&'\ffff'), '0 length sentinel');
select ok(wt_is_sentinel(u&'\fffe' || ' ' || u&'\ffff'), 'empty sentinel');
select ok(not wt_is_sentinel('default'), 'a word is not a sentinel');
select ok(not wt_is_sentinel(''), '0 length string is not a sentinel');
select ok(not wt_is_sentinel(' '), 'empty string is not a sentinel');
select ok(not wt_is_sentinel(u&'\fffe' || 'default'), 'missing suffix of ffff is not a sentinel');
select ok(not wt_is_sentinel('default' || u&'\ffff'), 'missing prefix of fffe is not a sentinel');
select * from finish();
rollback;
@@ -0,0 +1,21 @@
-- to_sentinel tests:
-- wt_to_sentinel function
begin;
select plan(10);
select has_domain('wt_sentinel');
select has_function('wt_to_sentinel', array['text']);
select is(wt_to_sentinel('default'), u&'\fffe' || 'default' || u&'\ffff', 'create basic sentinel');
select is(wt_to_sentinel(u&'\fffe' || 'default' || u&'\ffff'), u&'\fffe' || 'default' || u&'\ffff', 'no changes to existing sentinel');
select is(wt_to_sentinel(''), u&'\fffe' || u&'\ffff', '0 length sentinel');
select is(wt_to_sentinel(' '), u&'\fffe' || u&'\ffff', 'empty string becomes 0 length sentinel');
select is(wt_to_sentinel(u&'\fffe' || u&'\ffff'), u&'\fffe' || u&'\ffff', '0 length sentinel to 0 length sentinel');
select is(wt_to_sentinel(u&'\fffe'), u&'\fffe' || u&'\ffff', 'prefix to 0 length sentinel');
select is(wt_to_sentinel(u&'\ffff'), u&'\fffe' || u&'\ffff', 'suffix to 0 length sentinel');
select is(wt_to_sentinel(NULL), NULL, 'null to null');
select * from finish();
rollback;
+2
View File
@@ -108,6 +108,8 @@ const (
VaultTokenMissingCapabilities Code = 3013 // VaultTokenMissingCapabilities represents an error for a Vault token that is missing capabilities
VaultCredentialRequest Code = 3014 // VaultCredentialRequest represents an error returned from Vault when retrieving a credential
VaultEmptySecret Code = 3015 // VaultEmptySecret represents a empty secret was returned from Vault without error
VaultInvalidMappingOverride Code = 3016 // VaultInvalidMappingOverride represents an error returned when a credential mapping is unknown or does not match a credential type
VaultInvalidCredentialMapping Code = 3017 // VaultInvalidCredentialMapping represents an error returned when a Vault secret failed to be mapped to a specific credential type
// OIDC authentication provided errors
OidcProviderCallbackError Code = 4000 // OidcProviderCallbackError represents an error that is passed by the OIDC provider to the callback endpoint
+10
View File
@@ -287,6 +287,16 @@ func TestCode_Both_String_Info(t *testing.T) {
c: VaultEmptySecret,
want: VaultEmptySecret,
},
{
name: "VaultInvalidMappingOverride",
c: VaultInvalidMappingOverride,
want: VaultInvalidMappingOverride,
},
{
name: "VaultInvalidCredentialMapping",
c: VaultInvalidCredentialMapping,
want: VaultInvalidCredentialMapping,
},
{
name: "OidcProviderCallbackError",
c: OidcProviderCallbackError,
+8
View File
@@ -232,6 +232,14 @@ var errorCodeInfo = map[Code]Info{
Message: "vault secret is empty",
Kind: Integrity,
},
VaultInvalidMappingOverride: {
Message: "invalid credential mapping override",
Kind: Parameter,
},
VaultInvalidCredentialMapping: {
Message: "mapping vault secret to a credential type failed",
Kind: Integrity,
},
OidcProviderCallbackError: {
Message: "oidc provider callback error",
Kind: External,
+8
View File
@@ -3923,6 +3923,14 @@
},
"description": "Output only. The available actions on this resource for this user.",
"readOnly": true
},
"credential_type": {
"type": "string",
"title": "The type of credential this library will issue, defaults to Unspecified"
},
"credential_mapping_overrides": {
"type": "object",
"title": "The credential mapping overrides"
}
},
"title": "CredentialLibrary contains all fields related to an Credential Library resource"
@@ -45,6 +45,12 @@ message CredentialLibrary {
// Output only. The available actions on this resource for this user.
repeated string authorized_actions = 300 [json_name = "authorized_actions"];
// The type of credential this library will issue, defaults to Unspecified
string credential_type = 310 [json_name = "credential_type", (custom_options.v1.generate_sdk_option) = true];
// The credential mapping overrides
google.protobuf.Struct credential_mapping_overrides = 320 [json_name = "credential_mapping_overrides", (custom_options.v1.generate_sdk_option) = true];
}
// The attributes of a vault typed Credential Library.
@@ -22,6 +22,11 @@ message Library {
// The store_id of the owning store and must be set.
// @inject_tag: `gorm:"not_null"`
string store_id = 2;
// credential_type is optional. If set, it indicates the type of
// credential the library returns.
// @inject_tag: `gorm:"default:null"`
string credential_type = 3;
}
message Credential {
@@ -205,6 +205,11 @@ message CredentialLibrary {
// Can only be set if http_method is POST.
// @inject_tag: `gorm:"default:null"`
bytes http_request_body = 10 [(custom_options.v1.mask_mapping) = {this:"HttpRequestBody" that: "attributes.http_request_body"}];
// credential_type is optional. If set, it indicates the type of
// credential the library returns.
// @inject_tag: `gorm:"default:null"`
string credential_type = 11;
}
message Credential {
@@ -267,3 +272,29 @@ message Credential {
// @inject_tag: `gorm:"not_null"`
string status = 12;
}
message UserPasswordOverride {
// library_id of the owning vault credential library.
// @inject_tag: `gorm:"primary_key"`
string library_id = 1;
// username_attribute is the name of the attribute in the Data field of a
// Vault api.Secret that maps to a username.
// If set, it overrides any default attribute names the system uses to
// find a username attribute.
//
// See https://github.com/hashicorp/vault/blob/5e505ec039177e8212cbbab74ccb644c46e62e63/api/secret.go#L25
//
// @inject_tag: `gorm:"default:null"`
string username_attribute = 2;
// 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 = 3;
}
@@ -21,13 +21,22 @@ import (
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentiallibraries"
"github.com/hashicorp/go-secure-stdlib/strutil"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
const (
vaultPathField = "attributes.path"
httpMethodField = "attributes.http_method"
httpRequestBodyField = "attributes.http_request_body"
vaultPathField = "attributes.path"
httpMethodField = "attributes.http_method"
httpRequestBodyField = "attributes.http_request_body"
credentialMappingPathField = "credential_mapping_overrides"
)
// Credential mapping override attributes
const (
usernameAttribute string = "username_attribute"
passwordAttribute string = "password_attribute"
)
var (
@@ -220,14 +229,24 @@ func (s Service) CreateCredentialLibrary(ctx context.Context, req *pbs.CreateCre
func (s Service) UpdateCredentialLibrary(ctx context.Context, req *pbs.UpdateCredentialLibraryRequest) (*pbs.UpdateCredentialLibraryResponse, error) {
const op = "credentiallibraries.(Service).UpdateCredentialLibrary"
if err := validateUpdateRequest(req); err != nil {
repo, err := s.repoFn()
if err != nil {
return nil, err
}
cur, err := repo.LookupCredentialLibrary(ctx, req.Id)
if err != nil {
return nil, err
}
currentCredentialType := credential.Type(cur.GetCredentialType())
if err := validateUpdateRequest(req, currentCredentialType); err != nil {
return nil, err
}
authResults := s.authResult(ctx, req.GetId(), action.Update)
if authResults.Error != nil {
return nil, authResults.Error
}
cl, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
cl, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem(), currentCredentialType, cur.MappingOverride)
if err != nil {
return nil, err
}
@@ -319,23 +338,46 @@ func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Cred
return out, nil
}
func (s Service) updateInRepo(ctx context.Context, projId, id string, mask []string, item *pb.CredentialLibrary) (credential.Library, error) {
func (s Service) updateInRepo(
ctx context.Context,
projId, id string,
masks []string,
in *pb.CredentialLibrary,
currentCredentialType credential.Type,
currentMapping vault.MappingOverride) (credential.Library, error) {
const op = "credentiallibraries.(Service).updateInRepo"
var dbMasks []string
item := proto.Clone(in).(*pb.CredentialLibrary)
item.CredentialType = string(currentCredentialType)
mapping, update := getMappingUpdates(currentCredentialType, currentMapping, item.GetCredentialMappingOverrides().AsMap(), masks)
if update {
// got mapping update, append mapping override db field mask
dbMasks = append(dbMasks, vault.MappingOverrideField)
mappingStruct, err := structpb.NewStruct(mapping)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
item.CredentialMappingOverrides = mappingStruct
}
cl, err := toStorageVaultLibrary(item.GetCredentialStoreId(), item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cl.PublicId = id
dbMask := maskManager.Translate(mask)
if len(dbMask) == 0 {
dbMasks = append(dbMasks, maskManager.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."})
}
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
out, rowsUpdated, err := repo.UpdateCredentialLibrary(ctx, projId, cl, item.GetVersion(), dbMask)
out, rowsUpdated, err := repo.UpdateCredentialLibrary(ctx, projId, cl, item.GetVersion(), dbMasks)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential library"))
}
@@ -463,13 +505,36 @@ func toProto(in credential.Library, opt ...handlers.Option) (*pb.CredentialLibra
if outputFields.Has(globals.AuthorizedActionsField) {
out.AuthorizedActions = opts.WithAuthorizedActions
}
if outputFields.Has(globals.AttributesField) {
switch credential.SubtypeFromId(in.GetPublicId()) {
case vault.Subtype:
vaultIn, ok := in.(*vault.CredentialLibrary)
if !ok {
return nil, errors.NewDeprecated(errors.Internal, op, "unable to cast to vault credential library")
switch credential.SubtypeFromId(in.GetPublicId()) {
case vault.Subtype:
vaultIn, ok := in.(*vault.CredentialLibrary)
if !ok {
return nil, errors.NewDeprecated(errors.Internal, op, "unable to cast to vault credential library")
}
if outputFields.Has(globals.CredentialTypeField) && vaultIn.GetCredentialType() != string(credential.UnspecifiedType) {
out.CredentialType = vaultIn.GetCredentialType()
if outputFields.Has(globals.CredentialMappingOverridesField) && vaultIn.MappingOverride != nil {
m := make(map[string]interface{})
switch mapping := vaultIn.MappingOverride.(type) {
case *vault.UserPasswordOverride:
if mapping.UsernameAttribute != "" {
m[usernameAttribute] = mapping.UsernameAttribute
}
if mapping.PasswordAttribute != "" {
m[passwordAttribute] = mapping.PasswordAttribute
}
}
if len(m) > 0 {
mp, err := structpb.NewStruct(m)
if err != nil {
return nil, errors.NewDeprecated(errors.Internal, op, "creating proto struct for mapping override")
}
out.CredentialMappingOverrides = mp
}
}
}
if outputFields.Has(globals.AttributesField) {
attrs := &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(vaultIn.GetVaultPath()),
}
@@ -511,6 +576,23 @@ func toStorageVaultLibrary(storeId string, in *pb.CredentialLibrary) (out *vault
opts = append(opts, vault.WithRequestBody([]byte(attrs.GetHttpRequestBody().GetValue())))
}
credentialType := credential.Type(in.GetCredentialType())
switch credentialType {
case credential.UserPasswordType:
opts = append(opts, vault.WithCredentialType(credentialType))
overrides := in.CredentialMappingOverrides.AsMap()
var mapOpts []vault.Option
if username := overrides[usernameAttribute]; username != nil {
mapOpts = append(mapOpts, vault.WithOverrideUsernameAttribute(username.(string)))
}
if password := overrides[passwordAttribute]; password != nil {
mapOpts = append(mapOpts, vault.WithOverridePasswordAttribute(password.(string)))
}
if len(mapOpts) > 0 {
opts = append(opts, vault.WithMappingOverride(vault.NewUserPasswordOverride(mapOpts...)))
}
}
cs, err := vault.NewCredentialLibrary(storeId, attrs.GetPath().GetValue(), opts...)
if err != nil {
return nil, errors.WrapDeprecated(err, op, errors.WithMsg("unable to build credential library"))
@@ -549,6 +631,7 @@ func validateCreateRequest(req *pbs.CreateCredentialLibraryRequest) error {
if b := attrs.GetHttpRequestBody(); b != nil && strings.ToUpper(attrs.GetHttpMethod().GetValue()) != "POST" {
badFields[httpRequestBodyField] = fmt.Sprintf("Field can only be set if %q is set to the value 'POST'.", httpMethodField)
}
validateMapping(badFields, credential.Type(req.GetItem().GetCredentialType()), req.GetItem().CredentialMappingOverrides.AsMap())
default:
badFields[globals.CredentialStoreIdField] = "This field must be a valid credential store id."
}
@@ -556,7 +639,7 @@ func validateCreateRequest(req *pbs.CreateCredentialLibraryRequest) error {
})
}
func validateUpdateRequest(req *pbs.UpdateCredentialLibraryRequest) error {
func validateUpdateRequest(req *pbs.UpdateCredentialLibraryRequest, currentCredentialType credential.Type) error {
return handlers.ValidateUpdateRequest(req, req.GetItem(), func() map[string]string {
badFields := map[string]string{}
switch credential.SubtypeFromId(req.GetId()) {
@@ -564,6 +647,9 @@ func validateUpdateRequest(req *pbs.UpdateCredentialLibraryRequest) error {
if req.GetItem().GetType() != "" && credential.SubtypeFromType(req.GetItem().GetType()) != vault.Subtype {
badFields[globals.TypeField] = "Cannot modify resource type."
}
if req.GetItem().GetCredentialType() != "" && req.GetItem().GetCredentialType() != string(currentCredentialType) {
badFields[globals.CredentialTypeField] = "Cannot modify credential type."
}
attrs := &pb.VaultCredentialLibraryAttributes{}
if err := handlers.StructToProto(req.GetItem().GetAttributes(), attrs); err != nil {
badFields[globals.AttributesField] = "Attribute fields do not match the expected format."
@@ -578,6 +664,7 @@ func validateUpdateRequest(req *pbs.UpdateCredentialLibraryRequest) error {
if b := attrs.GetHttpRequestBody(); b != nil && strings.ToUpper(attrs.GetHttpMethod().GetValue()) == "GET" {
badFields[httpRequestBodyField] = fmt.Sprintf("Field can only be set if %q is set to the value 'POST'.", httpMethodField)
}
validateMapping(badFields, currentCredentialType, req.GetItem().CredentialMappingOverrides.AsMap())
}
return badFields
}, vault.CredentialLibraryPrefix)
@@ -600,3 +687,76 @@ func validateListRequest(req *pbs.ListCredentialLibrariesRequest) error {
}
return nil
}
func validateMapping(badFields map[string]string, credentialType credential.Type, overrides map[string]interface{}) {
validFields := make(map[string]bool)
switch credentialType {
case "", credential.UnspecifiedType:
if len(overrides) > 0 {
badFields[globals.CredentialMappingOverridesField] = fmt.Sprintf("This field can only be set if %q is set", globals.CredentialTypeField)
}
return
case credential.UserPasswordType:
validFields[usernameAttribute] = true
validFields[passwordAttribute] = true
default:
badFields[globals.CredentialTypeField] = fmt.Sprintf("Unknown credential type %q", credentialType)
return
}
for k, v := range overrides {
if ok := validFields[k]; !ok {
badFields[globals.CredentialMappingOverridesField+"."+k] = fmt.Sprintf("Invalid mapping override for credential type %q", credentialType)
continue
}
if _, ok := v.(string); v != nil && !ok {
badFields[globals.CredentialMappingOverridesField+"."+k] = fmt.Sprintf("Mapping value must be a string or a null to clear, got %T", v)
}
}
}
func getMappingUpdates(credentialType credential.Type, current vault.MappingOverride, new map[string]interface{}, apiMasks []string) (map[string]interface{}, bool) {
ret := make(map[string]interface{})
masks := make(map[string]bool)
for _, m := range apiMasks {
if m == credentialMappingPathField {
// got top level credential mapping change request, this mask
// can only be provided when clearing the entire override.
return nil, true
}
credMappingPrefix := fmt.Sprintf("%v.", credentialMappingPathField)
if s := strings.SplitN(m, credMappingPrefix, 2); len(s) == 2 {
masks[s[1]] = true
}
}
if len(masks) == 0 {
// no mapping updates
return nil, false
}
switch credentialType {
case credential.UserPasswordType:
var currentUser, currentPass interface{}
if overrides, ok := current.(*vault.UserPasswordOverride); ok {
currentUser = overrides.UsernameAttribute
currentPass = overrides.PasswordAttribute
}
switch {
case masks[usernameAttribute]:
ret[usernameAttribute] = new[usernameAttribute]
default:
ret[usernameAttribute] = currentUser
}
switch {
case masks[passwordAttribute]:
ret[passwordAttribute] = new[passwordAttribute]
default:
ret[passwordAttribute] = currentPass
}
}
return ret, true
}
@@ -1,12 +1,14 @@
package credentiallibraries
import (
"context"
"fmt"
"sort"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
@@ -326,6 +328,47 @@ func TestCreate(t *testing.T) {
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Invalid credential type",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Attributes: func() *structpb.Struct {
attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
})
require.NoError(t, err)
return attrs
}(),
CredentialType: "fake-type",
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Invalid user_password mapping",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Attributes: func() *structpb.Struct {
attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
})
require.NoError(t, err)
return attrs
}(),
CredentialType: string(credential.UserPasswordType),
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]interface{}{
usernameAttribute: "user-test",
"invalid": "invalid-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Using POST method",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
@@ -403,6 +446,151 @@ func TestCreate(t *testing.T) {
},
},
},
{
name: "Create a valid vault CredentialLibrary user_password type",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Attributes: func() *structpb.Struct {
attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
})
require.NoError(t, err)
return attrs
}(),
CredentialType: string(credential.UserPasswordType),
}},
idPrefix: vault.CredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", vault.CredentialLibraryPrefix),
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.Subtype.String(),
Attributes: func() *structpb.Struct {
attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("GET"),
})
require.NoError(t, err)
return attrs
}(),
CredentialType: string(credential.UserPasswordType),
AuthorizedActions: testAuthorizedActions,
},
},
},
{
name: "Create a valid vault CredentialLibrary user_password type with username mapping",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Attributes: func() *structpb.Struct {
attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
})
require.NoError(t, err)
return attrs
}(),
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]interface{}{
usernameAttribute: "user-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
CredentialType: string(credential.UserPasswordType),
}},
idPrefix: vault.CredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", vault.CredentialLibraryPrefix),
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.Subtype.String(),
Attributes: func() *structpb.Struct {
attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("GET"),
})
require.NoError(t, err)
return attrs
}(),
CredentialType: string(credential.UserPasswordType),
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]interface{}{
usernameAttribute: "user-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
AuthorizedActions: testAuthorizedActions,
},
},
},
{
name: "Create a valid vault CredentialLibrary user_password type with username/password mapping",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Attributes: func() *structpb.Struct {
attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
})
require.NoError(t, err)
return attrs
}(),
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]interface{}{
usernameAttribute: "user-test",
passwordAttribute: "pass-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
CredentialType: string(credential.UserPasswordType),
}},
idPrefix: vault.CredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", vault.CredentialLibraryPrefix),
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.Subtype.String(),
Attributes: func() *structpb.Struct {
attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("GET"),
})
require.NoError(t, err)
return attrs
}(),
CredentialType: string(credential.UserPasswordType),
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]interface{}{
usernameAttribute: "user-test",
passwordAttribute: "pass-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
AuthorizedActions: testAuthorizedActions,
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
@@ -461,10 +649,23 @@ func TestGet(t *testing.T) {
_, prj := iam.TestScopes(t, iamRepo)
store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
vl := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0]
unspecifiedLib := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0]
s, err := NewService(repoFn, iamRepoFn)
require.NoError(t, err)
repo, err := repoFn()
require.NoError(t, err)
lib, err := vault.NewCredentialLibrary(store.GetPublicId(), "vault/path",
vault.WithCredentialType("user_password"),
vault.WithMappingOverride(
vault.NewUserPasswordOverride(
vault.WithOverrideUsernameAttribute("user"),
vault.WithOverridePasswordAttribute("pass"),
)))
require.NoError(t, err)
userPassLib, err := repo.CreateCredentialLibrary(context.Background(), prj.GetPublicId(), lib)
require.NoError(t, err)
cases := []struct {
name string
id string
@@ -473,21 +674,21 @@ func TestGet(t *testing.T) {
}{
{
name: "success",
id: vl.GetPublicId(),
id: unspecifiedLib.GetPublicId(),
res: &pbs.GetCredentialLibraryResponse{
Item: &pb.CredentialLibrary{
Id: vl.GetPublicId(),
CredentialStoreId: vl.GetStoreId(),
Id: unspecifiedLib.GetPublicId(),
CredentialStoreId: unspecifiedLib.GetStoreId(),
Scope: &scopepb.ScopeInfo{Id: store.GetScopeId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
Type: vault.Subtype.String(),
AuthorizedActions: testAuthorizedActions,
CreatedTime: vl.CreateTime.GetTimestamp(),
UpdatedTime: vl.UpdateTime.GetTimestamp(),
CreatedTime: unspecifiedLib.CreateTime.GetTimestamp(),
UpdatedTime: unspecifiedLib.UpdateTime.GetTimestamp(),
Version: 1,
Attributes: func() *structpb.Struct {
attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(vl.GetVaultPath()),
HttpMethod: wrapperspb.String(vl.GetHttpMethod()),
Path: wrapperspb.String(unspecifiedLib.GetVaultPath()),
HttpMethod: wrapperspb.String(unspecifiedLib.GetHttpMethod()),
})
require.NoError(t, err)
return attrs
@@ -495,6 +696,40 @@ func TestGet(t *testing.T) {
},
},
},
{
name: "success-userpassword",
id: userPassLib.GetPublicId(),
res: &pbs.GetCredentialLibraryResponse{
Item: &pb.CredentialLibrary{
Id: userPassLib.GetPublicId(),
CredentialStoreId: userPassLib.GetStoreId(),
Scope: &scopepb.ScopeInfo{Id: store.GetScopeId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
Type: vault.Subtype.String(),
AuthorizedActions: testAuthorizedActions,
CreatedTime: userPassLib.CreateTime.GetTimestamp(),
UpdatedTime: userPassLib.UpdateTime.GetTimestamp(),
Version: 1,
Attributes: func() *structpb.Struct {
attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(userPassLib.GetVaultPath()),
HttpMethod: wrapperspb.String(userPassLib.GetHttpMethod()),
})
require.NoError(t, err)
return attrs
}(),
CredentialType: "user_password",
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]interface{}{
usernameAttribute: "user",
passwordAttribute: "pass",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
},
{
name: "not found error",
id: fmt.Sprintf("%s_1234567890", vault.CredentialLibraryPrefix),
@@ -612,8 +847,14 @@ func TestUpdate(t *testing.T) {
cs := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2)
store, diffStore := cs[0], cs[1]
freshLibrary := func() (*vault.CredentialLibrary, func()) {
vl := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0]
freshLibrary := func(opt ...vault.Option) (*vault.CredentialLibrary, func()) {
repo, err := repoFn()
require.NoError(t, err)
lib, err := vault.NewCredentialLibrary(store.GetPublicId(), "vault/path", opt...)
require.NoError(t, err)
vl, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), lib)
require.NoError(t, err)
clean := func() {
_, err := s.DeleteCredentialLibrary(ctx, &pbs.DeleteCredentialLibraryRequest{Id: vl.GetPublicId()})
require.NoError(t, err)
@@ -629,8 +870,12 @@ func TestUpdate(t *testing.T) {
_, token := v.CreateToken(t)
_ = token
usernameAttrField := fmt.Sprintf("%v.%v", credentialMappingPathField, usernameAttribute)
passwordAttrField := fmt.Sprintf("%v.%v", credentialMappingPathField, passwordAttribute)
successCases := []struct {
name string
opts []vault.Option
req *pbs.UpdateCredentialLibraryRequest
res func(*pb.CredentialLibrary) *pb.CredentialLibrary
}{
@@ -666,7 +911,7 @@ func TestUpdate(t *testing.T) {
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.Attributes.Fields["path"] = structpb.NewStringValue("vault/path0")
out.Attributes.Fields["path"] = structpb.NewStringValue("vault/path")
out.Attributes.Fields["http_method"] = structpb.NewStringValue("POST")
return out
},
@@ -688,7 +933,7 @@ func TestUpdate(t *testing.T) {
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.Attributes.Fields["path"] = structpb.NewStringValue("vault/path0")
out.Attributes.Fields["path"] = structpb.NewStringValue("vault/path")
out.Attributes.Fields["http_method"] = structpb.NewStringValue("POST")
out.Attributes.Fields["http_request_body"] = structpb.NewStringValue("body")
return out
@@ -714,12 +959,226 @@ func TestUpdate(t *testing.T) {
return out
},
},
{
name: "user-password-attributes-change-username-attribute",
opts: []vault.Option{
vault.WithCredentialType("user_password"),
vault.WithMappingOverride(
vault.NewUserPasswordOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
vault.WithOverridePasswordAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(usernameAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]interface{}{
usernameAttribute: "changed-user",
}
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[usernameAttribute] = structpb.NewStringValue("changed-user")
return out
},
},
{
name: "user-password-attributes-change-password-attribute",
opts: []vault.Option{
vault.WithCredentialType("user_password"),
vault.WithMappingOverride(
vault.NewUserPasswordOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
vault.WithOverridePasswordAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passwordAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]interface{}{
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: "user-password-attributes-change-username-and-password-attributes",
opts: []vault.Option{
vault.WithCredentialType("user_password"),
vault.WithMappingOverride(
vault.NewUserPasswordOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
vault.WithOverridePasswordAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passwordAttrField, usernameAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]interface{}{
usernameAttribute: "changed-user",
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[usernameAttribute] = structpb.NewStringValue("changed-user")
out.CredentialMappingOverrides.Fields[passwordAttribute] = structpb.NewStringValue("changed-pass")
return out
},
},
{
name: "no-mapping-override-change-username-and-password-attributes",
opts: []vault.Option{
vault.WithCredentialType("user_password"),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passwordAttrField, usernameAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]interface{}{
usernameAttribute: "new-user",
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]interface{}{
usernameAttribute: "new-user",
passwordAttribute: "new-pass",
}
var err error
out.CredentialMappingOverrides, err = structpb.NewStruct(v)
require.NoError(t, err)
return out
},
},
{
name: "user-password-attributes-delete-mapping-override",
opts: []vault.Option{
vault.WithCredentialType("user_password"),
vault.WithMappingOverride(
vault.NewUserPasswordOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
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{
vault.WithCredentialType("user_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: "user-password-attributes-delete-mapping-override-field-specific",
opts: []vault.Option{
vault.WithCredentialType("user_password"),
vault.WithMappingOverride(
vault.NewUserPasswordOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
vault.WithOverridePasswordAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passwordAttrField, usernameAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]interface{}{
usernameAttribute: nil,
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: "no-mapping-override-delete-mapping-override-field-specific",
opts: []vault.Option{
vault.WithCredentialType("user_password"),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passwordAttrField, usernameAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]interface{}{
usernameAttribute: nil,
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
},
},
}
for _, tc := range successCases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
st, cleanup := freshLibrary()
st, cleanup := freshLibrary(tc.opts...)
defer cleanup()
if tc.req.Item.GetVersion() == 0 {
@@ -782,6 +1241,11 @@ func TestUpdate(t *testing.T) {
path: "authorized actions",
item: &pb.CredentialLibrary{AuthorizedActions: append(testAuthorizedActions, "another")},
},
{
name: "read only credential type",
path: "credential_type",
item: &pb.CredentialLibrary{CredentialType: string(credential.UserPasswordType)},
},
}
for _, tc := range errCases {
t.Run(tc.name, func(t *testing.T) {
@@ -54,6 +54,10 @@ type CredentialLibrary struct {
Attributes *structpb.Struct `protobuf:"bytes,100,opt,name=attributes,proto3" json:"attributes,omitempty"`
// Output only. The available actions on this resource for this user.
AuthorizedActions []string `protobuf:"bytes,300,rep,name=authorized_actions,proto3" json:"authorized_actions,omitempty"`
// The type of credential this library will issue, defaults to Unspecified
CredentialType string `protobuf:"bytes,310,opt,name=credential_type,proto3" json:"credential_type,omitempty"`
// The credential mapping overrides
CredentialMappingOverrides *structpb.Struct `protobuf:"bytes,320,opt,name=credential_mapping_overrides,proto3" json:"credential_mapping_overrides,omitempty"`
}
func (x *CredentialLibrary) Reset() {
@@ -165,6 +169,20 @@ func (x *CredentialLibrary) GetAuthorizedActions() []string {
return nil
}
func (x *CredentialLibrary) GetCredentialType() string {
if x != nil {
return x.CredentialType
}
return ""
}
func (x *CredentialLibrary) GetCredentialMappingOverrides() *structpb.Struct {
if x != nil {
return x.CredentialMappingOverrides
}
return nil
}
// The attributes of a vault typed Credential Library.
type VaultCredentialLibraryAttributes struct {
state protoimpl.MessageState
@@ -254,7 +272,7 @@ var file_controller_api_resources_credentiallibraries_v1_credential_library_prot
0x63, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74,
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe4, 0x04, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x64, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf9, 0x05, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x64, 0x65,
0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02,
0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x30, 0x0a, 0x13,
0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65,
@@ -292,37 +310,47 @@ var file_controller_api_resources_credentiallibraries_v1_credential_library_prot
0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x12,
0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f,
0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xee, 0x02,
0x0a, 0x20, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61,
0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
0x65, 0x73, 0x12, 0x56, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x24,
0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1c, 0x0a, 0x0f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62,
0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x12, 0x09, 0x56, 0x61, 0x75, 0x6c, 0x74,
0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x6c, 0x0a, 0x0b, 0x68, 0x74,
0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x2c, 0xa0,
0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x24, 0x0a, 0x16, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75,
0x74, 0x65, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12,
0x0a, 0x48, 0x74, 0x74, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0b, 0x68, 0x74, 0x74,
0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x83, 0x01, 0x0a, 0x11, 0x68, 0x74, 0x74,
0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x1e,
0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2f, 0x0a,
0x0f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65,
0x18, 0xb6, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x0f, 0x63,
0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x12, 0x62,
0x0a, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6d, 0x61, 0x70,
0x70, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x73, 0x18, 0xc0,
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x04,
0xa0, 0xda, 0x29, 0x01, 0x52, 0x1c, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,
0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
0x65, 0x73, 0x22, 0xee, 0x02, 0x0a, 0x20, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x72, 0x65, 0x64,
0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x41, 0x74, 0x74,
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x56, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18,
0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61,
0x6c, 0x75, 0x65, 0x42, 0x24, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1c, 0x0a, 0x0f, 0x61,
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x12, 0x09,
0x56, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12,
0x6c, 0x0a, 0x0b, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x14,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c,
0x75, 0x65, 0x42, 0x37, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x2f, 0x0a, 0x1c, 0x61, 0x74,
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x0f, 0x48, 0x74, 0x74, 0x70,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x11, 0x68, 0x74, 0x74,
0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x42, 0x68,
0x5a, 0x66, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73,
0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f,
0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73,
0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x69, 0x62, 0x72, 0x61,
0x72, 0x69, 0x65, 0x73, 0x3b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c,
0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x75, 0x65, 0x42, 0x2c, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x24, 0x0a, 0x16, 0x61, 0x74,
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x6d, 0x65,
0x74, 0x68, 0x6f, 0x64, 0x12, 0x0a, 0x48, 0x74, 0x74, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64,
0x52, 0x0b, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x83, 0x01,
0x0a, 0x11, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x62,
0x6f, 0x64, 0x79, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69,
0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x37, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29,
0x2f, 0x0a, 0x1c, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x68, 0x74,
0x74, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x12,
0x0f, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79,
0x52, 0x11, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x62,
0x6f, 0x64, 0x79, 0x42, 0x68, 0x5a, 0x66, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e,
0x64, 0x61, 0x72, 0x79, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,
0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x3b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e,
0x74, 0x69, 0x61, 0x6c, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -347,20 +375,21 @@ var file_controller_api_resources_credentiallibraries_v1_credential_library_prot
(*structpb.Struct)(nil), // 5: google.protobuf.Struct
}
var file_controller_api_resources_credentiallibraries_v1_credential_library_proto_depIdxs = []int32{
2, // 0: controller.api.resources.credentiallibraries.v1.CredentialLibrary.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
3, // 1: controller.api.resources.credentiallibraries.v1.CredentialLibrary.name:type_name -> google.protobuf.StringValue
3, // 2: controller.api.resources.credentiallibraries.v1.CredentialLibrary.description:type_name -> google.protobuf.StringValue
4, // 3: controller.api.resources.credentiallibraries.v1.CredentialLibrary.created_time:type_name -> google.protobuf.Timestamp
4, // 4: controller.api.resources.credentiallibraries.v1.CredentialLibrary.updated_time:type_name -> google.protobuf.Timestamp
5, // 5: controller.api.resources.credentiallibraries.v1.CredentialLibrary.attributes:type_name -> google.protobuf.Struct
3, // 6: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.path:type_name -> google.protobuf.StringValue
3, // 7: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.http_method:type_name -> google.protobuf.StringValue
3, // 8: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.http_request_body:type_name -> google.protobuf.StringValue
9, // [9:9] is the sub-list for method output_type
9, // [9:9] is the sub-list for method input_type
9, // [9:9] is the sub-list for extension type_name
9, // [9:9] is the sub-list for extension extendee
0, // [0:9] is the sub-list for field type_name
2, // 0: controller.api.resources.credentiallibraries.v1.CredentialLibrary.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
3, // 1: controller.api.resources.credentiallibraries.v1.CredentialLibrary.name:type_name -> google.protobuf.StringValue
3, // 2: controller.api.resources.credentiallibraries.v1.CredentialLibrary.description:type_name -> google.protobuf.StringValue
4, // 3: controller.api.resources.credentiallibraries.v1.CredentialLibrary.created_time:type_name -> google.protobuf.Timestamp
4, // 4: controller.api.resources.credentiallibraries.v1.CredentialLibrary.updated_time:type_name -> google.protobuf.Timestamp
5, // 5: controller.api.resources.credentiallibraries.v1.CredentialLibrary.attributes:type_name -> google.protobuf.Struct
5, // 6: controller.api.resources.credentiallibraries.v1.CredentialLibrary.credential_mapping_overrides:type_name -> google.protobuf.Struct
3, // 7: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.path:type_name -> google.protobuf.StringValue
3, // 8: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.http_method:type_name -> google.protobuf.StringValue
3, // 9: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.http_request_body:type_name -> google.protobuf.StringValue
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
}
func init() { file_controller_api_resources_credentiallibraries_v1_credential_library_proto_init() }