chore: break down dbauthz.System into smaller roles (#6218)

- rbac: export rbac.Permissions
- dbauthz: move GetDeploymentDAUs, GetTemplateDAUs,
  GetTemplateAverageBuildTime from querier.go to system.go
  and removes auth checks
- dbauthz: remove AsSystem(), add individual roles for
  autostart, provisionerd, add restricted system role for 
  everything else
This commit is contained in:
Cian Johnston
2023-02-15 16:14:37 +00:00
committed by GitHub
parent 84da6056b2
commit f0f39b4892
25 changed files with 180 additions and 141 deletions

View File

@ -46,12 +46,12 @@ func logNotAuthorizedError(ctx context.Context, logger slog.Logger, err error) e
if err != nil && xerrors.As(err, &internalError) {
e := new(topdown.Error)
if xerrors.As(err, &e) || e.Code == topdown.CancelErr {
// For some reason rego changes a cancelled context to a topdown.CancelErr. We
// expect to check for cancelled context errors if the user cancels the request,
// For some reason rego changes a canceled context to a topdown.CancelErr. We
// expect to check for canceled context errors if the user cancels the request,
// so we should change the error to a context.Canceled error.
//
// NotAuthorizedError is == to sql.ErrNoRows, which is not correct
// if it's actually a cancelled context.
// if it's actually a canceled context.
internalError.SetInternal(context.Canceled)
return internalError
}
@ -117,29 +117,73 @@ func ActorFromContext(ctx context.Context) (rbac.Subject, bool) {
return a, ok
}
// AsSystem returns a context with a system actor. This is used for internal
// system operations that are not tied to any particular actor.
// When you use this function, be sure to add a //nolint comment
// explaining why it is necessary.
//
// We trust you have received the usual lecture from the local System
// Administrator. It usually boils down to these three things:
// #1) Respect the privacy of others.
// #2) Think before you type.
// #3) With great power comes great responsibility.
func AsSystem(ctx context.Context) context.Context {
// 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{
ID: uuid.Nil.String(),
Roles: rbac.Roles([]rbac.Role{
{
Name: "provisionerd",
DisplayName: "Provisioner Daemon",
Site: rbac.Permissions(map[string][]rbac.Action{
rbac.ResourceFile.Type: {rbac.ActionRead},
rbac.ResourceTemplate.Type: {rbac.ActionRead, rbac.ActionUpdate},
rbac.ResourceUser.Type: {rbac.ActionRead},
rbac.ResourceWorkspace.Type: {rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
}),
Org: map[string][]rbac.Permission{},
User: []rbac.Permission{},
},
}),
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{
ID: uuid.Nil.String(),
Roles: rbac.Roles([]rbac.Role{
{
Name: "autostart",
DisplayName: "Autostart Daemon",
Site: rbac.Permissions(map[string][]rbac.Action{
rbac.ResourceTemplate.Type: {rbac.ActionRead, rbac.ActionUpdate},
rbac.ResourceWorkspace.Type: {rbac.ActionRead, rbac.ActionUpdate},
}),
Org: map[string][]rbac.Permission{},
User: []rbac.Permission{},
},
}),
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{
ID: uuid.Nil.String(),
Roles: rbac.Roles([]rbac.Role{
{
Name: "system",
DisplayName: "System",
Site: []rbac.Permission{
{
ResourceType: rbac.ResourceWildcard.Type,
Action: rbac.WildcardSymbol,
},
},
DisplayName: "Coder",
Site: rbac.Permissions(map[string][]rbac.Action{
rbac.ResourceWildcard.Type: {rbac.ActionRead},
rbac.ResourceAPIKey.Type: {rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
rbac.ResourceGroup.Type: {rbac.ActionCreate, rbac.ActionUpdate},
rbac.ResourceRoleAssignment.Type: {rbac.ActionCreate},
rbac.ResourceOrganization.Type: {rbac.ActionCreate},
rbac.ResourceOrganizationMember.Type: {rbac.ActionCreate},
rbac.ResourceOrgRoleAssignment.Type: {rbac.ActionCreate},
rbac.ResourceUser.Type: {rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
rbac.ResourceUserData.Type: {rbac.ActionCreate, rbac.ActionUpdate},
rbac.ResourceWorkspace.Type: {rbac.ActionUpdate},
}),
Org: map[string][]rbac.Permission{},
User: []rbac.Permission{},
},

View File

@ -327,13 +327,6 @@ func (q *querier) GetProvisionerDaemons(ctx context.Context) ([]database.Provisi
return fetchWithPostFilter(q.auth, fetch)(ctx, nil)
}
func (q *querier) GetDeploymentDAUs(ctx context.Context) ([]database.GetDeploymentDAUsRow, error) {
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceUser.All()); err != nil {
return nil, err
}
return q.db.GetDeploymentDAUs(ctx)
}
func (q *querier) GetGroupsByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]database.Group, error) {
return fetchWithPostFilter(q.auth, q.db.GetGroupsByOrganizationID)(ctx, organizationID)
}
@ -622,16 +615,6 @@ func (q *querier) GetPreviousTemplateVersion(ctx context.Context, arg database.G
return q.db.GetPreviousTemplateVersion(ctx, arg)
}
func (q *querier) GetTemplateAverageBuildTime(ctx context.Context, arg database.GetTemplateAverageBuildTimeParams) (database.GetTemplateAverageBuildTimeRow, error) {
// An actor can read the average build time if they can read the related template.
// It doesn't make any sense to get the average build time for a template that doesn't
// exist, so omitting this check here.
if _, err := q.GetTemplateByID(ctx, arg.TemplateID.UUID); err != nil {
return database.GetTemplateAverageBuildTimeRow{}, err
}
return q.db.GetTemplateAverageBuildTime(ctx, arg)
}
func (q *querier) GetTemplateByID(ctx context.Context, id uuid.UUID) (database.Template, error) {
return fetch(q.log, q.auth, q.db.GetTemplateByID)(ctx, id)
}
@ -640,15 +623,6 @@ func (q *querier) GetTemplateByOrganizationAndName(ctx context.Context, arg data
return fetch(q.log, q.auth, q.db.GetTemplateByOrganizationAndName)(ctx, arg)
}
func (q *querier) GetTemplateDAUs(ctx context.Context, templateID uuid.UUID) ([]database.GetTemplateDAUsRow, error) {
// An actor can read the DAUs if they can read the related template.
// Again, it doesn't make sense to get DAUs for a template that doesn't exist.
if _, err := q.GetTemplateByID(ctx, templateID); err != nil {
return nil, err
}
return q.db.GetTemplateDAUs(ctx, templateID)
}
func (q *querier) GetTemplateVersionByID(ctx context.Context, tvid uuid.UUID) (database.TemplateVersion, error) {
tv, err := q.db.GetTemplateVersionByID(ctx, tvid)
if err != nil {

View File

@ -540,12 +540,6 @@ func (s *MethodTestSuite) TestTemplate() {
TemplateID: uuid.NullUUID{UUID: t1.ID, Valid: true},
}).Asserts(t1, rbac.ActionRead).Returns(b)
}))
s.Run("GetTemplateAverageBuildTime", s.Subtest(func(db database.Store, check *expects) {
t1 := dbgen.Template(s.T(), db, database.Template{})
check.Args(database.GetTemplateAverageBuildTimeParams{
TemplateID: uuid.NullUUID{UUID: t1.ID, Valid: true},
}).Asserts(t1, rbac.ActionRead)
}))
s.Run("GetTemplateByID", s.Subtest(func(db database.Store, check *expects) {
t1 := dbgen.Template(s.T(), db, database.Template{})
check.Args(t1.ID).Asserts(t1, rbac.ActionRead).Returns(t1)
@ -560,10 +554,6 @@ func (s *MethodTestSuite) TestTemplate() {
OrganizationID: o1.ID,
}).Asserts(t1, rbac.ActionRead).Returns(t1)
}))
s.Run("GetTemplateDAUs", s.Subtest(func(db database.Store, check *expects) {
t1 := dbgen.Template(s.T(), db, database.Template{})
check.Args(t1.ID).Asserts(t1, rbac.ActionRead)
}))
s.Run("GetTemplateVersionByJobID", s.Subtest(func(db database.Store, check *expects) {
t1 := dbgen.Template(s.T(), db, database.Template{})
tv := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
@ -1220,7 +1210,4 @@ func (s *MethodTestSuite) TestExtraMethods() {
s.NoError(err, "insert provisioner daemon")
check.Args().Asserts(d, rbac.ActionRead)
}))
s.Run("GetDeploymentDAUs", s.Subtest(func(db database.Store, check *expects) {
check.Args().Asserts(rbac.ResourceUser.All(), rbac.ActionRead)
}))
}

View File

@ -226,8 +226,8 @@ func (s *MethodTestSuite) NotAuthorizedErrorTest(ctx context.Context, az *coderd
}
})
s.Run("Cancelled", func() {
// Pass in a cancelled context
s.Run("Canceled", func() {
// Pass in a canceled context
ctx, cancel := context.WithCancel(ctx)
cancel()
az.AlwaysReturn = rbac.ForbiddenWithInternal(&topdown.Error{Code: topdown.CancelErr},

View File

@ -96,6 +96,21 @@ func (q *querier) GetTemplates(ctx context.Context) ([]database.Template, error)
return q.db.GetTemplates(ctx)
}
// Only used by metrics cache.
func (q *querier) GetTemplateAverageBuildTime(ctx context.Context, arg database.GetTemplateAverageBuildTimeParams) (database.GetTemplateAverageBuildTimeRow, error) {
return q.db.GetTemplateAverageBuildTime(ctx, arg)
}
// Only used by metrics cache.
func (q *querier) GetTemplateDAUs(ctx context.Context, templateID uuid.UUID) ([]database.GetTemplateDAUsRow, error) {
return q.db.GetTemplateDAUs(ctx, templateID)
}
// Only used by metrics cache.
func (q *querier) GetDeploymentDAUs(ctx context.Context) ([]database.GetDeploymentDAUsRow, error) {
return q.db.GetDeploymentDAUs(ctx)
}
// UpdateWorkspaceBuildCostByID is used by the provisioning system to update the cost of a workspace build.
func (q *querier) UpdateWorkspaceBuildCostByID(ctx context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) (database.WorkspaceBuild, error) {
return q.db.UpdateWorkspaceBuildCostByID(ctx, arg)