diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index 500b2d709..b4427bcb8 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -333,8 +333,8 @@ func New(ctx context.Context, conf *Config) (*Controller, error) { c.PasswordAuthRepoFn = func() (*password.Repository, error) { return password.NewRepository(dbase, dbase, c.kms) } - c.TargetRepoFn = func() (*target.Repository, error) { - return target.NewRepository(dbase, dbase, c.kms) + c.TargetRepoFn = func(o ...target.Option) (*target.Repository, error) { + return target.NewRepository(dbase, dbase, c.kms, o...) } c.SessionRepoFn = func(opt ...session.Option) (*session.Repository, error) { return session.NewRepository(ctx, dbase, dbase, c.kms, opt...) diff --git a/internal/daemon/controller/handlers/targets/tcp/target_service_test.go b/internal/daemon/controller/handlers/targets/tcp/target_service_test.go index bbbbf1c29..98b5c50c5 100644 --- a/internal/daemon/controller/handlers/targets/tcp/target_service_test.go +++ b/internal/daemon/controller/handlers/targets/tcp/target_service_test.go @@ -76,8 +76,8 @@ var testAuthorizedActions = []string{ func testService(t *testing.T, ctx context.Context, conn *db.DB, kms *kms.Kms, wrapper wrapping.Wrapper) (targets.Service, error) { rw := db.New(conn) sche := scheduler.TestScheduler(t, conn, wrapper) - repoFn := func() (*target.Repository, error) { - return target.NewRepository(rw, rw, kms) + repoFn := func(o ...target.Option) (*target.Repository, error) { + return target.NewRepository(rw, rw, kms, o...) } iamRepoFn := func() (*iam.Repository, error) { return iam.TestRepo(t, conn, wrapper), nil @@ -710,7 +710,7 @@ func TestUpdate(t *testing.T) { _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "id=*;type=*;actions=*") - repoFn := func() (*target.Repository, error) { + repoFn := func(o ...target.Option) (*target.Repository, error) { return target.NewRepository(rw, rw, kms) } repo, err := repoFn() @@ -1134,7 +1134,7 @@ func TestUpdate_BadVersion(t *testing.T) { _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "id=*;type=*;actions=*") - repoFn := func() (*target.Repository, error) { + repoFn := func(o ...target.Option) (*target.Repository, error) { return target.NewRepository(rw, rw, kms) } repo, err := repoFn() @@ -2739,7 +2739,7 @@ func TestAuthorizeSession(t *testing.T) { err := vault.RegisterJobs(context.Background(), sche, rw, rw, kms) require.NoError(t, err) - repoFn := func() (*target.Repository, error) { + repoFn := func(o ...target.Option) (*target.Repository, error) { return target.NewRepository(rw, rw, kms) } iamRepo := iam.TestRepo(t, conn, wrapper) @@ -2999,7 +2999,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { err := vault.RegisterJobs(context.Background(), sche, rw, rw, kms) require.NoError(t, err) - repoFn := func() (*target.Repository, error) { + repoFn := func(o ...target.Option) (*target.Repository, error) { return target.NewRepository(rw, rw, kms) } iamRepo := iam.TestRepo(t, conn, wrapper) @@ -3578,7 +3578,7 @@ func TestAuthorizeSession_Errors(t *testing.T) { err := vault.RegisterJobs(context.Background(), sche, rw, rw, kms) require.NoError(t, err) - repoFn := func() (*target.Repository, error) { + repoFn := func(o ...target.Option) (*target.Repository, error) { return target.NewRepository(rw, rw, kms) } iamRepo := iam.TestRepo(t, conn, wrapper) diff --git a/internal/target/options.go b/internal/target/options.go index cdfb6dd8c..18c1c8c08 100644 --- a/internal/target/options.go +++ b/internal/target/options.go @@ -3,6 +3,7 @@ package target import ( "time" + "github.com/hashicorp/boundary/internal/perms" "github.com/hashicorp/boundary/internal/types/subtypes" ) @@ -34,6 +35,7 @@ type options struct { WithStaticCredentials []*StaticCredential WithSessionMaxSeconds uint32 WithSessionConnectionLimit int32 + WithPermissions []perms.Permission WithPublicId string WithWorkerFilter string WithTargetIds []string @@ -55,6 +57,7 @@ func getDefaultOptions() options { WithStaticCredentials: nil, WithSessionMaxSeconds: uint32((8 * time.Hour).Seconds()), WithSessionConnectionLimit: -1, + WithPermissions: nil, WithPublicId: "", WithWorkerFilter: "", } @@ -178,3 +181,11 @@ func WithTargetIds(with []string) Option { o.WithTargetIds = with } } + +// WithPermissions is used by this repo to restrict a list +// request's results based on the given set of permissions. +func WithPermissions(perms []perms.Permission) Option { + return func(o *options) { + o.WithPermissions = perms + } +} diff --git a/internal/target/options_test.go b/internal/target/options_test.go index d88d8d6b1..c95c68443 100644 --- a/internal/target/options_test.go +++ b/internal/target/options_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/perms" "github.com/hashicorp/boundary/internal/target/store" "github.com/hashicorp/boundary/internal/types/subtypes" "github.com/stretchr/testify/assert" @@ -108,6 +109,13 @@ func Test_GetOpts(t *testing.T) { testOpts.WithWorkerFilter = `"/foo" == "bar"` assert.Equal(opts, testOpts) }) + t.Run("WithPermissions", func(t *testing.T) { + assert := assert.New(t) + opts := GetOpts(WithPermissions([]perms.Permission{{ScopeId: "test1"}, {ScopeId: "test2"}})) + testOpts := getDefaultOptions() + testOpts.WithPermissions = []perms.Permission{{ScopeId: "test1"}, {ScopeId: "test2"}} + assert.Equal(opts, testOpts) + }) t.Run("WithCredentialLibraries", func(t *testing.T) { assert := assert.New(t) opts := GetOpts(WithCredentialLibraries([]*CredentialLibrary{ diff --git a/internal/target/repository.go b/internal/target/repository.go index 874b9de89..07594e92d 100644 --- a/internal/target/repository.go +++ b/internal/target/repository.go @@ -11,13 +11,15 @@ import ( "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/perms" + "github.com/hashicorp/boundary/internal/types/resource" "github.com/hashicorp/boundary/internal/types/scope" "github.com/hashicorp/go-dbw" ) // RepositoryFactory enables `target.Repository` object instantiation, // and is used by the various service packages/controller object to do so. -type RepositoryFactory func() (*Repository, error) +type RepositoryFactory func(...Option) (*Repository, error) // Cloneable provides a cloning interface type Cloneable interface { @@ -32,10 +34,19 @@ type Repository struct { // defaultLimit provides a default for limiting the number of results returned from the repo defaultLimit int + + // permissions provides a set of user permissions - these directly correlate to what the user + // has access to in terms of actions and resources and we use it to build queries. + // These are passed in on the repository constructor using `WithPermissions`, meaning the + // `Repository` object is contextualized to whatever the request context is. + permissions []perms.Permission } -// NewRepository creates a new target Repository. Supports the options: WithLimit -// which sets a default limit on results returned by repo operations. +// NewRepository creates a new target Repository. +// Supports the following options: +// - WithLimit: sets a limit on the number of results returned by various repo operations. +// - WithPermissions: defines the permissions the user has to perform different +// actions and access resources within the created repo object. func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repository, error) { const op = "target.NewRepository" if r == nil { @@ -47,16 +58,25 @@ func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repo if kms == nil { return nil, errors.NewDeprecated(errors.InvalidParameter, op, "nil kms") } + opts := GetOpts(opt...) if opts.WithLimit == 0 { // zero signals the boundary defaults should be used. opts.WithLimit = db.DefaultLimit } + + for _, p := range opts.WithPermissions { + if p.Resource != resource.Target { + return nil, errors.New(context.Background(), errors.InvalidParameter, op, "permission for incorrect resource found") + } + } + return &Repository{ reader: r, writer: w, kms: kms, defaultLimit: opts.WithLimit, + permissions: opts.WithPermissions, }, nil } diff --git a/internal/target/repository_test.go b/internal/target/repository_test.go index 6187421a3..e26b06c4f 100644 --- a/internal/target/repository_test.go +++ b/internal/target/repository_test.go @@ -5,6 +5,8 @@ import ( "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/perms" + "github.com/hashicorp/boundary/internal/types/resource" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -16,9 +18,10 @@ func TestNewRepository(t *testing.T) { wrapper := db.TestWrapper(t) testKms := kms.TestKms(t, conn, wrapper) type args struct { - r db.Reader - w db.Writer - kms *kms.Kms + r db.Reader + w db.Writer + kms *kms.Kms + opts []Option } tests := []struct { name string @@ -75,11 +78,53 @@ func TestNewRepository(t *testing.T) { wantErr: true, wantErrString: "target.NewRepository: nil reader: parameter violation: error #100", }, + { + name: "WithPermissions sets object to `permissions`", + args: args{ + r: rw, + w: rw, + kms: testKms, + opts: []Option{ + WithPermissions([]perms.Permission{ + {ScopeId: "test1", Resource: resource.Target}, + {ScopeId: "test2", Resource: resource.Target}, + }), + }, + }, + want: &Repository{ + reader: rw, + writer: rw, + kms: testKms, + defaultLimit: db.DefaultLimit, + permissions: []perms.Permission{ + {ScopeId: "test1", Resource: resource.Target}, + {ScopeId: "test2", Resource: resource.Target}, + }, + }, + wantErr: false, + }, + { + name: "Don't accept permissions that aren't for the Target resource", + args: args{ + r: rw, + w: rw, + kms: testKms, + opts: []Option{ + WithPermissions([]perms.Permission{ + {ScopeId: "test1", Resource: resource.Target}, + {ScopeId: "test2", Resource: resource.Host}, + }), + }, + }, + want: nil, + wantErr: true, + wantErrString: "target.NewRepository: permission for incorrect resource found: parameter violation: error #100", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - got, err := NewRepository(tt.args.r, tt.args.w, tt.args.kms) + got, err := NewRepository(tt.args.r, tt.args.w, tt.args.kms, tt.args.opts...) if tt.wantErr { require.Error(err) assert.Equal(tt.wantErrString, err.Error())