2021-06-22 22:26:26 -04:00
// Copyright (c) HashiCorp, Inc.
2022-07-22 09:13:49 -04:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2021-06-22 22:26:26 -04:00
package vault
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
2022-01-11 15:55:26 -08:00
"github.com/hashicorp/boundary/internal/scheduler"
2022-08-24 14:11:49 -04:00
"github.com/hashicorp/go-dbw"
2021-07-14 17:40:39 -07:00
"github.com/hashicorp/go-secure-stdlib/parseutil"
2021-06-22 22:26:26 -04:00
vault "github.com/hashicorp/vault/api"
)
// CreateCredentialStore inserts cs into the repository and returns a new
// CredentialStore containing the credential store's PublicId. cs is not
// changed. cs must not contain a PublicId. The PublicId is generated and
2022-07-20 16:24:46 -07:00
// assigned by this method. cs must contain a valid ProjectId, VaultAddress,
2021-06-22 22:26:26 -04:00
// and Vault token. The Vault token must be renewable, periodic, and
// orphan. CreateCredentialStore calls the /auth/token/renew-self and
// /auth/token/lookup-self Vault endpoints.
//
// Both cs.Name and cs.Description are optional. If cs.Name is set, it must
2022-07-20 16:24:46 -07:00
// be unique within cs.ProjectId. Both cs.CreateTime and cs.UpdateTime are
2021-06-22 22:26:26 -04:00
// ignored.
//
// For more information about the required properties of the Vault token see:
// https://www.vaultproject.io/api-docs/auth/token#period,
// https://www.vaultproject.io/api-docs/auth/token#renewable,
// https://www.vaultproject.io/docs/concepts/tokens#token-hierarchies-and-orphan-tokens,
// https://www.vaultproject.io/docs/concepts/tokens#periodic-tokens, and
// https://www.vaultproject.io/docs/concepts/tokens#token-time-to-live-periodic-tokens-and-explicit-max-ttls.
//
// For more information about the Vault endpoints called by
// CreateCredentialStore see:
// https://www.vaultproject.io/api-docs/auth/token#renew-a-token-self and
// https://www.vaultproject.io/api-docs/auth/token#lookup-a-token-self.
func ( r * Repository ) CreateCredentialStore ( ctx context . Context , cs * CredentialStore , _ ... Option ) ( * CredentialStore , error ) {
const op = "vault.(Repository).CreateCredentialStore"
if cs == nil {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "nil CredentialStore" )
2021-06-22 22:26:26 -04:00
}
if cs . CredentialStore == nil {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "nil embedded CredentialStore" )
2021-06-22 22:26:26 -04:00
}
2022-07-20 16:24:46 -07:00
if cs . ProjectId == "" {
return nil , errors . New ( ctx , errors . InvalidParameter , op , "no project id" )
2021-06-22 22:26:26 -04:00
}
if len ( cs . inputToken ) == 0 {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "no vault token" )
2021-06-22 22:26:26 -04:00
}
if cs . VaultAddress == "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "no vault address" )
2021-06-22 22:26:26 -04:00
}
if cs . PublicId != "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "public id not empty" )
2021-06-22 22:26:26 -04:00
}
if cs . clientCert != nil && len ( cs . clientCert . CertificateKey ) == 0 {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "client certificate without private key" )
2021-06-22 22:26:26 -04:00
}
cs = cs . clone ()
2023-06-06 17:09:08 -04:00
id , err := newCredentialStoreId ( ctx )
2021-06-22 22:26:26 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2021-06-22 22:26:26 -04:00
}
cs . PublicId = id
if cs . clientCert != nil {
cs . clientCert . StoreId = id
}
2022-08-25 14:37:22 -04:00
client , err := cs . client ( ctx )
2021-06-22 22:26:26 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to create vault client" ))
2021-06-22 22:26:26 -04:00
}
2022-08-25 14:37:22 -04:00
tokenLookup , err := client . lookupToken ( ctx )
2021-06-22 22:26:26 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to lookup vault token" ))
2021-06-22 22:26:26 -04:00
}
2023-06-06 17:09:08 -04:00
if err := validateTokenLookup ( ctx , op , tokenLookup ); err != nil {
2021-06-22 22:26:26 -04:00
return nil , err
}
2022-08-25 14:37:22 -04:00
available , err := client . capabilities ( ctx , requiredCapabilities . paths ())
2021-06-22 22:26:26 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get vault capabilities" ))
2021-06-22 22:26:26 -04:00
}
missing := available . missing ( requiredCapabilities )
if len ( missing ) > 0 {
return nil ,
2024-02-16 08:30:16 -05:00
errors . New ( ctx , errors . VaultTokenMissingCapabilities , op , fmt . Sprintf ( "missing capabilities: %v" , missing ))
2021-06-22 22:26:26 -04:00
}
2022-08-25 14:37:22 -04:00
renewedToken , err := client . renewToken ( ctx )
2021-06-22 22:26:26 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to renew vault token" ))
2021-06-22 22:26:26 -04:00
}
tokenExpires , err := renewedToken . TokenTTL ()
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get vault token expiration" ))
2021-06-22 22:26:26 -04:00
}
accessor , err := renewedToken . TokenAccessor ()
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get vault token accessor" ))
2021-06-22 22:26:26 -04:00
}
2023-06-06 17:09:08 -04:00
token , err := newToken ( ctx , id , cs . inputToken , [] byte ( accessor ), tokenExpires )
2021-06-22 22:26:26 -04:00
if err != nil {
return nil , err
}
2022-09-13 10:43:49 -07:00
runJobsInterval := r . scheduler . GetRunJobsInterval ()
if token . expiration <= runJobsInterval {
return nil , errors . Wrap ( ctx , fmt . Errorf ( "scheduler interval must be greater than token ttl. scheduler jobs interval value: %s" , runJobsInterval . String ()), op )
}
2022-07-20 16:24:46 -07:00
oplogWrapper , err := r . kms . GetWrapper ( ctx , cs . ProjectId , kms . KeyPurposeOplog )
2021-06-22 22:26:26 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get oplog wrapper" ))
2021-06-22 22:26:26 -04:00
}
2022-07-20 16:24:46 -07:00
databaseWrapper , err := r . kms . GetWrapper ( ctx , cs . ProjectId , kms . KeyPurposeDatabase )
2021-06-22 22:26:26 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get database wrapper" ))
2021-06-22 22:26:26 -04:00
}
// encrypt
if err := token . encrypt ( ctx , databaseWrapper ); err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2021-06-22 22:26:26 -04:00
}
if cs . clientCert != nil {
if err := cs . clientCert . encrypt ( ctx , databaseWrapper ); err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2021-06-22 22:26:26 -04:00
}
}
var newToken * Token
var newClientCertificate * ClientCertificate
var newCredentialStore * CredentialStore
_ , err = r . writer . DoTx ( ctx , db . StdRetryCnt , db . ExpBackoff {},
func ( _ db . Reader , w db . Writer ) error {
msgs := make ([] * oplog . Message , 0 , 3 )
2022-01-05 14:38:38 -05:00
ticket , err := w . GetTicket ( ctx , cs )
2021-06-22 22:26:26 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get ticket" ))
2021-06-22 22:26:26 -04:00
}
// insert credential store
newCredentialStore = cs . clone ()
var csOplogMsg oplog . Message
if err := w . Create ( ctx , newCredentialStore , db . NewOplogMsg ( & csOplogMsg )); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2021-06-22 22:26:26 -04:00
}
msgs = append ( msgs , & csOplogMsg )
// insert token
newToken = token . clone ()
query , values := newToken . insertQuery ()
rows , err := w . Exec ( ctx , query , values )
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2021-06-22 22:26:26 -04:00
}
if rows > 1 {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , "more than 1 token would have been created" )
2021-06-22 22:26:26 -04:00
}
msgs = append ( msgs , newToken . oplogMessage ( db . CreateOp ))
newCredentialStore . inputToken = nil
newToken . Token . Token = nil
newToken . Token . CtToken = nil
newCredentialStore . outputToken = newToken
// insert client certificate (if exists)
if cs . clientCert != nil {
newClientCertificate = cs . clientCert . clone ()
var clientCertOplogMsg oplog . Message
if err := w . Create ( ctx , newClientCertificate , db . NewOplogMsg ( & clientCertOplogMsg )); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2021-06-22 22:26:26 -04:00
}
msgs = append ( msgs , & clientCertOplogMsg )
newClientCertificate . CertificateKey = nil
newClientCertificate . CtCertificateKey = nil
newCredentialStore . clientCert = newClientCertificate
}
metadata := cs . oplog ( oplog . OpType_OP_TYPE_CREATE )
if err := w . WriteOplogEntryWith ( ctx , oplogWrapper , ticket , metadata , msgs ); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to write oplog" ))
2021-06-22 22:26:26 -04:00
}
return nil
},
)
if err != nil {
if errors . IsUniqueError ( err ) {
2022-07-20 16:24:46 -07:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "in project: %s: name %s already exists" , cs . ProjectId , cs . Name )))
2021-06-22 22:26:26 -04:00
}
2022-07-20 16:24:46 -07:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "in project: %s" , cs . ProjectId )))
2021-06-22 22:26:26 -04:00
}
// Best effort update next run time of token renewal job, but an error should not
// cause update to fail.
// TODO (lcr 05/2021): log error once repo has logger
_ = r . scheduler . UpdateJobNextRunInAtLeast ( ctx , tokenRenewalJobName , token . renewalIn ())
return newCredentialStore , nil
}
2023-06-06 17:09:08 -04:00
func validateTokenLookup ( ctx context . Context , op errors . Op , s * vault . Secret ) error {
2021-06-22 22:26:26 -04:00
if s . Data == nil {
2023-06-06 17:09:08 -04:00
return errors . New ( ctx , errors . InvalidParameter , op , "vault secret is not a token lookup" )
2021-06-22 22:26:26 -04:00
}
if s . Data [ "renewable" ] == nil {
2023-06-06 17:09:08 -04:00
return errors . E ( ctx , errors . WithCode ( errors . VaultTokenNotRenewable ), errors . WithOp ( op ))
2021-06-22 22:26:26 -04:00
}
renewable , err := parseutil . ParseBool ( s . Data [ "renewable" ])
if err != nil {
2023-06-06 17:09:08 -04:00
return errors . Wrap ( ctx , err , op )
2021-06-22 22:26:26 -04:00
}
if ! renewable {
2023-06-06 17:09:08 -04:00
return errors . E ( ctx , errors . WithCode ( errors . VaultTokenNotRenewable ), errors . WithOp ( op ))
2021-06-22 22:26:26 -04:00
}
if s . Data [ "orphan" ] == nil {
2023-06-06 17:09:08 -04:00
return errors . E ( ctx , errors . WithCode ( errors . VaultTokenNotOrphan ), errors . WithOp ( op ))
2021-06-22 22:26:26 -04:00
}
orphan , err := parseutil . ParseBool ( s . Data [ "orphan" ])
if err != nil {
2023-06-06 17:09:08 -04:00
return errors . Wrap ( ctx , err , op )
2021-06-22 22:26:26 -04:00
}
if ! orphan {
2023-06-06 17:09:08 -04:00
return errors . E ( ctx , errors . WithCode ( errors . VaultTokenNotOrphan ), errors . WithOp ( op ))
2021-06-22 22:26:26 -04:00
}
if s . Data [ "period" ] == nil {
2023-06-06 17:09:08 -04:00
return errors . E ( ctx , errors . WithCode ( errors . VaultTokenNotPeriodic ), errors . WithOp ( op ))
2021-06-22 22:26:26 -04:00
}
return nil
}
// LookupCredentialStore returns the CredentialStore for publicId. Returns
// nil, nil if no CredentialStore is found for publicId.
func ( r * Repository ) LookupCredentialStore ( ctx context . Context , publicId string , _ ... Option ) ( * CredentialStore , error ) {
const op = "vault.(Repository).LookupCredentialStore"
if publicId == "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "no public id" )
2021-06-22 22:26:26 -04:00
}
2022-09-06 07:50:15 -07:00
agg := allocListLookupStore ()
2021-06-22 22:26:26 -04:00
agg . PublicId = publicId
if err := r . reader . LookupByPublicId ( ctx , agg ); err != nil {
if errors . IsNotFoundError ( err ) {
return nil , nil
}
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "failed for: %s" , publicId )))
2021-06-22 22:26:26 -04:00
}
return agg . toCredentialStore (), nil
}
2022-09-06 07:50:15 -07:00
type listLookupStore struct {
PublicId string `gorm:"primary_key"`
ProjectId string
Name string
Description string
CreateTime * timestamp . Timestamp
UpdateTime * timestamp . Timestamp
Version uint32
VaultAddress string
Namespace string
CaCert [] byte
TlsServerName string
TlsSkipVerify bool
WorkerFilter string
TokenHmac [] byte
TokenStatus string
ClientCert [] byte
ClientCertKeyHmac [] byte
2021-06-22 22:26:26 -04:00
}
2022-09-06 07:50:15 -07:00
func allocListLookupStore () * listLookupStore {
return & listLookupStore {}
2021-06-22 22:26:26 -04:00
}
2022-09-06 07:50:15 -07:00
func ( ps * listLookupStore ) toCredentialStore () * CredentialStore {
2021-06-22 22:26:26 -04:00
cs := allocCredentialStore ()
cs . PublicId = ps . PublicId
2022-07-20 16:24:46 -07:00
cs . ProjectId = ps . ProjectId
2021-06-22 22:26:26 -04:00
cs . Name = ps . Name
cs . Description = ps . Description
cs . CreateTime = ps . CreateTime
cs . UpdateTime = ps . UpdateTime
cs . Version = ps . Version
cs . VaultAddress = ps . VaultAddress
cs . Namespace = ps . Namespace
cs . CaCert = ps . CaCert
cs . TlsServerName = ps . TlsServerName
cs . TlsSkipVerify = ps . TlsSkipVerify
2022-07-22 09:13:49 -04:00
cs . WorkerFilter = ps . WorkerFilter
2021-06-22 22:26:26 -04:00
2022-09-06 07:50:15 -07:00
tk := allocToken ()
tk . TokenHmac = ps . TokenHmac
tk . Status = ps . TokenStatus
cs . outputToken = tk
2021-06-22 22:26:26 -04:00
if ps . ClientCert != nil {
cert := allocClientCertificate ()
cert . Certificate = ps . ClientCert
cert . CertificateKeyHmac = ps . ClientCertKeyHmac
cs . clientCert = cert
}
return cs
}
// TableName returns the table name for gorm.
2022-12-13 12:07:18 -05:00
func ( * listLookupStore ) TableName () string { return "credential_vault_store_list_lookup" }
2021-06-22 22:26:26 -04:00
// GetPublicId returns the public id.
2022-09-06 07:50:15 -07:00
func ( ps * listLookupStore ) GetPublicId () string { return ps . PublicId }
2021-06-22 22:26:26 -04:00
// UpdateCredentialStore updates the repository entry for cs.PublicId with
// the values in cs for the fields listed in fieldMaskPaths. It returns a
// new CredentialStore containing the updated values and a count of the
// number of records updated. cs is not changed.
//
// cs must contain a valid PublicId. Only Name, Description, Namespace,
// TlsServerName, TlsSkipVerify, CaCert, VaultAddress, ClientCertificate,
2022-07-22 09:13:49 -04:00
// ClientCertificateKey, workerFilter, and Token can be changed. If cs.Name is set to a
2022-07-20 16:24:46 -07:00
// non-empty string, it must be unique within cs.Projectid. If Token is changed,
2021-06-22 22:26:26 -04:00
// the new token must have the same properties defined in CreateCredentialStore
// and UpdateCredentialStore calls the same Vault endpoints described in
// CreateCredentialStore.
//
// An attribute of cs will be set to NULL in the database if the attribute
// in cs is the zero value and it is included in fieldMaskPaths.
func ( r * Repository ) UpdateCredentialStore ( ctx context . Context , cs * CredentialStore , version uint32 , fieldMaskPaths [] string , _ ... Option ) ( * CredentialStore , int , error ) {
const op = "vault.(Repository).UpdateCredentialStore"
if cs == nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing CredentialStore" )
2021-06-22 22:26:26 -04:00
}
if cs . CredentialStore == nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing embedded CredentialStore" )
2021-06-22 22:26:26 -04:00
}
if cs . PublicId == "" {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidPublicId , op , "missing public id" )
2021-06-22 22:26:26 -04:00
}
if version == 0 {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing version" )
2021-06-22 22:26:26 -04:00
}
2022-07-20 16:24:46 -07:00
if cs . ProjectId == "" {
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "missing project id" )
2021-06-22 22:26:26 -04:00
}
cs = cs . clone ()
var validateToken , updateToken bool
for _ , f := range fieldMaskPaths {
switch {
case strings . EqualFold ( nameField , f ):
case strings . EqualFold ( descriptionField , f ):
case strings . EqualFold ( namespaceField , f ):
case strings . EqualFold ( tlsServerNameField , f ):
case strings . EqualFold ( tlsSkipVerifyField , f ):
2022-07-22 09:13:49 -04:00
case strings . EqualFold ( workerFilterField , f ):
2021-06-22 22:26:26 -04:00
case strings . EqualFold ( caCertField , f ):
case strings . EqualFold ( vaultAddressField , f ):
validateToken = true
case strings . EqualFold ( certificateField , f ):
case strings . EqualFold ( certificateKeyField , f ):
case strings . EqualFold ( tokenField , f ):
if len ( cs . inputToken ) != 0 {
updateToken = true
validateToken = true
}
default :
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidFieldMask , op , f )
2021-06-22 22:26:26 -04:00
}
}
2022-08-24 14:11:49 -04:00
dbMask , nullFields := dbw . BuildUpdatePaths (
2022-10-25 18:29:38 -05:00
map [ string ] any {
2021-06-22 22:26:26 -04:00
nameField : cs . Name ,
descriptionField : cs . Description ,
namespaceField : cs . Namespace ,
tlsServerNameField : cs . TlsServerName ,
tlsSkipVerifyField : cs . TlsSkipVerify ,
2022-07-22 09:13:49 -04:00
workerFilterField : cs . WorkerFilter ,
2021-06-22 22:26:26 -04:00
caCertField : cs . CaCert ,
vaultAddressField : cs . VaultAddress ,
tokenField : cs . inputToken ,
},
fieldMaskPaths ,
[] string {
tlsSkipVerifyField ,
},
)
var clientCert , clientCertKey [] byte
if cs . ClientCertificate () != nil {
clientCert = cs . ClientCertificate (). GetCertificate ()
clientCertKey = cs . ClientCertificate (). GetCertificateKey ()
}
2022-08-24 14:11:49 -04:00
certDbMask , certNullFields := dbw . BuildUpdatePaths (
2022-10-25 18:29:38 -05:00
map [ string ] any {
2021-06-22 22:26:26 -04:00
certificateField : clientCert ,
certificateKeyField : clientCertKey ,
},
fieldMaskPaths , nil ,
)
if len ( certNullFields ) != 0 && len ( certNullFields ) != 2 {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "attempting to unset a required field on a client cert" )
2021-06-22 22:26:26 -04:00
}
if len ( append ( dbMask , certDbMask ... )) == 0 && len ( append ( nullFields , certNullFields ... )) == 0 {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . EmptyFieldMask , op , "missing field mask" )
2021-06-22 22:26:26 -04:00
}
var filteredDbMask , filteredNullFields [] string
for _ , f := range dbMask {
switch {
case strings . EqualFold ( tokenField , f ):
default :
filteredDbMask = append ( filteredDbMask , f )
}
}
for _ , f := range nullFields {
switch {
case strings . EqualFold ( tokenField , f ):
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "attempting to unset the value for token" )
2021-06-22 22:26:26 -04:00
default :
filteredNullFields = append ( filteredNullFields , f )
}
}
2022-07-20 16:24:46 -07:00
oplogWrapper , err := r . kms . GetWrapper ( ctx , cs . ProjectId , kms . KeyPurposeOplog )
2021-06-22 22:26:26 -04:00
if err != nil {
return nil , db . NoRowsAffected ,
2021-07-29 18:52:56 -06:00
errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get oplog wrapper" ))
2021-06-22 22:26:26 -04:00
}
2022-07-20 16:24:46 -07:00
databaseWrapper , err := r . kms . GetWrapper ( ctx , cs . ProjectId , kms . KeyPurposeDatabase )
2021-06-22 22:26:26 -04:00
if err != nil {
return nil , db . NoRowsAffected ,
2021-07-29 18:52:56 -06:00
errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get database wrapper" ))
2021-06-22 22:26:26 -04:00
}
2022-09-06 07:50:15 -07:00
ps , err := r . lookupClientStore ( ctx , cs . GetPublicId ())
2021-06-22 22:26:26 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to lookup private credential store" ))
2021-06-22 22:26:26 -04:00
}
if ps == nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . New ( ctx , errors . RecordNotFound , op , fmt . Sprintf ( "credential store %s" , cs . PublicId ))
2021-06-22 22:26:26 -04:00
}
origStore := ps . toCredentialStore ()
origStore . inputToken = ps . Token
if len ( ps . ClientCert ) > 0 {
2023-06-06 17:09:08 -04:00
origStore . clientCert , err = NewClientCertificate ( ctx , ps . ClientCert , ps . ClientKey )
2021-06-22 22:26:26 -04:00
}
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( "can't recreate client certificate for vault client creation" ))
2021-06-22 22:26:26 -04:00
}
updatedStore := origStore . applyUpdate ( cs , fieldMaskPaths )
if len ( certDbMask ) > 0 && updatedStore . clientCert != nil {
if err := updatedStore . clientCert . encrypt ( ctx , databaseWrapper ); err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
2021-06-22 22:26:26 -04:00
}
}
var token * Token
2022-08-25 14:37:22 -04:00
client , err := updatedStore . client ( ctx )
2021-06-22 22:26:26 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get client for updated store" ))
2021-06-22 22:26:26 -04:00
}
if validateToken {
2022-08-25 14:37:22 -04:00
tokenLookup , err := client . lookupToken ( ctx )
2021-06-22 22:26:26 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( "cannot lookup token for updated store" ))
2021-06-22 22:26:26 -04:00
}
2023-06-06 17:09:08 -04:00
if err := validateTokenLookup ( ctx , op , tokenLookup ); err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
2021-06-22 22:26:26 -04:00
}
2022-08-25 14:37:22 -04:00
available , err := client . capabilities ( ctx , requiredCapabilities . paths ())
2021-06-22 22:26:26 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get vault capabilities" ))
2021-06-22 22:26:26 -04:00
}
missing := available . missing ( requiredCapabilities )
if len ( missing ) > 0 {
return nil ,
db . NoRowsAffected ,
2024-02-16 08:30:16 -05:00
errors . New ( ctx , errors . VaultTokenMissingCapabilities , op , fmt . Sprintf ( "missing capabilities: %v" , missing ))
2021-06-22 22:26:26 -04:00
}
}
if updateToken {
2022-08-25 14:37:22 -04:00
renewedToken , err := client . renewToken ( ctx )
2021-06-22 22:26:26 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to renew vault token" ))
2021-06-22 22:26:26 -04:00
}
tokenExpires , err := renewedToken . TokenTTL ()
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get vault token expiration" ))
2021-06-22 22:26:26 -04:00
}
accessor , err := renewedToken . TokenAccessor ()
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get vault token accessor" ))
2021-06-22 22:26:26 -04:00
}
2023-06-06 17:09:08 -04:00
if token , err = newToken ( ctx , cs . GetPublicId (), cs . inputToken , [] byte ( accessor ), tokenExpires ); err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
2021-06-22 22:26:26 -04:00
}
2022-09-13 10:43:49 -07:00
runJobsInterval := r . scheduler . GetRunJobsInterval ()
if token . expiration <= runJobsInterval {
return nil , db . NoRowsAffected , errors . Wrap ( ctx , fmt . Errorf ( "scheduler interval must be greater than token ttl. scheduler jobs interval value: %s" , runJobsInterval . String ()), op )
}
2021-06-22 22:26:26 -04:00
// encrypt token
if err := token . encrypt ( ctx , databaseWrapper ); err != nil {
2021-07-29 18:52:56 -06:00
return nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
2021-06-22 22:26:26 -04:00
}
}
var rowsUpdated int
var returnedCredentialStore * CredentialStore
_ , err = r . writer . DoTx ( ctx , db . StdRetryCnt , db . ExpBackoff {},
func ( reader db . Reader , w db . Writer ) error {
msgs := make ([] * oplog . Message , 0 , 3 )
2022-01-05 14:38:38 -05:00
ticket , err := w . GetTicket ( ctx , cs )
2021-06-22 22:26:26 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get ticket" ))
2021-06-22 22:26:26 -04:00
}
cs := cs . clone ()
var csOplogMsg oplog . Message
switch {
case len ( filteredDbMask ) == 0 && len ( filteredNullFields ) == 0 :
// the credential store's fields are not being updated,
// just it's token or client certificate, so we need to
// just update the credential store's version.
cs . Version = version + 1
rowsUpdated , err = w . Update ( ctx , cs , [] string { "Version" }, nil , db . NewOplogMsg ( & csOplogMsg ), db . WithVersion ( & version ))
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to update credential store version" ))
2021-06-22 22:26:26 -04:00
}
switch rowsUpdated {
case 1 :
case 0 :
return nil
default :
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , fmt . Sprintf ( "updated credential store version and %d rows updated" , rowsUpdated ))
2021-06-22 22:26:26 -04:00
}
default :
rowsUpdated , err = w . Update ( ctx , cs , filteredDbMask , filteredNullFields , db . NewOplogMsg ( & csOplogMsg ), db . WithVersion ( & version ))
if err != nil {
2021-06-23 10:21:22 -07:00
if errors . IsUniqueError ( err ) {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . NotUnique , op ,
2021-06-23 10:21:22 -07:00
fmt . Sprintf ( "name %s already exists: %s" , cs . Name , cs . PublicId ))
}
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to update credential store" ))
2021-06-22 22:26:26 -04:00
}
switch rowsUpdated {
case 1 :
case 0 :
return nil
default :
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , fmt . Sprintf ( "updated credential store and %d rows updated" , rowsUpdated ))
2021-06-22 22:26:26 -04:00
}
}
msgs = append ( msgs , & csOplogMsg )
switch {
case len ( certNullFields ) == 2 :
// Delete the certificate
deleteCert := allocClientCertificate ()
deleteCert . StoreId = cs . GetPublicId ()
query , values := deleteCert . deleteQuery ()
rows , err := w . Exec ( ctx , query , values )
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to delete client certificate" ))
2021-06-22 22:26:26 -04:00
}
if rows > 1 {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , "more than 1 client certificate would have been deleted" )
2021-06-22 22:26:26 -04:00
}
msgs = append ( msgs , deleteCert . oplogMessage ( db . DeleteOp ))
case len ( certDbMask ) > 0 :
if updatedStore . clientCert == nil {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . InvalidParameter , op , "updated cert" )
2021-06-22 22:26:26 -04:00
}
query , values := updatedStore . clientCert . insertQuery ()
rows , err := w . Exec ( ctx , query , values )
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to upsert client certificate" ))
2021-06-22 22:26:26 -04:00
}
if rows > 1 {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , "more than 1 client certificate would have been upserted" )
2021-06-22 22:26:26 -04:00
}
}
if updateToken {
query , values := token . insertQuery ()
rows , err := w . Exec ( ctx , query , values )
if err != nil {
2021-06-23 10:21:22 -07:00
if errors . IsUniqueError ( err ) {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . NotUnique , op ,
2021-06-23 10:21:22 -07:00
fmt . Sprintf ( "token already exists" ))
}
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2021-06-22 22:26:26 -04:00
}
if rows > 1 {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , "more than 1 token would have been created" )
2021-06-22 22:26:26 -04:00
}
msgs = append ( msgs , token . oplogMessage ( db . CreateOp ))
}
publicId := cs . PublicId
2022-09-06 07:50:15 -07:00
agg := allocListLookupStore ()
2021-06-22 22:26:26 -04:00
agg . PublicId = publicId
if err := reader . LookupByPublicId ( ctx , agg ); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "unable to lookup credential store: %s" , publicId )))
2021-06-22 22:26:26 -04:00
}
returnedCredentialStore = agg . toCredentialStore ()
metadata := cs . oplog ( oplog . OpType_OP_TYPE_UPDATE )
if err := w . WriteOplogEntryWith ( ctx , oplogWrapper , ticket , metadata , msgs ); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to write oplog" ))
2021-06-22 22:26:26 -04:00
}
return nil
},
)
if err != nil {
return nil , db . NoRowsAffected , err
}
if updateToken && token != nil {
// Best effort update next run time of token renewal job, but an error should not
// cause update to fail.
// TODO (lcr 05/2021): log error once repo has logger
_ = r . scheduler . UpdateJobNextRunInAtLeast ( ctx , tokenRenewalJobName , token . renewalIn ())
}
return returnedCredentialStore , rowsUpdated , nil
}
// DeleteCredentialStore deletes publicId from the repository and returns
// the number of records deleted. All options are ignored.
func ( r * Repository ) DeleteCredentialStore ( ctx context . Context , publicId string , _ ... Option ) ( int , error ) {
const op = "vault.(Repository).DeleteCredentialStore"
if publicId == "" {
2021-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "no public id" )
2021-06-22 22:26:26 -04:00
}
cs := allocCredentialStore ()
cs . PublicId = publicId
if err := r . reader . LookupByPublicId ( ctx , cs ); err != nil {
if errors . IsNotFoundError ( err ) {
return db . NoRowsAffected , nil
}
2021-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , publicId )))
2021-06-22 22:26:26 -04:00
}
2022-07-20 16:24:46 -07:00
if cs . ProjectId == "" {
return db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "no project id" )
2021-06-22 22:26:26 -04:00
}
2022-07-20 16:24:46 -07:00
oplogWrapper , err := r . kms . GetWrapper ( ctx , cs . ProjectId , kms . KeyPurposeOplog )
2021-06-22 22:26:26 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get oplog wrapper" ))
2021-06-22 22:26:26 -04:00
}
var rows int
query , values := cs . softDeleteQuery ()
_ , err = r . writer . DoTx (
ctx , db . StdRetryCnt , db . ExpBackoff {},
func ( _ db . Reader , w db . Writer ) ( err error ) {
var msgs [] * oplog . Message
2022-01-05 14:38:38 -05:00
ticket , err := w . GetTicket ( ctx , cs )
2021-06-22 22:26:26 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get ticket" ))
2021-06-22 22:26:26 -04:00
}
rows , err = w . Exec ( ctx , query , values )
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2021-06-22 22:26:26 -04:00
}
if rows > 1 {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , "more than 1 credential store would have been deleted" )
2021-06-22 22:26:26 -04:00
}
msg := cs . oplogMessage ( db . UpdateOp )
msg . FieldMaskPaths = [] string { "DeleteTime" }
msgs = append ( msgs , msg )
metadata := cs . oplog ( oplog . OpType_OP_TYPE_UPDATE )
if err := w . WriteOplogEntryWith ( ctx , oplogWrapper , ticket , metadata , msgs ); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to write oplog" ))
2021-06-22 22:26:26 -04:00
}
return nil
},
)
if err != nil {
2021-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "delete failed for %s" , cs . PublicId )))
2021-06-22 22:26:26 -04:00
}
if rows > 0 {
// Schedule token revocation and credential store cleanup jobs to run immediately
2022-01-11 15:55:26 -08:00
_ = r . scheduler . UpdateJobNextRunInAtLeast ( ctx , tokenRevocationJobName , 0 , scheduler . WithRunNow ( true ))
_ = r . scheduler . UpdateJobNextRunInAtLeast ( ctx , credentialStoreCleanupJobName , 0 , scheduler . WithRunNow ( true ))
2021-06-22 22:26:26 -04:00
}
return rows , nil
}