Add ssh private key type and add type to static store (#2262)
Co-authored-by: Louis Ruch <louisruch@gmail.com>
This commit is contained in:
1 parent
4b3882c122
commit
011e2e7425
25 files changed
+2633
-320
No files matched your search
@@ -72,7 +72,7 @@ require (
|
||||
github.com/stretchr/testify v1.7.2
|
||||
github.com/zalando/go-keyring v0.2.1
|
||||
go.uber.org/atomic v1.9.0
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
|
||||
@@ -1361,8 +1361,8 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
||||
@@ -28,6 +28,7 @@ type Type string
|
||||
const (
|
||||
UnspecifiedType Type = "unspecified"
|
||||
UsernamePasswordType Type = "username_password"
|
||||
SshPrivateKeyType Type = "ssh_private_key"
|
||||
)
|
||||
|
||||
// A Library is a resource that provides credentials that are of the same
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package credential
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/types/subtypes"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := subtypes.Register(Domain, UsernamePasswordSubtype, UsernamePasswordCredentialPrefix, PreviousUsernamePasswordCredentialPrefix); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := subtypes.Register(Domain, SshPrivateKeySubtype, SshPrivateKeyCredentialPrefix); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
UsernamePasswordCredentialPrefix = "credup"
|
||||
PreviousUsernamePasswordCredentialPrefix = "cred"
|
||||
UsernamePasswordSubtype = subtypes.Subtype("username_password")
|
||||
|
||||
SshPrivateKeyCredentialPrefix = "credspk"
|
||||
SshPrivateKeySubtype = subtypes.Subtype("ssh_private_key")
|
||||
)
|
||||
|
||||
func NewUsernamePasswordCredentialId(ctx context.Context) (string, error) {
|
||||
id, err := db.NewPublicId(UsernamePasswordCredentialPrefix)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(ctx, err, "credential.NewUsernamePasswordCredentialId")
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func NewSshPrivateKeyCredentialId(ctx context.Context) (string, error) {
|
||||
id, err := db.NewPublicId(SshPrivateKeyCredentialPrefix)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(ctx, err, "credential.NewSshPrivateKeyCredentialId")
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
@@ -6,4 +6,5 @@ const (
|
||||
descriptionField = "Description"
|
||||
usernameField = "Username"
|
||||
passwordField = "Password"
|
||||
privateKeyField = "PrivateKey"
|
||||
)
|
||||
@@ -13,20 +13,14 @@ func init() {
|
||||
if err := subtypes.Register(credential.Domain, Subtype, CredentialStorePrefix, PreviousCredentialStorePrefix); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := subtypes.Register(credential.Domain, UsernamePasswordSubtype, UsernamePasswordCredentialPrefix, PreviousUsernamePasswordCredentialPrefix); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// PublicId prefixes for the resources in the static package.
|
||||
const (
|
||||
CredentialStorePrefix = "csst"
|
||||
PreviousCredentialStorePrefix = "cs"
|
||||
UsernamePasswordCredentialPrefix = "credup"
|
||||
PreviousUsernamePasswordCredentialPrefix = "cred"
|
||||
CredentialStorePrefix = "csst"
|
||||
PreviousCredentialStorePrefix = "cs"
|
||||
|
||||
Subtype = subtypes.Subtype("static")
|
||||
UsernamePasswordSubtype = subtypes.Subtype("username_password")
|
||||
Subtype = subtypes.Subtype("static")
|
||||
)
|
||||
|
||||
func newCredentialStoreId(ctx context.Context) (string, error) {
|
||||
@@ -36,11 +30,3 @@ func newCredentialStoreId(ctx context.Context) (string, error) {
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func newUsernamePasswordCredentialId(ctx context.Context) (string, error) {
|
||||
id, err := db.NewPublicId(UsernamePasswordCredentialPrefix)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(ctx, err, "static.newUsernamePasswordCredentialId")
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
@@ -5,11 +5,13 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/credential"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
dbcommon "github.com/hashicorp/boundary/internal/db/common"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
"github.com/hashicorp/boundary/internal/types/subtypes"
|
||||
)
|
||||
|
||||
// CreateUsernamePasswordCredential inserts c into the repository and returns a new
|
||||
@@ -53,7 +55,7 @@ func (r *Repository) CreateUsernamePasswordCredential(
|
||||
}
|
||||
|
||||
c = c.clone()
|
||||
id, err := newUsernamePasswordCredentialId(ctx)
|
||||
id, err := credential.NewUsernamePasswordCredentialId(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
@@ -91,33 +93,139 @@ func (r *Repository) CreateUsernamePasswordCredential(
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in store: %s", c.StoreId)))
|
||||
}
|
||||
|
||||
// Clear password fields, only passwordHmac should be returned
|
||||
// Clear password fields, only PasswordHmac should be returned
|
||||
newCred.CtPassword = nil
|
||||
newCred.Password = nil
|
||||
|
||||
return newCred, nil
|
||||
}
|
||||
|
||||
// CreateSshPrivateKeyCredential inserts c into the repository and returns a new
|
||||
// SshPrivateKeyCredential containing the credential's PublicId. c is not
|
||||
// changed. c must not contain a PublicId. The PublicId is generated and
|
||||
// assigned by this method. c must contain a valid StoreId.
|
||||
//
|
||||
// The private key is encrypted and a HmacSha256 of the private key is
|
||||
// calculated. Only the PrivateKeyHmac is returned, the plain-text and encrypted
|
||||
// private key is not returned.
|
||||
//
|
||||
// Both c.Name and c.Description are optional. If c.Name is set, it must be
|
||||
// unique within c.ScopeId. Both c.CreateTime and c.UpdateTime are ignored.
|
||||
func (r *Repository) CreateSshPrivateKeyCredential(
|
||||
ctx context.Context,
|
||||
scopeId string,
|
||||
c *SshPrivateKeyCredential,
|
||||
_ ...Option,
|
||||
) (*SshPrivateKeyCredential, error) {
|
||||
const op = "static.(Repository).CreateSshPrivateKeyCredential"
|
||||
if c == nil {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing credential")
|
||||
}
|
||||
if c.SshPrivateKeyCredential == nil {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing embedded credential")
|
||||
}
|
||||
if scopeId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
if c.Username == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing username")
|
||||
}
|
||||
if c.PrivateKey == nil {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private key")
|
||||
}
|
||||
if c.StoreId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing store id")
|
||||
}
|
||||
if c.PublicId != "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty")
|
||||
}
|
||||
|
||||
c = c.clone()
|
||||
id, err := credential.NewSshPrivateKeyCredentialId(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
c.PublicId = id
|
||||
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
|
||||
}
|
||||
|
||||
// encrypt
|
||||
databaseWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper"))
|
||||
}
|
||||
if err := c.encrypt(ctx, databaseWrapper); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
var newCred *SshPrivateKeyCredential
|
||||
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, w db.Writer) error {
|
||||
newCred = c.clone()
|
||||
if err := w.Create(ctx, newCred,
|
||||
db.WithOplog(oplogWrapper, newCred.oplog(oplog.OpType_OP_TYPE_CREATE))); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if errors.IsUniqueError(err) {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in store: %s: name %s already exists", c.StoreId, c.Name)))
|
||||
}
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in store: %s", c.StoreId)))
|
||||
}
|
||||
|
||||
// Clear private key fields, only PrivateKeyHmac should be returned
|
||||
newCred.PrivateKeyEncrypted = nil
|
||||
newCred.PrivateKey = nil
|
||||
|
||||
return newCred, nil
|
||||
}
|
||||
|
||||
// LookupCredential returns the Credential for the publicId. Returns
|
||||
// nil, nil if no Credential is found for the publicId.
|
||||
// TODO: This should hit a view and return the interface type...
|
||||
func (r *Repository) LookupCredential(ctx context.Context, publicId string, _ ...Option) (*UsernamePasswordCredential, error) {
|
||||
func (r *Repository) LookupCredential(ctx context.Context, publicId string, _ ...Option) (credential.Static, error) {
|
||||
const op = "static.(Repository).LookupCredential"
|
||||
if publicId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id")
|
||||
}
|
||||
cred := allocUsernamePasswordCredential()
|
||||
cred.PublicId = publicId
|
||||
if err := r.reader.LookupByPublicId(ctx, cred); err != nil {
|
||||
if errors.IsNotFoundError(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", publicId)))
|
||||
}
|
||||
|
||||
// Clear password fields, only passwordHmac should be returned
|
||||
cred.CtPassword = nil
|
||||
cred.Password = nil
|
||||
var cred credential.Static
|
||||
|
||||
switch subtypes.SubtypeFromId(credential.Domain, publicId) {
|
||||
case credential.UsernamePasswordSubtype:
|
||||
upCred := allocUsernamePasswordCredential()
|
||||
upCred.PublicId = publicId
|
||||
if err := r.reader.LookupByPublicId(ctx, upCred); err != nil {
|
||||
if errors.IsNotFoundError(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", publicId)))
|
||||
}
|
||||
// Clear password fields, only passwordHmac should be returned
|
||||
upCred.CtPassword = nil
|
||||
upCred.Password = nil
|
||||
cred = upCred
|
||||
|
||||
case credential.SshPrivateKeySubtype:
|
||||
spkCred := allocSshPrivateKeyCredential()
|
||||
spkCred.PublicId = publicId
|
||||
if err := r.reader.LookupByPublicId(ctx, spkCred); err != nil {
|
||||
if errors.IsNotFoundError(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", publicId)))
|
||||
}
|
||||
// Clear private key fields, only privateKeyHmac should be returned
|
||||
spkCred.PrivateKeyEncrypted = nil
|
||||
spkCred.PrivateKey = nil
|
||||
cred = spkCred
|
||||
}
|
||||
|
||||
return cred, nil
|
||||
}
|
||||
@@ -141,10 +249,10 @@ func (r *Repository) UpdateUsernamePasswordCredential(ctx context.Context,
|
||||
) (*UsernamePasswordCredential, int, error) {
|
||||
const op = "static.(Repository).UpdateUsernamePasswordCredential"
|
||||
if c == nil {
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing Credential")
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing credential")
|
||||
}
|
||||
if c.UsernamePasswordCredential == nil {
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing embedded Credential")
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing embedded credential")
|
||||
}
|
||||
if c.PublicId == "" {
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidPublicId, op, "missing public id")
|
||||
@@ -229,17 +337,132 @@ func (r *Repository) UpdateUsernamePasswordCredential(ctx context.Context,
|
||||
return nil, db.NoRowsAffected, err
|
||||
}
|
||||
|
||||
// Clear password fields, only passwordHmac should be returned
|
||||
// Clear password fields, only PasswordHmac should be returned
|
||||
returnedCredential.CtPassword = nil
|
||||
returnedCredential.Password = nil
|
||||
|
||||
return returnedCredential, rowsUpdated, nil
|
||||
}
|
||||
|
||||
// UpdateSshPrivateKeyCredential updates the repository entry for c.PublicId
|
||||
// with the values in c for the fields listed in fieldMaskPaths. It returns a
|
||||
// new SshPrivateKeyCredential containing the updated values and a count of the
|
||||
// number of records updated. c is not changed.
|
||||
//
|
||||
// c must contain a valid PublicId. Only Name, Description, Username and
|
||||
// PrivateKey can be changed. If c.Name is set to a non-empty string, it must be
|
||||
// unique within c.ScopeId.
|
||||
//
|
||||
// An attribute of c will be set to NULL in the database if the attribute in c
|
||||
// is the zero value and it is included in fieldMaskPaths.
|
||||
func (r *Repository) UpdateSshPrivateKeyCredential(ctx context.Context,
|
||||
scopeId string,
|
||||
c *SshPrivateKeyCredential,
|
||||
version uint32,
|
||||
fieldMaskPaths []string,
|
||||
_ ...Option,
|
||||
) (*SshPrivateKeyCredential, int, error) {
|
||||
const op = "static.(Repository).UpdateSshPrivateKeyCredential"
|
||||
if c == nil {
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing credential")
|
||||
}
|
||||
if c.SshPrivateKeyCredential == nil {
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing embedded credential")
|
||||
}
|
||||
if c.PublicId == "" {
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidPublicId, op, "missing public id")
|
||||
}
|
||||
if version == 0 {
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version")
|
||||
}
|
||||
if scopeId == "" {
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
if c.StoreId == "" {
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing store id")
|
||||
}
|
||||
c = c.clone()
|
||||
|
||||
for _, f := range fieldMaskPaths {
|
||||
switch {
|
||||
case strings.EqualFold(nameField, f):
|
||||
case strings.EqualFold(descriptionField, f):
|
||||
case strings.EqualFold(usernameField, f):
|
||||
case strings.EqualFold(privateKeyField, f):
|
||||
default:
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, f)
|
||||
}
|
||||
}
|
||||
dbMask, nullFields := dbcommon.BuildUpdatePaths(
|
||||
map[string]interface{}{
|
||||
nameField: c.Name,
|
||||
descriptionField: c.Description,
|
||||
usernameField: c.Username,
|
||||
privateKeyField: c.PrivateKey,
|
||||
},
|
||||
fieldMaskPaths,
|
||||
nil,
|
||||
)
|
||||
if len(dbMask) == 0 && len(nullFields) == 0 {
|
||||
return nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "missing field mask")
|
||||
}
|
||||
|
||||
for _, f := range fieldMaskPaths {
|
||||
if strings.EqualFold(privateKeyField, f) {
|
||||
// Private key has been updated, re-encrypt and recalculate hmac
|
||||
databaseWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper"))
|
||||
}
|
||||
if err := c.encrypt(ctx, databaseWrapper); err != nil {
|
||||
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
// Set PrivateKeyHmac and PrivateKeyEncrypted masks for update.
|
||||
dbMask = append(dbMask, "PrivateKeyHmac", "PrivateKeyEncrypted")
|
||||
}
|
||||
}
|
||||
|
||||
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
|
||||
if err != nil {
|
||||
return nil, db.NoRowsAffected,
|
||||
errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
|
||||
}
|
||||
|
||||
var rowsUpdated int
|
||||
var returnedCredential *SshPrivateKeyCredential
|
||||
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, w db.Writer) error {
|
||||
returnedCredential = c.clone()
|
||||
var err error
|
||||
rowsUpdated, err = w.Update(ctx, returnedCredential,
|
||||
dbMask, nullFields,
|
||||
db.WithOplog(oplogWrapper, returnedCredential.oplog(oplog.OpType_OP_TYPE_UPDATE)),
|
||||
db.WithVersion(&version))
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if rowsUpdated > 1 {
|
||||
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been updated")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, db.NoRowsAffected, err
|
||||
}
|
||||
|
||||
// Clear private key fields, only PrivateKeyHmac should be returned
|
||||
returnedCredential.PrivateKeyEncrypted = nil
|
||||
returnedCredential.PrivateKey = nil
|
||||
|
||||
return returnedCredential, rowsUpdated, nil
|
||||
}
|
||||
|
||||
// ListCredentials returns a slice of UsernamePasswordCredentials for the
|
||||
// scopeIds. WithLimit is the only option supported.
|
||||
// TODO: This should hit a view and return the interface type...
|
||||
func (r *Repository) ListCredentials(ctx context.Context, storeId string, opt ...Option) ([]*UsernamePasswordCredential, error) {
|
||||
func (r *Repository) ListCredentials(ctx context.Context, storeId string, opt ...Option) ([]credential.Static, error) {
|
||||
const op = "static.(Repository).ListCredentials"
|
||||
if storeId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no storeId")
|
||||
@@ -250,19 +473,36 @@ func (r *Repository) ListCredentials(ctx context.Context, storeId string, opt ..
|
||||
// non-zero signals an override of the default limit for the repo.
|
||||
limit = opts.withLimit
|
||||
}
|
||||
var creds []*UsernamePasswordCredential
|
||||
err := r.reader.SearchWhere(ctx, &creds, "store_id = ?", []interface{}{storeId}, db.WithLimit(limit))
|
||||
|
||||
var upCreds []*UsernamePasswordCredential
|
||||
err := r.reader.SearchWhere(ctx, &upCreds, "store_id = ?", []interface{}{storeId}, db.WithLimit(limit))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
for _, c := range creds {
|
||||
// Clear password fields, only passwordHmac should be returned
|
||||
c.CtPassword = nil
|
||||
c.Password = nil
|
||||
var spkCreds []*SshPrivateKeyCredential
|
||||
err = r.reader.SearchWhere(ctx, &spkCreds, "store_id = ?", []interface{}{storeId}, db.WithLimit(limit))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
return creds, nil
|
||||
ret := make([]credential.Static, 0, len(upCreds)+len(spkCreds))
|
||||
|
||||
for _, c := range upCreds {
|
||||
// Clear password fields, only PasswordHmac should be returned
|
||||
c.CtPassword = nil
|
||||
c.Password = nil
|
||||
ret = append(ret, c)
|
||||
}
|
||||
|
||||
for _, c := range spkCreds {
|
||||
// Clear private key fields, only PrivateKeyHmac should be returned
|
||||
c.PrivateKeyEncrypted = nil
|
||||
c.PrivateKey = nil
|
||||
ret = append(ret, c)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// DeleteCredential deletes publicId from the repository and returns
|
||||
@@ -277,8 +517,22 @@ func (r *Repository) DeleteCredential(ctx context.Context, scopeId, id string, _
|
||||
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no scope id")
|
||||
}
|
||||
|
||||
c := allocUsernamePasswordCredential()
|
||||
c.PublicId = id
|
||||
var input interface{}
|
||||
var md oplog.Metadata
|
||||
switch subtypes.SubtypeFromId(credential.Domain, id) {
|
||||
case credential.UsernamePasswordSubtype:
|
||||
c := allocUsernamePasswordCredential()
|
||||
c.PublicId = id
|
||||
input = c
|
||||
md = c.oplog(oplog.OpType_OP_TYPE_DELETE)
|
||||
case credential.SshPrivateKeySubtype:
|
||||
c := allocSshPrivateKeyCredential()
|
||||
c.PublicId = id
|
||||
input = c
|
||||
md = c.oplog(oplog.OpType_OP_TYPE_DELETE)
|
||||
default:
|
||||
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "unknown type")
|
||||
}
|
||||
|
||||
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
|
||||
if err != nil {
|
||||
@@ -291,7 +545,7 @@ func (r *Repository) DeleteCredential(ctx context.Context, scopeId, id string, _
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(_ db.Reader, w db.Writer) (err error) {
|
||||
rowsDeleted, err = w.Delete(ctx, c, db.WithOplog(oplogWrapper, c.oplog(oplog.OpType_OP_TYPE_DELETE)))
|
||||
rowsDeleted, err = w.Delete(ctx, input, db.WithOplog(oplogWrapper, md))
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
@@ -140,12 +140,14 @@ func TestRepository_CreateCredentialStore(t *testing.T) {
|
||||
assert.Equal(in.Description, got.Description)
|
||||
|
||||
in2, err := NewCredentialStore(prj.GetPublicId(), WithName("my-name"), WithDescription("desc"))
|
||||
require.NoError(err)
|
||||
got2, err := repo.CreateCredentialStore(ctx, in2)
|
||||
assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "want err code: %v got err: %v", errors.NotUnique, err)
|
||||
assert.Nil(got2)
|
||||
|
||||
// Creating credential in different scope should not conflict
|
||||
in3, err := NewCredentialStore(org.GetPublicId(), WithName("my-name"), WithDescription("desc"))
|
||||
require.NoError(err)
|
||||
got3, err := repo.CreateCredentialStore(ctx, in3)
|
||||
require.NoError(err)
|
||||
assert.Equal(in.Name, got3.Name)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/credential"
|
||||
"github.com/hashicorp/boundary/internal/credential/static/store"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
dbassert "github.com/hashicorp/boundary/internal/db/assert"
|
||||
@@ -121,7 +122,7 @@ func TestRepository_CreateUsernamePasswordCredential(t *testing.T) {
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assertPublicId(t, UsernamePasswordCredentialPrefix, got.PublicId)
|
||||
assertPublicId(t, credential.UsernamePasswordCredentialPrefix, got.PublicId)
|
||||
assert.Equal(tt.cred.Username, got.Username)
|
||||
assert.Nil(got.Password)
|
||||
assert.Nil(got.CtPassword)
|
||||
@@ -172,12 +173,186 @@ func TestRepository_CreateUsernamePasswordCredential(t *testing.T) {
|
||||
assert.Equal(in.Description, got.Description)
|
||||
|
||||
in2, err := NewUsernamePasswordCredential(prjCs.GetPublicId(), "user", "pass", WithName("my-name"), WithDescription("different"))
|
||||
require.NoError(err)
|
||||
got2, err := repo.CreateUsernamePasswordCredential(ctx, prj.GetPublicId(), in2)
|
||||
assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "want err code: %v got err: %v", errors.NotUnique, err)
|
||||
assert.Nil(got2)
|
||||
|
||||
// Creating credential in different scope should not conflict
|
||||
in3, err := NewUsernamePasswordCredential(orgCs.GetPublicId(), "user", "pass", WithName("my-name"), WithDescription("different"))
|
||||
require.NoError(err)
|
||||
got3, err := repo.CreateUsernamePasswordCredential(ctx, org.GetPublicId(), in3)
|
||||
require.NoError(err)
|
||||
assert.Equal(in3.Name, got3.Name)
|
||||
assert.Equal(in3.Description, got3.Description)
|
||||
|
||||
assert.NotEqual(got.PublicId, got3.PublicId)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRepository_CreateSshPrivateKeyCredential(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
|
||||
cs := TestCredentialStore(t, conn, wrapper, prj.PublicId)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
scopeId string
|
||||
cred *SshPrivateKeyCredential
|
||||
wantErr bool
|
||||
wantErrCode errors.Code
|
||||
}{
|
||||
{
|
||||
name: "missing-store",
|
||||
wantErr: true,
|
||||
wantErrCode: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "missing-embedded-cred",
|
||||
cred: &SshPrivateKeyCredential{},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "missing-scope-id",
|
||||
cred: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "my-user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
StoreId: cs.PublicId,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "missing-username",
|
||||
scopeId: prj.PublicId,
|
||||
cred: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
StoreId: cs.PublicId,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "missing-private-key",
|
||||
scopeId: prj.PublicId,
|
||||
cred: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "my-user",
|
||||
StoreId: cs.PublicId,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "missing-store-id",
|
||||
scopeId: prj.PublicId,
|
||||
cred: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "my-user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
scopeId: prj.PublicId,
|
||||
cred: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "my-user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
StoreId: cs.PublicId,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kkms := kms.TestKms(t, conn, wrapper)
|
||||
repo, err := NewRepository(ctx, rw, rw, kkms)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
|
||||
got, err := repo.CreateSshPrivateKeyCredential(ctx, tt.scopeId, tt.cred)
|
||||
if tt.wantErr {
|
||||
assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err)
|
||||
assert.Nil(got)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assertPublicId(t, credential.SshPrivateKeyCredentialPrefix, got.PublicId)
|
||||
assert.Equal(tt.cred.Username, got.Username)
|
||||
assert.Nil(got.PrivateKey)
|
||||
assert.Nil(got.PrivateKeyEncrypted)
|
||||
|
||||
// Validate password
|
||||
lookupCred := allocSshPrivateKeyCredential()
|
||||
lookupCred.PublicId = got.PublicId
|
||||
require.NoError(rw.LookupById(ctx, lookupCred))
|
||||
|
||||
databaseWrapper, err := kkms.GetWrapper(context.Background(), tt.scopeId, kms.KeyPurposeDatabase)
|
||||
require.NoError(err)
|
||||
require.NoError(lookupCred.decrypt(ctx, databaseWrapper))
|
||||
assert.Equal(tt.cred.PrivateKey, lookupCred.PrivateKey)
|
||||
|
||||
assert.Empty(got.PrivateKey)
|
||||
assert.Empty(got.PrivateKeyEncrypted)
|
||||
assert.NotEmpty(got.PrivateKeyHmac)
|
||||
|
||||
// Validate hmac
|
||||
hm, err := crypto.HmacSha256(ctx, tt.cred.PrivateKey, databaseWrapper, []byte(tt.cred.StoreId), nil)
|
||||
require.NoError(err)
|
||||
assert.Equal([]byte(hm), got.PrivateKeyHmac)
|
||||
|
||||
// Validate oplog
|
||||
assert.NoError(db.TestVerifyOplog(t, rw, got.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)))
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("duplicate-names", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
repo, err := NewRepository(ctx, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
require.NoError(err)
|
||||
|
||||
prjCs := TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
|
||||
orgCs := TestCredentialStore(t, conn, wrapper, org.GetPublicId())
|
||||
|
||||
in, err := NewUsernamePasswordCredential(prjCs.GetPublicId(), "user", "pass", WithName("my-name"), WithDescription("original"))
|
||||
assert.NoError(err)
|
||||
|
||||
got, err := repo.CreateUsernamePasswordCredential(ctx, prj.PublicId, in)
|
||||
require.NoError(err)
|
||||
assert.Equal(in.Name, got.Name)
|
||||
assert.Equal(in.Description, got.Description)
|
||||
|
||||
in2, err := NewUsernamePasswordCredential(prjCs.GetPublicId(), "user", "pass", WithName("my-name"), WithDescription("different"))
|
||||
require.NoError(err)
|
||||
got2, err := repo.CreateUsernamePasswordCredential(ctx, prj.GetPublicId(), in2)
|
||||
assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "want err code: %v got err: %v", errors.NotUnique, err)
|
||||
assert.Nil(got2)
|
||||
|
||||
// Creating credential in different scope should not conflict
|
||||
in3, err := NewUsernamePasswordCredential(orgCs.GetPublicId(), "user", "pass", WithName("my-name"), WithDescription("different"))
|
||||
require.NoError(err)
|
||||
got3, err := repo.CreateUsernamePasswordCredential(ctx, org.GetPublicId(), in3)
|
||||
require.NoError(err)
|
||||
assert.Equal(in3.Name, got3.Name)
|
||||
@@ -195,18 +370,24 @@ func TestRepository_LookupCredential(t *testing.T) {
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
store := TestCredentialStore(t, conn, wrapper, prj.PublicId)
|
||||
cred := TestUsernamePasswordCredential(t, conn, wrapper, "username", "password", store.PublicId, prj.PublicId)
|
||||
upCred := TestUsernamePasswordCredential(t, conn, wrapper, "username", "password", store.PublicId, prj.PublicId)
|
||||
spkCred := TestSshPrivateKeyCredential(t, conn, wrapper, "username", TestSshPrivateKeyPem, store.PublicId, prj.PublicId)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
id string
|
||||
want *UsernamePasswordCredential
|
||||
want credential.Static
|
||||
wantErr errors.Code
|
||||
}{
|
||||
{
|
||||
name: "valid-with-client-cert",
|
||||
id: cred.GetPublicId(),
|
||||
want: cred,
|
||||
name: "up-valid",
|
||||
id: upCred.GetPublicId(),
|
||||
want: upCred,
|
||||
},
|
||||
{
|
||||
name: "spk-valid",
|
||||
id: spkCred.GetPublicId(),
|
||||
want: spkCred,
|
||||
},
|
||||
{
|
||||
name: "empty-public-id",
|
||||
@@ -242,10 +423,19 @@ func TestRepository_LookupCredential(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.NotNil(got)
|
||||
assert.Empty(got.Password)
|
||||
assert.Empty(got.CtPassword)
|
||||
assert.NotEmpty(got.PasswordHmac)
|
||||
require.NotNil(got)
|
||||
switch v := got.(type) {
|
||||
case *UsernamePasswordCredential:
|
||||
assert.Empty(v.Password)
|
||||
assert.Empty(v.CtPassword)
|
||||
assert.NotEmpty(v.PasswordHmac)
|
||||
case *SshPrivateKeyCredential:
|
||||
assert.Empty(v.PrivateKey)
|
||||
assert.Empty(v.PrivateKeyEncrypted)
|
||||
assert.NotEmpty(v.PrivateKeyHmac)
|
||||
default:
|
||||
require.Fail("unknown type")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -257,15 +447,12 @@ func TestRepository_ListCredentials(t *testing.T) {
|
||||
wrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
repo, err := NewRepository(context.Background(), rw, rw, kms)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
|
||||
total := 10
|
||||
defaultLimit := 5
|
||||
total := 20
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
store := TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
|
||||
TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId(), total)
|
||||
TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId(), total/2)
|
||||
TestSshPrivateKeyCredentials(t, conn, wrapper, "user", TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId(), total/2)
|
||||
|
||||
type args struct {
|
||||
storeId string
|
||||
@@ -289,7 +476,7 @@ func TestRepository_ListCredentials(t *testing.T) {
|
||||
args: args{
|
||||
storeId: store.PublicId,
|
||||
},
|
||||
wantCnt: total,
|
||||
wantCnt: defaultLimit * 2,
|
||||
},
|
||||
{
|
||||
name: "custom-limit",
|
||||
@@ -297,7 +484,7 @@ func TestRepository_ListCredentials(t *testing.T) {
|
||||
storeId: store.PublicId,
|
||||
opt: []Option{WithLimit(3)},
|
||||
},
|
||||
wantCnt: 3,
|
||||
wantCnt: 3 * 2,
|
||||
},
|
||||
{
|
||||
name: "bad-store",
|
||||
@@ -308,16 +495,33 @@ func TestRepository_ListCredentials(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got, err := repo.ListCredentials(context.Background(), tt.args.storeId, tt.args.opt...)
|
||||
require.NoError(err)
|
||||
assert.Equal(tt.wantCnt, len(got))
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
repo, err := NewRepository(context.Background(), rw, rw, kms)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
repo.defaultLimit = defaultLimit
|
||||
|
||||
// Validate only passwordHmac is returned
|
||||
for _, c := range got {
|
||||
assert.Empty(c.Password)
|
||||
assert.Empty(c.CtPassword)
|
||||
assert.NotEmpty(c.PasswordHmac)
|
||||
}
|
||||
got, err := repo.ListCredentials(context.Background(), tt.args.storeId, tt.args.opt...)
|
||||
require.NoError(err)
|
||||
assert.Equal(tt.wantCnt, len(got))
|
||||
|
||||
// Validate only hmac values are returned
|
||||
for _, c := range got {
|
||||
switch v := c.(type) {
|
||||
case *UsernamePasswordCredential:
|
||||
assert.Empty(v.Password)
|
||||
assert.Empty(v.CtPassword)
|
||||
assert.NotEmpty(v.PasswordHmac)
|
||||
case *SshPrivateKeyCredential:
|
||||
assert.Empty(v.PrivateKey)
|
||||
assert.Empty(v.PrivateKeyEncrypted)
|
||||
assert.NotEmpty(v.PrivateKeyHmac)
|
||||
default:
|
||||
require.Fail("unknown type")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,7 +535,8 @@ func TestRepository_DeleteCredential(t *testing.T) {
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
store := TestCredentialStore(t, conn, wrapper, prj.PublicId)
|
||||
cred := TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId())
|
||||
upCred := TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId())
|
||||
spkCred := TestSshPrivateKeyCredential(t, conn, wrapper, "user", TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId())
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -351,8 +556,13 @@ func TestRepository_DeleteCredential(t *testing.T) {
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "With existing id",
|
||||
in: cred.GetPublicId(),
|
||||
name: "With existing username-password id",
|
||||
in: upCred.GetPublicId(),
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "With existing ssh private key id",
|
||||
in: spkCred.GetPublicId(),
|
||||
want: 1,
|
||||
},
|
||||
}
|
||||
@@ -407,9 +617,9 @@ func TestRepository_UpdateUsernamePasswordCredential(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
deletePublicId := func() func(*UsernamePasswordCredential) *UsernamePasswordCredential {
|
||||
setPublicId := func(n string) func(*UsernamePasswordCredential) *UsernamePasswordCredential {
|
||||
return func(c *UsernamePasswordCredential) *UsernamePasswordCredential {
|
||||
c.PublicId = ""
|
||||
c.PublicId = n
|
||||
return c
|
||||
}
|
||||
}
|
||||
@@ -428,13 +638,6 @@ func TestRepository_UpdateUsernamePasswordCredential(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
nonExistentPublicId := func() func(*UsernamePasswordCredential) *UsernamePasswordCredential {
|
||||
return func(c *UsernamePasswordCredential) *UsernamePasswordCredential {
|
||||
c.PublicId = "abcd_OOOOOOOOOO"
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
changeUser := func(n string) func(credential *UsernamePasswordCredential) *UsernamePasswordCredential {
|
||||
return func(c *UsernamePasswordCredential) *UsernamePasswordCredential {
|
||||
c.Username = n
|
||||
@@ -499,7 +702,7 @@ func TestRepository_UpdateUsernamePasswordCredential(t *testing.T) {
|
||||
Password: []byte("pass"),
|
||||
},
|
||||
},
|
||||
chgFn: deletePublicId(),
|
||||
chgFn: setPublicId(""),
|
||||
masks: []string{"Name", "Description"},
|
||||
wantErr: errors.InvalidPublicId,
|
||||
},
|
||||
@@ -536,7 +739,7 @@ func TestRepository_UpdateUsernamePasswordCredential(t *testing.T) {
|
||||
Password: []byte("pass"),
|
||||
},
|
||||
},
|
||||
chgFn: combine(nonExistentPublicId(), changeName("test-update-name-repo")),
|
||||
chgFn: combine(setPublicId("abcd_OOOOOOOOOO"), changeName("test-update-name-repo")),
|
||||
masks: []string{"Name"},
|
||||
wantErr: errors.RecordNotFound,
|
||||
},
|
||||
@@ -853,7 +1056,7 @@ func TestRepository_UpdateUsernamePasswordCredential(t *testing.T) {
|
||||
assert.NoError(err)
|
||||
assert.Empty(tt.orig.PublicId)
|
||||
require.NotNil(got)
|
||||
assertPublicId(t, UsernamePasswordCredentialPrefix, got.PublicId)
|
||||
assertPublicId(t, credential.UsernamePasswordCredentialPrefix, got.PublicId)
|
||||
assert.Equal(tt.wantCount, gotCount, "row count")
|
||||
assert.NotSame(tt.orig, got)
|
||||
assert.Equal(tt.orig.StoreId, got.StoreId)
|
||||
@@ -894,3 +1097,526 @@ func TestRepository_UpdateUsernamePasswordCredential(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_UpdateSshPrivateKeyCredential(t *testing.T) {
|
||||
const testSecondarySshPrivateKeyPem = `
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACDxfwhEAZKrnsbQxOjVA3PFiB3bW3tSpNKx8TdMiCqlzQAAAJDmpbfr5qW3
|
||||
6wAAAAtzc2gtZWQyNTUxOQAAACDxfwhEAZKrnsbQxOjVA3PFiB3bW3tSpNKx8TdMiCqlzQ
|
||||
AAAEBvvkQkH06ad2GpX1VVARzu9NkHA6gzamAaQ/hkn5FuZvF/CEQBkquextDE6NUDc8WI
|
||||
Hdtbe1Kk0rHxN0yIKqXNAAAACWplZmZAYXJjaAECAwQ=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
`
|
||||
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
changeName := func(n string) func(credential *SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
return func(c *SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
c.Name = n
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
changeDescription := func(d string) func(*SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
return func(c *SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
c.Description = d
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
makeNil := func() func(*SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
return func(_ *SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
makeEmbeddedNil := func() func(*SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
return func(_ *SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
return &SshPrivateKeyCredential{}
|
||||
}
|
||||
}
|
||||
|
||||
setPublicId := func(n string) func(*SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
return func(c *SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
c.PublicId = n
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
deleteStoreId := func() func(*SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
return func(c *SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
c.StoreId = ""
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
deleteVersion := func() func(*SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
return func(c *SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
c.Version = 0
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
changeUser := func(n string) func(credential *SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
return func(c *SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
c.Username = n
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
changePrivateKey := func(d string) func(*SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
return func(c *SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
c.PrivateKey = []byte(d)
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
combine := func(fns ...func(cs *SshPrivateKeyCredential) *SshPrivateKeyCredential) func(*SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
return func(c *SshPrivateKeyCredential) *SshPrivateKeyCredential {
|
||||
for _, fn := range fns {
|
||||
c = fn(c)
|
||||
}
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
orig *SshPrivateKeyCredential
|
||||
chgFn func(*SshPrivateKeyCredential) *SshPrivateKeyCredential
|
||||
masks []string
|
||||
want *SshPrivateKeyCredential
|
||||
wantCount int
|
||||
wantErr errors.Code
|
||||
}{
|
||||
{
|
||||
name: "nil-credential",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
chgFn: makeNil(),
|
||||
masks: []string{"Name", "Description"},
|
||||
wantErr: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "nil-embedded-credential",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
chgFn: makeEmbeddedNil(),
|
||||
masks: []string{"Name", "Description"},
|
||||
wantErr: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "no-public-id",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
chgFn: setPublicId(""),
|
||||
masks: []string{"Name", "Description"},
|
||||
wantErr: errors.InvalidPublicId,
|
||||
},
|
||||
{
|
||||
name: "no-store-id",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
chgFn: deleteStoreId(),
|
||||
masks: []string{"Name", "Description"},
|
||||
wantErr: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "no-version",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
chgFn: deleteVersion(),
|
||||
masks: []string{"Name", "Description"},
|
||||
wantErr: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "updating-non-existent-credential",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Name: "test-name-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
chgFn: combine(setPublicId("abcd_OOOOOOOOOO"), changeName("test-update-name-repo")),
|
||||
masks: []string{"Name"},
|
||||
wantErr: errors.RecordNotFound,
|
||||
},
|
||||
{
|
||||
name: "empty-field-mask",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Name: "test-name-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
chgFn: changeName("test-update-name-repo"),
|
||||
wantErr: errors.EmptyFieldMask,
|
||||
},
|
||||
{
|
||||
name: "read-only-fields-in-field-mask",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Name: "test-name-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
chgFn: changeName("test-update-name-repo"),
|
||||
masks: []string{"PublicId", "CreateTime", "UpdateTime", "ScopeId"},
|
||||
wantErr: errors.InvalidFieldMask,
|
||||
},
|
||||
{
|
||||
name: "unknown-field-in-field-mask",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Name: "test-name-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
chgFn: changeName("test-update-name-repo"),
|
||||
masks: []string{"Bilbo"},
|
||||
wantErr: errors.InvalidFieldMask,
|
||||
},
|
||||
{
|
||||
name: "change-name",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Name: "test-name-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
chgFn: changeName("test-update-name-repo"),
|
||||
masks: []string{"Name"},
|
||||
want: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Name: "test-update-name-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "change-description",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Description: "test-description-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
chgFn: changeDescription("test-update-description-repo"),
|
||||
masks: []string{"Description"},
|
||||
want: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Description: "test-update-description-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "change-name-and-description",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Name: "test-name-repo",
|
||||
Description: "test-description-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
chgFn: combine(changeDescription("test-update-description-repo"), changeName("test-update-name-repo")),
|
||||
masks: []string{"Name", "Description"},
|
||||
want: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Name: "test-update-name-repo",
|
||||
Description: "test-update-description-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "change-username",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
chgFn: changeUser("test-update-user"),
|
||||
masks: []string{"Username"},
|
||||
want: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "test-update-user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "change-private-keyu",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
chgFn: changePrivateKey(testSecondarySshPrivateKeyPem),
|
||||
masks: []string{"PrivateKey"},
|
||||
want: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "user",
|
||||
PrivateKey: []byte(testSecondarySshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "change-username-and-private-key",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
chgFn: combine(changeUser("test-update-user"), changePrivateKey(testSecondarySshPrivateKeyPem)),
|
||||
masks: []string{"Username", "PrivateKey"},
|
||||
want: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "test-update-user",
|
||||
PrivateKey: []byte(testSecondarySshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "do-not-delete-private-key",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
masks: []string{"Username"},
|
||||
chgFn: combine(changeUser("test-new-user"), changePrivateKey("")),
|
||||
want: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "test-new-user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "do-not-delete-username",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
masks: []string{"PrivateKey"},
|
||||
chgFn: combine(changeUser(""), changePrivateKey(testSecondarySshPrivateKeyPem)),
|
||||
want: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "user",
|
||||
PrivateKey: []byte(testSecondarySshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "delete-name",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Name: "test-name-repo",
|
||||
Description: "test-description-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
masks: []string{"Name"},
|
||||
chgFn: combine(changeDescription("test-update-description-repo"), changeName("")),
|
||||
want: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Description: "test-description-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "delete-description",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Name: "test-name-repo",
|
||||
Description: "test-description-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
masks: []string{"Description"},
|
||||
chgFn: combine(changeDescription(""), changeName("test-update-name-repo")),
|
||||
want: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Name: "test-name-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "do-not-delete-name",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Name: "test-name-repo",
|
||||
Description: "test-description-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
masks: []string{"Description"},
|
||||
chgFn: combine(changeDescription("test-update-description-repo"), changeName("")),
|
||||
want: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Name: "test-name-repo",
|
||||
Description: "test-update-description-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "do-not-delete-description",
|
||||
orig: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Name: "test-name-repo",
|
||||
Description: "test-description-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
masks: []string{"Name"},
|
||||
chgFn: combine(changeDescription(""), changeName("test-update-name-repo")),
|
||||
want: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Name: "test-update-name-repo",
|
||||
Description: "test-description-repo",
|
||||
Username: "user",
|
||||
PrivateKey: []byte(TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kkms := kms.TestKms(t, conn, wrapper)
|
||||
repo, err := NewRepository(ctx, rw, rw, kkms)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
store := TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
|
||||
tt.orig.StoreId = store.PublicId
|
||||
|
||||
orig, err := repo.CreateSshPrivateKeyCredential(ctx, prj.GetPublicId(), tt.orig)
|
||||
assert.NoError(err)
|
||||
require.NotNil(orig)
|
||||
|
||||
if tt.chgFn != nil {
|
||||
orig = tt.chgFn(orig)
|
||||
}
|
||||
var version uint32
|
||||
if orig != nil {
|
||||
version = orig.GetVersion()
|
||||
}
|
||||
got, gotCount, err := repo.UpdateSshPrivateKeyCredential(ctx, prj.GetPublicId(), orig, version, tt.masks)
|
||||
if tt.wantErr != 0 {
|
||||
assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err)
|
||||
assert.Equal(tt.wantCount, gotCount, "row count")
|
||||
assert.Nil(got)
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
assert.Empty(tt.orig.PublicId)
|
||||
require.NotNil(got)
|
||||
assertPublicId(t, credential.SshPrivateKeyCredentialPrefix, got.PublicId)
|
||||
assert.Equal(tt.wantCount, gotCount, "row count")
|
||||
assert.NotSame(tt.orig, got)
|
||||
assert.Equal(tt.orig.StoreId, got.StoreId)
|
||||
underlyingDB, err := conn.SqlDB(ctx)
|
||||
require.NoError(err)
|
||||
dbassert := dbassert.New(t, underlyingDB)
|
||||
if tt.want.Name == "" {
|
||||
got := got.clone()
|
||||
dbassert.IsNull(got, "name")
|
||||
} else {
|
||||
assert.Equal(tt.want.Name, got.Name)
|
||||
}
|
||||
|
||||
if tt.want.Description == "" {
|
||||
got := got.clone()
|
||||
dbassert.IsNull(got, "description")
|
||||
} else {
|
||||
assert.Equal(tt.want.Description, got.Description)
|
||||
}
|
||||
|
||||
assert.Equal(tt.want.Username, got.Username)
|
||||
|
||||
// Validate only PrivateKeyHmac is returned
|
||||
assert.Empty(got.PrivateKey)
|
||||
assert.Empty(got.PrivateKeyEncrypted)
|
||||
assert.NotEmpty(got.PrivateKeyHmac)
|
||||
|
||||
// Validate hmac
|
||||
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase)
|
||||
require.NoError(err)
|
||||
hm, err := crypto.HmacSha256(ctx, tt.want.PrivateKey, databaseWrapper, []byte(store.GetPublicId()), nil)
|
||||
require.NoError(err)
|
||||
assert.Equal([]byte(hm), got.PrivateKeyHmac)
|
||||
|
||||
if tt.wantCount > 0 {
|
||||
assert.NoError(db.TestVerifyOplog(t, rw, got.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/credential"
|
||||
"github.com/hashicorp/boundary/internal/credential/static/store"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/libs/crypto"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var _ credential.Static = (*SshPrivateKeyCredential)(nil)
|
||||
|
||||
// A SshPrivateKeyCredential contains the credential with a username and private key.
|
||||
// It is owned by a credential store.
|
||||
type SshPrivateKeyCredential struct {
|
||||
*store.SshPrivateKeyCredential
|
||||
tableName string `gorm:"-"`
|
||||
}
|
||||
|
||||
// NewSshPrivateKeyCredential creates a new in memory static Credential containing a
|
||||
// username and private key that is assigned to storeId. Name and description are the only
|
||||
// valid options. All other options are ignored.
|
||||
func NewSshPrivateKeyCredential(
|
||||
ctx context.Context,
|
||||
storeId string,
|
||||
username string,
|
||||
privateKey credential.PrivateKey,
|
||||
opt ...Option,
|
||||
) (*SshPrivateKeyCredential, error) {
|
||||
const op = "static.NewSshPrivateKeyCredential"
|
||||
|
||||
if len(privateKey) != 0 {
|
||||
_, err := ssh.ParsePrivateKey(privateKey)
|
||||
switch {
|
||||
case err == nil:
|
||||
case err.Error() == (&ssh.PassphraseMissingError{}).Error():
|
||||
// This is okay, if it's brokered and the client can use it, no worries
|
||||
default:
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
}
|
||||
|
||||
opts := getOpts(opt...)
|
||||
l := &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
StoreId: storeId,
|
||||
Name: opts.withName,
|
||||
Description: opts.withDescription,
|
||||
Username: username,
|
||||
PrivateKey: privateKey,
|
||||
},
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func allocSshPrivateKeyCredential() *SshPrivateKeyCredential {
|
||||
return &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SshPrivateKeyCredential) clone() *SshPrivateKeyCredential {
|
||||
cp := proto.Clone(c.SshPrivateKeyCredential)
|
||||
return &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: cp.(*store.SshPrivateKeyCredential),
|
||||
}
|
||||
}
|
||||
|
||||
// TableName returns the table name.
|
||||
func (c *SshPrivateKeyCredential) TableName() string {
|
||||
if c.tableName != "" {
|
||||
return c.tableName
|
||||
}
|
||||
return "credential_static_ssh_private_key_credential"
|
||||
}
|
||||
|
||||
// SetTableName sets the table name.
|
||||
func (c *SshPrivateKeyCredential) SetTableName(n string) {
|
||||
c.tableName = n
|
||||
}
|
||||
|
||||
func (c *SshPrivateKeyCredential) oplog(op oplog.OpType) oplog.Metadata {
|
||||
metadata := oplog.Metadata{
|
||||
"resource-public-id": []string{c.PublicId},
|
||||
"resource-type": []string{"credential-static-ssh-private-key"},
|
||||
"op-type": []string{op.String()},
|
||||
}
|
||||
if c.StoreId != "" {
|
||||
metadata["store-id"] = []string{c.StoreId}
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
|
||||
func (c *SshPrivateKeyCredential) encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
|
||||
const op = "static.(SshPrivateKeyCredential).encrypt"
|
||||
if len(c.PrivateKey) == 0 {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "no private key defined")
|
||||
}
|
||||
if err := structwrapping.WrapStruct(ctx, cipher, c.SshPrivateKeyCredential, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
|
||||
}
|
||||
keyId, err := cipher.KeyId(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("error reading cipher key id"))
|
||||
}
|
||||
c.KeyId = keyId
|
||||
if err := c.hmacPrivateKey(ctx, cipher); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SshPrivateKeyCredential) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
|
||||
const op = "static.(SshPrivateKeyCredential).decrypt"
|
||||
if err := structwrapping.UnwrapStruct(ctx, cipher, c.SshPrivateKeyCredential, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SshPrivateKeyCredential) hmacPrivateKey(ctx context.Context, cipher wrapping.Wrapper) error {
|
||||
const op = "static.(SshPrivateKeyCredential).hmacPrivateKey"
|
||||
if cipher == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
|
||||
}
|
||||
hm, err := crypto.HmacSha256(ctx, c.PrivateKey, cipher, []byte(c.StoreId), nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
c.PrivateKeyHmac = []byte(hm)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/boundary/internal/credential"
|
||||
"github.com/hashicorp/boundary/internal/credential/static/store"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/libs/crypto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
)
|
||||
|
||||
func TestSshPrivateKeyCredential_New(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
kkms := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
|
||||
privKey := []byte(TestSshPrivateKeyPem)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
cs := TestCredentialStore(t, conn, wrapper, prj.PublicId)
|
||||
|
||||
type args struct {
|
||||
username string
|
||||
sshPrivateKey credential.PrivateKey
|
||||
storeId string
|
||||
options []Option
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *SshPrivateKeyCredential
|
||||
wantCreateErr bool
|
||||
wantEncryptErr bool
|
||||
wantAllocError bool
|
||||
}{
|
||||
{
|
||||
name: "missing-private-key",
|
||||
args: args{
|
||||
username: "test-user",
|
||||
storeId: cs.PublicId,
|
||||
},
|
||||
want: allocSshPrivateKeyCredential(),
|
||||
wantEncryptErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing-username",
|
||||
args: args{
|
||||
sshPrivateKey: privKey,
|
||||
storeId: cs.PublicId,
|
||||
},
|
||||
want: allocSshPrivateKeyCredential(),
|
||||
wantCreateErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing-store-id",
|
||||
args: args{
|
||||
username: "test-user",
|
||||
sshPrivateKey: privKey,
|
||||
},
|
||||
want: allocSshPrivateKeyCredential(),
|
||||
wantCreateErr: true,
|
||||
},
|
||||
{
|
||||
name: "bad-private-key",
|
||||
args: args{
|
||||
username: "test-user",
|
||||
sshPrivateKey: []byte("foobar"),
|
||||
storeId: cs.PublicId,
|
||||
},
|
||||
want: allocSshPrivateKeyCredential(),
|
||||
wantAllocError: true,
|
||||
},
|
||||
{
|
||||
name: "valid-no-options",
|
||||
args: args{
|
||||
username: "test-user",
|
||||
sshPrivateKey: privKey,
|
||||
storeId: cs.PublicId,
|
||||
},
|
||||
want: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "test-user",
|
||||
PrivateKey: privKey,
|
||||
StoreId: cs.PublicId,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid-with-name",
|
||||
args: args{
|
||||
username: "test-user",
|
||||
sshPrivateKey: privKey,
|
||||
storeId: cs.PublicId,
|
||||
options: []Option{WithName("my-credential")},
|
||||
},
|
||||
want: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "test-user",
|
||||
PrivateKey: privKey,
|
||||
StoreId: cs.PublicId,
|
||||
Name: "my-credential",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid-with-description",
|
||||
args: args{
|
||||
username: "test-user",
|
||||
sshPrivateKey: privKey,
|
||||
storeId: cs.PublicId,
|
||||
options: []Option{WithDescription("my-credential-description")},
|
||||
},
|
||||
want: &SshPrivateKeyCredential{
|
||||
SshPrivateKeyCredential: &store.SshPrivateKeyCredential{
|
||||
Username: "test-user",
|
||||
PrivateKey: privKey,
|
||||
StoreId: cs.PublicId,
|
||||
Description: "my-credential-description",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
got, err := NewSshPrivateKeyCredential(ctx, tt.args.storeId, tt.args.username, tt.args.sshPrivateKey, tt.args.options...)
|
||||
if tt.wantAllocError {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
require.NotNil(got)
|
||||
assert.Emptyf(got.PublicId, "PublicId set")
|
||||
|
||||
id, err := credential.NewSshPrivateKeyCredentialId(ctx)
|
||||
require.NoError(err)
|
||||
|
||||
tt.want.PublicId = id
|
||||
got.PublicId = id
|
||||
|
||||
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(err)
|
||||
|
||||
err = got.encrypt(ctx, databaseWrapper)
|
||||
if tt.wantEncryptErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
|
||||
err = rw.Create(context.Background(), got)
|
||||
if tt.wantCreateErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
|
||||
got2 := allocSshPrivateKeyCredential()
|
||||
got2.PublicId = id
|
||||
assert.Equal(id, got2.GetPublicId())
|
||||
require.NoError(rw.LookupById(ctx, got2))
|
||||
|
||||
err = got2.decrypt(ctx, databaseWrapper)
|
||||
require.NoError(err)
|
||||
|
||||
// Timestamps and version are automatically set
|
||||
tt.want.CreateTime = got2.CreateTime
|
||||
tt.want.UpdateTime = got2.UpdateTime
|
||||
tt.want.Version = got2.Version
|
||||
|
||||
// KeyId is allocated via kms no need to validate in this test
|
||||
tt.want.KeyId = got2.KeyId
|
||||
got2.PrivateKeyEncrypted = nil
|
||||
|
||||
// encrypt also calculates the hmac, validate it is correct
|
||||
hm, err := crypto.HmacSha256(ctx, got.PrivateKey, databaseWrapper, []byte(got.StoreId), nil)
|
||||
require.NoError(err)
|
||||
tt.want.PrivateKeyHmac = []byte(hm)
|
||||
|
||||
assert.Empty(cmp.Diff(tt.want, got2.clone(), protocmp.Transform()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -300,6 +300,171 @@ func (x *UsernamePasswordCredential) GetKeyId() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type SshPrivateKeyCredential struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// public_id is a surrogate key suitable for use in a public API.
|
||||
// @inject_tag: `gorm:"primary_key"`
|
||||
PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"`
|
||||
// create_time is set by the database.
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
CreateTime *timestamp.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
|
||||
// update_time is set by the database.
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
UpdateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"`
|
||||
// name is optional. If set, it must be unique within scope_id.
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"`
|
||||
// description is optional.
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"`
|
||||
// store_id of the owning static credential store.
|
||||
// It must be set.
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
StoreId string `protobuf:"bytes,6,opt,name=store_id,json=storeId,proto3" json:"store_id,omitempty" gorm:"not_null"`
|
||||
// version allows optimistic locking of the resource.
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Version uint32 `protobuf:"varint,7,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"`
|
||||
// username is the username associated with the credential.
|
||||
// It must be set.
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
Username string `protobuf:"bytes,8,opt,name=username,proto3" json:"username,omitempty" gorm:"not_null"`
|
||||
// private_key is the plain-text of the private key associated with the
|
||||
// credential. We are not storing this plain-text private key in the database.
|
||||
// @inject_tag: `gorm:"-" wrapping:"pt,private_key"`
|
||||
PrivateKey []byte `protobuf:"bytes,9,opt,name=private_key,json=privateKey,proto3" json:"private_key,omitempty" gorm:"-" wrapping:"pt,private_key"`
|
||||
// private_key_encrypted is the ciphertext of the private key. It is stored in
|
||||
// the database.
|
||||
// @inject_tag: `gorm:"column:private_key_encrypted;not_null" wrapping:"ct,private_key"`
|
||||
PrivateKeyEncrypted []byte `protobuf:"bytes,10,opt,name=private_key_encrypted,json=privateKeyEncrypted,proto3" json:"private_key_encrypted,omitempty" gorm:"column:private_key_encrypted;not_null" wrapping:"ct,private_key"`
|
||||
// private_key_hmac is a sha256-hmac of the unencrypted private key. It is recalculated
|
||||
// everytime the private key is updated.
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
PrivateKeyHmac []byte `protobuf:"bytes,11,opt,name=private_key_hmac,json=privateKeyHmac,proto3" json:"private_key_hmac,omitempty" gorm:"not_null"`
|
||||
// The key_id of the kms database key used for encrypting this entry.
|
||||
// It must be set.
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
KeyId string `protobuf:"bytes,12,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty" gorm:"not_null"`
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyCredential) Reset() {
|
||||
*x = SshPrivateKeyCredential{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyCredential) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SshPrivateKeyCredential) ProtoMessage() {}
|
||||
|
||||
func (x *SshPrivateKeyCredential) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[2]
|
||||
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 SshPrivateKeyCredential.ProtoReflect.Descriptor instead.
|
||||
func (*SshPrivateKeyCredential) Descriptor() ([]byte, []int) {
|
||||
return file_controller_storage_credential_static_store_v1_static_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyCredential) GetPublicId() string {
|
||||
if x != nil {
|
||||
return x.PublicId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyCredential) GetCreateTime() *timestamp.Timestamp {
|
||||
if x != nil {
|
||||
return x.CreateTime
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyCredential) GetUpdateTime() *timestamp.Timestamp {
|
||||
if x != nil {
|
||||
return x.UpdateTime
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyCredential) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyCredential) GetDescription() string {
|
||||
if x != nil {
|
||||
return x.Description
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyCredential) GetStoreId() string {
|
||||
if x != nil {
|
||||
return x.StoreId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyCredential) GetVersion() uint32 {
|
||||
if x != nil {
|
||||
return x.Version
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyCredential) GetUsername() string {
|
||||
if x != nil {
|
||||
return x.Username
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyCredential) GetPrivateKey() []byte {
|
||||
if x != nil {
|
||||
return x.PrivateKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyCredential) GetPrivateKeyEncrypted() []byte {
|
||||
if x != nil {
|
||||
return x.PrivateKeyEncrypted
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyCredential) GetPrivateKeyHmac() []byte {
|
||||
if x != nil {
|
||||
return x.PrivateKeyHmac
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyCredential) GetKeyId() string {
|
||||
if x != nil {
|
||||
return x.KeyId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_controller_storage_credential_static_store_v1_static_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_storage_credential_static_store_v1_static_proto_rawDesc = []byte{
|
||||
@@ -378,11 +543,54 @@ var file_controller_storage_credential_static_store_v1_static_proto_rawDesc = []
|
||||
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x52, 0x0c, 0x70, 0x61, 0x73,
|
||||
0x73, 0x77, 0x6f, 0x72, 0x64, 0x48, 0x6d, 0x61, 0x63, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79,
|
||||
0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64,
|
||||
0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68,
|
||||
0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72,
|
||||
0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65,
|
||||
0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x2f, 0x73, 0x74, 0x6f,
|
||||
0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x22, 0xa1, 0x05, 0x0a, 0x17, 0x53, 0x73, 0x68, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b,
|
||||
0x65, 0x79, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x1b, 0x0a, 0x09,
|
||||
0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65,
|
||||
0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a,
|
||||
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72,
|
||||
0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31,
|
||||
0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61,
|
||||
0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,
|
||||
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f,
|
||||
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
|
||||
0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54,
|
||||
0x69, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||
0x09, 0x42, 0x10, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e,
|
||||
0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b,
|
||||
0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x73,
|
||||
0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73,
|
||||
0x74, 0x6f, 0x72, 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x12, 0x3f, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01,
|
||||
0x28, 0x09, 0x42, 0x23, 0xc2, 0xdd, 0x29, 0x1f, 0x0a, 0x08, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61,
|
||||
0x6d, 0x65, 0x12, 0x13, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75,
|
||||
0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x12, 0x49, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79,
|
||||
0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x28, 0xc2, 0xdd, 0x29, 0x24, 0x0a, 0x0a, 0x50, 0x72,
|
||||
0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x16, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62,
|
||||
0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79,
|
||||
0x52, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x15,
|
||||
0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x65, 0x6e, 0x63, 0x72,
|
||||
0x79, 0x70, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x70, 0x72, 0x69,
|
||||
0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64,
|
||||
0x12, 0x5b, 0x0a, 0x10, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f,
|
||||
0x68, 0x6d, 0x61, 0x63, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x31, 0xc2, 0xdd, 0x29, 0x2d,
|
||||
0x0a, 0x0e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x48, 0x6d, 0x61, 0x63,
|
||||
0x12, 0x1b, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x69,
|
||||
0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x52, 0x0e, 0x70,
|
||||
0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x48, 0x6d, 0x61, 0x63, 0x12, 0x15, 0x0a,
|
||||
0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b,
|
||||
0x65, 0x79, 0x49, 0x64, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75,
|
||||
0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63,
|
||||
0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63,
|
||||
0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -397,22 +605,25 @@ func file_controller_storage_credential_static_store_v1_static_proto_rawDescGZIP
|
||||
return file_controller_storage_credential_static_store_v1_static_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_controller_storage_credential_static_store_v1_static_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_controller_storage_credential_static_store_v1_static_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_controller_storage_credential_static_store_v1_static_proto_goTypes = []interface{}{
|
||||
(*CredentialStore)(nil), // 0: controller.storage.credential.static.store.v1.CredentialStore
|
||||
(*UsernamePasswordCredential)(nil), // 1: controller.storage.credential.static.store.v1.UsernamePasswordCredential
|
||||
(*timestamp.Timestamp)(nil), // 2: controller.storage.timestamp.v1.Timestamp
|
||||
(*SshPrivateKeyCredential)(nil), // 2: controller.storage.credential.static.store.v1.SshPrivateKeyCredential
|
||||
(*timestamp.Timestamp)(nil), // 3: controller.storage.timestamp.v1.Timestamp
|
||||
}
|
||||
var file_controller_storage_credential_static_store_v1_static_proto_depIdxs = []int32{
|
||||
2, // 0: controller.storage.credential.static.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
2, // 1: controller.storage.credential.static.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
2, // 2: controller.storage.credential.static.store.v1.UsernamePasswordCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
2, // 3: controller.storage.credential.static.store.v1.UsernamePasswordCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
4, // [4:4] is the sub-list for method output_type
|
||||
4, // [4:4] is the sub-list for method input_type
|
||||
4, // [4:4] is the sub-list for extension type_name
|
||||
4, // [4:4] is the sub-list for extension extendee
|
||||
0, // [0:4] is the sub-list for field type_name
|
||||
3, // 0: controller.storage.credential.static.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
3, // 1: controller.storage.credential.static.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
3, // 2: controller.storage.credential.static.store.v1.UsernamePasswordCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
3, // 3: controller.storage.credential.static.store.v1.UsernamePasswordCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
3, // 4: controller.storage.credential.static.store.v1.SshPrivateKeyCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
3, // 5: controller.storage.credential.static.store.v1.SshPrivateKeyCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
6, // [6:6] is the sub-list for method output_type
|
||||
6, // [6:6] is the sub-list for method input_type
|
||||
6, // [6:6] is the sub-list for extension type_name
|
||||
6, // [6:6] is the sub-list for extension extendee
|
||||
0, // [0:6] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_controller_storage_credential_static_store_v1_static_proto_init() }
|
||||
@@ -445,6 +656,18 @@ func file_controller_storage_credential_static_store_v1_static_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_storage_credential_static_store_v1_static_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*SshPrivateKeyCredential); 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{
|
||||
@@ -452,7 +675,7 @@ func file_controller_storage_credential_static_store_v1_static_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_controller_storage_credential_static_store_v1_static_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -12,6 +12,16 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const TestSshPrivateKeyPem = `
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACDxfwhEAZKrnsbQxOjVA3PFiB3bW3tSpNKx8TdMiCqlzQAAAJDmpbfr5qW3
|
||||
6wAAAAtzc2gtZWQyNTUxOQAAACDxfwhEAZKrnsbQxOjVA3PFiB3bW3tSpNKx8TdMiCqlzQ
|
||||
AAAEBvvkQkH06ad2GpX1VVARzu9NkHA6gzamAaQ/hkn5FuZvF/CEQBkquextDE6NUDc8WI
|
||||
Hdtbe1Kk0rHxN0yIKqXNAAAACWplZmZAYXJjaAECAwQ=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
`
|
||||
|
||||
// TestCredentialStore creates a static credential store in the provided DB with
|
||||
// the provided scope and any values passed in through the Options vars.
|
||||
// If any errors are encountered during the creation of the store, the test will fail.
|
||||
@@ -96,7 +106,7 @@ func TestUsernamePasswordCredential(
|
||||
|
||||
id := opts.withPublicId
|
||||
if id == "" {
|
||||
id, err = newUsernamePasswordCredentialId(ctx)
|
||||
id, err = credential.NewUsernamePasswordCredentialId(ctx)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
cred.PublicId = id
|
||||
@@ -139,3 +149,74 @@ func TestUsernamePasswordCredentials(
|
||||
}
|
||||
return creds
|
||||
}
|
||||
|
||||
// TestSshPrivateKeyCredential creates an ssh private key credential in the
|
||||
// provided DB with the provided scope and any values passed in through. If any
|
||||
// errors are encountered during the creation of the store, the test will fail.
|
||||
func TestSshPrivateKeyCredential(
|
||||
t testing.TB,
|
||||
conn *db.DB,
|
||||
wrapper wrapping.Wrapper,
|
||||
username, privateKey, storeId, scopeId string,
|
||||
opt ...Option,
|
||||
) *SshPrivateKeyCredential {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
w := db.New(conn)
|
||||
|
||||
opts := getOpts(opt...)
|
||||
|
||||
databaseWrapper, err := kmsCache.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
|
||||
assert.NoError(t, err)
|
||||
require.NotNil(t, databaseWrapper)
|
||||
|
||||
cred, err := NewSshPrivateKeyCredential(ctx, storeId, username, credential.PrivateKey(privateKey), opt...)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cred)
|
||||
|
||||
id := opts.withPublicId
|
||||
if id == "" {
|
||||
id, err = credential.NewSshPrivateKeyCredentialId(ctx)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
cred.PublicId = id
|
||||
|
||||
err = cred.encrypt(ctx, databaseWrapper)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, iw db.Writer) error {
|
||||
require.NoError(t, iw.Create(ctx, cred))
|
||||
return nil
|
||||
},
|
||||
)
|
||||
require.NoError(t, err2)
|
||||
|
||||
return cred
|
||||
}
|
||||
|
||||
// TestSshPrivateKeyCredentials creates count number of ssh private key
|
||||
// credentials in the provided DB with the provided scope id. If any errors are
|
||||
// encountered during the creation of the credentials, the test will fail.
|
||||
func TestSshPrivateKeyCredentials(
|
||||
t testing.TB,
|
||||
conn *db.DB,
|
||||
wrapper wrapping.Wrapper,
|
||||
username, privateKey, storeId, scopeId string,
|
||||
count int,
|
||||
) []*SshPrivateKeyCredential {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
|
||||
databaseWrapper, err := kmsCache.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
|
||||
assert.NoError(t, err)
|
||||
require.NotNil(t, databaseWrapper)
|
||||
|
||||
creds := make([]*SshPrivateKeyCredential, 0, count)
|
||||
for i := 0; i < count; i++ {
|
||||
creds = append(creds, TestSshPrivateKeyCredential(t, conn, wrapper, username, privateKey, storeId, scopeId))
|
||||
}
|
||||
return creds
|
||||
}
|
||||
@@ -64,6 +64,7 @@ func Test_TestUsernamePasswordCredential(t *testing.T) {
|
||||
|
||||
// Validate hmac
|
||||
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(err)
|
||||
hm, err := crypto.HmacSha256(context.Background(), cred.Password, databaseWrapper, []byte(cred.StoreId), nil, crypto.WithEd25519())
|
||||
require.NoError(err)
|
||||
assert.Equal([]byte(hm), cred.PasswordHmac)
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
)
|
||||
|
||||
func TestCredential_New(t *testing.T) {
|
||||
func TestUsernamePasswordCredential_New(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
@@ -128,13 +128,14 @@ func TestCredential_New(t *testing.T) {
|
||||
require.NotNil(got)
|
||||
assert.Emptyf(got.PublicId, "PublicId set")
|
||||
|
||||
id, err := newUsernamePasswordCredentialId(ctx)
|
||||
id, err := credential.NewUsernamePasswordCredentialId(ctx)
|
||||
require.NoError(err)
|
||||
|
||||
tt.want.PublicId = id
|
||||
got.PublicId = id
|
||||
|
||||
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(err)
|
||||
|
||||
err = got.encrypt(ctx, databaseWrapper)
|
||||
if tt.wantEncryptErr {
|
||||
|
||||
@@ -26,13 +26,15 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
usernameField = "attributes.username"
|
||||
passwordField = "attributes.password"
|
||||
domain = "credential"
|
||||
usernameField = "attributes.username"
|
||||
passwordField = "attributes.password"
|
||||
privateKeyField = "attributes.private_key"
|
||||
domain = "credential"
|
||||
)
|
||||
|
||||
var (
|
||||
maskManager handlers.MaskManager
|
||||
upMaskManager handlers.MaskManager
|
||||
spkMaskManager handlers.MaskManager
|
||||
|
||||
// IdActions contains the set of actions that can be performed on
|
||||
// individual resources
|
||||
@@ -53,10 +55,14 @@ var (
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
if maskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.UsernamePasswordCredential{}},
|
||||
if upMaskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.UsernamePasswordCredential{}},
|
||||
handlers.MaskSource{&pb.Credential{}, &pb.UsernamePasswordAttributes{}}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if spkMaskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.SshPrivateKeyCredential{}},
|
||||
handlers.MaskSource{&pb.Credential{}, &pb.SshPrivateKeyAttributes{}}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Service handles request as described by the pbs.CredentialServiceServer interface.
|
||||
@@ -293,9 +299,7 @@ func (s Service) listFromRepo(ctx context.Context, storeId string) ([]credential
|
||||
}
|
||||
|
||||
creds := make([]credential.Static, 0, len(up))
|
||||
for _, s := range up {
|
||||
creds = append(creds, s)
|
||||
}
|
||||
creds = append(creds, up...)
|
||||
|
||||
return creds, nil
|
||||
}
|
||||
@@ -319,7 +323,7 @@ func (s Service) getFromRepo(ctx context.Context, id string) (credential.Static,
|
||||
func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Credential) (credential.Static, error) {
|
||||
const op = "credentials.(Service).createInRepo"
|
||||
switch item.GetType() {
|
||||
case static.UsernamePasswordSubtype.String():
|
||||
case credential.UsernamePasswordSubtype.String():
|
||||
cred, err := toUsernamePasswordStorageCredential(item.GetCredentialStoreId(), item)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
@@ -336,6 +340,23 @@ func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Cred
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create credential but no error returned from repository.")
|
||||
}
|
||||
return out, nil
|
||||
case credential.SshPrivateKeySubtype.String():
|
||||
cred, err := toSshPrivateKeyStorageCredential(ctx, item.GetCredentialStoreId(), item)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
out, err := repo.CreateSshPrivateKeyCredential(ctx, scopeId, cred)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create credential"))
|
||||
}
|
||||
if out == nil {
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create credential but no error returned from repository.")
|
||||
}
|
||||
return out, nil
|
||||
default:
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, fmt.Sprintf("Unsupported credential type %q", item.GetType()))
|
||||
}
|
||||
@@ -352,13 +373,13 @@ func (s Service) updateInRepo(
|
||||
var dbMasks []string
|
||||
item := proto.Clone(in).(*pb.Credential)
|
||||
|
||||
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."})
|
||||
}
|
||||
|
||||
switch subtypes.SubtypeFromId(domain, id) {
|
||||
case static.UsernamePasswordSubtype:
|
||||
case credential.UsernamePasswordSubtype:
|
||||
dbMasks = append(dbMasks, upMaskManager.Translate(masks)...)
|
||||
if len(dbMasks) == 0 {
|
||||
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
|
||||
}
|
||||
|
||||
cred, err := toUsernamePasswordStorageCredential(storeId, in)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to convert to username/password storage credential"))
|
||||
@@ -376,6 +397,31 @@ func (s Service) updateInRepo(
|
||||
return nil, handlers.NotFoundErrorf("Credential %q doesn't exist or incorrect version provided.", id)
|
||||
}
|
||||
return out, nil
|
||||
|
||||
case credential.SshPrivateKeySubtype:
|
||||
dbMasks = append(dbMasks, spkMaskManager.Translate(masks)...)
|
||||
if len(dbMasks) == 0 {
|
||||
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
|
||||
}
|
||||
|
||||
cred, err := toSshPrivateKeyStorageCredential(ctx, storeId, in)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to convert to ssh private key storage credential"))
|
||||
}
|
||||
cred.PublicId = id
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
out, rowsUpdated, err := repo.UpdateSshPrivateKeyCredential(ctx, scopeId, cred, item.GetVersion(), dbMasks)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential"))
|
||||
}
|
||||
if rowsUpdated == 0 {
|
||||
return nil, handlers.NotFoundErrorf("Credential %q doesn't exist or incorrect version provided.", id)
|
||||
}
|
||||
return out, nil
|
||||
|
||||
default:
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, fmt.Sprintf("Unsupported credential type %q", item.GetType()))
|
||||
|
||||
@@ -465,7 +511,9 @@ func toProto(in credential.Static, opt ...handlers.Option) (*pb.Credential, erro
|
||||
if outputFields.Has(globals.TypeField) {
|
||||
switch in.(type) {
|
||||
case *static.UsernamePasswordCredential:
|
||||
out.Type = static.UsernamePasswordSubtype.String()
|
||||
out.Type = credential.UsernamePasswordSubtype.String()
|
||||
case *static.SshPrivateKeyCredential:
|
||||
out.Type = credential.SshPrivateKeySubtype.String()
|
||||
}
|
||||
}
|
||||
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
|
||||
@@ -500,12 +548,21 @@ func toProto(in credential.Static, opt ...handlers.Option) (*pb.Credential, erro
|
||||
},
|
||||
}
|
||||
}
|
||||
case *static.SshPrivateKeyCredential:
|
||||
if outputFields.Has(globals.AttributesField) {
|
||||
out.Attrs = &pb.Credential_SshPrivateKeyAttributes{
|
||||
SshPrivateKeyAttributes: &pb.SshPrivateKeyAttributes{
|
||||
Username: wrapperspb.String(cred.GetUsername()),
|
||||
PrivateKeyHmac: base64.RawURLEncoding.EncodeToString(cred.GetPrivateKeyHmac()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func toUsernamePasswordStorageCredential(storeId string, in *pb.Credential) (out *static.UsernamePasswordCredential, err error) {
|
||||
const op = "credentials.toStorageCredential"
|
||||
const op = "credentials.toUsernamePasswordStorageCredential"
|
||||
var opts []static.Option
|
||||
if in.GetName() != nil {
|
||||
opts = append(opts, static.WithName(in.GetName().GetValue()))
|
||||
@@ -527,30 +584,69 @@ func toUsernamePasswordStorageCredential(storeId string, in *pb.Credential) (out
|
||||
return cs, err
|
||||
}
|
||||
|
||||
func toSshPrivateKeyStorageCredential(ctx context.Context, storeId string, in *pb.Credential) (out *static.SshPrivateKeyCredential, err error) {
|
||||
const op = "credentials.toSshPrivateKeyStorageCredential"
|
||||
var opts []static.Option
|
||||
if in.GetName() != nil {
|
||||
opts = append(opts, static.WithName(in.GetName().GetValue()))
|
||||
}
|
||||
if in.GetDescription() != nil {
|
||||
opts = append(opts, static.WithDescription(in.GetDescription().GetValue()))
|
||||
}
|
||||
|
||||
attrs := in.GetSshPrivateKeyAttributes()
|
||||
cs, err := static.NewSshPrivateKeyCredential(
|
||||
ctx,
|
||||
storeId,
|
||||
attrs.GetUsername().GetValue(),
|
||||
credential.PrivateKey(attrs.GetPrivateKey().GetValue()),
|
||||
opts...)
|
||||
if err != nil {
|
||||
return nil, errors.WrapDeprecated(err, op, errors.WithMsg("unable to build credential"))
|
||||
}
|
||||
|
||||
return cs, err
|
||||
}
|
||||
|
||||
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
|
||||
// requirements on the structure of the request. They verify that:
|
||||
// * The path passed in is correctly formatted
|
||||
// * All required parameters are set
|
||||
// * There are no conflicting parameters provided
|
||||
func validateGetRequest(req *pbs.GetCredentialRequest) error {
|
||||
return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, static.UsernamePasswordCredentialPrefix, static.PreviousUsernamePasswordCredentialPrefix)
|
||||
return handlers.ValidateGetRequest(
|
||||
handlers.NoopValidatorFn,
|
||||
req,
|
||||
credential.UsernamePasswordCredentialPrefix,
|
||||
credential.PreviousUsernamePasswordCredentialPrefix,
|
||||
credential.SshPrivateKeyCredentialPrefix,
|
||||
)
|
||||
}
|
||||
|
||||
func validateCreateRequest(req *pbs.CreateCredentialRequest) error {
|
||||
return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string {
|
||||
badFields := map[string]string{}
|
||||
if req.Item.GetType() != static.UsernamePasswordSubtype.String() {
|
||||
badFields[globals.TypeField] = fmt.Sprintf("Unsupported credential type %q", req.Item.GetType())
|
||||
}
|
||||
if !handlers.ValidId(handlers.Id(req.Item.GetCredentialStoreId()), static.CredentialStorePrefix, static.PreviousCredentialStorePrefix) {
|
||||
badFields[globals.CredentialStoreIdField] = "This field must be a valid credential store id."
|
||||
}
|
||||
|
||||
if req.Item.GetUsernamePasswordAttributes().GetUsername().GetValue() == "" {
|
||||
badFields[usernameField] = "Field required for creating a username-password credential."
|
||||
}
|
||||
if req.Item.GetUsernamePasswordAttributes().GetPassword().GetValue() == "" {
|
||||
badFields[passwordField] = "Field required for creating a username-password credential."
|
||||
switch req.Item.GetType() {
|
||||
case credential.UsernamePasswordSubtype.String():
|
||||
if req.Item.GetUsernamePasswordAttributes().GetUsername().GetValue() == "" {
|
||||
badFields[usernameField] = "Field required for creating a username-password credential."
|
||||
}
|
||||
if req.Item.GetUsernamePasswordAttributes().GetPassword().GetValue() == "" {
|
||||
badFields[passwordField] = "Field required for creating a username-password credential."
|
||||
}
|
||||
case credential.SshPrivateKeySubtype.String():
|
||||
if req.Item.GetSshPrivateKeyAttributes().GetUsername().GetValue() == "" {
|
||||
badFields[usernameField] = "Field required for creating an SSH private key credential."
|
||||
}
|
||||
if req.Item.GetSshPrivateKeyAttributes().GetPrivateKey().GetValue() == "" {
|
||||
badFields[privateKeyField] = "Field required for creating an SSH private key credential."
|
||||
}
|
||||
default:
|
||||
badFields[globals.TypeField] = fmt.Sprintf("Unsupported credential type %q", req.Item.GetType())
|
||||
}
|
||||
|
||||
return badFields
|
||||
@@ -560,24 +656,44 @@ func validateCreateRequest(req *pbs.CreateCredentialRequest) error {
|
||||
func validateUpdateRequest(req *pbs.UpdateCredentialRequest) error {
|
||||
return handlers.ValidateUpdateRequest(req, req.GetItem(), func() map[string]string {
|
||||
badFields := map[string]string{}
|
||||
if req.GetItem().GetType() != "" && req.GetItem().GetType() != static.UsernamePasswordSubtype.String() {
|
||||
switch req.GetItem().GetType() {
|
||||
case "":
|
||||
case credential.UsernamePasswordSubtype.String():
|
||||
attrs := req.GetItem().GetUsernamePasswordAttributes()
|
||||
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), usernameField) && attrs.GetUsername().GetValue() == "" {
|
||||
badFields[usernameField] = "This is a required field and cannot be set to empty."
|
||||
}
|
||||
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), passwordField) && attrs.GetPassword().GetValue() == "" {
|
||||
badFields[passwordField] = "This is a required field and cannot be set to empty."
|
||||
}
|
||||
case credential.SshPrivateKeySubtype.String():
|
||||
attrs := req.GetItem().GetSshPrivateKeyAttributes()
|
||||
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), usernameField) && attrs.GetUsername().GetValue() == "" {
|
||||
badFields[usernameField] = "This is a required field and cannot be set to empty."
|
||||
}
|
||||
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), privateKeyField) && attrs.GetPrivateKey().GetValue() == "" {
|
||||
badFields[privateKeyField] = "This is a required field and cannot be set to empty."
|
||||
}
|
||||
default:
|
||||
badFields[globals.TypeField] = "Cannot modify resource type."
|
||||
}
|
||||
|
||||
attrs := req.GetItem().GetUsernamePasswordAttributes()
|
||||
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), usernameField) && attrs.GetUsername().GetValue() == "" {
|
||||
badFields[usernameField] = "This is a required field and cannot be set to empty."
|
||||
}
|
||||
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), passwordField) && attrs.GetPassword().GetValue() == "" {
|
||||
badFields[passwordField] = "This is a required field and cannot be set to empty."
|
||||
}
|
||||
|
||||
return badFields
|
||||
}, static.UsernamePasswordCredentialPrefix, static.PreviousUsernamePasswordCredentialPrefix)
|
||||
},
|
||||
credential.UsernamePasswordCredentialPrefix,
|
||||
credential.PreviousUsernamePasswordCredentialPrefix,
|
||||
credential.SshPrivateKeyCredentialPrefix,
|
||||
)
|
||||
}
|
||||
|
||||
func validateDeleteRequest(req *pbs.DeleteCredentialRequest) error {
|
||||
return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, static.UsernamePasswordCredentialPrefix, static.PreviousUsernamePasswordCredentialPrefix)
|
||||
return handlers.ValidateDeleteRequest(
|
||||
handlers.NoopValidatorFn,
|
||||
req,
|
||||
credential.UsernamePasswordCredentialPrefix,
|
||||
credential.PreviousUsernamePasswordCredentialPrefix,
|
||||
credential.SshPrivateKeyCredentialPrefix,
|
||||
)
|
||||
}
|
||||
|
||||
func validateListRequest(req *pbs.ListCredentialsRequest) error {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/boundary/internal/credential"
|
||||
"github.com/hashicorp/boundary/internal/credential/static"
|
||||
"github.com/hashicorp/boundary/internal/credential/vault"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
|
||||
@@ -52,17 +53,16 @@ func TestList(t *testing.T) {
|
||||
store := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
|
||||
storeNoCreds := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
|
||||
|
||||
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
|
||||
var wantCreds []*pb.Credential
|
||||
for i := 0; i < 10; i++ {
|
||||
user := fmt.Sprintf("user-%d", i)
|
||||
pass := fmt.Sprintf("pass-%d", i)
|
||||
c := static.TestUsernamePasswordCredential(t, conn, wrapper, user, pass, store.GetPublicId(), prj.GetPublicId())
|
||||
|
||||
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
hm, err := crypto.HmacSha256(context.Background(), []byte(pass), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
|
||||
require.NoError(t, err)
|
||||
|
||||
wantCreds = append(wantCreds, &pb.Credential{
|
||||
Id: c.GetPublicId(),
|
||||
CredentialStoreId: store.GetPublicId(),
|
||||
@@ -70,7 +70,7 @@ func TestList(t *testing.T) {
|
||||
CreatedTime: c.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: c.GetUpdateTime().GetTimestamp(),
|
||||
Version: c.GetVersion(),
|
||||
Type: static.UsernamePasswordSubtype.String(),
|
||||
Type: credential.UsernamePasswordSubtype.String(),
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
Attrs: &pb.Credential_UsernamePasswordAttributes{
|
||||
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
|
||||
@@ -79,6 +79,26 @@ func TestList(t *testing.T) {
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
spk := static.TestSshPrivateKeyCredential(t, conn, wrapper, user, static.TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId())
|
||||
hm, err = crypto.HmacSha256(context.Background(), []byte(static.TestSshPrivateKeyPem), databaseWrapper, []byte(store.GetPublicId()), nil)
|
||||
require.NoError(t, err)
|
||||
wantCreds = append(wantCreds, &pb.Credential{
|
||||
Id: spk.GetPublicId(),
|
||||
CredentialStoreId: store.GetPublicId(),
|
||||
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
|
||||
CreatedTime: spk.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: spk.GetUpdateTime().GetTimestamp(),
|
||||
Version: spk.GetVersion(),
|
||||
Type: credential.SshPrivateKeySubtype.String(),
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
Attrs: &pb.Credential_SshPrivateKeyAttributes{
|
||||
SshPrivateKeyAttributes: &pb.SshPrivateKeyAttributes{
|
||||
Username: wrapperspb.String(c.GetUsername()),
|
||||
PrivateKeyHmac: base64.RawURLEncoding.EncodeToString([]byte(hm)),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
@@ -109,7 +129,7 @@ func TestList(t *testing.T) {
|
||||
{
|
||||
name: "Filter on Attribute",
|
||||
req: &pbs.ListCredentialsRequest{CredentialStoreId: store.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/username"==%q`, wantCreds[2].GetUsernamePasswordAttributes().GetUsername().Value)},
|
||||
res: &pbs.ListCredentialsResponse{Items: wantCreds[2:3]},
|
||||
res: &pbs.ListCredentialsResponse{Items: wantCreds[2:4]},
|
||||
anonRes: &pbs.ListCredentialsResponse{}, // anonymous user does not have access to attributes
|
||||
},
|
||||
{
|
||||
@@ -169,17 +189,20 @@ func TestGet(t *testing.T) {
|
||||
}
|
||||
|
||||
_, prj := iam.TestScopes(t, iamRepo)
|
||||
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
|
||||
store := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
|
||||
cred := static.TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId())
|
||||
credPrev := static.TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId(), static.WithPublicId(fmt.Sprintf("%s_1234567890", static.PreviousUsernamePasswordCredentialPrefix)))
|
||||
s, err := NewService(staticRepoFn, iamRepoFn)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Calculate hmac
|
||||
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase)
|
||||
upCred := static.TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId())
|
||||
upCredPrev := static.TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId(), static.WithPublicId(fmt.Sprintf("%s_1234567890", credential.PreviousUsernamePasswordCredentialPrefix)))
|
||||
upHm, err := crypto.HmacSha256(context.Background(), []byte("pass"), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
|
||||
require.NoError(t, err)
|
||||
hm, err := crypto.HmacSha256(context.Background(), []byte("pass"), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
|
||||
|
||||
spkCred := static.TestSshPrivateKeyCredential(t, conn, wrapper, "user", static.TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId())
|
||||
spkHm, err := crypto.HmacSha256(context.Background(), []byte(static.TestSshPrivateKeyPem), databaseWrapper, []byte(store.GetPublicId()), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
cases := []struct {
|
||||
@@ -189,44 +212,66 @@ func TestGet(t *testing.T) {
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
id: cred.GetPublicId(),
|
||||
name: "success-up",
|
||||
id: upCred.GetPublicId(),
|
||||
res: &pbs.GetCredentialResponse{
|
||||
Item: &pb.Credential{
|
||||
Id: cred.GetPublicId(),
|
||||
CredentialStoreId: cred.GetStoreId(),
|
||||
Id: upCred.GetPublicId(),
|
||||
CredentialStoreId: upCred.GetStoreId(),
|
||||
Scope: &scopepb.ScopeInfo{Id: store.GetScopeId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
|
||||
Type: static.UsernamePasswordSubtype.String(),
|
||||
Type: credential.UsernamePasswordSubtype.String(),
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
CreatedTime: cred.CreateTime.GetTimestamp(),
|
||||
UpdatedTime: cred.UpdateTime.GetTimestamp(),
|
||||
CreatedTime: upCred.CreateTime.GetTimestamp(),
|
||||
UpdatedTime: upCred.UpdateTime.GetTimestamp(),
|
||||
Version: 1,
|
||||
Attrs: &pb.Credential_UsernamePasswordAttributes{
|
||||
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
|
||||
Username: wrapperspb.String("user"),
|
||||
PasswordHmac: base64.RawURLEncoding.EncodeToString([]byte(hm)),
|
||||
PasswordHmac: base64.RawURLEncoding.EncodeToString([]byte(upHm)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success-prev-prefix",
|
||||
id: credPrev.GetPublicId(),
|
||||
name: "success-up-prev-prefix",
|
||||
id: upCredPrev.GetPublicId(),
|
||||
res: &pbs.GetCredentialResponse{
|
||||
Item: &pb.Credential{
|
||||
Id: credPrev.GetPublicId(),
|
||||
CredentialStoreId: cred.GetStoreId(),
|
||||
Id: upCredPrev.GetPublicId(),
|
||||
CredentialStoreId: upCred.GetStoreId(),
|
||||
Scope: &scopepb.ScopeInfo{Id: store.GetScopeId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
|
||||
Type: static.UsernamePasswordSubtype.String(),
|
||||
Type: credential.UsernamePasswordSubtype.String(),
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
CreatedTime: credPrev.CreateTime.GetTimestamp(),
|
||||
UpdatedTime: credPrev.UpdateTime.GetTimestamp(),
|
||||
CreatedTime: upCredPrev.CreateTime.GetTimestamp(),
|
||||
UpdatedTime: upCredPrev.UpdateTime.GetTimestamp(),
|
||||
Version: 1,
|
||||
Attrs: &pb.Credential_UsernamePasswordAttributes{
|
||||
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
|
||||
Username: wrapperspb.String("user"),
|
||||
PasswordHmac: base64.RawURLEncoding.EncodeToString([]byte(hm)),
|
||||
PasswordHmac: base64.RawURLEncoding.EncodeToString([]byte(upHm)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success-spk",
|
||||
id: spkCred.GetPublicId(),
|
||||
res: &pbs.GetCredentialResponse{
|
||||
Item: &pb.Credential{
|
||||
Id: spkCred.GetPublicId(),
|
||||
CredentialStoreId: spkCred.GetStoreId(),
|
||||
Scope: &scopepb.ScopeInfo{Id: store.GetScopeId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
|
||||
Type: credential.SshPrivateKeySubtype.String(),
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
CreatedTime: spkCred.CreateTime.GetTimestamp(),
|
||||
UpdatedTime: spkCred.UpdateTime.GetTimestamp(),
|
||||
Version: 1,
|
||||
Attrs: &pb.Credential_SshPrivateKeyAttributes{
|
||||
SshPrivateKeyAttributes: &pb.SshPrivateKeyAttributes{
|
||||
Username: wrapperspb.String("user"),
|
||||
PrivateKeyHmac: base64.RawURLEncoding.EncodeToString([]byte(spkHm)),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -234,7 +279,7 @@ func TestGet(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "not found error",
|
||||
id: fmt.Sprintf("%s_1234567890", static.UsernamePasswordCredentialPrefix),
|
||||
id: fmt.Sprintf("%s_1234567890", credential.UsernamePasswordCredentialPrefix),
|
||||
err: handlers.NotFoundError(),
|
||||
},
|
||||
{
|
||||
@@ -286,10 +331,12 @@ func TestDelete(t *testing.T) {
|
||||
_, prj := iam.TestScopes(t, iamRepo)
|
||||
|
||||
store := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
|
||||
cred := static.TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId())
|
||||
s, err := NewService(staticRepoFn, iamRepoFn)
|
||||
require.NoError(t, err)
|
||||
|
||||
upCred := static.TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId())
|
||||
spkCred := static.TestSshPrivateKeyCredential(t, conn, wrapper, "user", static.TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId())
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
id string
|
||||
@@ -297,12 +344,16 @@ func TestDelete(t *testing.T) {
|
||||
res *pbs.DeleteCredentialResponse
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
id: cred.GetPublicId(),
|
||||
name: "success-up",
|
||||
id: upCred.GetPublicId(),
|
||||
},
|
||||
{
|
||||
name: "success-spk",
|
||||
id: spkCred.GetPublicId(),
|
||||
},
|
||||
{
|
||||
name: "not found error",
|
||||
id: fmt.Sprintf("%s_1234567890", static.UsernamePasswordCredentialPrefix),
|
||||
id: fmt.Sprintf("%s_1234567890", credential.UsernamePasswordCredentialPrefix),
|
||||
err: handlers.NotFoundError(),
|
||||
},
|
||||
{
|
||||
@@ -357,8 +408,8 @@ func TestCreate(t *testing.T) {
|
||||
name: "Can't specify Id",
|
||||
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
|
||||
CredentialStoreId: store.GetPublicId(),
|
||||
Id: static.UsernamePasswordCredentialPrefix + "_notallowed",
|
||||
Type: static.UsernamePasswordSubtype.String(),
|
||||
Id: credential.UsernamePasswordCredentialPrefix + "_notallowed",
|
||||
Type: credential.UsernamePasswordSubtype.String(),
|
||||
Attrs: &pb.Credential_UsernamePasswordAttributes{
|
||||
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
|
||||
Username: wrapperspb.String("username"),
|
||||
@@ -373,7 +424,7 @@ func TestCreate(t *testing.T) {
|
||||
name: "Invalid Credential Store Id",
|
||||
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
|
||||
CredentialStoreId: "p_invalidid",
|
||||
Type: static.UsernamePasswordSubtype.String(),
|
||||
Type: credential.UsernamePasswordSubtype.String(),
|
||||
Attrs: &pb.Credential_UsernamePasswordAttributes{
|
||||
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
|
||||
Username: wrapperspb.String("username"),
|
||||
@@ -389,7 +440,7 @@ func TestCreate(t *testing.T) {
|
||||
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
|
||||
CredentialStoreId: store.GetPublicId(),
|
||||
CreatedTime: timestamppb.Now(),
|
||||
Type: static.UsernamePasswordSubtype.String(),
|
||||
Type: credential.UsernamePasswordSubtype.String(),
|
||||
Attrs: &pb.Credential_UsernamePasswordAttributes{
|
||||
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
|
||||
Username: wrapperspb.String("username"),
|
||||
@@ -404,7 +455,7 @@ func TestCreate(t *testing.T) {
|
||||
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
|
||||
CredentialStoreId: store.GetPublicId(),
|
||||
UpdatedTime: timestamppb.Now(),
|
||||
Type: static.UsernamePasswordSubtype.String(),
|
||||
Type: credential.UsernamePasswordSubtype.String(),
|
||||
Attrs: &pb.Credential_UsernamePasswordAttributes{
|
||||
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
|
||||
Username: wrapperspb.String("username"),
|
||||
@@ -433,7 +484,7 @@ func TestCreate(t *testing.T) {
|
||||
name: "Must provide username",
|
||||
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
|
||||
CredentialStoreId: store.GetPublicId(),
|
||||
Type: static.UsernamePasswordSubtype.String(),
|
||||
Type: credential.UsernamePasswordSubtype.String(),
|
||||
Attrs: &pb.Credential_UsernamePasswordAttributes{
|
||||
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
|
||||
Password: wrapperspb.String("password"),
|
||||
@@ -444,10 +495,10 @@ func TestCreate(t *testing.T) {
|
||||
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
||||
},
|
||||
{
|
||||
name: "Must provide password]",
|
||||
name: "Must provide password",
|
||||
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
|
||||
CredentialStoreId: store.GetPublicId(),
|
||||
Type: static.UsernamePasswordSubtype.String(),
|
||||
Type: credential.UsernamePasswordSubtype.String(),
|
||||
Attrs: &pb.Credential_UsernamePasswordAttributes{
|
||||
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
|
||||
Username: wrapperspb.String("username"),
|
||||
@@ -458,10 +509,24 @@ func TestCreate(t *testing.T) {
|
||||
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
name: "Must provide private key",
|
||||
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
|
||||
CredentialStoreId: store.GetPublicId(),
|
||||
Type: static.UsernamePasswordSubtype.String(),
|
||||
Type: credential.SshPrivateKeySubtype.String(),
|
||||
Attrs: &pb.Credential_SshPrivateKeyAttributes{
|
||||
SshPrivateKeyAttributes: &pb.SshPrivateKeyAttributes{
|
||||
Username: wrapperspb.String("username"),
|
||||
},
|
||||
},
|
||||
}},
|
||||
res: nil,
|
||||
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
||||
},
|
||||
{
|
||||
name: "valid-up",
|
||||
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
|
||||
CredentialStoreId: store.GetPublicId(),
|
||||
Type: credential.UsernamePasswordSubtype.String(),
|
||||
Attrs: &pb.Credential_UsernamePasswordAttributes{
|
||||
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
|
||||
Username: wrapperspb.String("username"),
|
||||
@@ -469,9 +534,9 @@ func TestCreate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}},
|
||||
idPrefix: static.UsernamePasswordCredentialPrefix + "_",
|
||||
idPrefix: credential.UsernamePasswordCredentialPrefix + "_",
|
||||
res: &pbs.CreateCredentialResponse{
|
||||
Uri: fmt.Sprintf("credentials/%s_", static.UsernamePasswordCredentialPrefix),
|
||||
Uri: fmt.Sprintf("credentials/%s_", credential.UsernamePasswordCredentialPrefix),
|
||||
Item: &pb.Credential{
|
||||
Id: store.GetPublicId(),
|
||||
CredentialStoreId: store.GetPublicId(),
|
||||
@@ -479,7 +544,34 @@ func TestCreate(t *testing.T) {
|
||||
UpdatedTime: store.GetUpdateTime().GetTimestamp(),
|
||||
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()},
|
||||
Version: 1,
|
||||
Type: static.UsernamePasswordSubtype.String(),
|
||||
Type: credential.UsernamePasswordSubtype.String(),
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid-spk",
|
||||
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
|
||||
CredentialStoreId: store.GetPublicId(),
|
||||
Type: credential.SshPrivateKeySubtype.String(),
|
||||
Attrs: &pb.Credential_SshPrivateKeyAttributes{
|
||||
SshPrivateKeyAttributes: &pb.SshPrivateKeyAttributes{
|
||||
Username: wrapperspb.String("username"),
|
||||
PrivateKey: wrapperspb.String(static.TestSshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
}},
|
||||
idPrefix: credential.SshPrivateKeyCredentialPrefix + "_",
|
||||
res: &pbs.CreateCredentialResponse{
|
||||
Uri: fmt.Sprintf("credentials/%s_", credential.SshPrivateKeyCredentialPrefix),
|
||||
Item: &pb.Credential{
|
||||
Id: store.GetPublicId(),
|
||||
CredentialStoreId: store.GetPublicId(),
|
||||
CreatedTime: store.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: store.GetUpdateTime().GetTimestamp(),
|
||||
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()},
|
||||
Version: 1,
|
||||
Type: credential.SshPrivateKeySubtype.String(),
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
},
|
||||
},
|
||||
@@ -516,15 +608,33 @@ func TestCreate(t *testing.T) {
|
||||
|
||||
// Calculate hmac
|
||||
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase)
|
||||
password := tc.req.GetItem().GetUsernamePasswordAttributes().GetPassword().GetValue()
|
||||
hm, err := crypto.HmacSha256(context.Background(), []byte(password), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
|
||||
require.NoError(err)
|
||||
|
||||
// Validate attributes equal
|
||||
assert.Equal(tc.req.GetItem().GetUsernamePasswordAttributes().GetUsername().GetValue(),
|
||||
got.GetItem().GetUsernamePasswordAttributes().GetUsername().GetValue())
|
||||
assert.Equal(base64.RawURLEncoding.EncodeToString([]byte(hm)), got.GetItem().GetUsernamePasswordAttributes().GetPasswordHmac())
|
||||
assert.Empty(got.GetItem().GetUsernamePasswordAttributes().GetPassword())
|
||||
switch tc.req.Item.Type {
|
||||
case credential.UsernamePasswordSubtype.String():
|
||||
password := tc.req.GetItem().GetUsernamePasswordAttributes().GetPassword().GetValue()
|
||||
hm, err := crypto.HmacSha256(context.Background(), []byte(password), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
|
||||
require.NoError(err)
|
||||
|
||||
// Validate attributes equal
|
||||
assert.Equal(tc.req.GetItem().GetUsernamePasswordAttributes().GetUsername().GetValue(),
|
||||
got.GetItem().GetUsernamePasswordAttributes().GetUsername().GetValue())
|
||||
assert.Equal(base64.RawURLEncoding.EncodeToString([]byte(hm)), got.GetItem().GetUsernamePasswordAttributes().GetPasswordHmac())
|
||||
assert.Empty(got.GetItem().GetUsernamePasswordAttributes().GetPassword())
|
||||
|
||||
case credential.SshPrivateKeySubtype.String():
|
||||
hm, err := crypto.HmacSha256(context.Background(), []byte(static.TestSshPrivateKeyPem), databaseWrapper, []byte(store.GetPublicId()), nil)
|
||||
require.NoError(err)
|
||||
|
||||
// Validate attributes equal
|
||||
assert.Equal(tc.req.GetItem().GetSshPrivateKeyAttributes().GetUsername().GetValue(),
|
||||
got.GetItem().GetSshPrivateKeyAttributes().GetUsername().GetValue())
|
||||
assert.Equal(base64.RawURLEncoding.EncodeToString([]byte(hm)), got.GetItem().GetSshPrivateKeyAttributes().GetPrivateKeyHmac())
|
||||
assert.Empty(got.GetItem().GetSshPrivateKeyAttributes().GetPrivateKey())
|
||||
|
||||
default:
|
||||
require.Fail("unknown type")
|
||||
}
|
||||
|
||||
// Clear attributes for compare below
|
||||
got.Item.Attrs = nil
|
||||
@@ -539,6 +649,16 @@ func TestCreate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
const TestSecondarySshPrivateKeyPem = `
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACDxfwhEAZKrnsbQxOjVA3PFiB3bW3tSpNKx8TdMiCqlzQAAAJDmpbfr5qW3
|
||||
6wAAAAtzc2gtZWQyNTUxOQAAACDxfwhEAZKrnsbQxOjVA3PFiB3bW3tSpNKx8TdMiCqlzQ
|
||||
AAAEBvvkQkH06ad2GpX1VVARzu9NkHA6gzamAaQ/hkn5FuZvF/CEQBkquextDE6NUDc8WI
|
||||
Hdtbe1Kk0rHxN0yIKqXNAAAACWplZmZAYXJjaAECAwQ=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
`
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
@@ -565,7 +685,7 @@ func TestUpdate(t *testing.T) {
|
||||
|
||||
store := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
|
||||
|
||||
freshCred := func(user, pass string) (*static.UsernamePasswordCredential, func()) {
|
||||
freshCredUp := func(user, pass string) (*static.UsernamePasswordCredential, func()) {
|
||||
t.Helper()
|
||||
cred := static.TestUsernamePasswordCredential(t, conn, wrapper, user, pass, store.GetPublicId(), prj.GetPublicId())
|
||||
clean := func() {
|
||||
@@ -575,6 +695,16 @@ func TestUpdate(t *testing.T) {
|
||||
return cred, clean
|
||||
}
|
||||
|
||||
freshCredSpk := func(user string) (*static.SshPrivateKeyCredential, func()) {
|
||||
t.Helper()
|
||||
cred := static.TestSshPrivateKeyCredential(t, conn, wrapper, user, static.TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId())
|
||||
clean := func() {
|
||||
_, err := s.DeleteCredential(ctx, &pbs.DeleteCredentialRequest{Id: cred.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
return cred, clean
|
||||
}
|
||||
|
||||
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -628,7 +758,23 @@ func TestUpdate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update-username",
|
||||
name: "name-and-description-spk",
|
||||
req: &pbs.UpdateCredentialRequest{
|
||||
UpdateMask: fieldmask("name", "description"),
|
||||
Item: &pb.Credential{
|
||||
Name: wrapperspb.String("new-name"),
|
||||
Description: wrapperspb.String("new-description"),
|
||||
},
|
||||
},
|
||||
res: func(in *pb.Credential) *pb.Credential {
|
||||
out := proto.Clone(in).(*pb.Credential)
|
||||
out.Name = wrapperspb.String("new-name")
|
||||
out.Description = wrapperspb.String("new-description")
|
||||
return out
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update-username-up",
|
||||
req: &pbs.UpdateCredentialRequest{
|
||||
UpdateMask: fieldmask("attributes.username"),
|
||||
Item: &pb.Credential{
|
||||
@@ -645,6 +791,24 @@ func TestUpdate(t *testing.T) {
|
||||
return out
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update-username-spk",
|
||||
req: &pbs.UpdateCredentialRequest{
|
||||
UpdateMask: fieldmask("attributes.username"),
|
||||
Item: &pb.Credential{
|
||||
Attrs: &pb.Credential_SshPrivateKeyAttributes{
|
||||
SshPrivateKeyAttributes: &pb.SshPrivateKeyAttributes{
|
||||
Username: wrapperspb.String("new-user-name"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
res: func(in *pb.Credential) *pb.Credential {
|
||||
out := proto.Clone(in).(*pb.Credential)
|
||||
out.GetSshPrivateKeyAttributes().Username = wrapperspb.String("new-user-name")
|
||||
return out
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update-password",
|
||||
req: &pbs.UpdateCredentialRequest{
|
||||
@@ -665,6 +829,26 @@ func TestUpdate(t *testing.T) {
|
||||
return out
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update-spk",
|
||||
req: &pbs.UpdateCredentialRequest{
|
||||
UpdateMask: fieldmask("attributes.private_key"),
|
||||
Item: &pb.Credential{
|
||||
Attrs: &pb.Credential_SshPrivateKeyAttributes{
|
||||
SshPrivateKeyAttributes: &pb.SshPrivateKeyAttributes{
|
||||
PrivateKey: wrapperspb.String(TestSecondarySshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
res: func(in *pb.Credential) *pb.Credential {
|
||||
out := proto.Clone(in).(*pb.Credential)
|
||||
hm, err := crypto.HmacSha256(context.Background(), []byte(TestSecondarySshPrivateKeyPem), databaseWrapper, []byte(store.GetPublicId()), nil)
|
||||
require.NoError(t, err)
|
||||
out.GetSshPrivateKeyAttributes().PrivateKeyHmac = base64.RawURLEncoding.EncodeToString([]byte(hm))
|
||||
return out
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update-username-and-password",
|
||||
req: &pbs.UpdateCredentialRequest{
|
||||
@@ -689,12 +873,43 @@ func TestUpdate(t *testing.T) {
|
||||
return out
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update-username-and-spk",
|
||||
req: &pbs.UpdateCredentialRequest{
|
||||
UpdateMask: fieldmask("attributes.username", "attributes.private_key"),
|
||||
Item: &pb.Credential{
|
||||
Attrs: &pb.Credential_SshPrivateKeyAttributes{
|
||||
SshPrivateKeyAttributes: &pb.SshPrivateKeyAttributes{
|
||||
Username: wrapperspb.String("new-username"),
|
||||
PrivateKey: wrapperspb.String(TestSecondarySshPrivateKeyPem),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
res: func(in *pb.Credential) *pb.Credential {
|
||||
out := proto.Clone(in).(*pb.Credential)
|
||||
|
||||
out.GetSshPrivateKeyAttributes().Username = wrapperspb.String("new-username")
|
||||
|
||||
hm, err := crypto.HmacSha256(context.Background(), []byte(TestSecondarySshPrivateKeyPem), databaseWrapper, []byte(store.GetPublicId()), nil)
|
||||
require.NoError(t, err)
|
||||
out.GetSshPrivateKeyAttributes().PrivateKeyHmac = base64.RawURLEncoding.EncodeToString([]byte(hm))
|
||||
return out
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range successCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
cred, cleanup := freshCred("user", "pass")
|
||||
var cred credential.Static
|
||||
var cleanup func()
|
||||
switch {
|
||||
case strings.Contains(tc.name, "spk"):
|
||||
cred, cleanup = freshCredSpk("user")
|
||||
default:
|
||||
cred, cleanup = freshCredUp("user", "pass")
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
if tc.req.Item.GetVersion() == 0 {
|
||||
@@ -723,58 +938,60 @@ func TestUpdate(t *testing.T) {
|
||||
assert.Empty(cmp.Diff(got, want, protocmp.Transform()))
|
||||
})
|
||||
}
|
||||
/*
|
||||
|
||||
// cant update read only fields
|
||||
cred, cleanup := freshCred("user", "pass")
|
||||
defer cleanup()
|
||||
// cant update read only fields
|
||||
cred, cleanup := freshCred("user", "pass")
|
||||
defer cleanup()
|
||||
|
||||
newStore := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
|
||||
newStore := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
|
||||
|
||||
roCases := []struct {
|
||||
path string
|
||||
item *pb.Credential
|
||||
matcher func(t *testing.T, e error) // When not set defaults to checking against InvalidArgument Error
|
||||
}{
|
||||
{
|
||||
path: "type",
|
||||
item: &pb.Credential{Type: "something"},
|
||||
},
|
||||
{
|
||||
path: "store_id",
|
||||
item: &pb.Credential{CredentialStoreId: newStore.GetPublicId()},
|
||||
},
|
||||
{
|
||||
path: "updated_time",
|
||||
item: &pb.Credential{UpdatedTime: timestamppb.Now()},
|
||||
},
|
||||
{
|
||||
path: "created_time",
|
||||
item: &pb.Credential{UpdatedTime: timestamppb.Now()},
|
||||
},
|
||||
{
|
||||
path: "authorized_actions",
|
||||
item: &pb.Credential{AuthorizedActions: append(testAuthorizedActions, "another")},
|
||||
},
|
||||
}
|
||||
for _, tc := range roCases {
|
||||
t.Run(fmt.Sprintf("ReadOnlyField/%s", tc.path), func(t *testing.T) {
|
||||
req := &pbs.UpdateCredentialRequest{
|
||||
Id: cred.GetPublicId(),
|
||||
Item: tc.item,
|
||||
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{tc.path}},
|
||||
}
|
||||
req.Item.Version = cred.Version
|
||||
|
||||
got, gErr := s.UpdateCredential(ctx, req)
|
||||
assert.Error(t, gErr)
|
||||
matcher := tc.matcher
|
||||
if matcher == nil {
|
||||
matcher = func(t *testing.T, e error) {
|
||||
assert.Truef(t, errors.Is(gErr, handlers.ApiErrorWithCode(codes.InvalidArgument)), "got error %v, wanted invalid argument", gErr)
|
||||
roCases := []struct {
|
||||
path string
|
||||
item *pb.Credential
|
||||
matcher func(t *testing.T, e error) // When not set defaults to checking against InvalidArgument Error
|
||||
}{
|
||||
{
|
||||
path: "type",
|
||||
item: &pb.Credential{Type: "something"},
|
||||
},
|
||||
{
|
||||
path: "store_id",
|
||||
item: &pb.Credential{CredentialStoreId: newStore.GetPublicId()},
|
||||
},
|
||||
{
|
||||
path: "updated_time",
|
||||
item: &pb.Credential{UpdatedTime: timestamppb.Now()},
|
||||
},
|
||||
{
|
||||
path: "created_time",
|
||||
item: &pb.Credential{UpdatedTime: timestamppb.Now()},
|
||||
},
|
||||
{
|
||||
path: "authorized_actions",
|
||||
item: &pb.Credential{AuthorizedActions: append(testAuthorizedActions, "another")},
|
||||
},
|
||||
}
|
||||
for _, tc := range roCases {
|
||||
t.Run(fmt.Sprintf("ReadOnlyField/%s", tc.path), func(t *testing.T) {
|
||||
req := &pbs.UpdateCredentialRequest{
|
||||
Id: cred.GetPublicId(),
|
||||
Item: tc.item,
|
||||
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{tc.path}},
|
||||
}
|
||||
}
|
||||
matcher(t, gErr)
|
||||
assert.Nil(t, got)
|
||||
})
|
||||
}
|
||||
req.Item.Version = cred.Version
|
||||
|
||||
got, gErr := s.UpdateCredential(ctx, req)
|
||||
assert.Error(t, gErr)
|
||||
matcher := tc.matcher
|
||||
if matcher == nil {
|
||||
matcher = func(t *testing.T, e error) {
|
||||
assert.Truef(t, errors.Is(gErr, handlers.ApiErrorWithCode(codes.InvalidArgument)), "got error %v, wanted invalid argument", gErr)
|
||||
}
|
||||
}
|
||||
matcher(t, gErr)
|
||||
assert.Nil(t, got)
|
||||
})
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/credential"
|
||||
credstatic "github.com/hashicorp/boundary/internal/credential/static"
|
||||
"github.com/hashicorp/boundary/internal/credential/vault"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/common"
|
||||
@@ -1797,13 +1796,13 @@ func validateAddCredentialSourcesRequest(req *pbs.AddTargetCredentialSourcesRequ
|
||||
badFields[globals.EgressCredentialSourceIdsField] = "Application or Egress Credential Source IDs must be provided."
|
||||
}
|
||||
for _, cl := range req.GetApplicationCredentialSourceIds() {
|
||||
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.UsernamePasswordCredentialPrefix, credstatic.PreviousUsernamePasswordCredentialPrefix) {
|
||||
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credential.UsernamePasswordCredentialPrefix, credential.PreviousUsernamePasswordCredentialPrefix) {
|
||||
badFields[globals.ApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, cl := range req.GetEgressCredentialSourceIds() {
|
||||
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.UsernamePasswordCredentialPrefix, credstatic.PreviousUsernamePasswordCredentialPrefix) {
|
||||
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credential.UsernamePasswordCredentialPrefix, credential.PreviousUsernamePasswordCredentialPrefix) {
|
||||
badFields[globals.EgressCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
|
||||
break
|
||||
}
|
||||
@@ -1823,13 +1822,13 @@ func validateSetCredentialSourcesRequest(req *pbs.SetTargetCredentialSourcesRequ
|
||||
badFields[globals.VersionField] = "Required field."
|
||||
}
|
||||
for _, cl := range req.GetApplicationCredentialSourceIds() {
|
||||
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.UsernamePasswordCredentialPrefix, credstatic.PreviousUsernamePasswordCredentialPrefix) {
|
||||
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credential.UsernamePasswordCredentialPrefix, credential.PreviousUsernamePasswordCredentialPrefix) {
|
||||
badFields[globals.ApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, cl := range req.GetEgressCredentialSourceIds() {
|
||||
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.UsernamePasswordCredentialPrefix, credstatic.PreviousUsernamePasswordCredentialPrefix) {
|
||||
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credential.UsernamePasswordCredentialPrefix, credential.PreviousUsernamePasswordCredentialPrefix) {
|
||||
badFields[globals.EgressCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
|
||||
break
|
||||
}
|
||||
@@ -1853,13 +1852,13 @@ func validateRemoveCredentialSourcesRequest(req *pbs.RemoveTargetCredentialSourc
|
||||
badFields[globals.EgressCredentialSourceIdsField] = "Application or Egress Credential Source IDs must be provided."
|
||||
}
|
||||
for _, cl := range req.GetApplicationCredentialSourceIds() {
|
||||
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.UsernamePasswordCredentialPrefix, credstatic.PreviousUsernamePasswordCredentialPrefix) {
|
||||
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credential.UsernamePasswordCredentialPrefix, credential.PreviousUsernamePasswordCredentialPrefix) {
|
||||
badFields[globals.ApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, cl := range req.GetEgressCredentialSourceIds() {
|
||||
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.UsernamePasswordCredentialPrefix, credstatic.PreviousUsernamePasswordCredentialPrefix) {
|
||||
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credential.UsernamePasswordCredentialPrefix, credential.PreviousUsernamePasswordCredentialPrefix) {
|
||||
badFields[globals.EgressCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -2734,7 +2734,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
credResp, err := credService.CreateCredential(ctx, &pbs.CreateCredentialRequest{Item: &credpb.Credential{
|
||||
CredentialStoreId: staticStore.GetPublicId(),
|
||||
Type: credstatic.UsernamePasswordSubtype.String(),
|
||||
Type: credential.UsernamePasswordSubtype.String(),
|
||||
Name: wrapperspb.String("Cred Name"),
|
||||
Description: wrapperspb.String("Cred Description"),
|
||||
Attrs: &credpb.Credential_UsernamePasswordAttributes{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
begin;
|
||||
|
||||
-- updated in 39/01_static_ssh_private_key_creds
|
||||
-- drop constraint so we can migrate user_password to username_password
|
||||
alter table credential_type_enm
|
||||
drop constraint only_predefined_credential_types_allowed;
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
begin;
|
||||
|
||||
-- drop constraint so we can add ssh_private_key
|
||||
alter table credential_type_enm
|
||||
drop constraint only_predefined_credential_types_allowed;
|
||||
|
||||
-- Add new constraint that only allows known types
|
||||
-- This replaces the constraint defined in 36/01_credential_type_up
|
||||
alter table credential_type_enm
|
||||
add constraint only_predefined_credential_types_allowed
|
||||
check (
|
||||
name in (
|
||||
'unspecified',
|
||||
'username_password',
|
||||
'ssh_private_key'
|
||||
)
|
||||
);
|
||||
|
||||
create table credential_static_ssh_private_key_credential (
|
||||
public_id wt_public_id primary key,
|
||||
store_id wt_public_id not null
|
||||
constraint credential_static_store_fkey
|
||||
references credential_static_store (public_id)
|
||||
on delete cascade
|
||||
on update cascade,
|
||||
name wt_name,
|
||||
description wt_description,
|
||||
create_time wt_timestamp,
|
||||
update_time wt_timestamp,
|
||||
version wt_version,
|
||||
|
||||
username text not null
|
||||
constraint username_must_not_be_empty
|
||||
check(length(trim(username)) > 0),
|
||||
private_key_encrypted bytea not null
|
||||
constraint private_key_encrypted_must_not_be_empty
|
||||
check(length(private_key_encrypted) > 0),
|
||||
private_key_hmac bytea not null
|
||||
constraint ssh_private_key_hmac_must_not_be_empty
|
||||
check(length(private_key_hmac) > 0),
|
||||
key_id text not null
|
||||
constraint kms_data_key_version_fkey
|
||||
references kms_data_key_version (private_id)
|
||||
on delete restrict
|
||||
on update cascade,
|
||||
constraint credential_static_fkey
|
||||
foreign key (store_id, public_id)
|
||||
references credential_static (store_id, public_id)
|
||||
on delete cascade
|
||||
on update cascade,
|
||||
|
||||
constraint credential_static_ssh_private_key_credential_store_id_name_uq
|
||||
unique(store_id, name),
|
||||
|
||||
-- The name of this constraint does not follow our naming conventions for
|
||||
-- unique constraints because it would be to long. The max length for
|
||||
-- identifiers in PostgreSQL is 63 characters.
|
||||
-- credential_static_ssh_private_key_credential_store_id_public_id_uq
|
||||
-- is 67 characters.
|
||||
--
|
||||
-- https://www.postgresql.org/docs/current/limits.html
|
||||
constraint credential_static_ssh_private_key_store_id_public_id_uq
|
||||
unique(store_id, public_id)
|
||||
);
|
||||
comment on table credential_static_ssh_private_key_credential is
|
||||
'credential_static_ssh_private_key_credential is a table where each row is a resource that represents a static ssh private key credential. '
|
||||
'It is a credential_static subtype and an aggregate root.';
|
||||
|
||||
create trigger update_version_column after update on credential_static_ssh_private_key_credential
|
||||
for each row execute procedure update_version_column();
|
||||
|
||||
create trigger update_time_column before update on credential_static_ssh_private_key_credential
|
||||
for each row execute procedure update_time_column();
|
||||
|
||||
create trigger default_create_time_column before insert on credential_static_ssh_private_key_credential
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
create trigger immutable_columns before update on credential_static_ssh_private_key_credential
|
||||
for each row execute procedure immutable_columns('public_id', 'store_id','create_time');
|
||||
|
||||
create trigger insert_credential_static_subtype before insert on credential_static_ssh_private_key_credential
|
||||
for each row execute procedure insert_credential_static_subtype();
|
||||
|
||||
create trigger delete_credential_static_subtype after delete on credential_static_ssh_private_key_credential
|
||||
for each row execute procedure delete_credential_static_subtype();
|
||||
|
||||
insert into oplog_ticket (name, version)
|
||||
values
|
||||
('credential_static_ssh_private_key_credential', 1);
|
||||
|
||||
commit;
|
||||
@@ -64,6 +64,11 @@ message Credential {
|
||||
(custom_options.v1.generate_sdk_option) = true,
|
||||
(custom_options.v1.subtype) = "username_password"
|
||||
];
|
||||
SshPrivateKeyAttributes ssh_private_key_attributes = 102 [
|
||||
(google.api.field_visibility).restriction = "INTERNAL",
|
||||
(custom_options.v1.generate_sdk_option) = true,
|
||||
(custom_options.v1.subtype) = "ssh_private_key"
|
||||
];
|
||||
}
|
||||
|
||||
// Output only. The available actions on this resource for this user.
|
||||
@@ -100,3 +105,34 @@ message UsernamePasswordAttributes {
|
||||
}
|
||||
]; // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
// The attributes of a SshPrivateKey Credential.
|
||||
message SshPrivateKeyAttributes {
|
||||
// The username associated with the credential.
|
||||
google.protobuf.StringValue username = 10 [
|
||||
(custom_options.v1.generate_sdk_option) = true,
|
||||
(custom_options.v1.mask_mapping) = {
|
||||
this: "attributes.username"
|
||||
that: "Username"
|
||||
}
|
||||
]; // @gotags: `class:"public"`
|
||||
|
||||
// Input only. The SSH private key associated with the credential.
|
||||
google.protobuf.StringValue private_key = 20 [
|
||||
json_name = "private_key",
|
||||
(custom_options.v1.generate_sdk_option) = true,
|
||||
(custom_options.v1.mask_mapping) = {
|
||||
this: "attributes.private_key"
|
||||
that: "PrivateKey"
|
||||
}
|
||||
]; // @gotags: `class:"secret"`
|
||||
|
||||
// Output only. The hmac value of the SSH private key.
|
||||
string private_key_hmac = 30 [
|
||||
json_name = "private_key_hmac",
|
||||
(custom_options.v1.mask_mapping) = {
|
||||
this: "attributes.private_key_hmac"
|
||||
that: "PrivateKeyHmac"
|
||||
}
|
||||
]; // @gotags: `class:"public"`
|
||||
}
|
||||
@@ -116,3 +116,74 @@ message UsernamePasswordCredential {
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
string key_id = 12;
|
||||
}
|
||||
|
||||
message SshPrivateKeyCredential {
|
||||
// public_id is a surrogate key suitable for use in a public API.
|
||||
// @inject_tag: `gorm:"primary_key"`
|
||||
string public_id = 1;
|
||||
|
||||
// create_time is set by the database.
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
timestamp.v1.Timestamp create_time = 2;
|
||||
|
||||
// update_time is set by the database.
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
timestamp.v1.Timestamp update_time = 3;
|
||||
|
||||
// name is optional. If set, it must be unique within scope_id.
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string name = 4 [(custom_options.v1.mask_mapping) = {
|
||||
this: "Name"
|
||||
that: "name"
|
||||
}];
|
||||
|
||||
// description is optional.
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string description = 5 [(custom_options.v1.mask_mapping) = {
|
||||
this: "Description"
|
||||
that: "description"
|
||||
}];
|
||||
|
||||
// store_id of the owning static credential store.
|
||||
// It must be set.
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
string store_id = 6;
|
||||
|
||||
// version allows optimistic locking of the resource.
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
uint32 version = 7;
|
||||
|
||||
// username is the username associated with the credential.
|
||||
// It must be set.
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
string username = 8 [(custom_options.v1.mask_mapping) = {
|
||||
this: "Username"
|
||||
that: "attributes.username"
|
||||
}];
|
||||
|
||||
// private_key is the plain-text of the private key associated with the
|
||||
// credential. We are not storing this plain-text private key in the database.
|
||||
// @inject_tag: `gorm:"-" wrapping:"pt,private_key"`
|
||||
bytes private_key = 9 [(custom_options.v1.mask_mapping) = {
|
||||
this: "PrivateKey"
|
||||
that: "attributes.private_key"
|
||||
}];
|
||||
|
||||
// private_key_encrypted is the ciphertext of the private key. It is stored in
|
||||
// the database.
|
||||
// @inject_tag: `gorm:"column:private_key_encrypted;not_null" wrapping:"ct,private_key"`
|
||||
bytes private_key_encrypted = 10;
|
||||
|
||||
// private_key_hmac is a sha256-hmac of the unencrypted private key. It is recalculated
|
||||
// everytime the private key is updated.
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
bytes private_key_hmac = 11 [(custom_options.v1.mask_mapping) = {
|
||||
this: "PrivateKeyHmac"
|
||||
that: "attributes.private_key_hmac"
|
||||
}];
|
||||
|
||||
// The key_id of the kms database key used for encrypting this entry.
|
||||
// It must be set.
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
string key_id = 12;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentials"
|
||||
"github.com/hashicorp/boundary/api/credentialstores"
|
||||
"github.com/hashicorp/boundary/internal/credential/static"
|
||||
"github.com/hashicorp/boundary/internal/credential"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -35,7 +35,7 @@ func TestList(t *testing.T) {
|
||||
require.NoError(err)
|
||||
assert.Empty(ul.Items)
|
||||
|
||||
cred, err := credClient.Create(tc.Context(), static.UsernamePasswordSubtype.String(), cs.Item.Id,
|
||||
cred, err := credClient.Create(tc.Context(), credential.UsernamePasswordSubtype.String(), cs.Item.Id,
|
||||
credentials.WithName("0"),
|
||||
credentials.WithDescription("description"),
|
||||
credentials.WithUsernamePasswordCredentialUsername("user"),
|
||||
@@ -50,7 +50,7 @@ func TestList(t *testing.T) {
|
||||
assert.ElementsMatch(comparableCatalogSlice(expected[:1]), comparableCatalogSlice(ul.Items))
|
||||
|
||||
for i := 1; i < 10; i++ {
|
||||
cred, err := credClient.Create(tc.Context(), static.UsernamePasswordSubtype.String(), cs.Item.Id,
|
||||
cred, err := credClient.Create(tc.Context(), credential.UsernamePasswordSubtype.String(), cs.Item.Id,
|
||||
credentials.WithName(fmt.Sprintf("%d", i)),
|
||||
credentials.WithDescription("description"),
|
||||
credentials.WithUsernamePasswordCredentialUsername("user"),
|
||||
@@ -110,7 +110,7 @@ func TestCrud(t *testing.T) {
|
||||
}
|
||||
credClient := credentials.NewClient(client)
|
||||
|
||||
cred, err := credClient.Create(tc.Context(), static.UsernamePasswordSubtype.String(), cs.Item.Id, credentials.WithName("foo"),
|
||||
cred, err := credClient.Create(tc.Context(), credential.UsernamePasswordSubtype.String(), cs.Item.Id, credentials.WithName("foo"),
|
||||
credentials.WithUsernamePasswordCredentialUsername("user"), credentials.WithUsernamePasswordCredentialPassword("pass"))
|
||||
require.NoError(err)
|
||||
require.NotNil(cs)
|
||||
@@ -167,7 +167,7 @@ func TestErrors(t *testing.T) {
|
||||
|
||||
c := credentials.NewClient(client)
|
||||
|
||||
cred, err := c.Create(tc.Context(), static.UsernamePasswordSubtype.String(), cs.Item.Id, credentials.WithName("foo"),
|
||||
cred, err := c.Create(tc.Context(), credential.UsernamePasswordSubtype.String(), cs.Item.Id, credentials.WithName("foo"),
|
||||
credentials.WithUsernamePasswordCredentialUsername("user"), credentials.WithUsernamePasswordCredentialPassword("pass"))
|
||||
require.NoError(err)
|
||||
require.NotNil(cred)
|
||||
@@ -189,12 +189,12 @@ func TestErrors(t *testing.T) {
|
||||
assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode())
|
||||
|
||||
// Same name
|
||||
_, err = c.Create(tc.Context(), static.UsernamePasswordSubtype.String(), proj.GetPublicId(), credentials.WithName("foo"))
|
||||
_, err = c.Create(tc.Context(), credential.UsernamePasswordSubtype.String(), proj.GetPublicId(), credentials.WithName("foo"))
|
||||
require.Error(err)
|
||||
apiErr = api.AsServerError(err)
|
||||
assert.NotNil(apiErr)
|
||||
|
||||
_, err = c.Read(tc.Context(), static.UsernamePasswordCredentialPrefix+"_doesntexis")
|
||||
_, err = c.Read(tc.Context(), credential.UsernamePasswordCredentialPrefix+"_doesntexis")
|
||||
require.Error(err)
|
||||
apiErr = api.AsServerError(err)
|
||||
assert.NotNil(apiErr)
|
||||
|
||||
@@ -54,6 +54,7 @@ type Credential struct {
|
||||
// Types that are assignable to Attrs:
|
||||
// *Credential_Attributes
|
||||
// *Credential_UsernamePasswordAttributes
|
||||
// *Credential_SshPrivateKeyAttributes
|
||||
Attrs isCredential_Attrs `protobuf_oneof:"attrs"`
|
||||
// Output only. The available actions on this resource for this user.
|
||||
AuthorizedActions []string `protobuf:"bytes,300,rep,name=authorized_actions,proto3" json:"authorized_actions,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
@@ -175,6 +176,13 @@ func (x *Credential) GetUsernamePasswordAttributes() *UsernamePasswordAttributes
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Credential) GetSshPrivateKeyAttributes() *SshPrivateKeyAttributes {
|
||||
if x, ok := x.GetAttrs().(*Credential_SshPrivateKeyAttributes); ok {
|
||||
return x.SshPrivateKeyAttributes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Credential) GetAuthorizedActions() []string {
|
||||
if x != nil {
|
||||
return x.AuthorizedActions
|
||||
@@ -195,10 +203,16 @@ type Credential_UsernamePasswordAttributes struct {
|
||||
UsernamePasswordAttributes *UsernamePasswordAttributes `protobuf:"bytes,101,opt,name=username_password_attributes,json=usernamePasswordAttributes,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Credential_SshPrivateKeyAttributes struct {
|
||||
SshPrivateKeyAttributes *SshPrivateKeyAttributes `protobuf:"bytes,102,opt,name=ssh_private_key_attributes,json=sshPrivateKeyAttributes,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*Credential_Attributes) isCredential_Attrs() {}
|
||||
|
||||
func (*Credential_UsernamePasswordAttributes) isCredential_Attrs() {}
|
||||
|
||||
func (*Credential_SshPrivateKeyAttributes) isCredential_Attrs() {}
|
||||
|
||||
// The attributes of a UsernamePassword Credential.
|
||||
type UsernamePasswordAttributes struct {
|
||||
state protoimpl.MessageState
|
||||
@@ -266,6 +280,73 @@ func (x *UsernamePasswordAttributes) GetPasswordHmac() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// The attributes of a SshPrivateKey Credential.
|
||||
type SshPrivateKeyAttributes struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The username associated with the credential.
|
||||
Username *wrapperspb.StringValue `protobuf:"bytes,10,opt,name=username,proto3" json:"username,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
// Input only. The SSH private key associated with the credential.
|
||||
PrivateKey *wrapperspb.StringValue `protobuf:"bytes,20,opt,name=private_key,proto3" json:"private_key,omitempty" class:"secret"` // @gotags: `class:"secret"`
|
||||
// Output only. The hmac value of the SSH private key.
|
||||
PrivateKeyHmac string `protobuf:"bytes,30,opt,name=private_key_hmac,proto3" json:"private_key_hmac,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyAttributes) Reset() {
|
||||
*x = SshPrivateKeyAttributes{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_api_resources_credentials_v1_credential_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyAttributes) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SshPrivateKeyAttributes) ProtoMessage() {}
|
||||
|
||||
func (x *SshPrivateKeyAttributes) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_api_resources_credentials_v1_credential_proto_msgTypes[2]
|
||||
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 SshPrivateKeyAttributes.ProtoReflect.Descriptor instead.
|
||||
func (*SshPrivateKeyAttributes) Descriptor() ([]byte, []int) {
|
||||
return file_controller_api_resources_credentials_v1_credential_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyAttributes) GetUsername() *wrapperspb.StringValue {
|
||||
if x != nil {
|
||||
return x.Username
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyAttributes) GetPrivateKey() *wrapperspb.StringValue {
|
||||
if x != nil {
|
||||
return x.PrivateKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SshPrivateKeyAttributes) GetPrivateKeyHmac() string {
|
||||
if x != nil {
|
||||
return x.PrivateKeyHmac
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_controller_api_resources_credentials_v1_credential_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_api_resources_credentials_v1_credential_proto_rawDesc = []byte{
|
||||
@@ -288,7 +369,7 @@ var file_controller_api_resources_credentials_v1_credential_proto_rawDesc = []by
|
||||
0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61,
|
||||
0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa8, 0x06, 0x0a, 0x0a,
|
||||
0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd3, 0x07, 0x0a, 0x0a,
|
||||
0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
|
||||
0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x30, 0x0a, 0x13, 0x63, 0x72,
|
||||
0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69,
|
||||
@@ -335,37 +416,68 @@ var file_controller_api_resources_credentials_v1_credential_proto_rawDesc = []by
|
||||
0x72, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0xfa, 0xd2,
|
||||
0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x48, 0x00,
|
||||
0x52, 0x1a, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,
|
||||
0x72, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x12,
|
||||
0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f,
|
||||
0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x07, 0x0a,
|
||||
0x05, 0x61, 0x74, 0x74, 0x72, 0x73, 0x22, 0xb6, 0x02, 0x0a, 0x1a, 0x55, 0x73, 0x65, 0x72, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69,
|
||||
0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x61, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67,
|
||||
0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x27, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1f, 0x0a,
|
||||
0x13, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72,
|
||||
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x08, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x08,
|
||||
0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x61, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73,
|
||||
0x77, 0x6f, 0x72, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72,
|
||||
0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x27, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd,
|
||||
0x29, 0x1f, 0x0a, 0x13, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70,
|
||||
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x08, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
|
||||
0x64, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x52, 0x0a, 0x0d, 0x70,
|
||||
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x1e, 0x20, 0x01,
|
||||
0x28, 0x09, 0x42, 0x2c, 0xc2, 0xdd, 0x29, 0x28, 0x0a, 0x18, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62,
|
||||
0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d,
|
||||
0x61, 0x63, 0x12, 0x0c, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x48, 0x6d, 0x61, 0x63,
|
||||
0x52, 0x0d, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x42,
|
||||
0x58, 0x5a, 0x56, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61,
|
||||
0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79,
|
||||
0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
|
||||
0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
|
||||
0x73, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x3b, 0x63, 0x72,
|
||||
0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
0x72, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0xa8, 0x01, 0x0a,
|
||||
0x1a, 0x73, 0x73, 0x68, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79,
|
||||
0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x66, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x40, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61,
|
||||
0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x72, 0x65,
|
||||
0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x73, 0x68, 0x50,
|
||||
0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75,
|
||||
0x74, 0x65, 0x73, 0x42, 0x27, 0xa0, 0xda, 0x29, 0x01, 0x9a, 0xe3, 0x29, 0x0f, 0x73, 0x73, 0x68,
|
||||
0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0xfa, 0xd2, 0xe4, 0x93,
|
||||
0x02, 0x0a, 0x12, 0x08, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x48, 0x00, 0x52, 0x17,
|
||||
0x73, 0x73, 0x68, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x74, 0x74,
|
||||
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f,
|
||||
0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02,
|
||||
0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64,
|
||||
0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x61, 0x74, 0x74, 0x72,
|
||||
0x73, 0x22, 0xb6, 0x02, 0x0a, 0x1a, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x50, 0x61,
|
||||
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
|
||||
0x12, 0x61, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x42, 0x27, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1f, 0x0a, 0x13, 0x61, 0x74, 0x74, 0x72,
|
||||
0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12,
|
||||
0x08, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x12, 0x61, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18,
|
||||
0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x42, 0x27, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1f, 0x0a, 0x13, 0x61,
|
||||
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f,
|
||||
0x72, 0x64, 0x12, 0x08, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x08, 0x70, 0x61,
|
||||
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x52, 0x0a, 0x0d, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f,
|
||||
0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2c, 0xc2,
|
||||
0xdd, 0x29, 0x28, 0x0a, 0x18, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e,
|
||||
0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x12, 0x0c, 0x50,
|
||||
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x48, 0x6d, 0x61, 0x63, 0x52, 0x0d, 0x70, 0x61, 0x73,
|
||||
0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x22, 0xc9, 0x02, 0x0a, 0x17, 0x53,
|
||||
0x73, 0x68, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x74, 0x74, 0x72,
|
||||
0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x61, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,
|
||||
0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e,
|
||||
0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x27, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1f,
|
||||
0x0a, 0x13, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65,
|
||||
0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x08, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x52,
|
||||
0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x6c, 0x0a, 0x0b, 0x70, 0x72, 0x69,
|
||||
0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x2c, 0xa0, 0xda,
|
||||
0x29, 0x01, 0xc2, 0xdd, 0x29, 0x24, 0x0a, 0x16, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
|
||||
0x65, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x0a,
|
||||
0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x0b, 0x70, 0x72, 0x69, 0x76,
|
||||
0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x5d, 0x0a, 0x10, 0x70, 0x72, 0x69, 0x76, 0x61,
|
||||
0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x1e, 0x20, 0x01, 0x28,
|
||||
0x09, 0x42, 0x31, 0xc2, 0xdd, 0x29, 0x2d, 0x0a, 0x1b, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75,
|
||||
0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f,
|
||||
0x68, 0x6d, 0x61, 0x63, 0x12, 0x0e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79,
|
||||
0x48, 0x6d, 0x61, 0x63, 0x52, 0x10, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65,
|
||||
0x79, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x42, 0x58, 0x5a, 0x56, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62,
|
||||
0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72,
|
||||
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74,
|
||||
0x69, 0x61, 0x6c, 0x73, 0x3b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73,
|
||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -380,30 +492,34 @@ func file_controller_api_resources_credentials_v1_credential_proto_rawDescGZIP()
|
||||
return file_controller_api_resources_credentials_v1_credential_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_controller_api_resources_credentials_v1_credential_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_controller_api_resources_credentials_v1_credential_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_controller_api_resources_credentials_v1_credential_proto_goTypes = []interface{}{
|
||||
(*Credential)(nil), // 0: controller.api.resources.credentials.v1.Credential
|
||||
(*UsernamePasswordAttributes)(nil), // 1: controller.api.resources.credentials.v1.UsernamePasswordAttributes
|
||||
(*scopes.ScopeInfo)(nil), // 2: controller.api.resources.scopes.v1.ScopeInfo
|
||||
(*wrapperspb.StringValue)(nil), // 3: google.protobuf.StringValue
|
||||
(*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp
|
||||
(*structpb.Struct)(nil), // 5: google.protobuf.Struct
|
||||
(*SshPrivateKeyAttributes)(nil), // 2: controller.api.resources.credentials.v1.SshPrivateKeyAttributes
|
||||
(*scopes.ScopeInfo)(nil), // 3: controller.api.resources.scopes.v1.ScopeInfo
|
||||
(*wrapperspb.StringValue)(nil), // 4: google.protobuf.StringValue
|
||||
(*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp
|
||||
(*structpb.Struct)(nil), // 6: google.protobuf.Struct
|
||||
}
|
||||
var file_controller_api_resources_credentials_v1_credential_proto_depIdxs = []int32{
|
||||
2, // 0: controller.api.resources.credentials.v1.Credential.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
|
||||
3, // 1: controller.api.resources.credentials.v1.Credential.name:type_name -> google.protobuf.StringValue
|
||||
3, // 2: controller.api.resources.credentials.v1.Credential.description:type_name -> google.protobuf.StringValue
|
||||
4, // 3: controller.api.resources.credentials.v1.Credential.created_time:type_name -> google.protobuf.Timestamp
|
||||
4, // 4: controller.api.resources.credentials.v1.Credential.updated_time:type_name -> google.protobuf.Timestamp
|
||||
5, // 5: controller.api.resources.credentials.v1.Credential.attributes:type_name -> google.protobuf.Struct
|
||||
1, // 6: controller.api.resources.credentials.v1.Credential.username_password_attributes:type_name -> controller.api.resources.credentials.v1.UsernamePasswordAttributes
|
||||
3, // 7: controller.api.resources.credentials.v1.UsernamePasswordAttributes.username:type_name -> google.protobuf.StringValue
|
||||
3, // 8: controller.api.resources.credentials.v1.UsernamePasswordAttributes.password: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
|
||||
3, // 0: controller.api.resources.credentials.v1.Credential.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
|
||||
4, // 1: controller.api.resources.credentials.v1.Credential.name:type_name -> google.protobuf.StringValue
|
||||
4, // 2: controller.api.resources.credentials.v1.Credential.description:type_name -> google.protobuf.StringValue
|
||||
5, // 3: controller.api.resources.credentials.v1.Credential.created_time:type_name -> google.protobuf.Timestamp
|
||||
5, // 4: controller.api.resources.credentials.v1.Credential.updated_time:type_name -> google.protobuf.Timestamp
|
||||
6, // 5: controller.api.resources.credentials.v1.Credential.attributes:type_name -> google.protobuf.Struct
|
||||
1, // 6: controller.api.resources.credentials.v1.Credential.username_password_attributes:type_name -> controller.api.resources.credentials.v1.UsernamePasswordAttributes
|
||||
2, // 7: controller.api.resources.credentials.v1.Credential.ssh_private_key_attributes:type_name -> controller.api.resources.credentials.v1.SshPrivateKeyAttributes
|
||||
4, // 8: controller.api.resources.credentials.v1.UsernamePasswordAttributes.username:type_name -> google.protobuf.StringValue
|
||||
4, // 9: controller.api.resources.credentials.v1.UsernamePasswordAttributes.password:type_name -> google.protobuf.StringValue
|
||||
4, // 10: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.username:type_name -> google.protobuf.StringValue
|
||||
4, // 11: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.private_key:type_name -> google.protobuf.StringValue
|
||||
12, // [12:12] is the sub-list for method output_type
|
||||
12, // [12:12] is the sub-list for method input_type
|
||||
12, // [12:12] is the sub-list for extension type_name
|
||||
12, // [12:12] is the sub-list for extension extendee
|
||||
0, // [0:12] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_controller_api_resources_credentials_v1_credential_proto_init() }
|
||||
@@ -436,10 +552,23 @@ func file_controller_api_resources_credentials_v1_credential_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_api_resources_credentials_v1_credential_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*SshPrivateKeyAttributes); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
file_controller_api_resources_credentials_v1_credential_proto_msgTypes[0].OneofWrappers = []interface{}{
|
||||
(*Credential_Attributes)(nil),
|
||||
(*Credential_UsernamePasswordAttributes)(nil),
|
||||
(*Credential_SshPrivateKeyAttributes)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
@@ -447,7 +576,7 @@ func file_controller_api_resources_credentials_v1_credential_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_controller_api_resources_credentials_v1_credential_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user