refact(vault): Create issuingCredentialLibrary Interface

Allows for new issuing credential library implementations in the future.
Moved db and decrypt logic to privateCredentialLibraryAllTypes from
issueCredentialLibrary.

This also removes sessionId from retrieveCredential, it was not needed
and removing allows retrieveCredential to be reused for credentials
issued outside current workflow.
This commit is contained in:
Danielle authored and Timothy Messier committed 2023-02-02 17:43:16 +00:00
1 parent a15c5d80ce
commit 7833dd73c0
7 files changed
+338 -163

No files matched your search

+1 -39
View File
@@ -1,10 +1,8 @@
package vault
import (
"database/sql"
"time"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/db/sanitize"
"github.com/hashicorp/boundary/internal/db/sentinel"
@@ -46,14 +44,11 @@ type Credential struct {
expiration time.Duration `gorm:"-"`
}
func newCredential(libraryId, sessionId, externalId string, tokenHmac []byte, expiration time.Duration) (*Credential, error) {
func newCredential(libraryId, externalId string, tokenHmac []byte, expiration time.Duration) (*Credential, error) {
const op = "vault.newCredential"
if libraryId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "no library id")
}
if sessionId == "" {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "no session id")
}
if len(tokenHmac) == 0 {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "no tokenHmac")
}
@@ -69,7 +64,6 @@ func newCredential(libraryId, sessionId, externalId string, tokenHmac []byte, ex
expiration: expiration.Round(time.Second),
Credential: &store.Credential{
LibraryId: libraryId,
SessionId: sessionId,
ExternalId: externalId,
TokenHmac: tokenHmac,
Status: status,
@@ -117,38 +111,6 @@ func (c *Credential) oplog(op oplog.OpType) oplog.Metadata {
return metadata
}
func (c *Credential) insertQuery() (query string, queryValues []any) {
queryValues = []any{
sql.Named("public_id", c.PublicId),
sql.Named("library_id", c.LibraryId),
sql.Named("session_id", c.SessionId),
sql.Named("token_hmac", c.TokenHmac),
sql.Named("external_id", c.ExternalId),
sql.Named("is_renewable", c.IsRenewable),
sql.Named("status", c.Status),
sql.Named("last_renewal_time", "now()"),
}
switch {
case c.expiration == 0:
query = insertCredentialWithInfiniteExpirationQuery
default:
query = insertCredentialWithExpirationQuery
queryValues = append(queryValues, sql.Named("expiration_time", int(c.expiration.Round(time.Second).Seconds())))
}
return
}
func (c *Credential) updateSessionQuery(purpose credential.Purpose) (query string, queryValues []any) {
queryValues = []any{
sql.Named("public_id", c.PublicId),
sql.Named("library_id", c.LibraryId),
sql.Named("session_id", c.SessionId),
sql.Named("purpose", string(purpose)),
}
query = updateSessionCredentialQuery
return
}
func (c *Credential) updateExpirationQuery() (query string, queryValues []any) {
queryValues = []any{
int(c.expiration.Round(time.Second).Seconds()),
+3 -13
View File
@@ -56,17 +56,6 @@ func TestCredential_New(t *testing.T) {
want: nil,
wantErr: true,
},
{
name: "missing-session-id",
args: args{
libraryId: lib.GetPublicId(),
externalId: "some/vault/credential",
tokenHmac: token.GetTokenHmac(),
expiration: 5 * time.Minute,
},
want: nil,
wantErr: true,
},
{
name: "missing-tokenHmac",
args: args{
@@ -144,7 +133,7 @@ func TestCredential_New(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
got, err := newCredential(tt.args.libraryId, tt.args.sessionId,
got, err := newCredential(tt.args.libraryId,
tt.args.externalId, tt.args.tokenHmac, tt.args.expiration)
if tt.wantErr {
assert.Error(err)
@@ -162,7 +151,8 @@ func TestCredential_New(t *testing.T) {
tt.want.PublicId = id
got.PublicId = id
query, queryValues := got.insertQuery()
query, queryValues := insertQuery(got, tt.args.sessionId)
require.NoError(err)
rows, err2 := rw.Exec(ctx, query, queryValues)
assert.Equal(1, rows)
+180 -61
View File
@@ -25,7 +25,7 @@ var _ credential.Dynamic = (*baseCred)(nil)
type baseCred struct {
*Credential
lib *issueCredentialLibrary
lib *genericIssuingCredentialLibrary
secretData map[string]any
}
@@ -33,6 +33,7 @@ func (bc *baseCred) Secret() credential.SecretData { return bc.secretData }
func (bc *baseCred) Library() credential.Library { return bc.lib }
func (bc *baseCred) Purpose() credential.Purpose { return bc.lib.Purpose }
func (bc *baseCred) getExpiration() time.Duration { return bc.expiration }
func (bc *baseCred) getCredential() *Credential { return bc.Credential }
// convert converts bc to a specific credential type if bc is not
// UnspecifiedType.
@@ -132,12 +133,17 @@ func baseToSshPriKey(ctx context.Context, bc *baseCred) (*sshPrivateKeyCred, err
}, nil
}
var _ credential.Library = (*issueCredentialLibrary)(nil)
var _ credential.Library = (*genericIssuingCredentialLibrary)(nil)
// A issueCredentialLibrary contains all the values needed to connect to Vault and
type issuingCredentialLibrary interface {
GetPublicId() string
retrieveCredential(context.Context, errors.Op, ...credential.Option) (dynamicCred, error)
}
// A genericIssuingCredentialLibrary contains all the values needed to connect to Vault and
// retrieve credentials.
type issueCredentialLibrary struct {
PublicId string `gorm:"primary_key"`
type genericIssuingCredentialLibrary struct {
PublicId string
StoreId string
Name string
Description string
@@ -147,7 +153,7 @@ type issueCredentialLibrary struct {
VaultPath string
HttpMethod string
HttpRequestBody []byte
CredType string `gorm:"column:credential_type"`
CredType string
ProjectId string
VaultAddress string
Namespace string
@@ -167,13 +173,13 @@ type issueCredentialLibrary struct {
PasswordAttribute string
PrivateKeyAttribute string
PrivateKeyPassphraseAttribute string
Purpose credential.Purpose `gorm:"-"`
Purpose credential.Purpose
}
func (pl *issueCredentialLibrary) clone() *issueCredentialLibrary {
func (pl *genericIssuingCredentialLibrary) clone() *genericIssuingCredentialLibrary {
// The 'append(a[:0:0], a...)' comes from
// https://github.com/go101/go101/wiki/How-to-perfectly-clone-a-slice%3F
return &issueCredentialLibrary{
return &genericIssuingCredentialLibrary{
PublicId: pl.PublicId,
StoreId: pl.StoreId,
CredType: pl.CredType,
@@ -208,15 +214,15 @@ func (pl *issueCredentialLibrary) clone() *issueCredentialLibrary {
}
}
func (pl *issueCredentialLibrary) GetPublicId() string { return pl.PublicId }
func (pl *issueCredentialLibrary) GetStoreId() string { return pl.StoreId }
func (pl *issueCredentialLibrary) GetName() string { return pl.Name }
func (pl *issueCredentialLibrary) GetDescription() string { return pl.Description }
func (pl *issueCredentialLibrary) GetVersion() uint32 { return pl.Version }
func (pl *issueCredentialLibrary) GetCreateTime() *timestamp.Timestamp { return pl.CreateTime }
func (pl *issueCredentialLibrary) GetUpdateTime() *timestamp.Timestamp { return pl.UpdateTime }
func (pl *genericIssuingCredentialLibrary) GetPublicId() string { return pl.PublicId }
func (pl *genericIssuingCredentialLibrary) GetStoreId() string { return pl.StoreId }
func (pl *genericIssuingCredentialLibrary) GetName() string { return pl.Name }
func (pl *genericIssuingCredentialLibrary) GetDescription() string { return pl.Description }
func (pl *genericIssuingCredentialLibrary) GetVersion() uint32 { return pl.Version }
func (pl *genericIssuingCredentialLibrary) GetCreateTime() *timestamp.Timestamp { return pl.CreateTime }
func (pl *genericIssuingCredentialLibrary) GetUpdateTime() *timestamp.Timestamp { return pl.UpdateTime }
func (pl *issueCredentialLibrary) CredentialType() credential.Type {
func (pl *genericIssuingCredentialLibrary) CredentialType() credential.Type {
switch ct := pl.CredType; ct {
case "":
return credential.UnspecifiedType
@@ -225,41 +231,8 @@ func (pl *issueCredentialLibrary) CredentialType() credential.Type {
}
}
func (pl *issueCredentialLibrary) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "vault.(issueCredentialLibrary).decrypt"
if pl.CtToken != nil {
type ptk struct {
Token []byte `wrapping:"pt,token_data"`
CtToken []byte `wrapping:"ct,token_data"`
}
ptkv := &ptk{
CtToken: pl.CtToken,
}
if err := structwrapping.UnwrapStruct(ctx, cipher, ptkv, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("token"))
}
pl.Token = ptkv.Token
}
if pl.CtClientKey != nil && pl.ClientCert != nil {
type pck struct {
Key []byte `wrapping:"pt,key_data"`
CtKey []byte `wrapping:"ct,key_data"`
}
pckv := &pck{
CtKey: pl.CtClientKey,
}
if err := structwrapping.UnwrapStruct(ctx, cipher, pckv, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("client certificate"))
}
pl.ClientKey = pckv.Key
}
return nil
}
func (pl *issueCredentialLibrary) client(ctx context.Context) (vaultClient, error) {
const op = "vault.(issueCredentialLibrary).client"
func (pl *genericIssuingCredentialLibrary) client(ctx context.Context) (vaultClient, error) {
const op = "vault.(genericIssuingCredentialLibrary).client"
clientConfig := &clientConfig{
Addr: pl.VaultAddress,
Token: pl.Token,
@@ -284,15 +257,14 @@ func (pl *issueCredentialLibrary) client(ctx context.Context) (vaultClient, erro
type dynamicCred interface {
credential.Dynamic
getExpiration() time.Duration
insertQuery() (query string, queryValues []any)
updateSessionQuery(purpose credential.Purpose) (query string, queryValues []any)
getCredential() *Credential
}
// retrieveCredential retrieves a dynamic credential from Vault for the
// given sessionId.
//
// Supported options: credential.WithTemplateData
func (pl *issueCredentialLibrary) retrieveCredential(ctx context.Context, op errors.Op, sessionId string, opt ...credential.Option) (dynamicCred, error) {
func (pl *genericIssuingCredentialLibrary) retrieveCredential(ctx context.Context, op errors.Op, opt ...credential.Option) (dynamicCred, error) {
opts, err := credential.GetOpts(opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
@@ -358,7 +330,7 @@ func (pl *issueCredentialLibrary) retrieveCredential(ctx context.Context, op err
}
leaseDuration := time.Duration(secret.LeaseDuration) * time.Second
cred, err := newCredential(pl.GetPublicId(), sessionId, secret.LeaseID, pl.TokenHmac, leaseDuration)
cred, err := newCredential(pl.GetPublicId(), secret.LeaseID, pl.TokenHmac, leaseDuration)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
@@ -374,11 +346,11 @@ func (pl *issueCredentialLibrary) retrieveCredential(ctx context.Context, op err
}
// TableName returns the table name for gorm.
func (pl *issueCredentialLibrary) TableName() string {
func (pl *genericIssuingCredentialLibrary) TableName() string {
return "credential_vault_library_issue_credentials"
}
func (r *Repository) getIssueCredLibraries(ctx context.Context, requests []credential.Request) ([]*issueCredentialLibrary, error) {
func (r *Repository) getIssueCredLibraries(ctx context.Context, requests []credential.Request) ([]issuingCredentialLibrary, error) {
const op = "vault.(Repository).getIssueCredLibraries"
mapper, err := newMapper(requests)
@@ -405,9 +377,9 @@ func (r *Repository) getIssueCredLibraries(ctx context.Context, requests []crede
}
defer rows.Close()
var libs []*issueCredentialLibrary
var libs []*privateCredentialLibraryAllTypes
for rows.Next() {
var lib issueCredentialLibrary
var lib privateCredentialLibraryAllTypes
if err := r.reader.ScanRows(ctx, rows, &lib); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("scan row failed"))
}
@@ -422,6 +394,7 @@ func (r *Repository) getIssueCredLibraries(ctx context.Context, requests []crede
}
}
var decryptedLibs []issuingCredentialLibrary
for _, pl := range libs {
databaseWrapper, err := r.kms.GetWrapper(ctx, pl.ProjectId, kms.KeyPurposeDatabase)
if err != nil {
@@ -431,9 +404,155 @@ func (r *Repository) getIssueCredLibraries(ctx context.Context, requests []crede
if err := pl.decrypt(ctx, databaseWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
decryptedLibs = append(decryptedLibs, pl.toTypedIssuingCredentialLibrary())
}
return libs, nil
return decryptedLibs, nil
}
// privateCredentialLibraryAllTypes is a clone of genericIssuingCredentialLibrary. Contains
// all the values needed to connect to Vault and retrieve credentials.
type privateCredentialLibraryAllTypes struct {
PublicId string `gorm:"primary_key"`
StoreId string
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
Version uint32
VaultPath string
HttpMethod string
HttpRequestBody []byte
CredType string `gorm:"column:credential_type"`
ProjectId string
VaultAddress string
Namespace string
CaCert []byte
TlsServerName string
TlsSkipVerify bool
WorkerFilter string
Token TokenSecret
CtToken []byte
TokenHmac []byte
TokenKeyId string
ClientCert []byte
ClientKey KeySecret
CtClientKey []byte
ClientKeyId string
UsernameAttribute string
PasswordAttribute string
PrivateKeyAttribute string
PrivateKeyPassphraseAttribute string
Purpose credential.Purpose `gorm:"-"`
}
func (pl *privateCredentialLibraryAllTypes) GetPublicId() string { return pl.PublicId }
func (pl *privateCredentialLibraryAllTypes) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "vault.(privateCredentialLibraryAllTypes).decrypt"
if pl.CtToken != nil {
type ptk struct {
Token []byte `wrapping:"pt,token_data"`
CtToken []byte `wrapping:"ct,token_data"`
}
ptkv := &ptk{
CtToken: pl.CtToken,
}
if err := structwrapping.UnwrapStruct(ctx, cipher, ptkv, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("token"))
}
pl.Token = ptkv.Token
}
if pl.CtClientKey != nil && pl.ClientCert != nil {
type pck struct {
Key []byte `wrapping:"pt,key_data"`
CtKey []byte `wrapping:"ct,key_data"`
}
pckv := &pck{
CtKey: pl.CtClientKey,
}
if err := structwrapping.UnwrapStruct(ctx, cipher, pckv, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("client certificate"))
}
pl.ClientKey = pckv.Key
}
return nil
}
func (pl *privateCredentialLibraryAllTypes) clone() *privateCredentialLibraryAllTypes {
// The 'append(a[:0:0], a...)' comes from
// https://github.com/go101/go101/wiki/How-to-perfectly-clone-a-slice%3F
return &privateCredentialLibraryAllTypes{
PublicId: pl.PublicId,
StoreId: pl.StoreId,
CredType: pl.CredType,
UsernameAttribute: pl.UsernameAttribute,
PasswordAttribute: pl.PasswordAttribute,
PrivateKeyAttribute: pl.PrivateKeyAttribute,
PrivateKeyPassphraseAttribute: pl.PrivateKeyPassphraseAttribute,
Name: pl.Name,
Description: pl.Description,
CreateTime: proto.Clone(pl.CreateTime).(*timestamp.Timestamp),
UpdateTime: proto.Clone(pl.UpdateTime).(*timestamp.Timestamp),
Version: pl.Version,
ProjectId: pl.ProjectId,
VaultPath: pl.VaultPath,
HttpMethod: pl.HttpMethod,
HttpRequestBody: append(pl.HttpRequestBody[:0:0], pl.HttpRequestBody...),
VaultAddress: pl.VaultAddress,
Namespace: pl.Namespace,
CaCert: append(pl.CaCert[:0:0], pl.CaCert...),
TlsServerName: pl.TlsServerName,
TlsSkipVerify: pl.TlsSkipVerify,
WorkerFilter: pl.WorkerFilter,
TokenHmac: append(pl.TokenHmac[:0:0], pl.TokenHmac...),
Token: append(pl.Token[:0:0], pl.Token...),
CtToken: append(pl.CtToken[:0:0], pl.CtToken...),
TokenKeyId: pl.TokenKeyId,
ClientCert: append(pl.ClientCert[:0:0], pl.ClientCert...),
ClientKey: append(pl.ClientKey[:0:0], pl.ClientKey...),
CtClientKey: append(pl.CtClientKey[:0:0], pl.CtClientKey...),
ClientKeyId: pl.ClientKeyId,
Purpose: pl.Purpose,
}
}
func (pl *privateCredentialLibraryAllTypes) toTypedIssuingCredentialLibrary() issuingCredentialLibrary {
return &genericIssuingCredentialLibrary{
PublicId: pl.PublicId,
StoreId: pl.StoreId,
CredType: pl.CredType,
UsernameAttribute: pl.UsernameAttribute,
PasswordAttribute: pl.PasswordAttribute,
PrivateKeyAttribute: pl.PrivateKeyAttribute,
PrivateKeyPassphraseAttribute: pl.PrivateKeyPassphraseAttribute,
Name: pl.Name,
Description: pl.Description,
CreateTime: pl.CreateTime,
UpdateTime: pl.UpdateTime,
Version: pl.Version,
ProjectId: pl.ProjectId,
VaultPath: pl.VaultPath,
HttpMethod: pl.HttpMethod,
HttpRequestBody: pl.HttpRequestBody,
VaultAddress: pl.VaultAddress,
Namespace: pl.Namespace,
CaCert: pl.CaCert,
TlsServerName: pl.TlsServerName,
TlsSkipVerify: pl.TlsSkipVerify,
WorkerFilter: pl.WorkerFilter,
TokenHmac: pl.TokenHmac,
Token: pl.Token,
CtToken: pl.CtToken,
TokenKeyId: pl.TokenKeyId,
ClientCert: pl.ClientCert,
ClientKey: pl.ClientKey,
CtClientKey: pl.CtClientKey,
ClientKeyId: pl.ClientKeyId,
Purpose: pl.Purpose,
}
}
// requestMap takes a slice of credential requests and provides of list of
@@ -269,7 +269,10 @@ func TestRepository_getPrivateLibraries(t *testing.T) {
require.NotNil(gotLibs)
assert.Len(gotLibs, len(libs))
for _, got := range gotLibs {
for _, gotLib := range gotLibs {
got, ok := gotLib.(*genericIssuingCredentialLibrary)
require.True(ok)
assert.Equal(TokenSecret(token), got.Token)
if tt.tls == TestClientTLS {
require.NotNil(got.ClientKey)
@@ -414,7 +417,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "library-not-username-password-type",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UnspecifiedType),
},
},
@@ -423,7 +426,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-no-username-default-password-attribute",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -435,7 +438,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-no-password-default-username-attribute",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -447,7 +450,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-default-attributes",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -463,7 +466,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-override-attributes",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "test-username",
PasswordAttribute: "test-password",
@@ -483,7 +486,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-default-username-override-password",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
PasswordAttribute: "test-password",
},
@@ -502,7 +505,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-override-username-default-password",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "test-username",
},
@@ -521,7 +524,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-username-override",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "missing-username",
},
@@ -537,7 +540,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-password-override",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "missing-password",
},
@@ -553,7 +556,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-no-metadata-field",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -568,7 +571,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-no-data-field",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -580,7 +583,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-no-username-default-password-attribute",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -595,7 +598,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-no-passsword-default-username-attribute",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -610,7 +613,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -626,7 +629,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -639,7 +642,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-additional-field",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -656,7 +659,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-kv2-default-attributes",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]any{
@@ -675,7 +678,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-kv2-override-attributes",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "test-username",
PasswordAttribute: "test-password",
@@ -698,7 +701,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-kv2-default-username-override-password",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
PasswordAttribute: "test-password",
},
@@ -720,7 +723,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-kv2-override-username-default-password",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "test-username",
},
@@ -779,7 +782,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "library-not-ssh-private-key-type",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.UnspecifiedType),
},
},
@@ -788,7 +791,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-no-username-default-pk-attribute",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -800,7 +803,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-no-pk-default-username-attribute",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -812,7 +815,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-default-attributes",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -828,7 +831,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-default-attributes-with-passphrase",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -846,7 +849,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-override-attributes",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
PrivateKeyAttribute: "test-pk",
@@ -866,7 +869,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-override-attributes-with-passphrase",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
PrivateKeyAttribute: "test-pk",
@@ -890,7 +893,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-default-username-override-pk",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
PrivateKeyAttribute: "test-pk",
},
@@ -909,7 +912,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-override-username-default-pk",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
},
@@ -928,7 +931,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-username-override",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "missing-username",
},
@@ -944,7 +947,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-pk-override",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "missing-pk",
},
@@ -960,7 +963,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-no-metadata-field",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -975,7 +978,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-no-data-field",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -987,7 +990,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-no-username-default-pk-attribute",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -1002,7 +1005,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-no-pk-default-username-attribute",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -1017,7 +1020,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -1033,7 +1036,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-invalid-data-type",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -1046,7 +1049,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-additional-field",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -1063,7 +1066,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-default-attributes",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -1082,7 +1085,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-default-attributes-with-passphrase",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]any{
@@ -1103,7 +1106,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-override-attributes",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
PrivateKeyAttribute: "test-pk",
@@ -1126,7 +1129,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-override-attributes-with-passphrase",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
PrivateKeyAttribute: "test-pk",
@@ -1153,7 +1156,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-default-username-override-pk",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
PrivateKeyAttribute: "test-pk",
},
@@ -1175,7 +1178,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-override-username-default-pk",
given: &baseCred{
lib: &issueCredentialLibrary{
lib: &genericIssuingCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
},
@@ -2,6 +2,7 @@ package vault
import (
"context"
"database/sql"
"fmt"
"time"
@@ -13,6 +14,38 @@ import (
var _ credential.Issuer = (*Repository)(nil)
func insertQuery(c *Credential, sessionId string) (query string, queryValues []any) {
queryValues = []any{
sql.Named("public_id", c.PublicId),
sql.Named("library_id", c.LibraryId),
sql.Named("session_id", sessionId),
sql.Named("token_hmac", c.TokenHmac),
sql.Named("external_id", c.ExternalId),
sql.Named("is_renewable", c.IsRenewable),
sql.Named("status", c.Status),
sql.Named("last_renewal_time", "now()"),
}
switch {
case c.expiration == 0:
query = insertCredentialWithInfiniteExpirationQuery
default:
query = insertCredentialWithExpirationQuery
queryValues = append(queryValues, sql.Named("expiration_time", int(c.expiration.Round(time.Second).Seconds())))
}
return
}
func updateSessionQuery(c *Credential, sessionId string, purpose credential.Purpose) (query string, queryValues []any) {
queryValues = []any{
sql.Named("public_id", c.PublicId),
sql.Named("library_id", c.LibraryId),
sql.Named("session_id", sessionId),
sql.Named("purpose", string(purpose)),
}
query = updateSessionCredentialQuery
return
}
// Issue issues and returns dynamic credentials from Vault for all of the
// requests and assigns them to sessionId.
func (r *Repository) Issue(ctx context.Context, sessionId string, requests []credential.Request, opt ...credential.Option) ([]credential.Dynamic, error) {
@@ -37,7 +70,7 @@ func (r *Repository) Issue(ctx context.Context, sessionId string, requests []cre
var minLease time.Duration
runJobsInterval := r.scheduler.GetRunJobsInterval()
for _, lib := range libs {
cred, err := lib.retrieveCredential(ctx, op, sessionId, opt...)
cred, err := lib.retrieveCredential(ctx, op, opt...)
if err != nil {
return nil, err
}
@@ -54,8 +87,18 @@ func (r *Repository) Issue(ctx context.Context, sessionId string, requests []cre
if minLease > cred.getExpiration() {
minLease = cred.getExpiration()
}
insertQuery, insertQueryValues := cred.insertQuery()
updateQuery, updateQueryValues := cred.updateSessionQuery(lib.Purpose)
underlyingCred := cred.getCredential()
insertQuery, insertQueryValues := insertQuery(underlyingCred, sessionId)
if err != nil {
return nil, err
}
updateQuery, updateQueryValues := updateSessionQuery(underlyingCred, sessionId, cred.Purpose())
if err != nil {
return nil, err
}
if _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
rowsInserted, err := w.Exec(ctx, insertQuery, insertQueryValues)
@@ -9,6 +9,7 @@ import (
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/host/static"
"github.com/hashicorp/boundary/internal/iam"
@@ -21,6 +22,58 @@ import (
"github.com/stretchr/testify/require"
)
// since we're not using gorm tags to retrieve this, it's faster and easier to just make a new struct than import usrPassCred from the vault package
type userPassCred struct {
PublicId string
LibraryId string
SessionId string
TokenHmac []byte
ExternalId string
IsRenewable bool
Status string
LastRenewalTime *timestamp.Timestamp
ExpirationTime *timestamp.Timestamp
}
func lookupDbCred(t *testing.T, ctx context.Context, rw *db.Db, upc credential.UsernamePassword) userPassCred {
rows, err := rw.Query(ctx, `
select
public_id,
library_id,
session_id,
token_hmac,
external_id,
is_renewable,
status,
last_renewal_time,
expiration_time
from credential_vault_credential
where public_id = ?;
`, []any{upc.GetPublicId()})
require.NoError(t, err)
rowCount := 0
got := userPassCred{}
for rows.Next() {
rowCount++
require.NoError(t, rows.Scan(
&got.PublicId,
&got.LibraryId,
&got.SessionId,
&got.TokenHmac,
&got.ExternalId,
&got.IsRenewable,
&got.Status,
&got.LastRenewalTime,
&got.ExpirationTime,
))
}
assert.Equal(t, 1, rowCount)
return got
}
func TestRepository_IssueCredentials(t *testing.T) {
t.Parallel()
ctx := context.Background()
@@ -398,6 +451,11 @@ func TestRepository_IssueCredentials(t *testing.T) {
if upc, ok := dc.(credential.UsernamePassword); ok {
assert.NotEmpty(upc.Username())
assert.NotEmpty(upc.Password())
// we also want to retrieve the cred from the db and make sure it's the same as the one returned by Issue
retrieved := lookupDbCred(t, ctx, rw, upc)
require.Equal(retrieved.SessionId, sess.GetPublicId())
require.Equal(retrieved.PublicId, upc.GetPublicId())
// these asserts are super basic because the main check we want is that lookupDbCred succeeded
break
}
assert.Fail("want UsernamePassword credential from library with credential type UsernamePassword")
+2 -2
View File
@@ -168,7 +168,7 @@ func TestCredentials(t testing.TB, conn *db.DB, wrapper wrapping.Wrapper, librar
var credentials []*Credential
for i := 0; i < count; i++ {
credential, err := newCredential(lib.GetPublicId(), sessionId, fmt.Sprintf("vault/credential/%d", i), token.GetTokenHmac(), 5*time.Minute)
credential, err := newCredential(lib.GetPublicId(), fmt.Sprintf("vault/credential/%d", i), token.GetTokenHmac(), 5*time.Minute)
assert.NoError(err)
require.NotNil(credential)
@@ -177,7 +177,7 @@ func TestCredentials(t testing.TB, conn *db.DB, wrapper wrapping.Wrapper, librar
require.NotNil(id)
credential.PublicId = id
query, queryValues := credential.insertQuery()
query, queryValues := insertQuery(credential, sessionId)
rows, err2 := rw.Exec(ctx, query, queryValues)
assert.Equal(1, rows)
assert.NoError(err2)