mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
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:
@ -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{},
|
||||
},
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}))
|
||||
}
|
||||
|
@ -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},
|
||||
|
@ -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)
|
||||
|
Reference in New Issue
Block a user