You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/credential/vault/repository_credentials.go

164 lines
5.1 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package vault
import (
"context"
"database/sql"
"fmt"
"time"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/event"
)
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 query, queryValues
}
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 query, queryValues
}
// 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) {
const op = "vault.(Repository).Issue"
if sessionId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no session id")
}
if len(requests) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no requests")
}
libs, err := r.getIssueCredLibraries(ctx, requests)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
// TODO(mgaffney)(ICU-1329) 05/2021: if any error occurs, mark all credentials
// retrieved for revocation which will be handled by the revocation
// job.
var creds []credential.Dynamic
var minLease time.Duration
runJobsInterval := r.scheduler.GetRunJobsInterval()
for _, lib := range libs {
cred, err := lib.retrieveCredential(ctx, op, opt...)
if err != nil {
return nil, err
}
creds = append(creds, cred)
if !cred.isRevokable() {
// No need to persist since the credential cannot be revoked nor renewed
continue
}
if cred.getExpiration() < runJobsInterval {
event.WriteError(ctx, op,
fmt.Errorf("WARNING: credential will expire before job scheduler can run"),
event.WithInfo("credential_public_id", cred.GetPublicId()),
event.WithInfo("credential_library_public_id", lib.GetPublicId()),
event.WithInfo("runJobsInterval", runJobsInterval.String()),
)
}
if minLease > cred.getExpiration() {
minLease = cred.getExpiration()
}
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)
switch {
case err != nil:
return errors.Wrap(ctx, err, op)
case rowsInserted > 1:
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 credential would have been inserted")
}
rowsUpdated, err := w.Exec(ctx, updateQuery, updateQueryValues)
switch {
case err != nil:
return errors.Wrap(ctx, err, op)
case rowsUpdated == 0:
return errors.New(ctx, errors.InvalidDynamicCredential, op, "no matching dynamic credential for session found")
case rowsUpdated > 1:
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 session credential would have been updated")
}
return nil
},
); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
}
// Best effort update next run time of credential renewal job, but an error should not
// cause Issue to fail.
// TODO (lcr 06/2021): log error once repo has logger
_ = r.scheduler.UpdateJobNextRunInAtLeast(ctx, credentialRenewalJobName, minLease)
return creds, nil
}
var _ credential.Revoker = (*Repository)(nil)
// Revoke revokes all dynamic credentials issued from Vault for sessionId.
func (r *Repository) Revoke(ctx context.Context, sessionId string) error {
const op = "vault.(Repository).Revoke"
if sessionId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "no session id")
}
_, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
if _, err := w.Exec(ctx, revokeCredentialsQuery, []any{sessionId}); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
return err
}