Skip to content

refactor(dbauthz): add authz for system-level functions #6513

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,8 @@ func (api *API) CreateInMemoryProvisionerDaemon(ctx context.Context, debounce ti
}()

name := namesgenerator.GetRandomName(1)
daemon, err := api.Database.InsertProvisionerDaemon(ctx, database.InsertProvisionerDaemonParams{
// nolint:gocritic // Inserting a provisioner daemon is a system function.
daemon, err := api.Database.InsertProvisionerDaemon(dbauthz.AsSystemRestricted(ctx), database.InsertProvisionerDaemonParams{
ID: uuid.New(),
CreatedAt: database.Now(),
Name: name,
Expand Down
51 changes: 29 additions & 22 deletions coderd/database/dbauthz/dbauthz.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,17 +115,17 @@ func ActorFromContext(ctx context.Context) (rbac.Subject, bool) {
return a, ok
}

// AsProvisionerd returns a context with an actor that has permissions required
// for provisionerd to function.
func AsProvisionerd(ctx context.Context) context.Context {
return context.WithValue(ctx, authContextKey{}, rbac.Subject{
var (
subjectProvisionerd = rbac.Subject{
ID: uuid.Nil.String(),
Roles: rbac.Roles([]rbac.Role{
{
Name: "provisionerd",
DisplayName: "Provisioner Daemon",
Site: rbac.Permissions(map[string][]rbac.Action{
// TODO: Add ProvisionerJob resource type.
rbac.ResourceFile.Type: {rbac.ActionRead},
rbac.ResourceSystem.Type: {rbac.WildcardSymbol},
rbac.ResourceTemplate.Type: {rbac.ActionRead, rbac.ActionUpdate},
rbac.ResourceUser.Type: {rbac.ActionRead},
rbac.ResourceWorkspace.Type: {rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
Expand All @@ -135,20 +135,15 @@ func AsProvisionerd(ctx context.Context) context.Context {
},
}),
Scope: rbac.ScopeAll,
},
)
}

// AsAutostart returns a context with an actor that has permissions required
// for autostart to function.
func AsAutostart(ctx context.Context) context.Context {
return context.WithValue(ctx, authContextKey{}, rbac.Subject{
}
subjectAutostart = rbac.Subject{
ID: uuid.Nil.String(),
Roles: rbac.Roles([]rbac.Role{
{
Name: "autostart",
DisplayName: "Autostart Daemon",
Site: rbac.Permissions(map[string][]rbac.Action{
rbac.ResourceSystem.Type: {rbac.WildcardSymbol},
rbac.ResourceTemplate.Type: {rbac.ActionRead, rbac.ActionUpdate},
rbac.ResourceWorkspace.Type: {rbac.ActionRead, rbac.ActionUpdate},
}),
Expand All @@ -157,14 +152,8 @@ func AsAutostart(ctx context.Context) context.Context {
},
}),
Scope: rbac.ScopeAll,
},
)
}

// AsSystemRestricted returns a context with an actor that has permissions
// required for various system operations (login, logout, metrics cache).
func AsSystemRestricted(ctx context.Context) context.Context {
return context.WithValue(ctx, authContextKey{}, rbac.Subject{
}
subjectSystemRestricted = rbac.Subject{
ID: uuid.Nil.String(),
Roles: rbac.Roles([]rbac.Role{
{
Expand All @@ -175,6 +164,7 @@ func AsSystemRestricted(ctx context.Context) context.Context {
rbac.ResourceAPIKey.Type: {rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
rbac.ResourceGroup.Type: {rbac.ActionCreate, rbac.ActionUpdate},
rbac.ResourceRoleAssignment.Type: {rbac.ActionCreate},
rbac.ResourceSystem.Type: {rbac.WildcardSymbol},
rbac.ResourceOrganization.Type: {rbac.ActionCreate},
rbac.ResourceOrganizationMember.Type: {rbac.ActionCreate},
rbac.ResourceOrgRoleAssignment.Type: {rbac.ActionCreate},
Expand All @@ -187,8 +177,25 @@ func AsSystemRestricted(ctx context.Context) context.Context {
},
}),
Scope: rbac.ScopeAll,
},
)
}
)

// AsProvisionerd returns a context with an actor that has permissions required
// for provisionerd to function.
func AsProvisionerd(ctx context.Context) context.Context {
return context.WithValue(ctx, authContextKey{}, subjectProvisionerd)
}

// AsAutostart returns a context with an actor that has permissions required
// for autostart to function.
func AsAutostart(ctx context.Context) context.Context {
return context.WithValue(ctx, authContextKey{}, subjectAutostart)
}

// AsSystemRestricted returns a context with an actor that has permissions
// required for various system operations (login, logout, metrics cache).
func AsSystemRestricted(ctx context.Context) context.Context {
return context.WithValue(ctx, authContextKey{}, subjectSystemRestricted)
}

var AsRemoveActor = rbac.Subject{
Expand Down
12 changes: 12 additions & 0 deletions coderd/database/dbauthz/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,18 @@ func (q *querier) GetUserByID(ctx context.Context, id uuid.UUID) (database.User,
return fetch(q.log, q.auth, q.db.GetUserByID)(ctx, id)
}

// GetUsersByIDs is only used for usernames on workspace return data.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: Moved this to querier from system and set a simple authz check here. I can move it back to system but it's probably better to use rbac.ResourceUser here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair 👍.

// This function should be replaced by joining this data to the workspace query
// itself.
func (q *querier) GetUsersByIDs(ctx context.Context, ids []uuid.UUID) ([]database.User, error) {
for _, uid := range ids {
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceUser.WithID(uid)); err != nil {
return nil, err
}
}
return q.db.GetUsersByIDs(ctx, ids)
}

func (q *querier) GetAuthorizedUserCount(ctx context.Context, arg database.GetFilteredUserCountParams, prepared rbac.PreparedAuthorized) (int64, error) {
return q.db.GetAuthorizedUserCount(ctx, arg, prepared)
}
Expand Down
81 changes: 7 additions & 74 deletions coderd/database/dbauthz/querier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,11 +282,6 @@ func (s *MethodTestSuite) TestProvsionerJob() {
check.Args(database.UpdateProvisionerJobWithCancelByIDParams{ID: j.ID}).
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: most of the changes here are just moving the respective tests to system_test.go to keep things consistent.

Asserts(v.RBACObject(tpl), []rbac.Action{rbac.ActionRead, rbac.ActionUpdate}).Returns()
}))
s.Run("GetProvisionerJobsByIDs", s.Subtest(func(db database.Store, check *expects) {
a := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{})
b := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{})
check.Args([]uuid.UUID{a.ID, b.ID}).Asserts().Returns(slice.New(a, b))
}))
s.Run("GetProvisionerLogsByIDBetween", s.Subtest(func(db database.Store, check *expects) {
w := dbgen.Workspace(s.T(), db, database.Workspace{})
j := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{
Expand Down Expand Up @@ -619,22 +614,6 @@ func (s *MethodTestSuite) TestTemplate() {
})
check.Args(tv.ID).Asserts(t1, rbac.ActionRead).Returns(tv)
}))
s.Run("GetTemplateVersionsByIDs", s.Subtest(func(db database.Store, check *expects) {
t1 := dbgen.Template(s.T(), db, database.Template{})
t2 := dbgen.Template(s.T(), db, database.Template{})
tv1 := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
TemplateID: uuid.NullUUID{UUID: t1.ID, Valid: true},
})
tv2 := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
TemplateID: uuid.NullUUID{UUID: t2.ID, Valid: true},
})
tv3 := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
TemplateID: uuid.NullUUID{UUID: t2.ID, Valid: true},
})
check.Args([]uuid.UUID{tv1.ID, tv2.ID, tv3.ID}).
Asserts( /*t1, rbac.ActionRead, t2, rbac.ActionRead*/ ).
Returns(slice.New(tv1, tv2, tv3))
}))
s.Run("GetTemplateVersionsByTemplateID", s.Subtest(func(db database.Store, check *expects) {
t1 := dbgen.Template(s.T(), db, database.Template{})
a := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
Expand Down Expand Up @@ -784,6 +763,13 @@ func (s *MethodTestSuite) TestUser() {
u := dbgen.User(s.T(), db, database.User{})
check.Args(u.ID).Asserts(u, rbac.ActionRead).Returns(u)
}))
s.Run("GetUsersByIDs", s.Subtest(func(db database.Store, check *expects) {
a := dbgen.User(s.T(), db, database.User{CreatedAt: database.Now().Add(-time.Hour)})
b := dbgen.User(s.T(), db, database.User{CreatedAt: database.Now()})
check.Args([]uuid.UUID{a.ID, b.ID}).
Asserts(a, rbac.ActionRead, b, rbac.ActionRead).
Returns(slice.New(a, b))
}))
s.Run("GetAuthorizedUserCount", s.Subtest(func(db database.Store, check *expects) {
_ = dbgen.User(s.T(), db, database.User{})
check.Args(database.GetFilteredUserCountParams{}, emptyPreparedAuthorized{}).Asserts().Returns(int64(1))
Expand All @@ -803,13 +789,6 @@ func (s *MethodTestSuite) TestUser() {
b := dbgen.User(s.T(), db, database.User{CreatedAt: database.Now()})
check.Args(database.GetUsersParams{}).Asserts(a, rbac.ActionRead, b, rbac.ActionRead)
}))
s.Run("GetUsersByIDs", s.Subtest(func(db database.Store, check *expects) {
a := dbgen.User(s.T(), db, database.User{CreatedAt: database.Now().Add(-time.Hour)})
b := dbgen.User(s.T(), db, database.User{CreatedAt: database.Now()})
check.Args([]uuid.UUID{a.ID, b.ID}).
Asserts( /*a, rbac.ActionRead, b, rbac.ActionRead*/ ).
Returns(slice.New(a, b))
}))
s.Run("InsertUser", s.Subtest(func(db database.Store, check *expects) {
check.Args(database.InsertUserParams{
ID: uuid.New(),
Expand Down Expand Up @@ -977,14 +956,6 @@ func (s *MethodTestSuite) TestWorkspace() {
agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID})
check.Args(agt.AuthInstanceID.String).Asserts(ws, rbac.ActionRead).Returns(agt)
}))
s.Run("GetWorkspaceAgentsByResourceIDs", s.Subtest(func(db database.Store, check *expects) {
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID})
agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID})
check.Args([]uuid.UUID{res.ID}).Asserts( /*ws, rbac.ActionRead*/ ).
Returns([]database.WorkspaceAgent{agt})
}))
s.Run("UpdateWorkspaceAgentLifecycleStateByID", s.Subtest(func(db database.Store, check *expects) {
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
Expand Down Expand Up @@ -1026,23 +997,6 @@ func (s *MethodTestSuite) TestWorkspace() {

check.Args(agt.ID).Asserts(ws, rbac.ActionRead).Returns(slice.New(a, b))
}))
s.Run("GetWorkspaceAppsByAgentIDs", s.Subtest(func(db database.Store, check *expects) {
aWs := dbgen.Workspace(s.T(), db, database.Workspace{})
aBuild := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: aWs.ID, JobID: uuid.New()})
aRes := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: aBuild.JobID})
aAgt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: aRes.ID})
a := dbgen.WorkspaceApp(s.T(), db, database.WorkspaceApp{AgentID: aAgt.ID})

