mirror of https://github.com/hashicorp/boundary
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.
406 lines
15 KiB
406 lines
15 KiB
package target
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/boundary/internal/db"
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
"github.com/hashicorp/boundary/internal/kms"
|
|
"github.com/hashicorp/boundary/internal/oplog"
|
|
)
|
|
|
|
// AddTargetCredentialSources adds the cIds to the targetId in the repository. The target
|
|
// and the list of credential sources attached to the target, after cIds are added,
|
|
// will be returned on success.
|
|
// The targetVersion must match the current version of the targetId in the repository.
|
|
func (r *Repository) AddTargetCredentialSources(ctx context.Context, targetId string, targetVersion uint32, cIds []string, _ ...Option) (Target, []*TargetSet, []CredentialSource, error) {
|
|
const op = "target.(Repository).AddTargetCredentialSources"
|
|
if targetId == "" {
|
|
return nil, nil, nil, errors.New(errors.InvalidParameter, op, "missing target id")
|
|
}
|
|
if targetVersion == 0 {
|
|
return nil, nil, nil, errors.New(errors.InvalidParameter, op, "missing version")
|
|
}
|
|
if len(cIds) == 0 {
|
|
return nil, nil, nil, errors.New(errors.InvalidParameter, op, "missing credential source ids")
|
|
}
|
|
|
|
addCredLibs := make([]interface{}, 0, len(cIds))
|
|
for _, id := range cIds {
|
|
cl, err := NewCredentialLibrary(targetId, id)
|
|
if err != nil {
|
|
return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory credential library"))
|
|
}
|
|
addCredLibs = append(addCredLibs, cl)
|
|
}
|
|
|
|
t := allocTargetView()
|
|
t.PublicId = targetId
|
|
if err := r.reader.LookupByPublicId(ctx, &t); err != nil {
|
|
return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", targetId)))
|
|
}
|
|
var metadata oplog.Metadata
|
|
var target interface{}
|
|
switch t.Type {
|
|
case TcpTargetType.String():
|
|
tcpT := allocTcpTarget()
|
|
tcpT.PublicId = t.PublicId
|
|
tcpT.Version = targetVersion + 1
|
|
target = &tcpT
|
|
metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE)
|
|
metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String())
|
|
default:
|
|
return nil, nil, nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type))
|
|
}
|
|
|
|
oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog)
|
|
if err != nil {
|
|
return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
|
|
}
|
|
|
|
var hostSets []*TargetSet
|
|
var credSources []CredentialSource
|
|
var updatedTarget interface{}
|
|
_, err = r.writer.DoTx(
|
|
ctx,
|
|
db.StdRetryCnt,
|
|
db.ExpBackoff{},
|
|
func(reader db.Reader, w db.Writer) error {
|
|
msgs := make([]*oplog.Message, 0, 2)
|
|
targetTicket, err := w.GetTicket(target)
|
|
if err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
|
|
}
|
|
updatedTarget = target.(Cloneable).Clone()
|
|
var targetOplogMsg oplog.Message
|
|
rowsUpdated, err := w.Update(ctx, updatedTarget, []string{"Version"}, nil, db.NewOplogMsg(&targetOplogMsg), db.WithVersion(&targetVersion))
|
|
if err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to update target version"))
|
|
}
|
|
if rowsUpdated == 0 {
|
|
return errors.New(errors.VersionMismatch, op, "invalid target version")
|
|
}
|
|
if rowsUpdated > 1 {
|
|
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated target and %d rows updated", rowsUpdated))
|
|
}
|
|
msgs = append(msgs, &targetOplogMsg)
|
|
|
|
credLibsOplogMsgs := make([]*oplog.Message, 0, len(addCredLibs))
|
|
if err := w.CreateItems(ctx, addCredLibs, db.NewOplogMsgs(&credLibsOplogMsgs)); err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to create target credential sources"))
|
|
}
|
|
msgs = append(msgs, credLibsOplogMsgs...)
|
|
|
|
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
|
|
}
|
|
hostSets, err = fetchSets(ctx, reader, targetId)
|
|
if err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve credential sources after adding"))
|
|
}
|
|
credSources, err = fetchCredentialSources(ctx, reader, targetId)
|
|
if err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve credential sources after adding"))
|
|
}
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, nil, nil, errors.Wrap(err, op)
|
|
}
|
|
return updatedTarget.(Target), hostSets, credSources, nil
|
|
}
|
|
|
|
// DeleteTargetCredentialSources deletes credential sources from a target in the repository.
|
|
// The target's current db version must match the targetVersion or an error will be returned.
|
|
func (r *Repository) DeleteTargetCredentialSources(ctx context.Context, targetId string, targetVersion uint32, csIds []string, _ ...Option) (int, error) {
|
|
const op = "target.(Repository).DeleteTargetCredentialSources"
|
|
if targetId == "" {
|
|
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing target id")
|
|
}
|
|
if targetVersion == 0 {
|
|
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version")
|
|
}
|
|
if len(csIds) == 0 {
|
|
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing credential source ids")
|
|
}
|
|
|
|
deleteCredLibs := make([]interface{}, 0, len(csIds))
|
|
for _, id := range csIds {
|
|
cl, err := NewCredentialLibrary(targetId, id)
|
|
if err != nil {
|
|
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to create in memory credential library"))
|
|
}
|
|
deleteCredLibs = append(deleteCredLibs, cl)
|
|
}
|
|
|
|
t := allocTargetView()
|
|
t.PublicId = targetId
|
|
if err := r.reader.LookupByPublicId(ctx, &t); err != nil {
|
|
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", targetId)))
|
|
}
|
|
var metadata oplog.Metadata
|
|
var target interface{}
|
|
switch t.Type {
|
|
case TcpTargetType.String():
|
|
tcpT := allocTcpTarget()
|
|
tcpT.PublicId = t.PublicId
|
|
tcpT.Version = targetVersion + 1
|
|
target = &tcpT
|
|
metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE)
|
|
metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String())
|
|
default:
|
|
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type))
|
|
}
|
|
|
|
oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog)
|
|
if err != nil {
|
|
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
|
|
}
|
|
|
|
var rowsDeleted int
|
|
_, err = r.writer.DoTx(
|
|
ctx,
|
|
db.StdRetryCnt,
|
|
db.ExpBackoff{},
|
|
func(reader db.Reader, w db.Writer) error {
|
|
msgs := make([]*oplog.Message, 0, 2)
|
|
targetTicket, err := w.GetTicket(target)
|
|
if err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
|
|
}
|
|
updatedTarget := target.(Cloneable).Clone()
|
|
var targetOplogMsg oplog.Message
|
|
rowsUpdated, err := w.Update(ctx, updatedTarget, []string{"Version"}, nil, db.NewOplogMsg(&targetOplogMsg), db.WithVersion(&targetVersion))
|
|
if err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to update target version"))
|
|
}
|
|
if rowsUpdated == 0 {
|
|
return errors.New(errors.VersionMismatch, op, "invalid target version")
|
|
}
|
|
if rowsUpdated > 1 {
|
|
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated target and %d rows updated", rowsUpdated))
|
|
}
|
|
msgs = append(msgs, &targetOplogMsg)
|
|
|
|
credLibsOplogMsgs := make([]*oplog.Message, 0, len(deleteCredLibs))
|
|
rowsDeleted, err = w.DeleteItems(ctx, deleteCredLibs, db.NewOplogMsgs(&credLibsOplogMsgs))
|
|
if err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to delete target credential sources"))
|
|
}
|
|
if rowsDeleted != len(deleteCredLibs) {
|
|
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("credential sources deleted %d did not match request for %d", rowsDeleted, len(deleteCredLibs)))
|
|
}
|
|
msgs = append(msgs, credLibsOplogMsgs...)
|
|
|
|
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
|
|
}
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return db.NoRowsAffected, errors.Wrap(err, op)
|
|
}
|
|
return rowsDeleted, nil
|
|
}
|
|
|
|
// SetTargetCredentialSources will set the target's credential sources. Set will add
|
|
// and/or delete credential sources as need to reconcile the existing credential sources
|
|
// with the request. If clIds is empty, all the credential sources will be cleared from the target.
|
|
func (r *Repository) SetTargetCredentialSources(ctx context.Context, targetId string, targetVersion uint32, csIds []string, _ ...Option) ([]*TargetSet, []CredentialSource, int, error) {
|
|
const op = "target.(Repository).SetTargetCredentialSources"
|
|
if targetId == "" {
|
|
return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing target id")
|
|
}
|
|
if targetVersion == 0 {
|
|
return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version")
|
|
}
|
|
|
|
changes, err := r.changes(ctx, targetId, csIds)
|
|
if err != nil {
|
|
return nil, nil, db.NoRowsAffected, errors.Wrap(err, op)
|
|
}
|
|
if len(changes) == 0 {
|
|
// Nothing needs to be changed, return early
|
|
hostSets, err := fetchSets(ctx, r.reader, targetId)
|
|
if err != nil {
|
|
return nil, nil, db.NoRowsAffected, errors.Wrap(err, op)
|
|
}
|
|
credSources, err := fetchCredentialSources(ctx, r.reader, targetId)
|
|
if err != nil {
|
|
return nil, nil, db.NoRowsAffected, errors.Wrap(err, op)
|
|
}
|
|
return hostSets, credSources, db.NoRowsAffected, nil
|
|
}
|
|
|
|
var deleteCredLibs, addCredLibs []interface{}
|
|
for _, c := range changes {
|
|
cl, err := NewCredentialLibrary(targetId, c.LibraryId)
|
|
if err != nil {
|
|
return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to create in memory target credential library"))
|
|
}
|
|
switch c.Action {
|
|
case "delete":
|
|
deleteCredLibs = append(deleteCredLibs, cl)
|
|
case "add":
|
|
addCredLibs = append(addCredLibs, cl)
|
|
}
|
|
}
|
|
|
|
t := allocTargetView()
|
|
t.PublicId = targetId
|
|
if err := r.reader.LookupByPublicId(ctx, &t); err != nil {
|
|
return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", targetId)))
|
|
}
|
|
var metadata oplog.Metadata
|
|
var target interface{}
|
|
switch t.Type {
|
|
case TcpTargetType.String():
|
|
tcpT := allocTcpTarget()
|
|
tcpT.PublicId = t.PublicId
|
|
tcpT.Version = targetVersion + 1
|
|
target = &tcpT
|
|
metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE)
|
|
default:
|
|
return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type))
|
|
}
|
|
oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog)
|
|
if err != nil {
|
|
return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
|
|
}
|
|
|
|
var rowsAffected int
|
|
var hostSets []*TargetSet
|
|
var credSources []CredentialSource
|
|
_, err = r.writer.DoTx(
|
|
ctx,
|
|
db.StdRetryCnt,
|
|
db.ExpBackoff{},
|
|
func(reader db.Reader, w db.Writer) error {
|
|
msgs := make([]*oplog.Message, 0, 2)
|
|
targetTicket, err := w.GetTicket(target)
|
|
if err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
|
|
}
|
|
updatedTarget := target.(Cloneable).Clone()
|
|
var targetOplogMsg oplog.Message
|
|
rowsUpdated, err := w.Update(ctx, updatedTarget, []string{"Version"}, nil, db.NewOplogMsg(&targetOplogMsg), db.WithVersion(&targetVersion))
|
|
if err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to update target version"))
|
|
}
|
|
if rowsUpdated == 0 {
|
|
return errors.New(errors.VersionMismatch, op, "invalid target version")
|
|
}
|
|
if rowsUpdated > 1 {
|
|
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated target and %d rows updated", rowsUpdated))
|
|
}
|
|
msgs = append(msgs, &targetOplogMsg)
|
|
|
|
// add new credential libraries
|
|
if len(addCredLibs) > 0 {
|
|
addMsgs := make([]*oplog.Message, 0, len(addCredLibs))
|
|
if err := w.CreateItems(ctx, addCredLibs, db.NewOplogMsgs(&addMsgs)); err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to add target credential libraries"))
|
|
}
|
|
rowsAffected += len(addMsgs)
|
|
msgs = append(msgs, addMsgs...)
|
|
metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String())
|
|
}
|
|
|
|
// delete existing credential libraries not part of set
|
|
if len(deleteCredLibs) > 0 {
|
|
delMsgs := make([]*oplog.Message, 0, len(deleteCredLibs))
|
|
rowsDeleted, err := w.DeleteItems(ctx, deleteCredLibs, db.NewOplogMsgs(&delMsgs))
|
|
if err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to delete target credential libraries"))
|
|
}
|
|
if rowsDeleted != len(delMsgs) {
|
|
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("target credential libraries deleted %d did not match request for %d", rowsDeleted, len(deleteCredLibs)))
|
|
}
|
|
rowsAffected += rowsDeleted
|
|
msgs = append(msgs, delMsgs...)
|
|
metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String())
|
|
}
|
|
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
|
|
}
|
|
|
|
hostSets, err = fetchSets(ctx, reader, targetId)
|
|
if err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current target host sets after add/delete"))
|
|
}
|
|
credSources, err = fetchCredentialSources(ctx, reader, targetId)
|
|
if err != nil {
|
|
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current target credential sources after add/delete"))
|
|
}
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, nil, db.NoRowsAffected, errors.Wrap(err, op)
|
|
}
|
|
return hostSets, credSources, rowsAffected, nil
|
|
}
|
|
|
|
type change struct {
|
|
Action string
|
|
LibraryId string
|
|
}
|
|
|
|
func (r *Repository) changes(ctx context.Context, targetId string, clIds []string) ([]*change, error) {
|
|
const op = "target.(Repository).changes"
|
|
var inClauseSpots []string
|
|
// starts at 2 because there is already a $1 in the query
|
|
for i := 2; i < len(clIds)+2; i++ {
|
|
inClauseSpots = append(inClauseSpots, fmt.Sprintf("$%d", i))
|
|
}
|
|
inClause := strings.Join(inClauseSpots, ",")
|
|
if inClause == "" {
|
|
inClause = "''"
|
|
}
|
|
query := fmt.Sprintf(setChangesQuery, inClause)
|
|
|
|
var params []interface{}
|
|
params = append(params, targetId)
|
|
for _, id := range clIds {
|
|
params = append(params, id)
|
|
}
|
|
rows, err := r.reader.Query(ctx, query, params)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, op, errors.WithMsg("query failed"))
|
|
}
|
|
defer rows.Close()
|
|
|
|
var changes []*change
|
|
for rows.Next() {
|
|
var chg change
|
|
if err := r.reader.ScanRows(rows, &chg); err != nil {
|
|
return nil, errors.Wrap(err, op, errors.WithMsg("scan row failed"))
|
|
}
|
|
changes = append(changes, &chg)
|
|
}
|
|
return changes, nil
|
|
}
|
|
|
|
func fetchCredentialSources(ctx context.Context, r db.Reader, targetId string) ([]CredentialSource, error) {
|
|
const op = "target.fetchCredentialSources"
|
|
var libraries []*TargetLibrary
|
|
if err := r.SearchWhere(ctx, &libraries, "target_id = ?", []interface{}{targetId}); err != nil {
|
|
return nil, errors.Wrap(err, op)
|
|
}
|
|
// FIXME: When we have static creds, there will need to be an updated view
|
|
// that unions between libs and creds, at which point the type above will
|
|
// change. For now we just take the libraries and wrap them.
|
|
if len(libraries) == 0 {
|
|
return nil, nil
|
|
}
|
|
ret := make([]CredentialSource, len(libraries))
|
|
for i, lib := range libraries {
|
|
ret[i] = lib
|
|
}
|
|
return ret, nil
|
|
}
|