mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat: add boundary_log rbac resource (#24810)
RFC: [Bridge ↔ Boundaries Correlation RFC](https://www.notion.so/coderhq/Gateway-and-Firewall-Correlation-RFC-31ad579be592803aa8b3d48348ccdde9) Register a dedicated `boundary_log` RBAC resource type with `create`, `read`, and `delete` actions, replacing the placeholder `rbac.ResourceAuditLog` and `rbac.ResourceSystem` references previously used in the dbauthz layer. Create is granted at user-level so workspace agents can only write logs owned by their workspace owner, preventing cross-workspace log fabrication. Delete is restricted to `DBPurge` only; no human role (including owner) can delete boundary logs. | Subject | Create (own) | Create (other) | Read (all) | Delete | |---|---|---|---|---| | Workspace agent | yes | no | no | no | | Owner (site admin) | yes (via member) | no | yes | no | | Auditor | no | no | yes | no | | DBPurge | no | no | no | yes | ### Changes - **RBAC policy & resource definition**: add `boundary_log` to `policy.go` and generate `ResourceBoundaryLog` object, scope constants, and codersdk/TypeScript types. - **dbauthz authorization**: replace all `ResourceAuditLog`/`ResourceSystem` placeholders with `ResourceBoundaryLog`. `InsertBoundaryLog` and `InsertBoundarySession` derive the workspace owner from the agent and authorize with `.WithOwner()` for user-scoped create. - **Role assignments:** - **Owner (site):** read only. Excluded from `allPermsExcept` wildcard; create is inherited from member at user-level. - **Member (user-level):** create. User-scoped so agents can only write logs they own. - **Auditor (site):** read. - `boundary_log` is excluded from org-admin, org-member, and org-service-account `allPermsExcept` calls for consistency with `ResourceBoundaryUsage`. - **System subjects:** - **DB Purge** (`SubjectTypeDBPurge`): delete. The only subject that can remove boundary logs. - **Workspace agent scope**: `ResourceBoundaryLog` with wildcard ID in the agent scope allow-list (necessary for creation since no pre-existing ID exists). User-level role scoping prevents deployment-wide access. - **DB migration** (`000510_boundary_log_scopes`): add `boundary_log:*`, `boundary_log:create`, `boundary_log:delete`, `boundary_log:read` enum values to `api_key_scope`. - **Test coverage**: `BoundaryLogCreate` (user-scoped, only matching owner succeeds), `BoundaryLogDelete` (all human roles denied), `BoundaryLogRead` (owner + auditor). dbauthz mock tests set up workspace agent lookups for owner derivation. - **Generated docs**: update OpenAPI specs, API reference docs, and frontend type definitions. --------- Co-authored-by: Muhammad Danish <mdanishkhdev@gmail.com> Co-authored-by: Coder Agents <coder-agents-review[bot]@users.noreply.github.com>
This commit is contained in:
@@ -458,6 +458,7 @@ func BoundarySession(t testing.TB, db database.Store, seed database.BoundarySess
|
||||
session, err := db.InsertBoundarySession(genCtx, database.InsertBoundarySessionParams{
|
||||
ID: takeFirst(seed.ID, uuid.New()),
|
||||
WorkspaceAgentID: takeFirst(seed.WorkspaceAgentID, uuid.New()),
|
||||
OwnerID: takeFirst(seed.OwnerID, uuid.NullUUID{UUID: uuid.New(), Valid: true}),
|
||||
ConfinedProcessName: takeFirst(seed.ConfinedProcessName, "claude-code"),
|
||||
StartedAt: takeFirst(seed.StartedAt, dbtime.Now()),
|
||||
UpdatedAt: takeFirst(seed.UpdatedAt, dbtime.Now()),
|
||||
@@ -466,20 +467,52 @@ func BoundarySession(t testing.TB, db database.Store, seed database.BoundarySess
|
||||
return session
|
||||
}
|
||||
|
||||
func BoundaryLog(t testing.TB, db database.Store, seed database.BoundaryLog) database.BoundaryLog {
|
||||
log, err := db.InsertBoundaryLog(genCtx, database.InsertBoundaryLogParams{
|
||||
ID: takeFirst(seed.ID, uuid.New()),
|
||||
SessionID: seed.SessionID,
|
||||
SequenceNumber: takeFirst(seed.SequenceNumber, 0),
|
||||
CapturedAt: takeFirst(seed.CapturedAt, dbtime.Now()),
|
||||
CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()),
|
||||
Proto: takeFirst(seed.Proto, "http"),
|
||||
Method: takeFirst(seed.Method, "GET"),
|
||||
Detail: takeFirst(seed.Detail, "https://example.com"),
|
||||
MatchedRule: seed.MatchedRule,
|
||||
func BoundaryLogs(t testing.TB, db database.Store, seed []database.BoundaryLog) []database.BoundaryLog {
|
||||
ids := make([]uuid.UUID, 0, len(seed))
|
||||
sessionID := seed[0].SessionID
|
||||
sequenceNumbers := make([]int32, 0, len(seed))
|
||||
capturedAt := make([]time.Time, 0, len(seed))
|
||||
createdAt := make([]time.Time, 0, len(seed))
|
||||
protos := make([]string, 0, len(seed))
|
||||
method := make([]string, 0, len(seed))
|
||||
detail := make([]string, 0, len(seed))
|
||||
matchedRule := make([]string, 0, len(seed))
|
||||
for _, log := range seed {
|
||||
log = takeFirstBoundaryLog(log)
|
||||
ids = append(ids, log.ID)
|
||||
sequenceNumbers = append(sequenceNumbers, log.SequenceNumber)
|
||||
capturedAt = append(capturedAt, log.CapturedAt)
|
||||
createdAt = append(createdAt, log.CreatedAt)
|
||||
protos = append(protos, log.Proto)
|
||||
method = append(method, log.Method)
|
||||
detail = append(detail, log.Detail)
|
||||
matchedRule = append(matchedRule, log.MatchedRule.String)
|
||||
}
|
||||
logs, err := db.InsertBoundaryLogs(genCtx, database.InsertBoundaryLogsParams{
|
||||
ID: ids,
|
||||
SessionID: sessionID,
|
||||
SequenceNumber: sequenceNumbers,
|
||||
CapturedAt: capturedAt,
|
||||
CreatedAt: createdAt,
|
||||
Proto: protos,
|
||||
Method: method,
|
||||
Detail: detail,
|
||||
MatchedRule: matchedRule,
|
||||
})
|
||||
require.NoError(t, err, "insert boundary log")
|
||||
return log
|
||||
require.NoError(t, err, "insert boundary logs")
|
||||
return logs
|
||||
}
|
||||
|
||||
func takeFirstBoundaryLog(seed database.BoundaryLog) database.BoundaryLog {
|
||||
seed.ID = takeFirst(seed.ID, uuid.New())
|
||||
seed.SessionID = takeFirst(seed.SessionID, uuid.New())
|
||||
seed.SequenceNumber = takeFirst(seed.SequenceNumber, 0)
|
||||
seed.CapturedAt = takeFirst(seed.CapturedAt, dbtime.Now())
|
||||
seed.CreatedAt = takeFirst(seed.CreatedAt, dbtime.Now())
|
||||
seed.Proto = takeFirst(seed.Proto, "http")
|
||||
seed.Method = takeFirst(seed.Method, "GET")
|
||||
seed.Detail = takeFirst(seed.Detail, "https://example.com")
|
||||
return seed
|
||||
}
|
||||
|
||||
func Template(t testing.TB, db database.Store, seed database.Template) database.Template {
|
||||
|
||||
Reference in New Issue
Block a user