feat(target): Add support for credential library validation

Target subtypes must now implement another repository hook to validate
any CredentialLibraries that are being added/set. This allows for
subtypes to determine the valid set of CredentialLibraries.

For tcp.Targets only CredentialLibaries with
credential.ApplicationPurpose are allowed. If any other purpose is
provided an error is returned.
This commit is contained in:
Timothy Messier committed 2021-10-29 10:01:52 -04:00
1 parent 2cc58ff901
commit 1b7cfb1704
8 files changed
+227 -76

No files matched your search

@@ -168,8 +168,7 @@ func testSessionCredentialParams(t *testing.T, conn *db.DB, kms *kms.Kms, wrappe
libIds := vault.TestCredentialLibraries(t, conn, wrapper, stores[0].GetPublicId(), 2)
libs := []*target.CredentialLibrary{
target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[0].GetPublicId(), credential.ApplicationPurpose),
target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[0].GetPublicId(), credential.IngressPurpose),
target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[1].GetPublicId(), credential.EgressPurpose),
target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[1].GetPublicId(), credential.ApplicationPurpose),
}
targetRepo, err := target.NewRepository(rw, rw, kms)
@@ -178,8 +177,7 @@ func testSessionCredentialParams(t *testing.T, conn *db.DB, kms *kms.Kms, wrappe
require.NoError(t, err)
creds := []*session.DynamicCredential{
session.NewDynamicCredential(libIds[0].GetPublicId(), credential.ApplicationPurpose),
session.NewDynamicCredential(libIds[0].GetPublicId(), credential.IngressPurpose),
session.NewDynamicCredential(libIds[1].GetPublicId(), credential.EgressPurpose),
session.NewDynamicCredential(libIds[1].GetPublicId(), credential.ApplicationPurpose),
}
return creds
}
+2 -4
View File
@@ -1661,16 +1661,14 @@ func testSessionCredentialParams(t *testing.T, conn *db.DB, wrapper wrapping.Wra
libIds := vault.TestCredentialLibraries(t, conn, wrapper, stores[0].GetPublicId(), 2)
libs := []*target.CredentialLibrary{
target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[0].GetPublicId(), credential.ApplicationPurpose),
target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[0].GetPublicId(), credential.IngressPurpose),
target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[1].GetPublicId(), credential.EgressPurpose),
target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[1].GetPublicId(), credential.ApplicationPurpose),
}
_, _, _, err = targetRepo.AddTargetCredentialSources(ctx, tar.GetPublicId(), tar.GetVersion(), libs)
require.NoError(err)
creds := []*DynamicCredential{
NewDynamicCredential(libIds[0].GetPublicId(), credential.ApplicationPurpose),
NewDynamicCredential(libIds[0].GetPublicId(), credential.IngressPurpose),
NewDynamicCredential(libIds[1].GetPublicId(), credential.EgressPurpose),
NewDynamicCredential(libIds[1].GetPublicId(), credential.ApplicationPurpose),
}
params.DynamicCredentials = creds
return params
+21 -7
View File
@@ -15,10 +15,15 @@ type AllocFunc func() Target
// be used by the Repository.
type VetFunc func(context.Context, Target) error
// VetCredentialLibrariesFunc is a function that checks the given CredentialLibraries
// to ensure that they are valid for a Target subtype.
type VetCredentialLibrariesFunc func(context.Context, []*CredentialLibrary) error
type registryEntry struct {
alloc AllocFunc
vet VetFunc
prefix string
alloc AllocFunc
vet VetFunc
vetCredentialLibraries VetCredentialLibrariesFunc
prefix string
}
type registry struct {
@@ -71,6 +76,14 @@ func (r *registry) vetFunc(s subtypes.Subtype) (VetFunc, bool) {
return entry.vet, ok
}
func (r *registry) vetCredentialLibrariesFunc(s subtypes.Subtype) (VetCredentialLibrariesFunc, bool) {
entry, ok := r.get(s)
if !ok {
return nil, ok
}
return entry.vetCredentialLibraries, ok
}
func (r *registry) idPrefix(s subtypes.Subtype) (string, bool) {
entry, ok := r.get(s)
if !ok {
@@ -103,10 +116,11 @@ func SubtypeFromId(id string) subtypes.Subtype {
// Register registers repository hooks and the prefixes for a provided Subtype. Register
// panics if the subtype has already been registered or if any of the
// prefixes are associated with another subtype.
func Register(s subtypes.Subtype, af AllocFunc, vf VetFunc, prefix string) {
func Register(s subtypes.Subtype, af AllocFunc, vf VetFunc, vclf VetCredentialLibrariesFunc, prefix string) {
subtypeRegistry.set(s, &registryEntry{
alloc: af,
vet: vf,
prefix: prefix,
alloc: af,
vet: vf,
vetCredentialLibraries: vclf,
prefix: prefix,
})
}
@@ -74,6 +74,14 @@ func (r *Repository) AddTargetCredentialSources(ctx context.Context, targetId st
return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type))
}
vetCredentialLibraries, ok := subtypeRegistry.vetCredentialLibrariesFunc(t.Subtype())
if !ok {
return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type))
}
if err := vetCredentialLibraries(ctx, cls); err != nil {
return nil, nil, nil, err
}
target := alloc()
target.SetPublicId(ctx, t.PublicId)
target.SetVersion(targetVersion + 1)
@@ -284,6 +292,15 @@ func (r *Repository) SetTargetCredentialSources(ctx context.Context, targetId st
if !ok {
return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type))
}
vetCredentialLibraries, ok := subtypeRegistry.vetCredentialLibrariesFunc(t.Subtype())
if !ok {
return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type))
}
if err := vetCredentialLibraries(ctx, cls); err != nil {
return nil, nil, db.NoRowsAffected, err
}
target := alloc()
target.SetPublicId(ctx, t.PublicId)
target.SetVersion(targetVersion + 1)
@@ -17,7 +17,7 @@ import (
)
func TestRepository_SetTargetCredentialSources(t *testing.T) {
target.Register(targettest.Subtype, targettest.Alloc, targettest.Vet, targettest.TargetPrefix)
target.Register(targettest.Subtype, targettest.Alloc, targettest.Vet, targettest.VetCredentialLibraries, targettest.TargetPrefix)
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
@@ -189,15 +189,19 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) {
origCredSources, origCredLibraries = tt.setup(tar)
}
wantCredSources := make(map[string]*target.CredentialLibrary)
wantCredSources := make(map[string]target.CredentialSource)
for _, cl := range tt.args.cls {
cl.TargetId = tar.GetPublicId()
wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = cl
wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = &target.TargetLibrary{
CredentialLibrary: cl.CredentialLibrary,
}
}
if tt.args.addToOrigLibs {
tt.args.cls = append(tt.args.cls, origCredLibraries...)
for _, cl := range origCredLibraries {
wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = cl
wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = &target.TargetLibrary{
CredentialLibrary: cl.CredentialLibrary,
}
}
}
@@ -221,8 +225,8 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) {
for _, cs := range got {
w, ok := wantCredSources[cs.Id()+"_"+string(cs.CredentialPurpose())]
assert.True(ok, "got unexpected credentialsource %v", cs)
assert.Equal(w.CredentialLibraryId, cs.Id())
assert.Equal(w.CredentialPurpose, string(cs.CredentialPurpose()))
assert.Equal(w.Id(), cs.Id())
assert.Equal(w.CredentialPurpose(), cs.CredentialPurpose())
}
foundTarget, _, _, err := repo.LookupTarget(context.Background(), tar.GetPublicId())
+5
View File
@@ -203,6 +203,11 @@ func Vet(ctx context.Context, t target.Target) error {
return nil
}
// VetCredentialLibraries allows for any CredentialLibraries.
func VetCredentialLibraries(_ context.Context, _ []*target.CredentialLibrary) error {
return nil
}
// New creates a targettest.Target.
func New(scopeId string, opt ...target.Option) (*Target, error) {
const op = "target_test.New"
+16 -1
View File
@@ -2,13 +2,15 @@ package tcp
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/target"
)
func init() {
target.Register(Subtype, allocTarget, vet, TargetPrefix)
target.Register(Subtype, allocTarget, vet, vetCredentialLibraries, TargetPrefix)
}
const (
@@ -35,3 +37,16 @@ func vet(ctx context.Context, t target.Target) error {
}
return nil
}
// vetCredentialLibraries checks that all of the provided credential libriaries have a CredentialPurpose
// of ApplicationPurpose. Any other CredentialPurpose will result in an error.
func vetCredentialLibraries(ctx context.Context, cls []*target.CredentialLibrary) error {
const op = "tcp.vetCredentialLibraries"
for _, cl := range cls {
if cl.CredentialPurpose != string(credential.ApplicationPurpose) {
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("tcp.Target only supports credential purpose: %q", credential.ApplicationPurpose))
}
}
return nil
}
@@ -2,7 +2,6 @@ package tcp_test
import (
"context"
"sort"
"testing"
"time"
@@ -14,6 +13,7 @@ import (
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/boundary/internal/target"
"github.com/hashicorp/boundary/internal/target/store"
"github.com/hashicorp/boundary/internal/target/tcp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -40,14 +40,14 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) {
type args struct {
targetVersion uint32
credLibIds []string
cls []*target.CredentialLibrary
}
tests := []struct {
name string
args args
wantCredLibIds []string
wantErr bool
wantErrCode errors.Code
name string
args args
wantCredSources map[string]target.CredentialSource
wantErr bool
wantErrCode errors.Code
}{
{
name: "zero-version",
@@ -69,34 +69,95 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) {
name: "valid-single-source",
args: args{
targetVersion: 1,
credLibIds: []string{lib1.PublicId},
cls: []*target.CredentialLibrary{
target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose),
},
},
wantCredLibIds: []string{lib1.PublicId},
wantErr: false,
wantCredSources: map[string]target.CredentialSource{
lib1.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetLibrary{
CredentialLibrary: &store.CredentialLibrary{
CredentialLibraryId: lib1.PublicId,
CredentialPurpose: string(credential.ApplicationPurpose),
},
},
},
wantErr: false,
},
{
name: "valid-multiple-sources",
args: args{
targetVersion: 1,
credLibIds: []string{lib1.PublicId, lib2.PublicId, lib3.PublicId},
cls: []*target.CredentialLibrary{
target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose),
target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose),
target.TestNewCredentialLibrary("", lib3.PublicId, credential.ApplicationPurpose),
},
},
wantCredLibIds: []string{lib1.PublicId, lib2.PublicId, lib3.PublicId},
wantErr: false,
wantCredSources: map[string]target.CredentialSource{
lib1.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetLibrary{
CredentialLibrary: &store.CredentialLibrary{
CredentialLibraryId: lib1.PublicId,
CredentialPurpose: string(credential.ApplicationPurpose),
},
},
lib2.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetLibrary{
CredentialLibrary: &store.CredentialLibrary{
CredentialLibraryId: lib2.PublicId,
CredentialPurpose: string(credential.ApplicationPurpose),
},
},
lib3.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetLibrary{
CredentialLibrary: &store.CredentialLibrary{
CredentialLibraryId: lib3.PublicId,
CredentialPurpose: string(credential.ApplicationPurpose),
},
},
},
wantErr: false,
},
{
name: "invalid-source-id",
args: args{
targetVersion: 1,
credLibIds: []string{lib1.PublicId, lib2.PublicId, lib3.PublicId, "invalid-source-id"},
cls: []*target.CredentialLibrary{
target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose),
target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose),
target.TestNewCredentialLibrary("", lib3.PublicId, credential.ApplicationPurpose),
target.TestNewCredentialLibrary("", "invalid-source-id", credential.ApplicationPurpose),
},
},
wantErr: true,
wantErrCode: errors.NotSpecificIntegrity,
},
{
name: "egress-credential-purpose",
args: args{
targetVersion: 1,
cls: []*target.CredentialLibrary{
target.TestNewCredentialLibrary("", lib1.PublicId, credential.EgressPurpose),
},
},
wantErr: true,
wantErrCode: errors.InvalidParameter,
},
{
name: "ingress-credential-purpose",
args: args{
targetVersion: 1,
cls: []*target.CredentialLibrary{
target.TestNewCredentialLibrary("", lib1.PublicId, credential.IngressPurpose),
},
},
wantErr: true,
wantErrCode: errors.InvalidParameter,
},
{
name: "bad-version",
args: args{
targetVersion: 1000,
credLibIds: []string{lib1.PublicId},
cls: []*target.CredentialLibrary{
target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose),
},
},
wantErr: true,
wantErrCode: errors.VersionMismatch,
@@ -107,25 +168,25 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) {
assert, require := assert.New(t), require.New(t)
projTarget := tcp.TestTarget(t, conn, staticProj.PublicId, tt.name)
credLibs := make([]*target.CredentialLibrary, 0, len(tt.args.credLibIds))
for _, clid := range tt.args.credLibIds {
credLibs = append(credLibs, target.TestNewCredentialLibrary(projTarget.PublicId, clid, credential.ApplicationPurpose))
for _, cl := range tt.args.cls {
cl.TargetId = projTarget.PublicId
}
gotTarget, _, gotCredSources, err := repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, tt.args.targetVersion, credLibs)
gotTarget, _, gotCredSources, err := repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, tt.args.targetVersion, tt.args.cls)
if tt.wantErr {
require.Error(err)
assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "unexpected error %s", err.Error())
return
}
require.NoError(err)
assert.Len(gotCredSources, len(tt.wantCredLibIds))
gotCredSourcesMap := map[string]target.CredentialSource{}
for _, s := range gotCredSources {
gotCredSourcesMap[s.Id()] = s
}
for _, id := range tt.wantCredLibIds {
assert.NotEmpty(gotCredSourcesMap[id])
assert.Len(gotCredSources, len(tt.wantCredSources))
for _, cs := range gotCredSources {
w, ok := tt.wantCredSources[cs.Id()+"_"+string(cs.CredentialPurpose())]
assert.True(ok, "got unexpected credentialsource %v", cs)
assert.Equal(w.Id(), cs.Id())
assert.Equal(w.CredentialPurpose(), cs.CredentialPurpose())
}
// test to see of the target version update oplog was created
@@ -138,9 +199,13 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) {
assert.Equal(projTarget.GetVersion(), tar.GetVersion()-1)
assert.True(proto.Equal(gotTarget.(*tcp.Target), tar.(*tcp.Target)))
assert.Equal(gotCredSources, lookupCredSources)
for _, s := range lookupCredSources {
assert.NotEmpty(gotCredSourcesMap[s.Id()])
assert.Equal(projTarget.PublicId, s.TargetId())
for _, cs := range lookupCredSources {
w, ok := tt.wantCredSources[cs.Id()+"_"+string(cs.CredentialPurpose())]
assert.True(ok, "got unexpected credentialsource %v", cs)
assert.Equal(w.Id(), cs.Id())
assert.Equal(w.CredentialPurpose(), cs.CredentialPurpose())
assert.Equal(projTarget.PublicId, cs.TargetId())
}
})
}
@@ -402,7 +467,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) {
}
type args struct {
targetVersion uint32
clIds []string
cls []*target.CredentialLibrary
addToOrigLibs bool
}
tests := []struct {
@@ -418,7 +483,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) {
setup: setupFn,
args: args{
targetVersion: 2,
clIds: []string{},
cls: []*target.CredentialLibrary{},
},
wantErr: false,
wantAffectedRows: 10,
@@ -428,7 +493,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) {
setup: setupFn,
args: args{
targetVersion: 2,
clIds: []string{},
cls: []*target.CredentialLibrary{},
addToOrigLibs: true,
},
wantErr: false,
@@ -439,7 +504,10 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) {
setup: setupFn,
args: args{
targetVersion: 2,
clIds: []string{lib1.PublicId, lib2.PublicId},
cls: []*target.CredentialLibrary{
target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose),
target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose),
},
addToOrigLibs: true,
},
wantErr: false,
@@ -450,7 +518,10 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) {
setup: setupFn,
args: args{
targetVersion: 0,
clIds: []string{lib1.PublicId, lib2.PublicId},
cls: []*target.CredentialLibrary{
target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose),
target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose),
},
addToOrigLibs: true,
},
wantErr: true,
@@ -461,7 +532,10 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) {
setup: setupFn,
args: args{
targetVersion: 1000,
clIds: []string{lib1.PublicId, lib2.PublicId},
cls: []*target.CredentialLibrary{
target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose),
target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose),
},
addToOrigLibs: true,
},
wantErr: true,
@@ -472,12 +546,39 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) {
setup: setupFn,
args: args{
targetVersion: 2,
clIds: []string{lib1.PublicId, lib2.PublicId},
cls: []*target.CredentialLibrary{
target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose),
target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose),
},
addToOrigLibs: false,
},
wantErr: false,
wantAffectedRows: 12,
},
{
name: "egress-credential-purpose",
setup: setupFn,
args: args{
targetVersion: 1,
cls: []*target.CredentialLibrary{
target.TestNewCredentialLibrary("", lib1.PublicId, credential.EgressPurpose),
},
},
wantErr: true,
wantErrCode: errors.InvalidParameter,
},
{
name: "ingress-credential-purpose",
setup: setupFn,
args: args{
targetVersion: 1,
cls: []*target.CredentialLibrary{
target.TestNewCredentialLibrary("", lib1.PublicId, credential.IngressPurpose),
},
},
wantErr: true,
wantErrCode: errors.InvalidParameter,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -491,16 +592,19 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) {
origCredSources, origCredLibraries = tt.setup(tar)
}
cls := make([]*target.CredentialLibrary, 0, len(tt.args.clIds))
for _, clid := range tt.args.clIds {
cls = append(cls, target.TestNewCredentialLibrary(tar.GetPublicId(), clid, credential.ApplicationPurpose))
wantCredSources := make(map[string]target.CredentialSource)
for _, cl := range tt.args.cls {
cl.TargetId = tar.GetPublicId()
wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = &target.TargetLibrary{
CredentialLibrary: cl.CredentialLibrary,
}
}
var wantIds []string
wantIds = append(wantIds, tt.args.clIds...)
if tt.args.addToOrigLibs {
cls = append(cls, origCredLibraries...)
tt.args.cls = append(tt.args.cls, origCredLibraries...)
for _, cl := range origCredLibraries {
wantIds = append(wantIds, cl.CredentialLibraryId)
wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = &target.TargetLibrary{
CredentialLibrary: cl.CredentialLibrary,
}
}
}
@@ -508,7 +612,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) {
require.NoError(err)
assert.Equal(origCredSources, lookupCredSources)
_, got, affectedRows, err := repo.SetTargetCredentialSources(context.Background(), tar.GetPublicId(), tt.args.targetVersion, cls)
_, got, affectedRows, err := repo.SetTargetCredentialSources(context.Background(), tar.GetPublicId(), tt.args.targetVersion, tt.args.cls)
if tt.wantErr {
require.Error(err)
assert.Equal(0, affectedRows)
@@ -519,18 +623,14 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) {
require.NoError(err)
assert.Equal(tt.wantAffectedRows, affectedRows)
assert.Equal(len(wantIds), len(got))
sort.Strings(wantIds)
assert.Equal(len(wantCredSources), len(got))
var gotIds []string
if len(got) > 0 {
gotIds = make([]string, 0, len(got))
for _, s := range got {
gotIds = append(gotIds, s.Id())
}
for _, cs := range got {
w, ok := wantCredSources[cs.Id()+"_"+string(cs.CredentialPurpose())]
assert.True(ok, "got unexpected credentialsource %v", cs)
assert.Equal(w.Id(), cs.Id())
assert.Equal(w.CredentialPurpose(), cs.CredentialPurpose())
}
sort.Strings(gotIds)
assert.Equal(wantIds, gotIds)
foundTarget, _, _, err := repo.LookupTarget(context.Background(), tar.GetPublicId())
require.NoError(err)