bWs := dbgen.Workspace(s.T(), db, database.Workspace{})
bBuild := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: bWs.ID, JobID: uuid.New()})
bRes := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: bBuild.JobID})
bAgt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: bRes.ID})
b := dbgen.WorkspaceApp(s.T(), db, database.WorkspaceApp{AgentID: bAgt.ID})

check.Args([]uuid.UUID{a.AgentID, b.AgentID}).
Asserts( /*aWs, rbac.ActionRead, bWs, rbac.ActionRead*/ ).
Returns([]database.WorkspaceApp{a, b})
}))
s.Run("GetWorkspaceBuildByID", s.Subtest(func(db database.Store, check *expects) {
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID})
Expand Down Expand Up @@ -1096,15 +1050,6 @@ func (s *MethodTestSuite) TestWorkspace() {
res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID})
check.Args(res.ID).Asserts(ws, rbac.ActionRead).Returns(res)
}))
s.Run("GetWorkspaceResourceMetadataByResourceIDs", s.Subtest(func(db database.Store, check *expects) {
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
_ = dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{ID: build.JobID, Type: database.ProvisionerJobTypeWorkspaceBuild})
a := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID})
b := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID})
check.Args([]uuid.UUID{a.ID, b.ID}).
Asserts( /*ws, []rbac.Action{rbac.ActionRead, rbac.ActionRead}*/ )
}))
s.Run("Build/GetWorkspaceResourcesByJobID", s.Subtest(func(db database.Store, check *expects) {
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
Expand All @@ -1117,18 +1062,6 @@ func (s *MethodTestSuite) TestWorkspace() {
job := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{ID: v.JobID, Type: database.ProvisionerJobTypeTemplateVersionImport})
check.Args(job.ID).Asserts(v.RBACObject(tpl), []rbac.Action{rbac.ActionRead, rbac.ActionRead}).Returns([]database.WorkspaceResource{})
}))
s.Run("GetWorkspaceResourcesByJobIDs", s.Subtest(func(db database.Store, check *expects) {
tpl := dbgen.Template(s.T(), db, database.Template{})
v := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{TemplateID: uuid.NullUUID{UUID: tpl.ID, Valid: true}, JobID: uuid.New()})
tJob := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{ID: v.JobID, Type: database.ProvisionerJobTypeTemplateVersionImport})

ws := dbgen.Workspace(s.T(), db, database.Workspace{})
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
wJob := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{ID: build.JobID, Type: database.ProvisionerJobTypeWorkspaceBuild})
check.Args([]uuid.UUID{tJob.ID, wJob.ID}).
Asserts( /*v.RBACObject(tpl), rbac.ActionRead, ws, rbac.ActionRead*/ ).
Returns([]database.WorkspaceResource{})
}))
s.Run("InsertWorkspace", s.Subtest(func(db database.Store, check *expects) {
u := dbgen.User(s.T(), db, database.User{})
o := dbgen.Organization(s.T(), db, database.Organization{})
Expand Down
Loading