mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add boundary usage tracking database schema and tracker skeleton (#21670)
feat: add boundary usage telemetry database schema and RBAC
Adds the foundation for tracking boundary usage telemetry across Coder
replicas. This includes:
- Database schema: `boundary_usage_stats` table with per-replica stats
(unique workspaces, unique users, allowed/denied request counts)
- Database queries: upsert stats, get aggregated summary, reset stats,
delete by replica ID
- RBAC: `boundary_usage` resource type with read/update/delete actions,
accessible only via system `BoundaryUsageTracker` subject (not regular
user roles)
- Tracker skeleton + docs: stub implementation in `coderd/boundaryusage/`
The tracker accumulates stats in memory and periodically flushes to the
database. Stats are aggregated across replicas for telemetry reporting,
then reset when a new reporting period begins. The tracker implementation
and plumbing will be done in a subsequent commit/PR.
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -80,6 +80,7 @@ const (
|
||||
SubjectTypeUsagePublisher SubjectType = "usage_publisher"
|
||||
SubjectAibridged SubjectType = "aibridged"
|
||||
SubjectTypeDBPurge SubjectType = "dbpurge"
|
||||
SubjectTypeBoundaryUsageTracker SubjectType = "boundary_usage_tracker"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -63,6 +63,15 @@ var (
|
||||
Type: "audit_log",
|
||||
}
|
||||
|
||||
// ResourceBoundaryUsage
|
||||
// Valid Actions
|
||||
// - "ActionDelete" :: delete boundary usage statistics
|
||||
// - "ActionRead" :: read boundary usage statistics
|
||||
// - "ActionUpdate" :: upsert boundary usage statistics
|
||||
ResourceBoundaryUsage = Object{
|
||||
Type: "boundary_usage",
|
||||
}
|
||||
|
||||
// ResourceConnectionLog
|
||||
// Valid Actions
|
||||
// - "ActionRead" :: read connection logs
|
||||
@@ -417,6 +426,7 @@ func AllResources() []Objecter {
|
||||
ResourceAssignOrgRole,
|
||||
ResourceAssignRole,
|
||||
ResourceAuditLog,
|
||||
ResourceBoundaryUsage,
|
||||
ResourceConnectionLog,
|
||||
ResourceCryptoKey,
|
||||
ResourceDebugInfo,
|
||||
|
||||
@@ -380,4 +380,11 @@ var RBACPermissions = map[string]PermissionDefinition{
|
||||
ActionCreate: "create aibridge interceptions & related records",
|
||||
},
|
||||
},
|
||||
"boundary_usage": {
|
||||
Actions: map[Action]ActionDefinition{
|
||||
ActionRead: "read boundary usage statistics",
|
||||
ActionUpdate: "upsert boundary usage statistics",
|
||||
ActionDelete: "delete boundary usage statistics",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
// Workspace dormancy and workspace are omitted.
|
||||
// Workspace is specifically handled based on the opts.NoOwnerWorkspaceExec.
|
||||
// Owners cannot access other users' secrets.
|
||||
allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUserSecret, ResourceUsageEvent),
|
||||
allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUserSecret, ResourceUsageEvent, ResourceBoundaryUsage),
|
||||
// This adds back in the Workspace permissions.
|
||||
Permissions(map[string][]policy.Action{
|
||||
ResourceWorkspace.Type: ownerWorkspaceActions,
|
||||
@@ -309,7 +309,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
ResourceOauth2App.Type: {policy.ActionRead},
|
||||
ResourceWorkspaceProxy.Type: {policy.ActionRead},
|
||||
}),
|
||||
User: append(allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUser, ResourceOrganizationMember, ResourceOrganizationMember),
|
||||
User: append(allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUser, ResourceOrganizationMember, ResourceOrganizationMember, ResourceBoundaryUsage),
|
||||
Permissions(map[string][]policy.Action{
|
||||
// Users cannot do create/update/delete on themselves, but they
|
||||
// can read their own details.
|
||||
@@ -433,7 +433,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
ByOrgID: map[string]OrgPermissions{
|
||||
// Org admins should not have workspace exec perms.
|
||||
organizationID.String(): {
|
||||
Org: append(allPermsExcept(ResourceWorkspace, ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceAssignRole, ResourceUserSecret), Permissions(map[string][]policy.Action{
|
||||
Org: append(allPermsExcept(ResourceWorkspace, ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceAssignRole, ResourceUserSecret, ResourceBoundaryUsage), Permissions(map[string][]policy.Action{
|
||||
ResourceWorkspaceDormant.Type: {policy.ActionRead, policy.ActionDelete, policy.ActionCreate, policy.ActionUpdate, policy.ActionWorkspaceStop, policy.ActionCreateAgent, policy.ActionDeleteAgent},
|
||||
ResourceWorkspace.Type: slice.Omit(ResourceWorkspace.AvailableActions(), policy.ActionApplicationConnect, policy.ActionSSH),
|
||||
// PrebuiltWorkspaces are a subset of Workspaces.
|
||||
|
||||
@@ -1003,6 +1003,14 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "BoundaryUsage",
|
||||
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceBoundaryUsage,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
false: {owner, setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// We expect every permission to be tested above.
|
||||
|
||||
@@ -25,6 +25,9 @@ const (
|
||||
ScopeAssignRoleUnassign ScopeName = "assign_role:unassign"
|
||||
ScopeAuditLogCreate ScopeName = "audit_log:create"
|
||||
ScopeAuditLogRead ScopeName = "audit_log:read"
|
||||
ScopeBoundaryUsageDelete ScopeName = "boundary_usage:delete"
|
||||
ScopeBoundaryUsageRead ScopeName = "boundary_usage:read"
|
||||
ScopeBoundaryUsageUpdate ScopeName = "boundary_usage:update"
|
||||
ScopeConnectionLogRead ScopeName = "connection_log:read"
|
||||
ScopeConnectionLogUpdate ScopeName = "connection_log:update"
|
||||
ScopeCryptoKeyCreate ScopeName = "crypto_key:create"
|
||||
@@ -180,6 +183,9 @@ func (e ScopeName) Valid() bool {
|
||||
ScopeAssignRoleUnassign,
|
||||
ScopeAuditLogCreate,
|
||||
ScopeAuditLogRead,
|
||||
ScopeBoundaryUsageDelete,
|
||||
ScopeBoundaryUsageRead,
|
||||
ScopeBoundaryUsageUpdate,
|
||||
ScopeConnectionLogRead,
|
||||
ScopeConnectionLogUpdate,
|
||||
ScopeCryptoKeyCreate,
|
||||
@@ -336,6 +342,9 @@ func AllScopeNameValues() []ScopeName {
|
||||
ScopeAssignRoleUnassign,
|
||||
ScopeAuditLogCreate,
|
||||
ScopeAuditLogRead,
|
||||
ScopeBoundaryUsageDelete,
|
||||
ScopeBoundaryUsageRead,
|
||||
ScopeBoundaryUsageUpdate,
|
||||
ScopeConnectionLogRead,
|
||||
ScopeConnectionLogUpdate,
|
||||
ScopeCryptoKeyCreate,
|
||||
|
||||
Reference in New Issue
Block a user