mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat: add rbac specificity for dbpurge (#21088)
Related to [`internal#1139`](https://github.com/coder/internal/issues/1139) Continuation of #21074 This implements some RBAC role specificity for `dbpurge`, ensuring that we follow the least-privileged model for removing data from the database. It is specified as following. ```go Site: rbac.Permissions(map[string][]policy.Action{ // DeleteOldWorkspaceAgentLogs // DeleteOldWorkspaceAgentStats // DeleteOldProvisionerDaemons // DeleteOldTelemetryLocks // DeleteOldAuditLogConnectionEvents // DeleteOldConnectionLogs rbac.ResourceSystem.Type: {policy.ActionDelete}, // DeleteOldNotificationMessages rbac.ResourceNotificationMessage.Type: {policy.ActionDelete}, // ExpirePrebuildsAPIKeys // DeleteExpiredAPIKeys rbac.ResourceApiKey.Type: {policy.ActionDelete}, // DeleteOldAIBridgeRecords rbac.ResourceAibridgeInterception.Type: {policy.ActionDelete}, }), ``` | Position | Pull-request | | -------- | ------------ | | | [feat: add prometheus observability metrics for `dbpurge`](https://github.com/coder/coder/pull/21074) | | ✅ | [feat: add rbac specificity for `dbpurge`](https://github.com/coder/coder/pull/21088) |
This commit is contained in:
@@ -616,6 +616,27 @@ var (
|
||||
}),
|
||||
Scope: rbac.ScopeAll,
|
||||
}.WithCachedASTValue()
|
||||
|
||||
subjectDBPurge = rbac.Subject{
|
||||
Type: rbac.SubjectTypeDBPurge,
|
||||
FriendlyName: "DB Purge",
|
||||
ID: uuid.Nil.String(),
|
||||
Roles: rbac.Roles([]rbac.Role{
|
||||
{
|
||||
Identifier: rbac.RoleIdentifier{Name: "dbpurge"},
|
||||
DisplayName: "DB Purge Daemon",
|
||||
Site: rbac.Permissions(map[string][]policy.Action{
|
||||
rbac.ResourceSystem.Type: {policy.ActionDelete},
|
||||
rbac.ResourceNotificationMessage.Type: {policy.ActionDelete},
|
||||
rbac.ResourceApiKey.Type: {policy.ActionDelete},
|
||||
rbac.ResourceAibridgeInterception.Type: {policy.ActionDelete},
|
||||
}),
|
||||
User: []rbac.Permission{},
|
||||
ByOrgID: map[string]rbac.OrgPermissions{},
|
||||
},
|
||||
}),
|
||||
Scope: rbac.ScopeAll,
|
||||
}.WithCachedASTValue()
|
||||
)
|
||||
|
||||
// AsProvisionerd returns a context with an actor that has permissions required
|
||||
@@ -710,6 +731,12 @@ func AsAIBridged(ctx context.Context) context.Context {
|
||||
return As(ctx, subjectAibridged)
|
||||
}
|
||||
|
||||
// AsDBPurge returns a context with an actor that has permissions required
|
||||
// for dbpurge to delete old database records.
|
||||
func AsDBPurge(ctx context.Context) context.Context {
|
||||
return As(ctx, subjectDBPurge)
|
||||
}
|
||||
|
||||
var AsRemoveActor = rbac.Subject{
|
||||
ID: "remove-actor",
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ func New(ctx context.Context, logger slog.Logger, db database.Store, vals *coder
|
||||
closed := make(chan struct{})
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(ctx)
|
||||
//nolint:gocritic // The system purges old db records without user input.
|
||||
ctx = dbauthz.AsSystemRestricted(ctx)
|
||||
//nolint:gocritic // Use dbpurge-specific subject with minimal permissions.
|
||||
ctx = dbauthz.AsDBPurge(ctx)
|
||||
|
||||
iterationDuration := prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: "coderd",
|
||||
|
||||
@@ -22,8 +22,10 @@ import (
|
||||
"cdr.dev/slog"
|
||||
"cdr.dev/slog/sloggers/slogtest"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/coderdtest/promhelp"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||
"github.com/coder/coder/v2/coderd/database/dbmock"
|
||||
"github.com/coder/coder/v2/coderd/database/dbpurge"
|
||||
@@ -31,6 +33,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/provisionerdserver"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/provisionerd/proto"
|
||||
"github.com/coder/coder/v2/provisionersdk"
|
||||
@@ -1631,6 +1634,62 @@ func TestDeleteExpiredAPIKeys(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBPurgeAuthorization(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("DBPurgeActorCanCallPurgeOperations", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
rawDB, _ := dbtestutil.NewDB(t)
|
||||
|
||||
authz := rbac.NewAuthorizer(prometheus.NewRegistry())
|
||||
db := dbauthz.New(rawDB, authz, testutil.Logger(t), coderdtest.AccessControlStorePointer())
|
||||
|
||||
ctx = dbauthz.AsDBPurge(ctx)
|
||||
|
||||
actor, ok := dbauthz.ActorFromContext(ctx)
|
||||
require.True(t, ok, "actor should be present")
|
||||
require.Equal(t, rbac.SubjectTypeDBPurge, actor.Type, "should be DBPurge type")
|
||||
require.Contains(t, actor.Roles.Names(), rbac.RoleIdentifier{Name: "dbpurge"},
|
||||
"should have dbpurge role")
|
||||
|
||||
_, err := db.DeleteOldWorkspaceAgentLogs(ctx, time.Now().Add(-24*time.Hour))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.DeleteOldWorkspaceAgentStats(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.DeleteOldProvisionerDaemons(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.DeleteOldNotificationMessages(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.ExpirePrebuildsAPIKeys(ctx, time.Now().Add(-24*time.Hour))
|
||||
require.NoError(t, err)
|
||||
|
||||
params := database.DeleteExpiredAPIKeysParams{
|
||||
Before: time.Now().Add(-24 * time.Hour),
|
||||
LimitCount: 100,
|
||||
}
|
||||
_, err = db.DeleteExpiredAPIKeys(ctx, params)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.DeleteOldAuditLogConnectionEvents(ctx, database.DeleteOldAuditLogConnectionEventsParams{
|
||||
BeforeTime: time.Now().Add(-24 * time.Hour),
|
||||
LimitCount: 100,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = db.DeleteOldAuditLogs(ctx, database.DeleteOldAuditLogsParams{
|
||||
BeforeTime: time.Now().Add(-24 * time.Hour),
|
||||
LimitCount: 100,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
// ptr is a helper to create a pointer to a value.
|
||||
func ptr[T any](v T) *T {
|
||||
return &v
|
||||
|
||||
@@ -50,6 +50,7 @@ var actorLogOrder = []rbac.SubjectType{
|
||||
rbac.SubjectTypeAutostart,
|
||||
rbac.SubjectTypeCryptoKeyReader,
|
||||
rbac.SubjectTypeCryptoKeyRotator,
|
||||
rbac.SubjectTypeDBPurge,
|
||||
rbac.SubjectTypeJobReaper,
|
||||
rbac.SubjectTypeNotifier,
|
||||
rbac.SubjectTypePrebuildsOrchestrator,
|
||||
|
||||
@@ -79,6 +79,7 @@ const (
|
||||
SubjectTypeFileReader SubjectType = "file_reader"
|
||||
SubjectTypeUsagePublisher SubjectType = "usage_publisher"
|
||||
SubjectAibridged SubjectType = "aibridged"
|
||||
SubjectTypeDBPurge SubjectType = "dbpurge"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
Reference in New Issue
Block a user