mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08: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:
co-authored by
Muhammad Danish
Coder Agents
parent
88060b846e
commit
a586b7e5e0
Generated
+76
-53
@@ -3627,7 +3627,7 @@ func (q *sqlQuerier) GetBoundaryLogByID(ctx context.Context, id uuid.UUID) (Boun
|
||||
}
|
||||
|
||||
const getBoundarySessionByID = `-- name: GetBoundarySessionByID :one
|
||||
SELECT id, workspace_agent_id, confined_process_name, started_at, updated_at FROM boundary_sessions WHERE id = $1
|
||||
SELECT id, workspace_agent_id, confined_process_name, started_at, updated_at, owner_id FROM boundary_sessions WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetBoundarySessionByID(ctx context.Context, id uuid.UUID) (BoundarySession, error) {
|
||||
@@ -3639,11 +3639,12 @@ func (q *sqlQuerier) GetBoundarySessionByID(ctx context.Context, id uuid.UUID) (
|
||||
&i.ConfinedProcessName,
|
||||
&i.StartedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OwnerID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertBoundaryLog = `-- name: InsertBoundaryLog :one
|
||||
const insertBoundaryLogs = `-- name: InsertBoundaryLogs :many
|
||||
INSERT INTO boundary_logs (
|
||||
id,
|
||||
session_id,
|
||||
@@ -3654,62 +3655,80 @@ INSERT INTO boundary_logs (
|
||||
method,
|
||||
detail,
|
||||
matched_rule
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
$7,
|
||||
$8,
|
||||
$9
|
||||
) RETURNING id, session_id, sequence_number, captured_at, created_at, proto, method, detail, matched_rule
|
||||
)
|
||||
SELECT
|
||||
unnest($1 :: uuid[]),
|
||||
$2 :: uuid,
|
||||
unnest($3 :: int[]),
|
||||
unnest($4 :: timestamptz[]),
|
||||
unnest($5 :: timestamptz[]),
|
||||
unnest($6 :: text[]),
|
||||
unnest($7 :: text[]),
|
||||
unnest($8 :: text[]),
|
||||
unnest($9 :: text[])
|
||||
RETURNING id, session_id, sequence_number, captured_at, created_at, proto, method, detail, matched_rule
|
||||
`
|
||||
|
||||
type InsertBoundaryLogParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
SessionID uuid.UUID `db:"session_id" json:"session_id"`
|
||||
SequenceNumber int32 `db:"sequence_number" json:"sequence_number"`
|
||||
CapturedAt time.Time `db:"captured_at" json:"captured_at"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
Proto string `db:"proto" json:"proto"`
|
||||
Method string `db:"method" json:"method"`
|
||||
Detail string `db:"detail" json:"detail"`
|
||||
MatchedRule sql.NullString `db:"matched_rule" json:"matched_rule"`
|
||||
type InsertBoundaryLogsParams struct {
|
||||
ID []uuid.UUID `db:"id" json:"id"`
|
||||
SessionID uuid.UUID `db:"session_id" json:"session_id"`
|
||||
SequenceNumber []int32 `db:"sequence_number" json:"sequence_number"`
|
||||
CapturedAt []time.Time `db:"captured_at" json:"captured_at"`
|
||||
CreatedAt []time.Time `db:"created_at" json:"created_at"`
|
||||
Proto []string `db:"proto" json:"proto"`
|
||||
Method []string `db:"method" json:"method"`
|
||||
Detail []string `db:"detail" json:"detail"`
|
||||
MatchedRule []string `db:"matched_rule" json:"matched_rule"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertBoundaryLog(ctx context.Context, arg InsertBoundaryLogParams) (BoundaryLog, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertBoundaryLog,
|
||||
arg.ID,
|
||||
func (q *sqlQuerier) InsertBoundaryLogs(ctx context.Context, arg InsertBoundaryLogsParams) ([]BoundaryLog, error) {
|
||||
rows, err := q.db.QueryContext(ctx, insertBoundaryLogs,
|
||||
pq.Array(arg.ID),
|
||||
arg.SessionID,
|
||||
arg.SequenceNumber,
|
||||
arg.CapturedAt,
|
||||
arg.CreatedAt,
|
||||
arg.Proto,
|
||||
arg.Method,
|
||||
arg.Detail,
|
||||
arg.MatchedRule,
|
||||
pq.Array(arg.SequenceNumber),
|
||||
pq.Array(arg.CapturedAt),
|
||||
pq.Array(arg.CreatedAt),
|
||||
pq.Array(arg.Proto),
|
||||
pq.Array(arg.Method),
|
||||
pq.Array(arg.Detail),
|
||||
pq.Array(arg.MatchedRule),
|
||||
)
|
||||
var i BoundaryLog
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.SessionID,
|
||||
&i.SequenceNumber,
|
||||
&i.CapturedAt,
|
||||
&i.CreatedAt,
|
||||
&i.Proto,
|
||||
&i.Method,
|
||||
&i.Detail,
|
||||
&i.MatchedRule,
|
||||
)
|
||||
return i, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []BoundaryLog
|
||||
for rows.Next() {
|
||||
var i BoundaryLog
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.SessionID,
|
||||
&i.SequenceNumber,
|
||||
&i.CapturedAt,
|
||||
&i.CreatedAt,
|
||||
&i.Proto,
|
||||
&i.Method,
|
||||
&i.Detail,
|
||||
&i.MatchedRule,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const insertBoundarySession = `-- name: InsertBoundarySession :one
|
||||
INSERT INTO boundary_sessions (
|
||||
id,
|
||||
workspace_agent_id,
|
||||
owner_id,
|
||||
confined_process_name,
|
||||
started_at,
|
||||
updated_at
|
||||
@@ -3718,22 +3737,25 @@ INSERT INTO boundary_sessions (
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5
|
||||
) RETURNING id, workspace_agent_id, confined_process_name, started_at, updated_at
|
||||
$5,
|
||||
$6
|
||||
) RETURNING id, workspace_agent_id, confined_process_name, started_at, updated_at, owner_id
|
||||
`
|
||||
|
||||
type InsertBoundarySessionParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"`
|
||||
ConfinedProcessName string `db:"confined_process_name" json:"confined_process_name"`
|
||||
StartedAt time.Time `db:"started_at" json:"started_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"`
|
||||
OwnerID uuid.NullUUID `db:"owner_id" json:"owner_id"`
|
||||
ConfinedProcessName string `db:"confined_process_name" json:"confined_process_name"`
|
||||
StartedAt time.Time `db:"started_at" json:"started_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertBoundarySession(ctx context.Context, arg InsertBoundarySessionParams) (BoundarySession, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertBoundarySession,
|
||||
arg.ID,
|
||||
arg.WorkspaceAgentID,
|
||||
arg.OwnerID,
|
||||
arg.ConfinedProcessName,
|
||||
arg.StartedAt,
|
||||
arg.UpdatedAt,
|
||||
@@ -3745,6 +3767,7 @@ func (q *sqlQuerier) InsertBoundarySession(ctx context.Context, arg InsertBounda
|
||||
&i.ConfinedProcessName,
|
||||
&i.StartedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OwnerID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user