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
+10
@@ -15199,6 +15199,10 @@ const docTemplate = `{
|
||||
"audit_log:*",
|
||||
"audit_log:create",
|
||||
"audit_log:read",
|
||||
"boundary_log:*",
|
||||
"boundary_log:create",
|
||||
"boundary_log:delete",
|
||||
"boundary_log:read",
|
||||
"boundary_usage:*",
|
||||
"boundary_usage:delete",
|
||||
"boundary_usage:read",
|
||||
@@ -15425,6 +15429,10 @@ const docTemplate = `{
|
||||
"APIKeyScopeAuditLogAll",
|
||||
"APIKeyScopeAuditLogCreate",
|
||||
"APIKeyScopeAuditLogRead",
|
||||
"APIKeyScopeBoundaryLogAll",
|
||||
"APIKeyScopeBoundaryLogCreate",
|
||||
"APIKeyScopeBoundaryLogDelete",
|
||||
"APIKeyScopeBoundaryLogRead",
|
||||
"APIKeyScopeBoundaryUsageAll",
|
||||
"APIKeyScopeBoundaryUsageDelete",
|
||||
"APIKeyScopeBoundaryUsageRead",
|
||||
@@ -22223,6 +22231,7 @@ const docTemplate = `{
|
||||
"assign_org_role",
|
||||
"assign_role",
|
||||
"audit_log",
|
||||
"boundary_log",
|
||||
"boundary_usage",
|
||||
"chat",
|
||||
"connection_log",
|
||||
@@ -22273,6 +22282,7 @@ const docTemplate = `{
|
||||
"ResourceAssignOrgRole",
|
||||
"ResourceAssignRole",
|
||||
"ResourceAuditLog",
|
||||
"ResourceBoundaryLog",
|
||||
"ResourceBoundaryUsage",
|
||||
"ResourceChat",
|
||||
"ResourceConnectionLog",
|
||||
|
||||
Generated
+10
@@ -13595,6 +13595,10 @@
|
||||
"audit_log:*",
|
||||
"audit_log:create",
|
||||
"audit_log:read",
|
||||
"boundary_log:*",
|
||||
"boundary_log:create",
|
||||
"boundary_log:delete",
|
||||
"boundary_log:read",
|
||||
"boundary_usage:*",
|
||||
"boundary_usage:delete",
|
||||
"boundary_usage:read",
|
||||
@@ -13821,6 +13825,10 @@
|
||||
"APIKeyScopeAuditLogAll",
|
||||
"APIKeyScopeAuditLogCreate",
|
||||
"APIKeyScopeAuditLogRead",
|
||||
"APIKeyScopeBoundaryLogAll",
|
||||
"APIKeyScopeBoundaryLogCreate",
|
||||
"APIKeyScopeBoundaryLogDelete",
|
||||
"APIKeyScopeBoundaryLogRead",
|
||||
"APIKeyScopeBoundaryUsageAll",
|
||||
"APIKeyScopeBoundaryUsageDelete",
|
||||
"APIKeyScopeBoundaryUsageRead",
|
||||
@@ -20366,6 +20374,7 @@
|
||||
"assign_org_role",
|
||||
"assign_role",
|
||||
"audit_log",
|
||||
"boundary_log",
|
||||
"boundary_usage",
|
||||
"chat",
|
||||
"connection_log",
|
||||
@@ -20416,6 +20425,7 @@
|
||||
"ResourceAssignOrgRole",
|
||||
"ResourceAssignRole",
|
||||
"ResourceAuditLog",
|
||||
"ResourceBoundaryLog",
|
||||
"ResourceBoundaryUsage",
|
||||
"ResourceChat",
|
||||
"ResourceConnectionLog",
|
||||
|
||||
@@ -651,6 +651,8 @@ var (
|
||||
rbac.ResourceAibridgeInterception.Type: {policy.ActionDelete},
|
||||
// Chat auto-archive sets archived=true on inactive chats.
|
||||
rbac.ResourceChat.Type: {policy.ActionRead, policy.ActionUpdate},
|
||||
// Purge old boundary logs past the retention period.
|
||||
rbac.ResourceBoundaryLog.Type: {policy.ActionDelete},
|
||||
}),
|
||||
User: []rbac.Permission{},
|
||||
ByOrgID: map[string]rbac.OrgPermissions{},
|
||||
@@ -2191,9 +2193,8 @@ func (q *querier) DeleteOldAuditLogs(ctx context.Context, arg database.DeleteOld
|
||||
return q.db.DeleteOldAuditLogs(ctx, arg)
|
||||
}
|
||||
|
||||
// TODO (PR #24810): Replace rbac.ResourceSystem with dedicated boundary_log resource type.
|
||||
func (q *querier) DeleteOldBoundaryLogs(ctx context.Context, arg database.DeleteOldBoundaryLogsParams) (int64, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceSystem); err != nil {
|
||||
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceBoundaryLog); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return q.db.DeleteOldBoundaryLogs(ctx, arg)
|
||||
@@ -2780,17 +2781,15 @@ func (q *querier) GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUI
|
||||
return q.db.GetAuthorizationUserRoles(ctx, userID)
|
||||
}
|
||||
|
||||
// TODO (PR #24810): Replace rbac.ResourceAuditLog with dedicated boundary_log resource type.
|
||||
func (q *querier) GetBoundaryLogByID(ctx context.Context, id uuid.UUID) (database.BoundaryLog, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceAuditLog); err != nil {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceBoundaryLog); err != nil {
|
||||
return database.BoundaryLog{}, err
|
||||
}
|
||||
return q.db.GetBoundaryLogByID(ctx, id)
|
||||
}
|
||||
|
||||
// TODO (PR #24810): Replace rbac.ResourceAuditLog with dedicated boundary_log resource type.
|
||||
func (q *querier) GetBoundarySessionByID(ctx context.Context, id uuid.UUID) (database.BoundarySession, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceAuditLog); err != nil {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceBoundaryLog); err != nil {
|
||||
return database.BoundarySession{}, err
|
||||
}
|
||||
return q.db.GetBoundarySessionByID(ctx, id)
|
||||
@@ -5468,14 +5467,29 @@ func (q *querier) InsertAuditLog(ctx context.Context, arg database.InsertAuditLo
|
||||
return insert(q.log, q.auth, rbac.ResourceAuditLog, q.db.InsertAuditLog)(ctx, arg)
|
||||
}
|
||||
|
||||
// TODO (PR #24810): Replace rbac.ResourceAuditLog with dedicated boundary_log resource type.
|
||||
func (q *querier) InsertBoundaryLog(ctx context.Context, arg database.InsertBoundaryLogParams) (database.BoundaryLog, error) {
|
||||
return insert(q.log, q.auth, rbac.ResourceAuditLog, q.db.InsertBoundaryLog)(ctx, arg)
|
||||
func (q *querier) InsertBoundaryLogs(ctx context.Context, arg database.InsertBoundaryLogsParams) ([]database.BoundaryLog, error) {
|
||||
session, err := q.db.GetBoundarySessionByID(ctx, arg.SessionID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get boundary session for owner: %w", err)
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionCreate,
|
||||
rbac.ResourceBoundaryLog.WithOwner(session.OwnerID.UUID.String())); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.InsertBoundaryLogs(ctx, arg)
|
||||
}
|
||||
|
||||
// TODO (PR #24810): Replace rbac.ResourceAuditLog with dedicated boundary_log resource type.
|
||||
func (q *querier) InsertBoundarySession(ctx context.Context, arg database.InsertBoundarySessionParams) (database.BoundarySession, error) {
|
||||
return insert(q.log, q.auth, rbac.ResourceAuditLog, q.db.InsertBoundarySession)(ctx, arg)
|
||||
row, err := q.db.GetWorkspaceAgentAndWorkspaceByID(ctx, arg.WorkspaceAgentID)
|
||||
if err != nil {
|
||||
return database.BoundarySession{}, xerrors.Errorf("get workspace for boundary session owner: %w", err)
|
||||
}
|
||||
arg.OwnerID = uuid.NullUUID{UUID: row.WorkspaceTable.OwnerID, Valid: true}
|
||||
if err := q.authorizeContext(ctx, policy.ActionCreate,
|
||||
rbac.ResourceBoundaryLog.WithOwner(arg.OwnerID.UUID.String())); err != nil {
|
||||
return database.BoundarySession{}, err
|
||||
}
|
||||
return q.db.InsertBoundarySession(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertChat(ctx context.Context, arg database.InsertChatParams) (database.Chat, error) {
|
||||
@@ -6191,9 +6205,8 @@ func (q *querier) ListAIBridgeUserPromptsByInterceptionIDs(ctx context.Context,
|
||||
return q.db.ListAIBridgeUserPromptsByInterceptionIDs(ctx, interceptionIDs)
|
||||
}
|
||||
|
||||
// TODO (PR #24810): Replace rbac.ResourceAuditLog with dedicated boundary_log resource type.
|
||||
func (q *querier) ListBoundaryLogsBySessionID(ctx context.Context, arg database.ListBoundaryLogsBySessionIDParams) ([]database.BoundaryLog, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceAuditLog); err != nil {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceBoundaryLog); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.ListBoundaryLogsBySessionID(ctx, arg)
|
||||
|
||||
@@ -440,35 +440,55 @@ func (s *MethodTestSuite) TestAuditLogs() {
|
||||
}))
|
||||
}
|
||||
|
||||
// TODO (PR #24810): These RBAC assertions use placeholder resource types.
|
||||
// They will be updated when the dedicated boundary_log resource type is added.
|
||||
func (s *MethodTestSuite) TestBoundaryLogs() {
|
||||
s.Run("InsertBoundarySession", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
arg := database.InsertBoundarySessionParams{}
|
||||
dbm.EXPECT().InsertBoundarySession(gomock.Any(), arg).Return(database.BoundarySession{}, nil).AnyTimes()
|
||||
check.Args(arg).Asserts(rbac.ResourceAuditLog, policy.ActionCreate)
|
||||
s.Run("InsertBoundarySession", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
aww := testutil.Fake(s.T(), faker, database.GetWorkspaceAgentAndWorkspaceByIDRow{})
|
||||
arg := database.InsertBoundarySessionParams{
|
||||
WorkspaceAgentID: aww.WorkspaceAgent.ID,
|
||||
}
|
||||
dbm.EXPECT().GetWorkspaceAgentAndWorkspaceByID(gomock.Any(), aww.WorkspaceAgent.ID).Return(aww, nil).AnyTimes()
|
||||
expectedArg := database.InsertBoundarySessionParams{
|
||||
WorkspaceAgentID: aww.WorkspaceAgent.ID,
|
||||
OwnerID: uuid.NullUUID{UUID: aww.WorkspaceTable.OwnerID, Valid: true},
|
||||
}
|
||||
dbm.EXPECT().InsertBoundarySession(gomock.Any(), expectedArg).Return(database.BoundarySession{}, nil).AnyTimes()
|
||||
check.Args(arg).Asserts(
|
||||
rbac.ResourceBoundaryLog.WithOwner(aww.WorkspaceTable.OwnerID.String()), policy.ActionCreate,
|
||||
)
|
||||
}))
|
||||
s.Run("GetBoundarySessionByID", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
dbm.EXPECT().GetBoundarySessionByID(gomock.Any(), uuid.Nil).Return(database.BoundarySession{}, nil).AnyTimes()
|
||||
check.Args(uuid.Nil).Asserts(rbac.ResourceAuditLog, policy.ActionRead)
|
||||
check.Args(uuid.Nil).Asserts(rbac.ResourceBoundaryLog, policy.ActionRead)
|
||||
}))
|
||||
s.Run("InsertBoundaryLog", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
arg := database.InsertBoundaryLogParams{}
|
||||
dbm.EXPECT().InsertBoundaryLog(gomock.Any(), arg).Return(database.BoundaryLog{}, nil).AnyTimes()
|
||||
check.Args(arg).Asserts(rbac.ResourceAuditLog, policy.ActionCreate)
|
||||
s.Run("InsertBoundaryLogs", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
ownerID := uuid.New()
|
||||
sessionID := uuid.New()
|
||||
session := database.BoundarySession{
|
||||
ID: sessionID,
|
||||
OwnerID: uuid.NullUUID{UUID: ownerID, Valid: true},
|
||||
}
|
||||
arg := database.InsertBoundaryLogsParams{
|
||||
SessionID: sessionID,
|
||||
ID: []uuid.UUID{uuid.New(), uuid.New()},
|
||||
}
|
||||
dbm.EXPECT().GetBoundarySessionByID(gomock.Any(), sessionID).Return(session, nil).AnyTimes()
|
||||
dbm.EXPECT().InsertBoundaryLogs(gomock.Any(), arg).Return([]database.BoundaryLog{}, nil).AnyTimes()
|
||||
check.Args(arg).Asserts(
|
||||
rbac.ResourceBoundaryLog.WithOwner(ownerID.String()), policy.ActionCreate,
|
||||
)
|
||||
}))
|
||||
s.Run("GetBoundaryLogByID", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
dbm.EXPECT().GetBoundaryLogByID(gomock.Any(), uuid.Nil).Return(database.BoundaryLog{}, nil).AnyTimes()
|
||||
check.Args(uuid.Nil).Asserts(rbac.ResourceAuditLog, policy.ActionRead)
|
||||
check.Args(uuid.Nil).Asserts(rbac.ResourceBoundaryLog, policy.ActionRead)
|
||||
}))
|
||||
s.Run("ListBoundaryLogsBySessionID", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
arg := database.ListBoundaryLogsBySessionIDParams{}
|
||||
dbm.EXPECT().ListBoundaryLogsBySessionID(gomock.Any(), arg).Return([]database.BoundaryLog{}, nil).AnyTimes()
|
||||
check.Args(arg).Asserts(rbac.ResourceAuditLog, policy.ActionRead)
|
||||
check.Args(arg).Asserts(rbac.ResourceBoundaryLog, policy.ActionRead)
|
||||
}))
|
||||
s.Run("DeleteOldBoundaryLogs", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
dbm.EXPECT().DeleteOldBoundaryLogs(gomock.Any(), database.DeleteOldBoundaryLogsParams{}).Return(int64(0), nil).AnyTimes()
|
||||
check.Args(database.DeleteOldBoundaryLogsParams{}).Asserts(rbac.ResourceSystem, policy.ActionDelete)
|
||||
check.Args(database.DeleteOldBoundaryLogsParams{}).Asserts(rbac.ResourceBoundaryLog, policy.ActionDelete)
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+4
-4
@@ -3745,11 +3745,11 @@ func (m queryMetricsStore) InsertAuditLog(ctx context.Context, arg database.Inse
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertBoundaryLog(ctx context.Context, arg database.InsertBoundaryLogParams) (database.BoundaryLog, error) {
|
||||
func (m queryMetricsStore) InsertBoundaryLogs(ctx context.Context, arg database.InsertBoundaryLogsParams) ([]database.BoundaryLog, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.InsertBoundaryLog(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("InsertBoundaryLog").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "InsertBoundaryLog").Inc()
|
||||
r0, r1 := m.s.InsertBoundaryLogs(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("InsertBoundaryLogs").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "InsertBoundaryLogs").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
|
||||
Generated
+7
-7
@@ -7034,19 +7034,19 @@ func (mr *MockStoreMockRecorder) InsertAuditLog(ctx, arg any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertAuditLog", reflect.TypeOf((*MockStore)(nil).InsertAuditLog), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertBoundaryLog mocks base method.
|
||||
func (m *MockStore) InsertBoundaryLog(ctx context.Context, arg database.InsertBoundaryLogParams) (database.BoundaryLog, error) {
|
||||
// InsertBoundaryLogs mocks base method.
|
||||
func (m *MockStore) InsertBoundaryLogs(ctx context.Context, arg database.InsertBoundaryLogsParams) ([]database.BoundaryLog, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InsertBoundaryLog", ctx, arg)
|
||||
ret0, _ := ret[0].(database.BoundaryLog)
|
||||
ret := m.ctrl.Call(m, "InsertBoundaryLogs", ctx, arg)
|
||||
ret0, _ := ret[0].([]database.BoundaryLog)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// InsertBoundaryLog indicates an expected call of InsertBoundaryLog.
|
||||
func (mr *MockStoreMockRecorder) InsertBoundaryLog(ctx, arg any) *gomock.Call {
|
||||
// InsertBoundaryLogs indicates an expected call of InsertBoundaryLogs.
|
||||
func (mr *MockStoreMockRecorder) InsertBoundaryLogs(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertBoundaryLog", reflect.TypeOf((*MockStore)(nil).InsertBoundaryLog), ctx, arg)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertBoundaryLogs", reflect.TypeOf((*MockStore)(nil).InsertBoundaryLogs), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertBoundarySession mocks base method.
|
||||
|
||||
Generated
+12
-2
@@ -249,7 +249,11 @@ CREATE TYPE api_key_scope AS ENUM (
|
||||
'user_skill:read',
|
||||
'user_skill:update',
|
||||
'user_skill:delete',
|
||||
'user_skill:*'
|
||||
'user_skill:*',
|
||||
'boundary_log:*',
|
||||
'boundary_log:create',
|
||||
'boundary_log:delete',
|
||||
'boundary_log:read'
|
||||
);
|
||||
|
||||
CREATE TYPE app_sharing_level AS ENUM (
|
||||
@@ -1485,7 +1489,8 @@ CREATE TABLE boundary_sessions (
|
||||
workspace_agent_id uuid NOT NULL,
|
||||
confined_process_name text NOT NULL,
|
||||
started_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
owner_id uuid
|
||||
);
|
||||
|
||||
COMMENT ON TABLE boundary_sessions IS 'Boundary session metadata. Each row represents a single invocation of a Boundary process wrapping a confined agent.';
|
||||
@@ -1500,6 +1505,8 @@ COMMENT ON COLUMN boundary_sessions.started_at IS 'Time when the first log for t
|
||||
|
||||
COMMENT ON COLUMN boundary_sessions.updated_at IS 'Time when the session was last updated.';
|
||||
|
||||
COMMENT ON COLUMN boundary_sessions.owner_id IS 'The ID of the user who owns the workspace. NULL if the user has been deleted.';
|
||||
|
||||
CREATE TABLE boundary_usage_stats (
|
||||
replica_id uuid NOT NULL,
|
||||
unique_workspaces_count bigint DEFAULT 0 NOT NULL,
|
||||
@@ -4509,6 +4516,9 @@ ALTER TABLE ONLY api_keys
|
||||
ALTER TABLE ONLY boundary_logs
|
||||
ADD CONSTRAINT boundary_logs_session_id_fkey FOREIGN KEY (session_id) REFERENCES boundary_sessions(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY boundary_sessions
|
||||
ADD CONSTRAINT boundary_sessions_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY boundary_sessions
|
||||
ADD CONSTRAINT boundary_sessions_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id);
|
||||
|
||||
|
||||
+1
@@ -13,6 +13,7 @@ const (
|
||||
ForeignKeyAibridgeInterceptionsInitiatorID ForeignKeyConstraint = "aibridge_interceptions_initiator_id_fkey" // ALTER TABLE ONLY aibridge_interceptions ADD CONSTRAINT aibridge_interceptions_initiator_id_fkey FOREIGN KEY (initiator_id) REFERENCES users(id);
|
||||
ForeignKeyAPIKeysUserIDUUID ForeignKeyConstraint = "api_keys_user_id_uuid_fkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyBoundaryLogsSessionID ForeignKeyConstraint = "boundary_logs_session_id_fkey" // ALTER TABLE ONLY boundary_logs ADD CONSTRAINT boundary_logs_session_id_fkey FOREIGN KEY (session_id) REFERENCES boundary_sessions(id) ON DELETE CASCADE;
|
||||
ForeignKeyBoundarySessionsOwnerID ForeignKeyConstraint = "boundary_sessions_owner_id_fkey" // ALTER TABLE ONLY boundary_sessions ADD CONSTRAINT boundary_sessions_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
ForeignKeyBoundarySessionsWorkspaceAgentID ForeignKeyConstraint = "boundary_sessions_workspace_agent_id_fkey" // ALTER TABLE ONLY boundary_sessions ADD CONSTRAINT boundary_sessions_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id);
|
||||
ForeignKeyChatDebugRunsChatID ForeignKeyConstraint = "chat_debug_runs_chat_id_fkey" // ALTER TABLE ONLY chat_debug_runs ADD CONSTRAINT chat_debug_runs_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
|
||||
ForeignKeyChatDebugStepsChatID ForeignKeyConstraint = "chat_debug_steps_chat_id_fkey" // ALTER TABLE ONLY chat_debug_steps ADD CONSTRAINT chat_debug_steps_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
-- No-op for boundary_log scopes: keep enum values to avoid dependency churn.
|
||||
@@ -0,0 +1,5 @@
|
||||
-- Add boundary_log scopes for RBAC.
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'boundary_log:*';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'boundary_log:create';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'boundary_log:delete';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'boundary_log:read';
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE boundary_sessions DROP CONSTRAINT IF EXISTS boundary_sessions_owner_id_fkey;
|
||||
ALTER TABLE boundary_sessions DROP COLUMN IF EXISTS owner_id;
|
||||
@@ -0,0 +1,28 @@
|
||||
-- Add owner_id to boundary_sessions to avoid expensive JOINs when
|
||||
-- deriving the workspace owner for RBAC checks during log insertion.
|
||||
ALTER TABLE boundary_sessions ADD COLUMN owner_id uuid;
|
||||
|
||||
COMMENT ON COLUMN boundary_sessions.owner_id IS 'The ID of the user who owns the workspace. NULL if the user has been deleted.';
|
||||
|
||||
-- Backfill owner_id from the workspace agent -> workspace -> owner chain.
|
||||
-- Soft-deleted agents and workspaces are included so that their audit
|
||||
-- data is preserved.
|
||||
UPDATE boundary_sessions bs
|
||||
SET owner_id = w.owner_id
|
||||
FROM workspace_agents wa
|
||||
JOIN workspace_resources wr ON wa.resource_id = wr.id
|
||||
JOIN provisioner_jobs pj ON wr.job_id = pj.id
|
||||
JOIN workspace_builds wb ON pj.id = wb.job_id
|
||||
JOIN workspaces w ON wb.workspace_id = w.id
|
||||
WHERE wa.id = bs.workspace_agent_id
|
||||
AND pj.type = 'workspace_build';
|
||||
|
||||
-- Delete any sessions that could not be backfilled (orphaned data
|
||||
-- with no resolvable workspace agent or workspace build chain).
|
||||
DELETE FROM boundary_sessions WHERE owner_id IS NULL;
|
||||
|
||||
-- Add FK constraint. SET NULL preserves audit data when a user is
|
||||
-- hard-deleted; the session and its logs survive with a NULL owner.
|
||||
ALTER TABLE boundary_sessions
|
||||
ADD CONSTRAINT boundary_sessions_owner_id_fkey
|
||||
FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
-- Re-insert boundary session and log fixture data after migration 000511
|
||||
-- deletes orphaned rows (the original fixture's workspace_agent links to a
|
||||
-- template_version_import job, not a workspace_build, so the backfill
|
||||
-- cannot resolve the owner).
|
||||
|
||||
INSERT INTO boundary_sessions (
|
||||
id,
|
||||
workspace_agent_id,
|
||||
confined_process_name,
|
||||
started_at,
|
||||
updated_at,
|
||||
owner_id
|
||||
) VALUES (
|
||||
'a1b2c3d4-e5f6-4890-abcd-ef1234567890',
|
||||
'45e89705-e09d-4850-bcec-f9a937f5d78d',
|
||||
'claude-code',
|
||||
'2026-04-01 10:00:00+00',
|
||||
'2026-04-01 10:00:00+00',
|
||||
'30095c71-380b-457a-8995-97b8ee6e5307'
|
||||
);
|
||||
|
||||
INSERT INTO boundary_logs (
|
||||
id,
|
||||
session_id,
|
||||
sequence_number,
|
||||
captured_at,
|
||||
created_at,
|
||||
proto,
|
||||
method,
|
||||
detail,
|
||||
matched_rule
|
||||
) VALUES (
|
||||
'b2c3d4e5-f6a7-4901-bcde-f12345678901',
|
||||
'a1b2c3d4-e5f6-4890-abcd-ef1234567890',
|
||||
0,
|
||||
'2026-04-01 10:00:01+00',
|
||||
'2026-04-01 10:00:00+00',
|
||||
'http',
|
||||
'GET',
|
||||
'https://api.anthropic.com/v1/messages',
|
||||
'domain=api.anthropic.com'
|
||||
);
|
||||
@@ -1003,3 +1003,10 @@ type UpsertConnectionLogParams struct {
|
||||
func (r GetLatestWorkspaceBuildWithStatusByWorkspaceIDRow) RBACObject() rbac.Object {
|
||||
return r.WorkspaceTable.RBACObject()
|
||||
}
|
||||
|
||||
func (s BoundarySession) RBACObject() rbac.Object {
|
||||
if s.OwnerID.Valid {
|
||||
return rbac.ResourceBoundaryLog.WithOwner(s.OwnerID.UUID.String())
|
||||
}
|
||||
return rbac.ResourceBoundaryLog
|
||||
}
|
||||
|
||||
Generated
+15
-1
@@ -320,6 +320,10 @@ const (
|
||||
ApiKeyScopeUserSkillUpdate APIKeyScope = "user_skill:update"
|
||||
ApiKeyScopeUserSkillDelete APIKeyScope = "user_skill:delete"
|
||||
ApiKeyScopeUserSkill APIKeyScope = "user_skill:*"
|
||||
ApiKeyScopeBoundaryLog APIKeyScope = "boundary_log:*"
|
||||
ApiKeyScopeBoundaryLogCreate APIKeyScope = "boundary_log:create"
|
||||
ApiKeyScopeBoundaryLogDelete APIKeyScope = "boundary_log:delete"
|
||||
ApiKeyScopeBoundaryLogRead APIKeyScope = "boundary_log:read"
|
||||
)
|
||||
|
||||
func (e *APIKeyScope) Scan(src interface{}) error {
|
||||
@@ -580,7 +584,11 @@ func (e APIKeyScope) Valid() bool {
|
||||
ApiKeyScopeUserSkillRead,
|
||||
ApiKeyScopeUserSkillUpdate,
|
||||
ApiKeyScopeUserSkillDelete,
|
||||
ApiKeyScopeUserSkill:
|
||||
ApiKeyScopeUserSkill,
|
||||
ApiKeyScopeBoundaryLog,
|
||||
ApiKeyScopeBoundaryLogCreate,
|
||||
ApiKeyScopeBoundaryLogDelete,
|
||||
ApiKeyScopeBoundaryLogRead:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -810,6 +818,10 @@ func AllAPIKeyScopeValues() []APIKeyScope {
|
||||
ApiKeyScopeUserSkillUpdate,
|
||||
ApiKeyScopeUserSkillDelete,
|
||||
ApiKeyScopeUserSkill,
|
||||
ApiKeyScopeBoundaryLog,
|
||||
ApiKeyScopeBoundaryLogCreate,
|
||||
ApiKeyScopeBoundaryLogDelete,
|
||||
ApiKeyScopeBoundaryLogRead,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4543,6 +4555,8 @@ type BoundarySession struct {
|
||||
StartedAt time.Time `db:"started_at" json:"started_at"`
|
||||
// Time when the session was last updated.
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
// The ID of the user who owns the workspace. NULL if the user has been deleted.
|
||||
OwnerID uuid.NullUUID `db:"owner_id" json:"owner_id"`
|
||||
}
|
||||
|
||||
// Per-replica boundary usage statistics for telemetry aggregation.
|
||||
|
||||
Generated
+1
-1
@@ -920,7 +920,7 @@ type sqlcQuerier interface {
|
||||
// every member of the org.
|
||||
InsertAllUsersGroup(ctx context.Context, organizationID uuid.UUID) (Group, error)
|
||||
InsertAuditLog(ctx context.Context, arg InsertAuditLogParams) (AuditLog, error)
|
||||
InsertBoundaryLog(ctx context.Context, arg InsertBoundaryLogParams) (BoundaryLog, error)
|
||||
InsertBoundaryLogs(ctx context.Context, arg InsertBoundaryLogsParams) ([]BoundaryLog, error)
|
||||
InsertBoundarySession(ctx context.Context, arg InsertBoundarySessionParams) (BoundarySession, error)
|
||||
InsertChat(ctx context.Context, arg InsertChatParams) (Chat, error)
|
||||
// updated_at is the retention clock used by DeleteOldChatDebugRuns.
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
INSERT INTO boundary_sessions (
|
||||
id,
|
||||
workspace_agent_id,
|
||||
owner_id,
|
||||
confined_process_name,
|
||||
started_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
@id,
|
||||
@workspace_agent_id,
|
||||
@owner_id,
|
||||
@confined_process_name,
|
||||
@started_at,
|
||||
@updated_at
|
||||
@@ -16,7 +18,7 @@ INSERT INTO boundary_sessions (
|
||||
-- name: GetBoundarySessionByID :one
|
||||
SELECT * FROM boundary_sessions WHERE id = @id;
|
||||
|
||||
-- name: InsertBoundaryLog :one
|
||||
-- name: InsertBoundaryLogs :many
|
||||
INSERT INTO boundary_logs (
|
||||
id,
|
||||
session_id,
|
||||
@@ -27,17 +29,18 @@ INSERT INTO boundary_logs (
|
||||
method,
|
||||
detail,
|
||||
matched_rule
|
||||
) VALUES (
|
||||
@id,
|
||||
@session_id,
|
||||
@sequence_number,
|
||||
@captured_at,
|
||||
@created_at,
|
||||
@proto,
|
||||
@method,
|
||||
@detail,
|
||||
@matched_rule
|
||||
) RETURNING *;
|
||||
)
|
||||
SELECT
|
||||
unnest(@id :: uuid[]),
|
||||
@session_id :: uuid,
|
||||
unnest(@sequence_number :: int[]),
|
||||
unnest(@captured_at :: timestamptz[]),
|
||||
unnest(@created_at :: timestamptz[]),
|
||||
unnest(@proto :: text[]),
|
||||
unnest(@method :: text[]),
|
||||
unnest(@detail :: text[]),
|
||||
unnest(@matched_rule :: text[])
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetBoundaryLogByID :one
|
||||
SELECT * FROM boundary_logs WHERE id = @id;
|
||||
|
||||
@@ -89,6 +89,15 @@ var (
|
||||
Type: "audit_log",
|
||||
}
|
||||
|
||||
// ResourceBoundaryLog
|
||||
// Valid Actions
|
||||
// - "ActionCreate" :: create boundary log records
|
||||
// - "ActionDelete" :: delete boundary logs
|
||||
// - "ActionRead" :: read boundary logs and session metadata
|
||||
ResourceBoundaryLog = Object{
|
||||
Type: "boundary_log",
|
||||
}
|
||||
|
||||
// ResourceBoundaryUsage
|
||||
// Valid Actions
|
||||
// - "ActionDelete" :: delete boundary usage statistics
|
||||
@@ -478,6 +487,7 @@ func AllResources() []Objecter {
|
||||
ResourceAssignOrgRole,
|
||||
ResourceAssignRole,
|
||||
ResourceAuditLog,
|
||||
ResourceBoundaryLog,
|
||||
ResourceBoundaryUsage,
|
||||
ResourceChat,
|
||||
ResourceConnectionLog,
|
||||
|
||||
@@ -422,6 +422,13 @@ var RBACPermissions = map[string]PermissionDefinition{
|
||||
ActionRead: "read AI seat state",
|
||||
},
|
||||
},
|
||||
"boundary_log": {
|
||||
Actions: map[Action]ActionDefinition{
|
||||
ActionCreate: "create boundary log records",
|
||||
ActionRead: "read boundary logs and session metadata",
|
||||
ActionDelete: "delete boundary logs",
|
||||
},
|
||||
},
|
||||
"boundary_usage": {
|
||||
Actions: map[Action]ActionDefinition{
|
||||
ActionRead: "read boundary usage statistics",
|
||||
|
||||
+15
-3
@@ -303,7 +303,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
// Workspace is specifically handled based on the opts.NoOwnerWorkspaceExec.
|
||||
// Owners can inspect and delete personal skills for operability and
|
||||
// abuse handling, but cannot create or edit user-authored instructions.
|
||||
allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUserSecret, ResourceUserSkill, ResourceUsageEvent, ResourceBoundaryUsage, ResourceAiSeat),
|
||||
allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUserSecret, ResourceUserSkill, ResourceUsageEvent, ResourceBoundaryUsage, ResourceBoundaryLog, ResourceAiSeat),
|
||||
// This adds back in the Workspace permissions.
|
||||
Permissions(map[string][]policy.Action{
|
||||
ResourceWorkspace.Type: ownerWorkspaceActions,
|
||||
@@ -313,6 +313,9 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
// Explicitly setting PrebuiltWorkspace permissions for clarity.
|
||||
// Note: even without PrebuiltWorkspace permissions, access is still granted via Workspace permissions.
|
||||
ResourcePrebuiltWorkspace.Type: {policy.ActionUpdate, policy.ActionDelete},
|
||||
// Owners can read all boundary logs. Delete is reserved for
|
||||
// DBPurge only. Create is user-scoped (inherited from member).
|
||||
ResourceBoundaryLog.Type: {policy.ActionRead},
|
||||
})...,
|
||||
),
|
||||
User: []Permission{},
|
||||
@@ -332,7 +335,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
denyPermissions...,
|
||||
),
|
||||
User: append(
|
||||
allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUser, ResourceOrganizationMember, ResourceBoundaryUsage, ResourceAibridgeInterception, ResourceChat, ResourceAiSeat),
|
||||
allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUser, ResourceOrganizationMember, ResourceBoundaryUsage, ResourceBoundaryLog, ResourceAibridgeInterception, ResourceChat, ResourceAiSeat),
|
||||
Permissions(map[string][]policy.Action{
|
||||
// Users cannot do create/update/delete on themselves, but they
|
||||
// can read their own details.
|
||||
@@ -342,6 +345,11 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
// Members can create and update AI Bridge interceptions but
|
||||
// cannot read them back.
|
||||
ResourceAibridgeInterception.Type: {policy.ActionCreate, policy.ActionUpdate},
|
||||
// Workspace agents create boundary logs under their owner's
|
||||
// identity. Create is user-scoped so agents can only write
|
||||
// logs owned by their workspace owner.
|
||||
// Read: owners and auditors. Delete: DBPurge only.
|
||||
ResourceBoundaryLog.Type: {policy.ActionCreate},
|
||||
})...,
|
||||
),
|
||||
ByOrgID: map[string]OrgPermissions{},
|
||||
@@ -366,6 +374,8 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
ResourceDeploymentConfig.Type: {policy.ActionRead},
|
||||
// Allow auditors to query AI Bridge interceptions.
|
||||
ResourceAibridgeInterception.Type: {policy.ActionRead},
|
||||
// Allow auditors to read boundary logs.
|
||||
ResourceBoundaryLog.Type: {policy.ActionRead},
|
||||
}),
|
||||
User: []Permission{},
|
||||
ByOrgID: map[string]OrgPermissions{},
|
||||
@@ -465,7 +475,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
// Org admins should not have workspace exec perms.
|
||||
organizationID.String(): {
|
||||
Org: append(
|
||||
allPermsExcept(ResourceWorkspace, ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceAssignRole, ResourceUserSecret, ResourceBoundaryUsage, ResourceAiSeat),
|
||||
allPermsExcept(ResourceWorkspace, ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceAssignRole, ResourceUserSecret, ResourceBoundaryUsage, ResourceBoundaryLog, ResourceAiSeat),
|
||||
Permissions(map[string][]policy.Action{
|
||||
ResourceWorkspace.Type: slice.Omit(ResourceWorkspace.AvailableActions(), policy.ActionApplicationConnect, policy.ActionSSH),
|
||||
ResourceWorkspaceDormant.Type: {policy.ActionRead, policy.ActionDelete, policy.ActionCreate, policy.ActionUpdate, policy.ActionWorkspaceStop, policy.ActionCreateAgent, policy.ActionDeleteAgent, policy.ActionUpdateAgent},
|
||||
@@ -1052,6 +1062,7 @@ func OrgMemberPermissions(org OrgSettings) OrgRolePermissions {
|
||||
ResourcePrebuiltWorkspace,
|
||||
ResourceUser,
|
||||
ResourceOrganizationMember,
|
||||
ResourceBoundaryLog,
|
||||
ResourceAibridgeInterception,
|
||||
// Chat access requires the agents-access role.
|
||||
ResourceChat,
|
||||
@@ -1137,6 +1148,7 @@ func OrgServiceAccountPermissions(org OrgSettings) OrgRolePermissions {
|
||||
ResourcePrebuiltWorkspace,
|
||||
ResourceUser,
|
||||
ResourceOrganizationMember,
|
||||
ResourceBoundaryLog,
|
||||
ResourceAibridgeInterception,
|
||||
// Chat access requires the agents-access role.
|
||||
ResourceChat,
|
||||
|
||||
@@ -1229,6 +1229,75 @@ func TestRolePermissions(t *testing.T) {
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Boundary logs: members can create logs they own (user-scoped).
|
||||
// memberMe and agentsAccessUser have ID == currentUser, so they
|
||||
// match the resource owner. Other subjects have different IDs.
|
||||
Name: "BoundaryLogCreate",
|
||||
Actions: []policy.Action{policy.ActionCreate},
|
||||
Resource: rbac.ResourceBoundaryLog.WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {memberMe, agentsAccessUser},
|
||||
false: {
|
||||
owner,
|
||||
orgAdmin, otherOrgAdmin,
|
||||
orgAuditor, otherOrgAuditor, auditor,
|
||||
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
||||
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Cross-user isolation: no subject can create boundary logs
|
||||
// owned by a different user. The resource owner is a random
|
||||
// UUID that does not match any test subject's ID.
|
||||
Name: "BoundaryLogCreateOther",
|
||||
Actions: []policy.Action{policy.ActionCreate},
|
||||
Resource: rbac.ResourceBoundaryLog.WithOwner(uuid.New().String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {},
|
||||
false: {
|
||||
owner, memberMe, agentsAccessUser,
|
||||
orgAdmin, otherOrgAdmin,
|
||||
orgAuditor, otherOrgAuditor, auditor,
|
||||
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
||||
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Boundary logs: only DBPurge can delete. No human role
|
||||
// has delete; DBPurge is a system subject outside this matrix.
|
||||
Name: "BoundaryLogDelete",
|
||||
Actions: []policy.Action{policy.ActionDelete},
|
||||
Resource: rbac.ResourceBoundaryLog,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {},
|
||||
false: {
|
||||
owner, memberMe, agentsAccessUser,
|
||||
orgAdmin, otherOrgAdmin,
|
||||
orgAuditor, otherOrgAuditor, auditor,
|
||||
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
||||
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Boundary logs: owner and auditor get read.
|
||||
Name: "BoundaryLogRead",
|
||||
Actions: []policy.Action{policy.ActionRead},
|
||||
Resource: rbac.ResourceBoundaryLog,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, auditor},
|
||||
false: {
|
||||
memberMe, agentsAccessUser,
|
||||
orgAdmin, otherOrgAdmin,
|
||||
orgAuditor, otherOrgAuditor,
|
||||
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
||||
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ChatUsageCRU",
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate},
|
||||
@@ -1471,3 +1540,121 @@ func TestChangeSet(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestWorkspaceAgentScopeBoundaryLog verifies that a real workspace agent
|
||||
// scope (not ScopeAll) can create boundary logs for its own owner but
|
||||
// cannot create them for other users, and cannot read or delete them.
|
||||
func TestWorkspaceAgentScopeBoundaryLog(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
auth := rbac.NewStrictAuthorizer(prometheus.NewRegistry())
|
||||
|
||||
ownerID := uuid.New()
|
||||
otherOwnerID := uuid.New()
|
||||
workspaceID := uuid.New()
|
||||
templateID := uuid.New()
|
||||
versionID := uuid.New()
|
||||
|
||||
agentScope := rbac.WorkspaceAgentScope(rbac.WorkspaceAgentScopeParams{
|
||||
WorkspaceID: workspaceID,
|
||||
OwnerID: ownerID,
|
||||
TemplateID: templateID,
|
||||
VersionID: versionID,
|
||||
})
|
||||
|
||||
memberRole, err := rbac.RoleByName(rbac.RoleMember())
|
||||
require.NoError(t, err)
|
||||
|
||||
agent := rbac.Subject{
|
||||
ID: ownerID.String(),
|
||||
Roles: rbac.Roles{memberRole},
|
||||
Scope: agentScope,
|
||||
}.WithCachedASTValue()
|
||||
|
||||
// Agent can create boundary logs for its own owner.
|
||||
err = auth.Authorize(context.Background(), agent, policy.ActionCreate,
|
||||
rbac.ResourceBoundaryLog.WithOwner(ownerID.String()))
|
||||
require.NoError(t, err, "agent should create boundary logs for own owner")
|
||||
|
||||
// Agent cannot create boundary logs for a different owner.
|
||||
err = auth.Authorize(context.Background(), agent, policy.ActionCreate,
|
||||
rbac.ResourceBoundaryLog.WithOwner(otherOwnerID.String()))
|
||||
require.Error(t, err, "agent must not create boundary logs for other owner")
|
||||
|
||||
// Agent cannot read boundary logs (even its own owner's).
|
||||
err = auth.Authorize(context.Background(), agent, policy.ActionRead,
|
||||
rbac.ResourceBoundaryLog.WithOwner(ownerID.String()))
|
||||
require.Error(t, err, "agent must not read boundary logs")
|
||||
|
||||
// Agent cannot delete boundary logs (even its own owner's).
|
||||
err = auth.Authorize(context.Background(), agent, policy.ActionDelete,
|
||||
rbac.ResourceBoundaryLog.WithOwner(ownerID.String()))
|
||||
require.Error(t, err, "agent must not delete boundary logs")
|
||||
|
||||
// When the workspace owner is a site admin, the agent scope
|
||||
// wildcard for boundary_log combined with the owner role's site-level
|
||||
// read grant means the agent CAN read all boundary logs. This is an
|
||||
// accepted consequence of the wildcard scope needed for creation.
|
||||
ownerRole, err := rbac.RoleByName(rbac.RoleOwner())
|
||||
require.NoError(t, err)
|
||||
|
||||
adminAgent := rbac.Subject{
|
||||
ID: ownerID.String(),
|
||||
Roles: rbac.Roles{memberRole, ownerRole},
|
||||
Scope: agentScope,
|
||||
}.WithCachedASTValue()
|
||||
|
||||
// Admin-owned agent CAN read boundary logs due to site-level owner
|
||||
// role + wildcard scope.
|
||||
err = auth.Authorize(context.Background(), adminAgent, policy.ActionRead,
|
||||
rbac.ResourceBoundaryLog.WithOwner(otherOwnerID.String()))
|
||||
require.NoError(t, err, "admin agent inherits site-level read via owner role")
|
||||
|
||||
// Admin-owned agent still cannot create boundary logs for another owner
|
||||
// because member-level create is user-scoped (subject.id must match owner).
|
||||
err = auth.Authorize(context.Background(), adminAgent, policy.ActionCreate,
|
||||
rbac.ResourceBoundaryLog.WithOwner(otherOwnerID.String()))
|
||||
require.Error(t, err, "admin agent must not create boundary logs for other owner")
|
||||
}
|
||||
|
||||
// TestDBPurgeBoundaryLogDelete verifies that the DBPurge system subject
|
||||
// can delete boundary logs but cannot create or read them.
|
||||
func TestDBPurgeBoundaryLogDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
auth := rbac.NewStrictAuthorizer(prometheus.NewRegistry())
|
||||
|
||||
// Build the DBPurge subject the same way dbauthz does.
|
||||
dbPurge := 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.ResourceBoundaryLog.Type: {policy.ActionDelete},
|
||||
}),
|
||||
User: []rbac.Permission{},
|
||||
ByOrgID: map[string]rbac.OrgPermissions{},
|
||||
},
|
||||
}),
|
||||
Scope: rbac.ScopeAll,
|
||||
}.WithCachedASTValue()
|
||||
|
||||
// DBPurge can delete boundary logs.
|
||||
err := auth.Authorize(context.Background(), dbPurge, policy.ActionDelete,
|
||||
rbac.ResourceBoundaryLog)
|
||||
require.NoError(t, err, "DBPurge should delete boundary logs")
|
||||
|
||||
// DBPurge cannot create boundary logs.
|
||||
err = auth.Authorize(context.Background(), dbPurge, policy.ActionCreate,
|
||||
rbac.ResourceBoundaryLog.WithOwner(uuid.New().String()))
|
||||
require.Error(t, err, "DBPurge must not create boundary logs")
|
||||
|
||||
// DBPurge cannot read boundary logs.
|
||||
err = auth.Authorize(context.Background(), dbPurge, policy.ActionRead,
|
||||
rbac.ResourceBoundaryLog)
|
||||
require.Error(t, err, "DBPurge must not read boundary logs")
|
||||
}
|
||||
|
||||
@@ -65,6 +65,11 @@ func WorkspaceAgentScope(params WorkspaceAgentScopeParams) Scope {
|
||||
{Type: ResourceTemplate.Type, ID: params.TemplateID.String()},
|
||||
{Type: ResourceTemplate.Type, ID: params.VersionID.String()},
|
||||
{Type: ResourceUser.Type, ID: params.OwnerID.String()},
|
||||
// No pre-existing ID for new records; wildcard is required.
|
||||
// Owner-scoped create (user-level) limits agents to their own
|
||||
// logs. Adding site-level actions to the member role would
|
||||
// bypass this and grant deployment-wide access.
|
||||
{Type: ResourceBoundaryLog.Type, ID: policy.WildcardSymbol},
|
||||
}, extraAllowList...),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ const (
|
||||
ScopeAssignRoleUnassign ScopeName = "assign_role:unassign"
|
||||
ScopeAuditLogCreate ScopeName = "audit_log:create"
|
||||
ScopeAuditLogRead ScopeName = "audit_log:read"
|
||||
ScopeBoundaryLogCreate ScopeName = "boundary_log:create"
|
||||
ScopeBoundaryLogDelete ScopeName = "boundary_log:delete"
|
||||
ScopeBoundaryLogRead ScopeName = "boundary_log:read"
|
||||
ScopeBoundaryUsageDelete ScopeName = "boundary_usage:delete"
|
||||
ScopeBoundaryUsageRead ScopeName = "boundary_usage:read"
|
||||
ScopeBoundaryUsageUpdate ScopeName = "boundary_usage:update"
|
||||
@@ -210,6 +213,9 @@ func (e ScopeName) Valid() bool {
|
||||
ScopeAssignRoleUnassign,
|
||||
ScopeAuditLogCreate,
|
||||
ScopeAuditLogRead,
|
||||
ScopeBoundaryLogCreate,
|
||||
ScopeBoundaryLogDelete,
|
||||
ScopeBoundaryLogRead,
|
||||
ScopeBoundaryUsageDelete,
|
||||
ScopeBoundaryUsageRead,
|
||||
ScopeBoundaryUsageUpdate,
|
||||
@@ -388,6 +394,9 @@ func AllScopeNameValues() []ScopeName {
|
||||
ScopeAssignRoleUnassign,
|
||||
ScopeAuditLogCreate,
|
||||
ScopeAuditLogRead,
|
||||
ScopeBoundaryLogCreate,
|
||||
ScopeBoundaryLogDelete,
|
||||
ScopeBoundaryLogRead,
|
||||
ScopeBoundaryUsageDelete,
|
||||
ScopeBoundaryUsageRead,
|
||||
ScopeBoundaryUsageUpdate,
|
||||
|
||||
@@ -40,6 +40,10 @@ const (
|
||||
APIKeyScopeAuditLogAll APIKeyScope = "audit_log:*"
|
||||
APIKeyScopeAuditLogCreate APIKeyScope = "audit_log:create"
|
||||
APIKeyScopeAuditLogRead APIKeyScope = "audit_log:read"
|
||||
APIKeyScopeBoundaryLogAll APIKeyScope = "boundary_log:*"
|
||||
APIKeyScopeBoundaryLogCreate APIKeyScope = "boundary_log:create"
|
||||
APIKeyScopeBoundaryLogDelete APIKeyScope = "boundary_log:delete"
|
||||
APIKeyScopeBoundaryLogRead APIKeyScope = "boundary_log:read"
|
||||
APIKeyScopeBoundaryUsageAll APIKeyScope = "boundary_usage:*"
|
||||
APIKeyScopeBoundaryUsageDelete APIKeyScope = "boundary_usage:delete"
|
||||
APIKeyScopeBoundaryUsageRead APIKeyScope = "boundary_usage:read"
|
||||
|
||||
@@ -13,6 +13,7 @@ const (
|
||||
ResourceAssignOrgRole RBACResource = "assign_org_role"
|
||||
ResourceAssignRole RBACResource = "assign_role"
|
||||
ResourceAuditLog RBACResource = "audit_log"
|
||||
ResourceBoundaryLog RBACResource = "boundary_log"
|
||||
ResourceBoundaryUsage RBACResource = "boundary_usage"
|
||||
ResourceChat RBACResource = "chat"
|
||||
ResourceConnectionLog RBACResource = "connection_log"
|
||||
@@ -89,6 +90,7 @@ var RBACResourceActions = map[RBACResource][]RBACAction{
|
||||
ResourceAssignOrgRole: {ActionAssign, ActionCreate, ActionDelete, ActionRead, ActionUnassign, ActionUpdate},
|
||||
ResourceAssignRole: {ActionAssign, ActionRead, ActionUnassign},
|
||||
ResourceAuditLog: {ActionCreate, ActionRead},
|
||||
ResourceBoundaryLog: {ActionCreate, ActionDelete, ActionRead},
|
||||
ResourceBoundaryUsage: {ActionDelete, ActionRead, ActionUpdate},
|
||||
ResourceChat: {ActionCreate, ActionDelete, ActionRead, ActionShare, ActionUpdate},
|
||||
ResourceConnectionLog: {ActionRead, ActionUpdate},
|
||||
|
||||
Generated
+20
-20
@@ -193,10 +193,10 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `ai_model_price`, `ai_provider`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `user_skill`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Property | Value(s) |
|
||||
|-----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `ai_model_price`, `ai_provider`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `user_skill`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
@@ -326,10 +326,10 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `ai_model_price`, `ai_provider`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `user_skill`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Property | Value(s) |
|
||||
|-----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `ai_model_price`, `ai_provider`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `user_skill`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
@@ -459,10 +459,10 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `ai_model_price`, `ai_provider`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `user_skill`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Property | Value(s) |
|
||||
|-----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `ai_model_price`, `ai_provider`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `user_skill`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
@@ -554,10 +554,10 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `ai_model_price`, `ai_provider`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `user_skill`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Property | Value(s) |
|
||||
|-----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `ai_model_price`, `ai_provider`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `user_skill`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
@@ -960,9 +960,9 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `ai_model_price`, `ai_provider`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `user_skill`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Property | Value(s) |
|
||||
|-----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `ai_model_price`, `ai_provider`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `user_skill`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
Generated
+6
-6
File diff suppressed because one or more lines are too long
Generated
+5
-5
@@ -865,11 +865,11 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `type` | `*`, `ai_model_price`, `ai_provider`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `user_skill`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| `login_type` | `github`, `oidc`, `password`, `token` |
|
||||
| `scope` | `all`, `application_connect` |
|
||||
| Property | Value(s) |
|
||||
|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `type` | `*`, `ai_model_price`, `ai_provider`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `user_skill`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| `login_type` | `github`, `oidc`, `password`, `token` |
|
||||
| `scope` | `all`, `application_connect` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
|
||||
@@ -50,6 +50,11 @@ export const RBACResourceActions: Partial<
|
||||
create: "create new audit log entries",
|
||||
read: "read audit logs",
|
||||
},
|
||||
boundary_log: {
|
||||
create: "create boundary log records",
|
||||
delete: "delete boundary logs",
|
||||
read: "read boundary logs and session metadata",
|
||||
},
|
||||
boundary_usage: {
|
||||
delete: "delete boundary usage statistics",
|
||||
read: "read boundary usage statistics",
|
||||
|
||||
Generated
+10
@@ -554,6 +554,10 @@ export type APIKeyScope =
|
||||
| "audit_log:*"
|
||||
| "audit_log:create"
|
||||
| "audit_log:read"
|
||||
| "boundary_log:*"
|
||||
| "boundary_log:create"
|
||||
| "boundary_log:delete"
|
||||
| "boundary_log:read"
|
||||
| "boundary_usage:*"
|
||||
| "boundary_usage:delete"
|
||||
| "boundary_usage:read"
|
||||
@@ -780,6 +784,10 @@ export const APIKeyScopes: APIKeyScope[] = [
|
||||
"audit_log:*",
|
||||
"audit_log:create",
|
||||
"audit_log:read",
|
||||
"boundary_log:*",
|
||||
"boundary_log:create",
|
||||
"boundary_log:delete",
|
||||
"boundary_log:read",
|
||||
"boundary_usage:*",
|
||||
"boundary_usage:delete",
|
||||
"boundary_usage:read",
|
||||
@@ -6870,6 +6878,7 @@ export type RBACResource =
|
||||
| "assign_org_role"
|
||||
| "assign_role"
|
||||
| "audit_log"
|
||||
| "boundary_log"
|
||||
| "boundary_usage"
|
||||
| "chat"
|
||||
| "connection_log"
|
||||
@@ -6920,6 +6929,7 @@ export const RBACResources: RBACResource[] = [
|
||||
"assign_org_role",
|
||||
"assign_role",
|
||||
"audit_log",
|
||||
"boundary_log",
|
||||
"boundary_usage",
|
||||
"chat",
|
||||
"connection_log",
|
||||
|
||||
Reference in New Issue
Block a user