mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08: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,
|
Scope: rbac.ScopeAll,
|
||||||
}.WithCachedASTValue()
|
}.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
|
// 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)
|
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{
|
var AsRemoveActor = rbac.Subject{
|
||||||
ID: "remove-actor",
|
ID: "remove-actor",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ func New(ctx context.Context, logger slog.Logger, db database.Store, vals *coder
|
|||||||
closed := make(chan struct{})
|
closed := make(chan struct{})
|
||||||
|
|
||||||
ctx, cancelFunc := context.WithCancel(ctx)
|
ctx, cancelFunc := context.WithCancel(ctx)
|
||||||
//nolint:gocritic // The system purges old db records without user input.
|
//nolint:gocritic // Use dbpurge-specific subject with minimal permissions.
|
||||||
ctx = dbauthz.AsSystemRestricted(ctx)
|
ctx = dbauthz.AsDBPurge(ctx)
|
||||||
|
|
||||||
iterationDuration := prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
iterationDuration := prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||||
Namespace: "coderd",
|
Namespace: "coderd",
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ import (
|
|||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
"cdr.dev/slog/sloggers/slogtest"
|
"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/coderdtest/promhelp"
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"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/dbgen"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbmock"
|
"github.com/coder/coder/v2/coderd/database/dbmock"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbpurge"
|
"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/dbtestutil"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||||
"github.com/coder/coder/v2/coderd/provisionerdserver"
|
"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/codersdk"
|
||||||
"github.com/coder/coder/v2/provisionerd/proto"
|
"github.com/coder/coder/v2/provisionerd/proto"
|
||||||
"github.com/coder/coder/v2/provisionersdk"
|
"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.
|
// ptr is a helper to create a pointer to a value.
|
||||||
func ptr[T any](v T) *T {
|
func ptr[T any](v T) *T {
|
||||||
return &v
|
return &v
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ var actorLogOrder = []rbac.SubjectType{
|
|||||||
rbac.SubjectTypeAutostart,
|
rbac.SubjectTypeAutostart,
|
||||||
rbac.SubjectTypeCryptoKeyReader,
|
rbac.SubjectTypeCryptoKeyReader,
|
||||||
rbac.SubjectTypeCryptoKeyRotator,
|
rbac.SubjectTypeCryptoKeyRotator,
|
||||||
|
rbac.SubjectTypeDBPurge,
|
||||||
rbac.SubjectTypeJobReaper,
|
rbac.SubjectTypeJobReaper,
|
||||||
rbac.SubjectTypeNotifier,
|
rbac.SubjectTypeNotifier,
|
||||||
rbac.SubjectTypePrebuildsOrchestrator,
|
rbac.SubjectTypePrebuildsOrchestrator,
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ const (
|
|||||||
SubjectTypeFileReader SubjectType = "file_reader"
|
SubjectTypeFileReader SubjectType = "file_reader"
|
||||||
SubjectTypeUsagePublisher SubjectType = "usage_publisher"
|
SubjectTypeUsagePublisher SubjectType = "usage_publisher"
|
||||||
SubjectAibridged SubjectType = "aibridged"
|
SubjectAibridged SubjectType = "aibridged"
|
||||||
|
SubjectTypeDBPurge SubjectType = "dbpurge"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
Reference in New Issue
Block a user