347 lines
13 KiB
Go
347 lines
13 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package vault
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/boundary/globals"
|
|
"github.com/hashicorp/boundary/internal/db"
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
"github.com/hashicorp/boundary/internal/kms"
|
|
"github.com/hashicorp/boundary/internal/oplog"
|
|
"github.com/hashicorp/go-dbw"
|
|
)
|
|
|
|
// CreateSSHCertificateCredentialLibrary inserts l into the repository and returns a new
|
|
// SSHCertificateCredentialLibrary containing the credential library's PublicId. l is not
|
|
// changed. l must contain a valid StoreId. l must not contain a PublicId.
|
|
// The PublicId is generated and assigned by this method.
|
|
//
|
|
// Both l.Name and l.Description are optional. If l.Name is set, it must be
|
|
// unique within l.StoreId.
|
|
//
|
|
// Both l.CreateTime and l.UpdateTime are ignored.
|
|
func (r *Repository) CreateSSHCertificateCredentialLibrary(ctx context.Context, projectId string, l *SSHCertificateCredentialLibrary, _ ...Option) (*SSHCertificateCredentialLibrary, error) {
|
|
const op = "vault.(Repository).CreateSSHCertificateCredentialLibrary"
|
|
if l == nil {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil SSHCertificateCredentialLibrary")
|
|
}
|
|
if l.SSHCertificateCredentialLibrary == nil {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil embedded l")
|
|
}
|
|
if l.StoreId == "" {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "no store id")
|
|
}
|
|
if l.VaultPath == "" {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "no vault path")
|
|
}
|
|
if l.PublicId != "" {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty")
|
|
}
|
|
if projectId == "" {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "no project id")
|
|
}
|
|
if l.Username == "" {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "no username")
|
|
}
|
|
|
|
l = l.clone()
|
|
|
|
if l.KeyType == "" {
|
|
l.KeyType = KeyTypeEd25519
|
|
}
|
|
|
|
if l.KeyBits == KeyBitsDefault {
|
|
l.KeyBits = l.getDefaultKeyBits()
|
|
}
|
|
|
|
if l.GetCredentialType() == "" {
|
|
l.SSHCertificateCredentialLibrary.CredentialType = string(globals.SshCertificateCredentialType)
|
|
}
|
|
if l.GetCredentialType() != string(globals.SshCertificateCredentialType) {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "invalid credential type")
|
|
}
|
|
|
|
id, err := newSSHCertificateCredentialLibraryId(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op)
|
|
}
|
|
l.setId(id)
|
|
|
|
oplogWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeOplog)
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
|
|
}
|
|
|
|
var newSSHCertificateCredentialLibrary *SSHCertificateCredentialLibrary
|
|
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
|
func(_ db.Reader, w db.Writer) error {
|
|
var msgs []*oplog.Message
|
|
ticket, err := w.GetTicket(ctx, l)
|
|
if err != nil {
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
|
|
}
|
|
|
|
// insert credential library
|
|
newSSHCertificateCredentialLibrary = l.clone()
|
|
var lOplogMsg oplog.Message
|
|
if err := w.Create(ctx, newSSHCertificateCredentialLibrary, db.NewOplogMsg(&lOplogMsg)); err != nil {
|
|
return errors.Wrap(ctx, err, op)
|
|
}
|
|
msgs = append(msgs, &lOplogMsg)
|
|
|
|
metadata := l.oplog(oplog.OpType_OP_TYPE_CREATE)
|
|
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil {
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
|
|
}
|
|
return nil
|
|
},
|
|
)
|
|
|
|
if err != nil {
|
|
if errors.IsUniqueError(err) {
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in credential store: %s: name %s already exists", l.StoreId, l.Name)))
|
|
}
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in credential store: %s", l.StoreId)))
|
|
}
|
|
return newSSHCertificateCredentialLibrary, nil
|
|
}
|
|
|
|
// UpdateSSHCertificateCredentialLibrary updates the repository entry for l.PublicId with
|
|
// the values in l for the fields listed in fieldMaskPaths. It returns a
|
|
// new SSHCertificateCredentialLibrary containing the updated values and a count of the
|
|
// number of records updated. l is not changed.
|
|
//
|
|
// l must contain a valid PublicId. Name, Description, VaultPath, Username,
|
|
// KeyType, KeyBits, Ttl, KeyId, CriticalOptions, and Extensions can be updated. If
|
|
// l.Name is set to a non-empty string, it must be unique within l.StoreId.
|
|
//
|
|
// An attribute of l will be set to NULL in the database if the attribute
|
|
// in l is the zero value and it is included in fieldMaskPaths.
|
|
func (r *Repository) UpdateSSHCertificateCredentialLibrary(ctx context.Context, projectId string, l *SSHCertificateCredentialLibrary, version uint32, fieldMaskPaths []string, _ ...Option) (*SSHCertificateCredentialLibrary, int, error) {
|
|
const op = "vault.(Repository).UpdateSSHCertificateCredentialLibrary"
|
|
if l == nil {
|
|
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing SSHCertificateCredentialLibrary")
|
|
}
|
|
if l.SSHCertificateCredentialLibrary == nil {
|
|
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing embedded SSHCertificateCredentialLibrary")
|
|
}
|
|
if l.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 projectId == "" {
|
|
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing project id")
|
|
}
|
|
l = l.clone()
|
|
|
|
var keyTypeChange, keyBitChangeDefault bool
|
|
|
|
for _, f := range fieldMaskPaths {
|
|
switch {
|
|
case strings.EqualFold(nameField, f):
|
|
case strings.EqualFold(descriptionField, f):
|
|
case strings.EqualFold(vaultPathField, f):
|
|
case strings.EqualFold(usernameField, f):
|
|
case strings.EqualFold(keyTypeField, f):
|
|
keyTypeChange = true
|
|
case strings.EqualFold(keyBitsField, f):
|
|
keyBitChangeDefault = l.KeyBits == KeyBitsDefault
|
|
case strings.EqualFold(ttlField, f):
|
|
case strings.EqualFold(keyIdField, f):
|
|
case strings.EqualFold(CriticalOptionsField, f):
|
|
case strings.EqualFold(ExtensionsField, f):
|
|
case strings.EqualFold(AdditionalValidPrincipalsField, f):
|
|
default:
|
|
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, f)
|
|
}
|
|
}
|
|
|
|
if keyTypeChange && l.KeyType == "" {
|
|
l.KeyType = KeyTypeEd25519
|
|
}
|
|
|
|
if keyTypeChange && keyBitChangeDefault {
|
|
l.KeyBits = l.getDefaultKeyBits()
|
|
}
|
|
|
|
origLib, err := r.LookupSSHCertificateCredentialLibrary(ctx, l.PublicId)
|
|
switch {
|
|
case err != nil:
|
|
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op)
|
|
case origLib == nil:
|
|
return nil, db.NoRowsAffected, errors.New(ctx, errors.RecordNotFound, op, fmt.Sprintf("credential library %s", l.PublicId))
|
|
}
|
|
|
|
if keyBitChangeDefault && !keyTypeChange {
|
|
l.KeyBits = origLib.getDefaultKeyBits()
|
|
}
|
|
|
|
var dbMask, nullFields []string
|
|
dbMask, nullFields = dbw.BuildUpdatePaths(
|
|
map[string]any{
|
|
nameField: l.Name,
|
|
descriptionField: l.Description,
|
|
vaultPathField: l.VaultPath,
|
|
usernameField: l.Username,
|
|
keyTypeField: l.KeyType,
|
|
keyBitsField: l.KeyBits,
|
|
ttlField: l.Ttl,
|
|
keyIdField: l.KeyId,
|
|
CriticalOptionsField: l.CriticalOptions,
|
|
ExtensionsField: l.Extensions,
|
|
AdditionalValidPrincipalsField: l.AdditionalValidPrincipals,
|
|
},
|
|
fieldMaskPaths,
|
|
[]string{keyBitsField},
|
|
)
|
|
|
|
if len(dbMask) == 0 && len(nullFields) == 0 {
|
|
return nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "missing field mask")
|
|
}
|
|
|
|
oplogWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeOplog)
|
|
if err != nil {
|
|
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt),
|
|
errors.WithMsg("unable to get oplog wrapper"))
|
|
}
|
|
|
|
var rowsUpdated int
|
|
var returnedCredentialLibrary *SSHCertificateCredentialLibrary
|
|
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
|
func(rr db.Reader, w db.Writer) error {
|
|
var msgs []*oplog.Message
|
|
ticket, err := w.GetTicket(ctx, l)
|
|
if err != nil {
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
|
|
}
|
|
|
|
l := l.clone()
|
|
var lOplogMsg oplog.Message
|
|
|
|
// Update the credential library table
|
|
switch {
|
|
case len(dbMask) == 0 && len(nullFields) == 0:
|
|
// the credential library's fields are not being updated,
|
|
// just one of it's child objects, so we just need to
|
|
// update the library's version.
|
|
l.Version = version + 1
|
|
rowsUpdated, err = w.Update(ctx, l, []string{"Version"}, nil, db.NewOplogMsg(&lOplogMsg), db.WithVersion(&version))
|
|
if err != nil {
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential library version"))
|
|
}
|
|
switch rowsUpdated {
|
|
case 1:
|
|
case 0:
|
|
return nil
|
|
default:
|
|
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated credential library version and %d rows updated", rowsUpdated))
|
|
}
|
|
default:
|
|
rowsUpdated, err = w.Update(ctx, l, dbMask, nullFields, db.NewOplogMsg(&lOplogMsg), db.WithVersion(&version))
|
|
if err != nil {
|
|
if errors.IsUniqueError(err) {
|
|
return errors.New(ctx, errors.NotUnique, op,
|
|
fmt.Sprintf("name %s already exists: %s", l.Name, l.PublicId))
|
|
}
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential library"))
|
|
}
|
|
switch rowsUpdated {
|
|
case 1:
|
|
case 0:
|
|
return nil
|
|
default:
|
|
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated credential library and %d rows updated", rowsUpdated))
|
|
}
|
|
}
|
|
msgs = append(msgs, &lOplogMsg)
|
|
|
|
metadata := l.oplog(oplog.OpType_OP_TYPE_UPDATE)
|
|
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil {
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
|
|
}
|
|
|
|
accl := allocSSHCertificateCredentialLibrary()
|
|
accl.PublicId = l.PublicId
|
|
if err = rr.LookupById(ctx, accl); err != nil {
|
|
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to retrieve updated credential library"))
|
|
}
|
|
returnedCredentialLibrary = accl
|
|
return nil
|
|
},
|
|
)
|
|
|
|
if err != nil {
|
|
if errors.IsUniqueError(err) {
|
|
return nil, db.NoRowsAffected, errors.New(ctx, errors.NotUnique, op,
|
|
fmt.Sprintf("name %s already exists: %s", l.Name, l.PublicId))
|
|
}
|
|
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(l.PublicId))
|
|
}
|
|
|
|
return returnedCredentialLibrary, rowsUpdated, nil
|
|
}
|
|
|
|
// LookupSSHCertificateCredentialLibrary returns the SSHCertificateCredentialLibrary for publicId.
|
|
// Returns nil, nil if no SSHCertificateCredentialLibrary is found for publicId.
|
|
func (r *Repository) LookupSSHCertificateCredentialLibrary(ctx context.Context, publicId string, _ ...Option) (*SSHCertificateCredentialLibrary, error) {
|
|
const op = "vault.(Repository).LookupCredentialLibrary"
|
|
if publicId == "" {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id")
|
|
}
|
|
l := allocSSHCertificateCredentialLibrary()
|
|
l.PublicId = publicId
|
|
if err := r.reader.LookupByPublicId(ctx, l); err != nil {
|
|
if errors.IsNotFoundError(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", publicId)))
|
|
}
|
|
return l, nil
|
|
}
|
|
|
|
// DeleteSSHCertificateCredentialLibrary deletes publicId from the repository and returns
|
|
// the number of records deleted.
|
|
func (r *Repository) DeleteSSHCertificateCredentialLibrary(ctx context.Context, projectId string, publicId string, _ ...Option) (int, error) {
|
|
const op = "vault.(Repository).DeleteSSHCertificateCredentialLibrary"
|
|
if publicId == "" {
|
|
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no public id")
|
|
}
|
|
if projectId == "" {
|
|
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no project id")
|
|
}
|
|
|
|
l := allocSSHCertificateCredentialLibrary()
|
|
l.PublicId = publicId
|
|
|
|
oplogWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeOplog)
|
|
if err != nil {
|
|
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
|
|
}
|
|
|
|
var rowsDeleted int
|
|
_, err = r.writer.DoTx(
|
|
ctx, db.StdRetryCnt, db.ExpBackoff{},
|
|
func(_ db.Reader, w db.Writer) (err error) {
|
|
dl := l.clone()
|
|
rowsDeleted, err = w.Delete(ctx, dl, db.WithOplog(oplogWrapper, l.oplog(oplog.OpType_OP_TYPE_DELETE)))
|
|
if err == nil && rowsDeleted > 1 {
|
|
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 CredentialLibrary would have been deleted")
|
|
}
|
|
return err
|
|
},
|
|
)
|
|
|
|
if err != nil {
|
|
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("delete failed for %s", l.PublicId)))
|
|
}
|
|
|
|
return rowsDeleted, nil
|
|
}
|