feat: add boundary usage tracking database schema and tracker skeleton (#21670)

feat: add boundary usage telemetry database schema and RBAC

Adds the foundation for tracking boundary usage telemetry across Coder
replicas. This includes:

  - Database schema: `boundary_usage_stats` table with per-replica stats
    (unique workspaces, unique users, allowed/denied request counts)
  - Database queries: upsert stats, get aggregated summary, reset stats,
    delete by replica ID
  - RBAC: `boundary_usage` resource type with read/update/delete actions,
    accessible only via system `BoundaryUsageTracker` subject (not regular
    user roles)
  - Tracker skeleton + docs: stub implementation in `coderd/boundaryusage/`

The tracker accumulates stats in memory and periodically flushes to the
database. Stats are aggregated across replicas for telemetry reporting,
then reset when a new reporting period begins. The tracker implementation
and plumbing will be done in a subsequent commit/PR.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Zach
2026-01-27 13:29:21 -07:00
committed by GitHub
parent e008f720b6
commit 7dfa33b410
30 changed files with 660 additions and 37 deletions
+10
View File
@@ -12467,6 +12467,10 @@ const docTemplate = `{
"audit_log:*",
"audit_log:create",
"audit_log:read",
"boundary_usage:*",
"boundary_usage:delete",
"boundary_usage:read",
"boundary_usage:update",
"coder:all",
"coder:apikeys.manage_self",
"coder:application_connect",
@@ -12665,6 +12669,10 @@ const docTemplate = `{
"APIKeyScopeAuditLogAll",
"APIKeyScopeAuditLogCreate",
"APIKeyScopeAuditLogRead",
"APIKeyScopeBoundaryUsageAll",
"APIKeyScopeBoundaryUsageDelete",
"APIKeyScopeBoundaryUsageRead",
"APIKeyScopeBoundaryUsageUpdate",
"APIKeyScopeCoderAll",
"APIKeyScopeCoderApikeysManageSelf",
"APIKeyScopeCoderApplicationConnect",
@@ -17740,6 +17748,7 @@ const docTemplate = `{
"assign_org_role",
"assign_role",
"audit_log",
"boundary_usage",
"connection_log",
"crypto_key",
"debug_info",
@@ -17784,6 +17793,7 @@ const docTemplate = `{
"ResourceAssignOrgRole",
"ResourceAssignRole",
"ResourceAuditLog",
"ResourceBoundaryUsage",
"ResourceConnectionLog",
"ResourceCryptoKey",
"ResourceDebugInfo",
+10
View File
@@ -11105,6 +11105,10 @@
"audit_log:*",
"audit_log:create",
"audit_log:read",
"boundary_usage:*",
"boundary_usage:delete",
"boundary_usage:read",
"boundary_usage:update",
"coder:all",
"coder:apikeys.manage_self",
"coder:application_connect",
@@ -11303,6 +11307,10 @@
"APIKeyScopeAuditLogAll",
"APIKeyScopeAuditLogCreate",
"APIKeyScopeAuditLogRead",
"APIKeyScopeBoundaryUsageAll",
"APIKeyScopeBoundaryUsageDelete",
"APIKeyScopeBoundaryUsageRead",
"APIKeyScopeBoundaryUsageUpdate",
"APIKeyScopeCoderAll",
"APIKeyScopeCoderApikeysManageSelf",
"APIKeyScopeCoderApplicationConnect",
@@ -16182,6 +16190,7 @@
"assign_org_role",
"assign_role",
"audit_log",
"boundary_usage",
"connection_log",
"crypto_key",
"debug_info",
@@ -16226,6 +16235,7 @@
"ResourceAssignOrgRole",
"ResourceAssignRole",
"ResourceAuditLog",
"ResourceBoundaryUsage",
"ResourceConnectionLog",
"ResourceCryptoKey",
"ResourceDebugInfo",
+79
View File
@@ -0,0 +1,79 @@
// Package boundaryusage tracks workspace boundary usage for telemetry reporting.
// The design intent is to track trends and rough usage patterns.
//
// Each replica does in-memory usage tracking. Boundary usage is inferred at the
// control plane when workspace agents call the ReportBoundaryLogs RPC. Accumulated
// stats are periodically flushed to a database table keyed by replica ID. Telemetry
// aggregates are computed across all replicas when generating snapshots.
//
// Aggregate Precision:
//
// The aggregated stats represent approximate usage over roughly the telemetry
// snapshot interval, not a precise time window. This imprecision arises because:
//
// - Each replica flushes independently, so their data covers slightly different
// time ranges (varying by up to the flush interval)
// - Unflushed in-memory data at snapshot time rolls into the next period
// - The snapshot captures "data flushed since last reset" rather than "usage
// during exactly the last N minutes"
//
// We accept this imprecision to keep the architecture simple. Each replica
// operates independently and flushes to the database on their own schedule.
// This approach also minimizes database load. The table contains at most one
// row per replica, so flushes are just upserts, and resets only delete N
// rows. There's no accumulation of historical data to clean up. The only
// synchronization is a database lock that ensures exactly one replica reports
// telemetry per period.
//
// Known Shortcomings:
//
// - Unique workspace/user counts may be inflated when the same workspace or
// user connects through multiple replicas, as each replica tracks its own
// unique set
// - Ad-hoc boundary usage in a workspace may not be accounted for e.g. if
// the boundary command is invoked directly with the --log-proxy-socket-path
// flag set to something other than the Workspace agent server.
//
// Implementation:
//
// The Tracker maintains sets of unique workspace IDs and user IDs, plus request
// counters. When boundary logs are reported, Track() adds the IDs to the sets
// and increments request counters.
//
// FlushToDB() writes stats to the database, replacing all values with the current
// in-memory state. Stats accumulate in memory throughout the telemetry period.
//
// A new period is detected when the upsert results in an INSERT (meaning
// telemetry deleted the replica's row). At that point, all in-memory stats are
// reset so they only count usage within the new period.
//
// Below is a sequence diagram showing the flow of boundary usage tracking.
//
// ┌───────┐ ┌───────────────┐ ┌──────────┐ ┌────┐ ┌───────────┐
// │ Agent │ │BoundaryLogsAPI│ │ Tracker │ │ DB │ │ Telemetry │
// └───┬───┘ └───────┬───────┘ └────┬─────┘ └──┬─┘ └─────┬─────┘
// │ │ │ │ │
// │ ReportBoundaryLogs│ │ │ │
// ├──────────────────►│ │ │ │
// │ │ Track(...) │ │ │
// │ ├────────────────►│ │ │
// │ : │ │ │ │
// │ : │ │ │ │
// │ ReportBoundaryLogs│ │ │ │
// ├──────────────────►│ │ │ │
// │ │ Track(...) │ │ │
// │ ├────────────────►│ │ │
// │ │ │ │ │
// │ │ │ FlushToDB │ │
// │ │ ├────────────►│ │
// │ │ │ : │ │
// │ │ │ : │ │
// │ │ │ FlushToDB │ │
// │ │ ├────────────►│ │
// │ │ │ │ │
// │ │ │ │ Snapshot │
// │ │ │ │ interval │
// │ │ │ │◄───────────┤
// │ │ │ │ Aggregate │
// │ │ │ │ & Reset │
package boundaryusage
+40
View File
@@ -0,0 +1,40 @@
package boundaryusage
import (
"context"
"sync"
"github.com/google/uuid"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/database"
)
// Tracker tracks boundary usage for telemetry reporting.
//
// All stats accumulate in memory throughout a telemetry period and are only
// reset when a new period begins.
type Tracker struct {
mu sync.Mutex //nolint:unused // Will be used when implemented.
workspaces map[uuid.UUID]struct{} //nolint:unused // Will be used when implemented.
users map[uuid.UUID]struct{} //nolint:unused // Will be used when implemented.
allowedRequests int64 //nolint:unused // Will be used when implemented.
deniedRequests int64 //nolint:unused // Will be used when implemented.
}
// NewTracker creates a new boundary usage tracker.
func NewTracker() (*Tracker, error) {
return nil, xerrors.New("not implemented")
}
// Track records boundary usage for a workspace.
func (*Tracker) Track(_, _ uuid.UUID, _, _ int64) error {
return xerrors.New("not implemented")
}
// FlushToDB writes the accumulated stats to the database. All values are
// replaced in the database (they represent the current in-memory state). If the
// database row was deleted (new telemetry period), all in-memory stats are reset.
func (*Tracker) FlushToDB(_ context.Context, _ database.Store, _ uuid.UUID) error {
return xerrors.New("not implemented")
}
+53
View File
@@ -649,6 +649,25 @@ var (
}),
Scope: rbac.ScopeAll,
}.WithCachedASTValue()
// Used by the boundary usage tracker to record telemetry statistics.
subjectBoundaryUsageTracker = rbac.Subject{
Type: rbac.SubjectTypeBoundaryUsageTracker,
FriendlyName: "Boundary Usage Tracker",
ID: uuid.Nil.String(),
Roles: rbac.Roles([]rbac.Role{
{
Identifier: rbac.RoleIdentifier{Name: "boundary-usage-tracker"},
DisplayName: "Boundary Usage Tracker",
Site: rbac.Permissions(map[string][]policy.Action{
rbac.ResourceBoundaryUsage.Type: rbac.ResourceBoundaryUsage.AvailableActions(),
}),
User: []rbac.Permission{},
ByOrgID: map[string]rbac.OrgPermissions{},
},
}),
Scope: rbac.ScopeAll,
}.WithCachedASTValue()
)
// AsProvisionerd returns a context with an actor that has permissions required
@@ -749,6 +768,12 @@ func AsDBPurge(ctx context.Context) context.Context {
return As(ctx, subjectDBPurge)
}
// AsBoundaryUsageTracker returns a context with an actor that has permissions
// required for the boundary usage tracker to record telemetry statistics.
func AsBoundaryUsageTracker(ctx context.Context) context.Context {
return As(ctx, subjectBoundaryUsageTracker)
}
var AsRemoveActor = rbac.Subject{
ID: "remove-actor",
}
@@ -1678,6 +1703,13 @@ func (q *querier) DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, u
return q.db.DeleteApplicationConnectAPIKeysByUserID(ctx, userID)
}
func (q *querier) DeleteBoundaryUsageStatsByReplicaID(ctx context.Context, replicaID uuid.UUID) error {
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceBoundaryUsage); err != nil {
return err
}
return q.db.DeleteBoundaryUsageStatsByReplicaID(ctx, replicaID)
}
func (q *querier) DeleteCryptoKey(ctx context.Context, arg database.DeleteCryptoKeyParams) (database.CryptoKey, error) {
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceCryptoKey); err != nil {
return database.CryptoKey{}, err
@@ -2239,6 +2271,13 @@ func (q *querier) GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUI
return q.db.GetAuthorizationUserRoles(ctx, userID)
}
func (q *querier) GetBoundaryUsageSummary(ctx context.Context, maxStalenessMs int64) (database.GetBoundaryUsageSummaryRow, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceBoundaryUsage); err != nil {
return database.GetBoundaryUsageSummaryRow{}, err
}
return q.db.GetBoundaryUsageSummary(ctx, maxStalenessMs)
}
func (q *querier) GetConnectionLogsOffset(ctx context.Context, arg database.GetConnectionLogsOffsetParams) ([]database.GetConnectionLogsOffsetRow, error) {
// Just like with the audit logs query, shortcut if the user is an owner.
err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceConnectionLog)
@@ -4852,6 +4891,13 @@ func (q *querier) RemoveUserFromGroups(ctx context.Context, arg database.RemoveU
return q.db.RemoveUserFromGroups(ctx, arg)
}
func (q *querier) ResetBoundaryUsageStats(ctx context.Context) error {
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceBoundaryUsage); err != nil {
return err
}
return q.db.ResetBoundaryUsageStats(ctx)
}
func (q *querier) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceSystem); err != nil {
return err
@@ -5943,6 +5989,13 @@ func (q *querier) UpsertApplicationName(ctx context.Context, value string) error
return q.db.UpsertApplicationName(ctx, value)
}
func (q *querier) UpsertBoundaryUsageStats(ctx context.Context, arg database.UpsertBoundaryUsageStatsParams) (bool, error) {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceBoundaryUsage); err != nil {
return false, err
}
return q.db.UpsertBoundaryUsageStats(ctx, arg)
}
func (q *querier) UpsertConnectionLog(ctx context.Context, arg database.UpsertConnectionLogParams) (database.ConnectionLog, error) {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceConnectionLog); err != nil {
return database.ConnectionLog{}, err
+18
View File
@@ -278,6 +278,11 @@ func (s *MethodTestSuite) TestAPIKey() {
dbm.EXPECT().DeleteApplicationConnectAPIKeysByUserID(gomock.Any(), a.UserID).Return(nil).AnyTimes()
check.Args(a.UserID).Asserts(rbac.ResourceApiKey.WithOwner(a.UserID.String()), policy.ActionDelete).Returns()
}))
s.Run("DeleteBoundaryUsageStatsByReplicaID", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
replicaID := uuid.New()
dbm.EXPECT().DeleteBoundaryUsageStatsByReplicaID(gomock.Any(), replicaID).Return(nil).AnyTimes()
check.Args(replicaID).Asserts(rbac.ResourceBoundaryUsage, policy.ActionDelete)
}))
s.Run("DeleteExternalAuthLink", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
a := testutil.Fake(s.T(), faker, database.ExternalAuthLink{})
dbm.EXPECT().GetExternalAuthLink(gomock.Any(), database.GetExternalAuthLinkParams{ProviderID: a.ProviderID, UserID: a.UserID}).Return(a, nil).AnyTimes()
@@ -528,6 +533,10 @@ func (s *MethodTestSuite) TestGroup() {
dbm.EXPECT().RemoveUserFromGroups(gomock.Any(), arg).Return(slice.New(g1.ID, g2.ID), nil).AnyTimes()
check.Args(arg).Asserts(rbac.ResourceSystem, policy.ActionUpdate).Returns(slice.New(g1.ID, g2.ID))
}))
s.Run("ResetBoundaryUsageStats", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
dbm.EXPECT().ResetBoundaryUsageStats(gomock.Any()).Return(nil).AnyTimes()
check.Args().Asserts(rbac.ResourceBoundaryUsage, policy.ActionDelete)
}))
s.Run("UpdateGroupByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
g := testutil.Fake(s.T(), faker, database.Group{})
@@ -2972,6 +2981,10 @@ func (s *MethodTestSuite) TestSystemFunctions() {
dbm.EXPECT().GetAuthorizationUserRoles(gomock.Any(), u.ID).Return(database.GetAuthorizationUserRolesRow{}, nil).AnyTimes()
check.Args(u.ID).Asserts(rbac.ResourceSystem, policy.ActionRead)
}))
s.Run("GetBoundaryUsageSummary", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
dbm.EXPECT().GetBoundaryUsageSummary(gomock.Any(), int64(1000)).Return(database.GetBoundaryUsageSummaryRow{}, nil).AnyTimes()
check.Args(int64(1000)).Asserts(rbac.ResourceBoundaryUsage, policy.ActionRead)
}))
s.Run("GetDERPMeshKey", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
dbm.EXPECT().GetDERPMeshKey(gomock.Any()).Return("testing", nil).AnyTimes()
check.Args().Asserts(rbac.ResourceSystem, policy.ActionRead)
@@ -3342,6 +3355,11 @@ func (s *MethodTestSuite) TestSystemFunctions() {
dbm.EXPECT().UpsertApplicationName(gomock.Any(), "").Return(nil).AnyTimes()
check.Args("").Asserts(rbac.ResourceDeploymentConfig, policy.ActionUpdate)
}))
s.Run("UpsertBoundaryUsageStats", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
arg := database.UpsertBoundaryUsageStatsParams{ReplicaID: uuid.New()}
dbm.EXPECT().UpsertBoundaryUsageStats(gomock.Any(), arg).Return(false, nil).AnyTimes()
check.Args(arg).Asserts(rbac.ResourceBoundaryUsage, policy.ActionUpdate)
}))
s.Run("GetHealthSettings", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
dbm.EXPECT().GetHealthSettings(gomock.Any()).Return("{}", nil).AnyTimes()
check.Args().Asserts()
+32
View File
@@ -335,6 +335,14 @@ func (m queryMetricsStore) DeleteApplicationConnectAPIKeysByUserID(ctx context.C
return r0
}
func (m queryMetricsStore) DeleteBoundaryUsageStatsByReplicaID(ctx context.Context, replicaID uuid.UUID) error {
start := time.Now()
r0 := m.s.DeleteBoundaryUsageStatsByReplicaID(ctx, replicaID)
m.queryLatencies.WithLabelValues("DeleteBoundaryUsageStatsByReplicaID").Observe(time.Since(start).Seconds())
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "DeleteBoundaryUsageStatsByReplicaID").Inc()
return r0
}
func (m queryMetricsStore) DeleteCryptoKey(ctx context.Context, arg database.DeleteCryptoKeyParams) (database.CryptoKey, error) {
start := time.Now()
r0, r1 := m.s.DeleteCryptoKey(ctx, arg)
@@ -894,6 +902,14 @@ func (m queryMetricsStore) GetAuthorizationUserRoles(ctx context.Context, userID
return r0, r1
}
func (m queryMetricsStore) GetBoundaryUsageSummary(ctx context.Context, maxStalenessMs int64) (database.GetBoundaryUsageSummaryRow, error) {
start := time.Now()
r0, r1 := m.s.GetBoundaryUsageSummary(ctx, maxStalenessMs)
m.queryLatencies.WithLabelValues("GetBoundaryUsageSummary").Observe(time.Since(start).Seconds())
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetBoundaryUsageSummary").Inc()
return r0, r1
}
func (m queryMetricsStore) GetConnectionLogsOffset(ctx context.Context, arg database.GetConnectionLogsOffsetParams) ([]database.GetConnectionLogsOffsetRow, error) {
start := time.Now()
r0, r1 := m.s.GetConnectionLogsOffset(ctx, arg)
@@ -3318,6 +3334,14 @@ func (m queryMetricsStore) RemoveUserFromGroups(ctx context.Context, arg databas
return r0, r1
}
func (m queryMetricsStore) ResetBoundaryUsageStats(ctx context.Context) error {
start := time.Now()
r0 := m.s.ResetBoundaryUsageStats(ctx)
m.queryLatencies.WithLabelValues("ResetBoundaryUsageStats").Observe(time.Since(start).Seconds())
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "ResetBoundaryUsageStats").Inc()
return r0
}
func (m queryMetricsStore) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error {
start := time.Now()
r0 := m.s.RevokeDBCryptKey(ctx, activeKeyDigest)
@@ -4069,6 +4093,14 @@ func (m queryMetricsStore) UpsertApplicationName(ctx context.Context, value stri
return r0
}
func (m queryMetricsStore) UpsertBoundaryUsageStats(ctx context.Context, arg database.UpsertBoundaryUsageStatsParams) (bool, error) {
start := time.Now()
r0, r1 := m.s.UpsertBoundaryUsageStats(ctx, arg)
m.queryLatencies.WithLabelValues("UpsertBoundaryUsageStats").Observe(time.Since(start).Seconds())
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UpsertBoundaryUsageStats").Inc()
return r0, r1
}
func (m queryMetricsStore) UpsertConnectionLog(ctx context.Context, arg database.UpsertConnectionLogParams) (database.ConnectionLog, error) {
start := time.Now()
r0, r1 := m.s.UpsertConnectionLog(ctx, arg)
+58
View File
@@ -511,6 +511,20 @@ func (mr *MockStoreMockRecorder) DeleteApplicationConnectAPIKeysByUserID(ctx, us
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteApplicationConnectAPIKeysByUserID", reflect.TypeOf((*MockStore)(nil).DeleteApplicationConnectAPIKeysByUserID), ctx, userID)
}
// DeleteBoundaryUsageStatsByReplicaID mocks base method.
func (m *MockStore) DeleteBoundaryUsageStatsByReplicaID(ctx context.Context, replicaID uuid.UUID) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteBoundaryUsageStatsByReplicaID", ctx, replicaID)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteBoundaryUsageStatsByReplicaID indicates an expected call of DeleteBoundaryUsageStatsByReplicaID.
func (mr *MockStoreMockRecorder) DeleteBoundaryUsageStatsByReplicaID(ctx, replicaID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBoundaryUsageStatsByReplicaID", reflect.TypeOf((*MockStore)(nil).DeleteBoundaryUsageStatsByReplicaID), ctx, replicaID)
}
// DeleteCryptoKey mocks base method.
func (m *MockStore) DeleteCryptoKey(ctx context.Context, arg database.DeleteCryptoKeyParams) (database.CryptoKey, error) {
m.ctrl.T.Helper()
@@ -1634,6 +1648,21 @@ func (mr *MockStoreMockRecorder) GetAuthorizedWorkspacesAndAgentsByOwnerID(ctx,
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthorizedWorkspacesAndAgentsByOwnerID", reflect.TypeOf((*MockStore)(nil).GetAuthorizedWorkspacesAndAgentsByOwnerID), ctx, ownerID, prepared)
}
// GetBoundaryUsageSummary mocks base method.
func (m *MockStore) GetBoundaryUsageSummary(ctx context.Context, maxStalenessMs int64) (database.GetBoundaryUsageSummaryRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBoundaryUsageSummary", ctx, maxStalenessMs)
ret0, _ := ret[0].(database.GetBoundaryUsageSummaryRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetBoundaryUsageSummary indicates an expected call of GetBoundaryUsageSummary.
func (mr *MockStoreMockRecorder) GetBoundaryUsageSummary(ctx, maxStalenessMs any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoundaryUsageSummary", reflect.TypeOf((*MockStore)(nil).GetBoundaryUsageSummary), ctx, maxStalenessMs)
}
// GetConnectionLogsOffset mocks base method.
func (m *MockStore) GetConnectionLogsOffset(ctx context.Context, arg database.GetConnectionLogsOffsetParams) ([]database.GetConnectionLogsOffsetRow, error) {
m.ctrl.T.Helper()
@@ -6249,6 +6278,20 @@ func (mr *MockStoreMockRecorder) RemoveUserFromGroups(ctx, arg any) *gomock.Call
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveUserFromGroups", reflect.TypeOf((*MockStore)(nil).RemoveUserFromGroups), ctx, arg)
}
// ResetBoundaryUsageStats mocks base method.
func (m *MockStore) ResetBoundaryUsageStats(ctx context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ResetBoundaryUsageStats", ctx)
ret0, _ := ret[0].(error)
return ret0
}
// ResetBoundaryUsageStats indicates an expected call of ResetBoundaryUsageStats.
func (mr *MockStoreMockRecorder) ResetBoundaryUsageStats(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetBoundaryUsageStats", reflect.TypeOf((*MockStore)(nil).ResetBoundaryUsageStats), ctx)
}
// RevokeDBCryptKey mocks base method.
func (m *MockStore) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error {
m.ctrl.T.Helper()
@@ -7603,6 +7646,21 @@ func (mr *MockStoreMockRecorder) UpsertApplicationName(ctx, value any) *gomock.C
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertApplicationName", reflect.TypeOf((*MockStore)(nil).UpsertApplicationName), ctx, value)
}
// UpsertBoundaryUsageStats mocks base method.
func (m *MockStore) UpsertBoundaryUsageStats(ctx context.Context, arg database.UpsertBoundaryUsageStatsParams) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpsertBoundaryUsageStats", ctx, arg)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpsertBoundaryUsageStats indicates an expected call of UpsertBoundaryUsageStats.
func (mr *MockStoreMockRecorder) UpsertBoundaryUsageStats(ctx, arg any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertBoundaryUsageStats", reflect.TypeOf((*MockStore)(nil).UpsertBoundaryUsageStats), ctx, arg)
}
// UpsertConnectionLog mocks base method.
func (m *MockStore) UpsertConnectionLog(ctx context.Context, arg database.UpsertConnectionLogParams) (database.ConnectionLog, error) {
m.ctrl.T.Helper()
+35 -2
View File
@@ -204,7 +204,11 @@ CREATE TYPE api_key_scope AS ENUM (
'task:delete',
'task:*',
'workspace:share',
'workspace_dormant:share'
'workspace_dormant:share',
'boundary_usage:*',
'boundary_usage:delete',
'boundary_usage:read',
'boundary_usage:update'
);
CREATE TYPE app_sharing_level AS ENUM (
@@ -1111,6 +1115,32 @@ CREATE TABLE audit_logs (
resource_icon text NOT NULL
);
CREATE TABLE boundary_usage_stats (
replica_id uuid NOT NULL,
unique_workspaces_count bigint DEFAULT 0 NOT NULL,
unique_users_count bigint DEFAULT 0 NOT NULL,
allowed_requests bigint DEFAULT 0 NOT NULL,
denied_requests bigint DEFAULT 0 NOT NULL,
window_start timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL
);
COMMENT ON TABLE boundary_usage_stats IS 'Per-replica boundary usage statistics for telemetry aggregation.';
COMMENT ON COLUMN boundary_usage_stats.replica_id IS 'The unique identifier of the replica reporting stats.';
COMMENT ON COLUMN boundary_usage_stats.unique_workspaces_count IS 'Count of unique workspaces that used boundary on this replica.';
COMMENT ON COLUMN boundary_usage_stats.unique_users_count IS 'Count of unique users that used boundary on this replica.';
COMMENT ON COLUMN boundary_usage_stats.allowed_requests IS 'Total allowed requests through boundary on this replica.';
COMMENT ON COLUMN boundary_usage_stats.denied_requests IS 'Total denied requests through boundary on this replica.';
COMMENT ON COLUMN boundary_usage_stats.window_start IS 'Start of the time window for these stats, set on first flush after reset.';
COMMENT ON COLUMN boundary_usage_stats.updated_at IS 'Timestamp of the last update to this row.';
CREATE TABLE connection_logs (
id uuid NOT NULL,
connect_time timestamp with time zone NOT NULL,
@@ -2002,7 +2032,7 @@ CREATE TABLE telemetry_items (
CREATE TABLE telemetry_locks (
event_type text NOT NULL,
period_ending_at timestamp with time zone NOT NULL,
CONSTRAINT telemetry_lock_event_type_constraint CHECK ((event_type = 'aibridge_interceptions_summary'::text))
CONSTRAINT telemetry_lock_event_type_constraint CHECK ((event_type = ANY (ARRAY['aibridge_interceptions_summary'::text, 'boundary_usage_summary'::text])))
);
COMMENT ON TABLE telemetry_locks IS 'Telemetry lock tracking table for deduplication of heartbeat events across replicas.';
@@ -2941,6 +2971,9 @@ ALTER TABLE ONLY api_keys
ALTER TABLE ONLY audit_logs
ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id);
ALTER TABLE ONLY boundary_usage_stats
ADD CONSTRAINT boundary_usage_stats_pkey PRIMARY KEY (replica_id);
ALTER TABLE ONLY connection_logs
ADD CONSTRAINT connection_logs_pkey PRIMARY KEY (id);
@@ -0,0 +1,8 @@
-- Restore the original telemetry_locks event_type constraint.
ALTER TABLE telemetry_locks DROP CONSTRAINT telemetry_lock_event_type_constraint;
ALTER TABLE telemetry_locks ADD CONSTRAINT telemetry_lock_event_type_constraint
CHECK (event_type IN ('aibridge_interceptions_summary'));
DROP TABLE boundary_usage_stats;
-- No-op for boundary_usage scopes: keep enum values to avoid dependency churn.
@@ -0,0 +1,29 @@
CREATE TABLE boundary_usage_stats (
replica_id UUID PRIMARY KEY,
unique_workspaces_count BIGINT NOT NULL DEFAULT 0,
unique_users_count BIGINT NOT NULL DEFAULT 0,
allowed_requests BIGINT NOT NULL DEFAULT 0,
denied_requests BIGINT NOT NULL DEFAULT 0,
window_start TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE boundary_usage_stats IS 'Per-replica boundary usage statistics for telemetry aggregation.';
COMMENT ON COLUMN boundary_usage_stats.replica_id IS 'The unique identifier of the replica reporting stats.';
COMMENT ON COLUMN boundary_usage_stats.unique_workspaces_count IS 'Count of unique workspaces that used boundary on this replica.';
COMMENT ON COLUMN boundary_usage_stats.unique_users_count IS 'Count of unique users that used boundary on this replica.';
COMMENT ON COLUMN boundary_usage_stats.allowed_requests IS 'Total allowed requests through boundary on this replica.';
COMMENT ON COLUMN boundary_usage_stats.denied_requests IS 'Total denied requests through boundary on this replica.';
COMMENT ON COLUMN boundary_usage_stats.window_start IS 'Start of the time window for these stats, set on first flush after reset.';
COMMENT ON COLUMN boundary_usage_stats.updated_at IS 'Timestamp of the last update to this row.';
-- Add boundary_usage_summary to the telemetry_locks event_type constraint.
ALTER TABLE telemetry_locks DROP CONSTRAINT telemetry_lock_event_type_constraint;
ALTER TABLE telemetry_locks ADD CONSTRAINT telemetry_lock_event_type_constraint
CHECK (event_type IN ('aibridge_interceptions_summary', 'boundary_usage_summary'));
-- Add boundary_usage scopes for RBAC.
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'boundary_usage:*';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'boundary_usage:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'boundary_usage:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'boundary_usage:update';
@@ -0,0 +1,2 @@
INSERT INTO boundary_usage_stats (replica_id, unique_workspaces_count, unique_users_count, allowed_requests, denied_requests, window_start, updated_at)
VALUES ('00000000-0000-0000-0000-000000000001', 10, 5, 100, 20, NOW(), NOW());
+31 -1
View File
@@ -213,6 +213,10 @@ const (
ApiKeyScopeTask APIKeyScope = "task:*"
ApiKeyScopeWorkspaceShare APIKeyScope = "workspace:share"
ApiKeyScopeWorkspaceDormantShare APIKeyScope = "workspace_dormant:share"
ApiKeyScopeBoundaryUsage APIKeyScope = "boundary_usage:*"
ApiKeyScopeBoundaryUsageDelete APIKeyScope = "boundary_usage:delete"
ApiKeyScopeBoundaryUsageRead APIKeyScope = "boundary_usage:read"
ApiKeyScopeBoundaryUsageUpdate APIKeyScope = "boundary_usage:update"
)
func (e *APIKeyScope) Scan(src interface{}) error {
@@ -445,7 +449,11 @@ func (e APIKeyScope) Valid() bool {
ApiKeyScopeTaskDelete,
ApiKeyScopeTask,
ApiKeyScopeWorkspaceShare,
ApiKeyScopeWorkspaceDormantShare:
ApiKeyScopeWorkspaceDormantShare,
ApiKeyScopeBoundaryUsage,
ApiKeyScopeBoundaryUsageDelete,
ApiKeyScopeBoundaryUsageRead,
ApiKeyScopeBoundaryUsageUpdate:
return true
}
return false
@@ -647,6 +655,10 @@ func AllAPIKeyScopeValues() []APIKeyScope {
ApiKeyScopeTask,
ApiKeyScopeWorkspaceShare,
ApiKeyScopeWorkspaceDormantShare,
ApiKeyScopeBoundaryUsage,
ApiKeyScopeBoundaryUsageDelete,
ApiKeyScopeBoundaryUsageRead,
ApiKeyScopeBoundaryUsageUpdate,
}
}
@@ -3702,6 +3714,24 @@ type AuditLog struct {
ResourceIcon string `db:"resource_icon" json:"resource_icon"`
}
// Per-replica boundary usage statistics for telemetry aggregation.
type BoundaryUsageStat struct {
// The unique identifier of the replica reporting stats.
ReplicaID uuid.UUID `db:"replica_id" json:"replica_id"`
// Count of unique workspaces that used boundary on this replica.
UniqueWorkspacesCount int64 `db:"unique_workspaces_count" json:"unique_workspaces_count"`
// Count of unique users that used boundary on this replica.
UniqueUsersCount int64 `db:"unique_users_count" json:"unique_users_count"`
// Total allowed requests through boundary on this replica.
AllowedRequests int64 `db:"allowed_requests" json:"allowed_requests"`
// Total denied requests through boundary on this replica.
DeniedRequests int64 `db:"denied_requests" json:"denied_requests"`
// Start of the time window for these stats, set on first flush after reset.
WindowStart time.Time `db:"window_start" json:"window_start"`
// Timestamp of the last update to this row.
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
type ConnectionLog struct {
ID uuid.UUID `db:"id" json:"id"`
ConnectTime time.Time `db:"connect_time" json:"connect_time"`
+13
View File
@@ -88,6 +88,8 @@ type sqlcQuerier interface {
// be recreated.
DeleteAllWebpushSubscriptions(ctx context.Context) error
DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error
// Deletes boundary usage statistics for a specific replica.
DeleteBoundaryUsageStatsByReplicaID(ctx context.Context, replicaID uuid.UUID) error
DeleteCryptoKey(ctx context.Context, arg DeleteCryptoKeyParams) (CryptoKey, error)
DeleteCustomRole(ctx context.Context, arg DeleteCustomRoleParams) error
DeleteExpiredAPIKeys(ctx context.Context, arg DeleteExpiredAPIKeysParams) (int64, error)
@@ -194,6 +196,10 @@ type sqlcQuerier interface {
// This function returns roles for authorization purposes. Implied member roles
// are included.
GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUID) (GetAuthorizationUserRolesRow, error)
// Aggregates boundary usage statistics across all replicas. Filters to only
// include data where window_start is within the given interval to exclude
// stale data.
GetBoundaryUsageSummary(ctx context.Context, maxStalenessMs int64) (GetBoundaryUsageSummaryRow, error)
GetConnectionLogsOffset(ctx context.Context, arg GetConnectionLogsOffsetParams) ([]GetConnectionLogsOffsetRow, error)
GetCoordinatorResumeTokenSigningKey(ctx context.Context) (string, error)
GetCryptoKeyByFeatureAndSequence(ctx context.Context, arg GetCryptoKeyByFeatureAndSequenceParams) (CryptoKey, error)
@@ -646,6 +652,9 @@ type sqlcQuerier interface {
RegisterWorkspaceProxy(ctx context.Context, arg RegisterWorkspaceProxyParams) (WorkspaceProxy, error)
RemoveUserFromAllGroups(ctx context.Context, userID uuid.UUID) error
RemoveUserFromGroups(ctx context.Context, arg RemoveUserFromGroupsParams) ([]uuid.UUID, error)
// Deletes all boundary usage statistics. Called after telemetry reports the
// aggregated stats. Each replica will insert a fresh row on its next flush.
ResetBoundaryUsageStats(ctx context.Context) error
RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error
// Note that this selects from the CTE, not the original table. The CTE is named
// the same as the original table to trick sqlc into reusing the existing struct
@@ -753,6 +762,10 @@ type sqlcQuerier interface {
UpsertAnnouncementBanners(ctx context.Context, value string) error
UpsertAppSecurityKey(ctx context.Context, value string) error
UpsertApplicationName(ctx context.Context, value string) error
// Upserts boundary usage statistics for a replica. All values are replaced with
// the current in-memory state. Returns true if this was an insert (new period),
// false if update.
UpsertBoundaryUsageStats(ctx context.Context, arg UpsertBoundaryUsageStatsParams) (bool, error)
UpsertConnectionLog(ctx context.Context, arg UpsertConnectionLogParams) (ConnectionLog, error)
UpsertCoordinatorResumeTokenSigningKey(ctx context.Context, value string) error
// The default proxy is implied and not actually stored in the database.
+103
View File
@@ -1980,6 +1980,109 @@ func (q *sqlQuerier) InsertAuditLog(ctx context.Context, arg InsertAuditLogParam
return i, err
}
const deleteBoundaryUsageStatsByReplicaID = `-- name: DeleteBoundaryUsageStatsByReplicaID :exec
DELETE FROM boundary_usage_stats WHERE replica_id = $1
`
// Deletes boundary usage statistics for a specific replica.
func (q *sqlQuerier) DeleteBoundaryUsageStatsByReplicaID(ctx context.Context, replicaID uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteBoundaryUsageStatsByReplicaID, replicaID)
return err
}
const getBoundaryUsageSummary = `-- name: GetBoundaryUsageSummary :one
SELECT
COALESCE(SUM(unique_workspaces_count), 0)::bigint AS unique_workspaces,
COALESCE(SUM(unique_users_count), 0)::bigint AS unique_users,
COALESCE(SUM(allowed_requests), 0)::bigint AS allowed_requests,
COALESCE(SUM(denied_requests), 0)::bigint AS denied_requests
FROM boundary_usage_stats
WHERE window_start >= NOW() - ($1::bigint || ' ms')::interval
`
type GetBoundaryUsageSummaryRow struct {
UniqueWorkspaces int64 `db:"unique_workspaces" json:"unique_workspaces"`
UniqueUsers int64 `db:"unique_users" json:"unique_users"`
AllowedRequests int64 `db:"allowed_requests" json:"allowed_requests"`
DeniedRequests int64 `db:"denied_requests" json:"denied_requests"`
}
// Aggregates boundary usage statistics across all replicas. Filters to only
// include data where window_start is within the given interval to exclude
// stale data.
func (q *sqlQuerier) GetBoundaryUsageSummary(ctx context.Context, maxStalenessMs int64) (GetBoundaryUsageSummaryRow, error) {
row := q.db.QueryRowContext(ctx, getBoundaryUsageSummary, maxStalenessMs)
var i GetBoundaryUsageSummaryRow
err := row.Scan(
&i.UniqueWorkspaces,
&i.UniqueUsers,
&i.AllowedRequests,
&i.DeniedRequests,
)
return i, err
}
const resetBoundaryUsageStats = `-- name: ResetBoundaryUsageStats :exec
DELETE FROM boundary_usage_stats
`
// Deletes all boundary usage statistics. Called after telemetry reports the
// aggregated stats. Each replica will insert a fresh row on its next flush.
func (q *sqlQuerier) ResetBoundaryUsageStats(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, resetBoundaryUsageStats)
return err
}
const upsertBoundaryUsageStats = `-- name: UpsertBoundaryUsageStats :one
INSERT INTO boundary_usage_stats (
replica_id,
unique_workspaces_count,
unique_users_count,
allowed_requests,
denied_requests,
window_start,
updated_at
) VALUES (
$1,
$2,
$3,
$4,
$5,
NOW(),
NOW()
) ON CONFLICT (replica_id) DO UPDATE SET
unique_workspaces_count = EXCLUDED.unique_workspaces_count,
unique_users_count = EXCLUDED.unique_users_count,
allowed_requests = EXCLUDED.allowed_requests,
denied_requests = EXCLUDED.denied_requests,
updated_at = NOW()
RETURNING (xmax = 0) AS new_period
`
type UpsertBoundaryUsageStatsParams struct {
ReplicaID uuid.UUID `db:"replica_id" json:"replica_id"`
UniqueWorkspacesCount int64 `db:"unique_workspaces_count" json:"unique_workspaces_count"`
UniqueUsersCount int64 `db:"unique_users_count" json:"unique_users_count"`
AllowedRequests int64 `db:"allowed_requests" json:"allowed_requests"`
DeniedRequests int64 `db:"denied_requests" json:"denied_requests"`
}
// Upserts boundary usage statistics for a replica. All values are replaced with
// the current in-memory state. Returns true if this was an insert (new period),
// false if update.
func (q *sqlQuerier) UpsertBoundaryUsageStats(ctx context.Context, arg UpsertBoundaryUsageStatsParams) (bool, error) {
row := q.db.QueryRowContext(ctx, upsertBoundaryUsageStats,
arg.ReplicaID,
arg.UniqueWorkspacesCount,
arg.UniqueUsersCount,
arg.AllowedRequests,
arg.DeniedRequests,
)
var new_period bool
err := row.Scan(&new_period)
return new_period, err
}
const countConnectionLogs = `-- name: CountConnectionLogs :one
SELECT
COUNT(*) AS count
@@ -0,0 +1,48 @@
-- name: UpsertBoundaryUsageStats :one
-- Upserts boundary usage statistics for a replica. All values are replaced with
-- the current in-memory state. Returns true if this was an insert (new period),
-- false if update.
INSERT INTO boundary_usage_stats (
replica_id,
unique_workspaces_count,
unique_users_count,
allowed_requests,
denied_requests,
window_start,
updated_at
) VALUES (
@replica_id,
@unique_workspaces_count,
@unique_users_count,
@allowed_requests,
@denied_requests,
NOW(),
NOW()
) ON CONFLICT (replica_id) DO UPDATE SET
unique_workspaces_count = EXCLUDED.unique_workspaces_count,
unique_users_count = EXCLUDED.unique_users_count,
allowed_requests = EXCLUDED.allowed_requests,
denied_requests = EXCLUDED.denied_requests,
updated_at = NOW()
RETURNING (xmax = 0) AS new_period;
-- name: GetBoundaryUsageSummary :one
-- Aggregates boundary usage statistics across all replicas. Filters to only
-- include data where window_start is within the given interval to exclude
-- stale data.
SELECT
COALESCE(SUM(unique_workspaces_count), 0)::bigint AS unique_workspaces,
COALESCE(SUM(unique_users_count), 0)::bigint AS unique_users,
COALESCE(SUM(allowed_requests), 0)::bigint AS allowed_requests,
COALESCE(SUM(denied_requests), 0)::bigint AS denied_requests
FROM boundary_usage_stats
WHERE window_start >= NOW() - (@max_staleness_ms::bigint || ' ms')::interval;
-- name: ResetBoundaryUsageStats :exec
-- Deletes all boundary usage statistics. Called after telemetry reports the
-- aggregated stats. Each replica will insert a fresh row on its next flush.
DELETE FROM boundary_usage_stats;
-- name: DeleteBoundaryUsageStatsByReplicaID :exec
-- Deletes boundary usage statistics for a specific replica.
DELETE FROM boundary_usage_stats WHERE replica_id = @replica_id;
+1
View File
@@ -13,6 +13,7 @@ const (
UniqueAibridgeUserPromptsPkey UniqueConstraint = "aibridge_user_prompts_pkey" // ALTER TABLE ONLY aibridge_user_prompts ADD CONSTRAINT aibridge_user_prompts_pkey PRIMARY KEY (id);
UniqueAPIKeysPkey UniqueConstraint = "api_keys_pkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id);
UniqueAuditLogsPkey UniqueConstraint = "audit_logs_pkey" // ALTER TABLE ONLY audit_logs ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id);
UniqueBoundaryUsageStatsPkey UniqueConstraint = "boundary_usage_stats_pkey" // ALTER TABLE ONLY boundary_usage_stats ADD CONSTRAINT boundary_usage_stats_pkey PRIMARY KEY (replica_id);
UniqueConnectionLogsPkey UniqueConstraint = "connection_logs_pkey" // ALTER TABLE ONLY connection_logs ADD CONSTRAINT connection_logs_pkey PRIMARY KEY (id);
UniqueCryptoKeysPkey UniqueConstraint = "crypto_keys_pkey" // ALTER TABLE ONLY crypto_keys ADD CONSTRAINT crypto_keys_pkey PRIMARY KEY (feature, sequence);
UniqueCustomRolesUniqueKey UniqueConstraint = "custom_roles_unique_key" // ALTER TABLE ONLY custom_roles ADD CONSTRAINT custom_roles_unique_key UNIQUE (name, organization_id);
+1
View File
@@ -80,6 +80,7 @@ const (
SubjectTypeUsagePublisher SubjectType = "usage_publisher"
SubjectAibridged SubjectType = "aibridged"
SubjectTypeDBPurge SubjectType = "dbpurge"
SubjectTypeBoundaryUsageTracker SubjectType = "boundary_usage_tracker"
)
const (
+10
View File
@@ -63,6 +63,15 @@ var (
Type: "audit_log",
}
// ResourceBoundaryUsage
// Valid Actions
// - "ActionDelete" :: delete boundary usage statistics
// - "ActionRead" :: read boundary usage statistics
// - "ActionUpdate" :: upsert boundary usage statistics
ResourceBoundaryUsage = Object{
Type: "boundary_usage",
}
// ResourceConnectionLog
// Valid Actions
// - "ActionRead" :: read connection logs
@@ -417,6 +426,7 @@ func AllResources() []Objecter {
ResourceAssignOrgRole,
ResourceAssignRole,
ResourceAuditLog,
ResourceBoundaryUsage,
ResourceConnectionLog,
ResourceCryptoKey,
ResourceDebugInfo,
+7
View File
@@ -380,4 +380,11 @@ var RBACPermissions = map[string]PermissionDefinition{
ActionCreate: "create aibridge interceptions & related records",
},
},
"boundary_usage": {
Actions: map[Action]ActionDefinition{
ActionRead: "read boundary usage statistics",
ActionUpdate: "upsert boundary usage statistics",
ActionDelete: "delete boundary usage statistics",
},
},
}
+3 -3
View File
@@ -286,7 +286,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
// Workspace dormancy and workspace are omitted.
// Workspace is specifically handled based on the opts.NoOwnerWorkspaceExec.
// Owners cannot access other users' secrets.
allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUserSecret, ResourceUsageEvent),
allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUserSecret, ResourceUsageEvent, ResourceBoundaryUsage),
// This adds back in the Workspace permissions.
Permissions(map[string][]policy.Action{
ResourceWorkspace.Type: ownerWorkspaceActions,
@@ -309,7 +309,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
ResourceOauth2App.Type: {policy.ActionRead},
ResourceWorkspaceProxy.Type: {policy.ActionRead},
}),
User: append(allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUser, ResourceOrganizationMember, ResourceOrganizationMember),
User: append(allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUser, ResourceOrganizationMember, ResourceOrganizationMember, ResourceBoundaryUsage),
Permissions(map[string][]policy.Action{
// Users cannot do create/update/delete on themselves, but they
// can read their own details.
@@ -433,7 +433,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
ByOrgID: map[string]OrgPermissions{
// Org admins should not have workspace exec perms.
organizationID.String(): {
Org: append(allPermsExcept(ResourceWorkspace, ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceAssignRole, ResourceUserSecret), Permissions(map[string][]policy.Action{
Org: append(allPermsExcept(ResourceWorkspace, ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceAssignRole, ResourceUserSecret, ResourceBoundaryUsage), Permissions(map[string][]policy.Action{
ResourceWorkspaceDormant.Type: {policy.ActionRead, policy.ActionDelete, policy.ActionCreate, policy.ActionUpdate, policy.ActionWorkspaceStop, policy.ActionCreateAgent, policy.ActionDeleteAgent},
ResourceWorkspace.Type: slice.Omit(ResourceWorkspace.AvailableActions(), policy.ActionApplicationConnect, policy.ActionSSH),
// PrebuiltWorkspaces are a subset of Workspaces.
+8
View File
@@ -1003,6 +1003,14 @@ func TestRolePermissions(t *testing.T) {
},
},
},
{
Name: "BoundaryUsage",
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
Resource: rbac.ResourceBoundaryUsage,
AuthorizeMap: map[bool][]hasAuthSubjects{
false: {owner, setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin},
},
},
}
// We expect every permission to be tested above.
+9
View File
@@ -25,6 +25,9 @@ const (
ScopeAssignRoleUnassign ScopeName = "assign_role:unassign"
ScopeAuditLogCreate ScopeName = "audit_log:create"
ScopeAuditLogRead ScopeName = "audit_log:read"
ScopeBoundaryUsageDelete ScopeName = "boundary_usage:delete"
ScopeBoundaryUsageRead ScopeName = "boundary_usage:read"
ScopeBoundaryUsageUpdate ScopeName = "boundary_usage:update"
ScopeConnectionLogRead ScopeName = "connection_log:read"
ScopeConnectionLogUpdate ScopeName = "connection_log:update"
ScopeCryptoKeyCreate ScopeName = "crypto_key:create"
@@ -180,6 +183,9 @@ func (e ScopeName) Valid() bool {
ScopeAssignRoleUnassign,
ScopeAuditLogCreate,
ScopeAuditLogRead,
ScopeBoundaryUsageDelete,
ScopeBoundaryUsageRead,
ScopeBoundaryUsageUpdate,
ScopeConnectionLogRead,
ScopeConnectionLogUpdate,
ScopeCryptoKeyCreate,
@@ -336,6 +342,9 @@ func AllScopeNameValues() []ScopeName {
ScopeAssignRoleUnassign,
ScopeAuditLogCreate,
ScopeAuditLogRead,
ScopeBoundaryUsageDelete,
ScopeBoundaryUsageRead,
ScopeBoundaryUsageUpdate,
ScopeConnectionLogRead,
ScopeConnectionLogUpdate,
ScopeCryptoKeyCreate,
+4
View File
@@ -29,6 +29,10 @@ const (
APIKeyScopeAuditLogAll APIKeyScope = "audit_log:*"
APIKeyScopeAuditLogCreate APIKeyScope = "audit_log:create"
APIKeyScopeAuditLogRead APIKeyScope = "audit_log:read"
APIKeyScopeBoundaryUsageAll APIKeyScope = "boundary_usage:*"
APIKeyScopeBoundaryUsageDelete APIKeyScope = "boundary_usage:delete"
APIKeyScopeBoundaryUsageRead APIKeyScope = "boundary_usage:read"
APIKeyScopeBoundaryUsageUpdate APIKeyScope = "boundary_usage:update"
APIKeyScopeCoderAll APIKeyScope = "coder:all"
APIKeyScopeCoderApikeysManageSelf APIKeyScope = "coder:apikeys.manage_self"
APIKeyScopeCoderApplicationConnect APIKeyScope = "coder:application_connect"
+2
View File
@@ -10,6 +10,7 @@ const (
ResourceAssignOrgRole RBACResource = "assign_org_role"
ResourceAssignRole RBACResource = "assign_role"
ResourceAuditLog RBACResource = "audit_log"
ResourceBoundaryUsage RBACResource = "boundary_usage"
ResourceConnectionLog RBACResource = "connection_log"
ResourceCryptoKey RBACResource = "crypto_key"
ResourceDebugInfo RBACResource = "debug_info"
@@ -79,6 +80,7 @@ var RBACResourceActions = map[RBACResource][]RBACAction{
ResourceAssignOrgRole: {ActionAssign, ActionCreate, ActionDelete, ActionRead, ActionUnassign, ActionUpdate},
ResourceAssignRole: {ActionAssign, ActionRead, ActionUnassign},
ResourceAuditLog: {ActionCreate, ActionRead},
ResourceBoundaryUsage: {ActionDelete, ActionRead, ActionUpdate},
ResourceConnectionLog: {ActionRead, ActionUpdate},
ResourceCryptoKey: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
ResourceDebugInfo: {ActionRead},
+20 -20
View File
@@ -172,10 +172,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_personal`, `use`, `view_insights` |
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `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`, `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_personal`, `use`, `view_insights` |
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `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`, `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).
@@ -305,10 +305,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_personal`, `use`, `view_insights` |
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `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`, `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_personal`, `use`, `view_insights` |
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `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`, `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).
@@ -438,10 +438,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_personal`, `use`, `view_insights` |
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `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`, `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_personal`, `use`, `view_insights` |
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `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`, `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).
@@ -533,10 +533,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_personal`, `use`, `view_insights` |
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `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`, `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_personal`, `use`, `view_insights` |
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `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`, `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).
@@ -850,9 +850,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_personal`, `use`, `view_insights` |
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `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`, `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_personal`, `use`, `view_insights` |
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `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`, `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).
+6 -6
View File
@@ -862,9 +862,9 @@
#### Enumerated Values
| Value(s) |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `aibridge_interception:*`, `aibridge_interception:create`, `aibridge_interception:read`, `aibridge_interception:update`, `all`, `api_key:*`, `api_key:create`, `api_key:delete`, `api_key:read`, `api_key:update`, `application_connect`, `assign_org_role:*`, `assign_org_role:assign`, `assign_org_role:create`, `assign_org_role:delete`, `assign_org_role:read`, `assign_org_role:unassign`, `assign_org_role:update`, `assign_role:*`, `assign_role:assign`, `assign_role:read`, `assign_role:unassign`, `audit_log:*`, `audit_log:create`, `audit_log:read`, `coder:all`, `coder:apikeys.manage_self`, `coder:application_connect`, `coder:templates.author`, `coder:templates.build`, `coder:workspaces.access`, `coder:workspaces.create`, `coder:workspaces.delete`, `coder:workspaces.operate`, `connection_log:*`, `connection_log:read`, `connection_log:update`, `crypto_key:*`, `crypto_key:create`, `crypto_key:delete`, `crypto_key:read`, `crypto_key:update`, `debug_info:*`, `debug_info:read`, `deployment_config:*`, `deployment_config:read`, `deployment_config:update`, `deployment_stats:*`, `deployment_stats:read`, `file:*`, `file:create`, `file:read`, `group:*`, `group:create`, `group:delete`, `group:read`, `group:update`, `group_member:*`, `group_member:read`, `idpsync_settings:*`, `idpsync_settings:read`, `idpsync_settings:update`, `inbox_notification:*`, `inbox_notification:create`, `inbox_notification:read`, `inbox_notification:update`, `license:*`, `license:create`, `license:delete`, `license:read`, `notification_message:*`, `notification_message:create`, `notification_message:delete`, `notification_message:read`, `notification_message:update`, `notification_preference:*`, `notification_preference:read`, `notification_preference:update`, `notification_template:*`, `notification_template:read`, `notification_template:update`, `oauth2_app:*`, `oauth2_app:create`, `oauth2_app:delete`, `oauth2_app:read`, `oauth2_app:update`, `oauth2_app_code_token:*`, `oauth2_app_code_token:create`, `oauth2_app_code_token:delete`, `oauth2_app_code_token:read`, `oauth2_app_secret:*`, `oauth2_app_secret:create`, `oauth2_app_secret:delete`, `oauth2_app_secret:read`, `oauth2_app_secret:update`, `organization:*`, `organization:create`, `organization:delete`, `organization:read`, `organization:update`, `organization_member:*`, `organization_member:create`, `organization_member:delete`, `organization_member:read`, `organization_member:update`, `prebuilt_workspace:*`, `prebuilt_workspace:delete`, `prebuilt_workspace:update`, `provisioner_daemon:*`, `provisioner_daemon:create`, `provisioner_daemon:delete`, `provisioner_daemon:read`, `provisioner_daemon:update`, `provisioner_jobs:*`, `provisioner_jobs:create`, `provisioner_jobs:read`, `provisioner_jobs:update`, `replicas:*`, `replicas:read`, `system:*`, `system:create`, `system:delete`, `system:read`, `system:update`, `tailnet_coordinator:*`, `tailnet_coordinator:create`, `tailnet_coordinator:delete`, `tailnet_coordinator:read`, `tailnet_coordinator:update`, `task:*`, `task:create`, `task:delete`, `task:read`, `task:update`, `template:*`, `template:create`, `template:delete`, `template:read`, `template:update`, `template:use`, `template:view_insights`, `usage_event:*`, `usage_event:create`, `usage_event:read`, `usage_event:update`, `user:*`, `user:create`, `user:delete`, `user:read`, `user:read_personal`, `user:update`, `user:update_personal`, `user_secret:*`, `user_secret:create`, `user_secret:delete`, `user_secret:read`, `user_secret:update`, `webpush_subscription:*`, `webpush_subscription:create`, `webpush_subscription:delete`, `webpush_subscription:read`, `workspace:*`, `workspace:application_connect`, `workspace:create`, `workspace:create_agent`, `workspace:delete`, `workspace:delete_agent`, `workspace:read`, `workspace:share`, `workspace:ssh`, `workspace:start`, `workspace:stop`, `workspace:update`, `workspace_agent_devcontainers:*`, `workspace_agent_devcontainers:create`, `workspace_agent_resource_monitor:*`, `workspace_agent_resource_monitor:create`, `workspace_agent_resource_monitor:read`, `workspace_agent_resource_monitor:update`, `workspace_dormant:*`, `workspace_dormant:application_connect`, `workspace_dormant:create`, `workspace_dormant:create_agent`, `workspace_dormant:delete`, `workspace_dormant:delete_agent`, `workspace_dormant:read`, `workspace_dormant:share`, `workspace_dormant:ssh`, `workspace_dormant:start`, `workspace_dormant:stop`, `workspace_dormant:update`, `workspace_proxy:*`, `workspace_proxy:create`, `workspace_proxy:delete`, `workspace_proxy:read`, `workspace_proxy:update` |
| Value(s) |
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `aibridge_interception:*`, `aibridge_interception:create`, `aibridge_interception:read`, `aibridge_interception:update`, `all`, `api_key:*`, `api_key:create`, `api_key:delete`, `api_key:read`, `api_key:update`, `application_connect`, `assign_org_role:*`, `assign_org_role:assign`, `assign_org_role:create`, `assign_org_role:delete`, `assign_org_role:read`, `assign_org_role:unassign`, `assign_org_role:update`, `assign_role:*`, `assign_role:assign`, `assign_role:read`, `assign_role:unassign`, `audit_log:*`, `audit_log:create`, `audit_log:read`, `boundary_usage:*`, `boundary_usage:delete`, `boundary_usage:read`, `boundary_usage:update`, `coder:all`, `coder:apikeys.manage_self`, `coder:application_connect`, `coder:templates.author`, `coder:templates.build`, `coder:workspaces.access`, `coder:workspaces.create`, `coder:workspaces.delete`, `coder:workspaces.operate`, `connection_log:*`, `connection_log:read`, `connection_log:update`, `crypto_key:*`, `crypto_key:create`, `crypto_key:delete`, `crypto_key:read`, `crypto_key:update`, `debug_info:*`, `debug_info:read`, `deployment_config:*`, `deployment_config:read`, `deployment_config:update`, `deployment_stats:*`, `deployment_stats:read`, `file:*`, `file:create`, `file:read`, `group:*`, `group:create`, `group:delete`, `group:read`, `group:update`, `group_member:*`, `group_member:read`, `idpsync_settings:*`, `idpsync_settings:read`, `idpsync_settings:update`, `inbox_notification:*`, `inbox_notification:create`, `inbox_notification:read`, `inbox_notification:update`, `license:*`, `license:create`, `license:delete`, `license:read`, `notification_message:*`, `notification_message:create`, `notification_message:delete`, `notification_message:read`, `notification_message:update`, `notification_preference:*`, `notification_preference:read`, `notification_preference:update`, `notification_template:*`, `notification_template:read`, `notification_template:update`, `oauth2_app:*`, `oauth2_app:create`, `oauth2_app:delete`, `oauth2_app:read`, `oauth2_app:update`, `oauth2_app_code_token:*`, `oauth2_app_code_token:create`, `oauth2_app_code_token:delete`, `oauth2_app_code_token:read`, `oauth2_app_secret:*`, `oauth2_app_secret:create`, `oauth2_app_secret:delete`, `oauth2_app_secret:read`, `oauth2_app_secret:update`, `organization:*`, `organization:create`, `organization:delete`, `organization:read`, `organization:update`, `organization_member:*`, `organization_member:create`, `organization_member:delete`, `organization_member:read`, `organization_member:update`, `prebuilt_workspace:*`, `prebuilt_workspace:delete`, `prebuilt_workspace:update`, `provisioner_daemon:*`, `provisioner_daemon:create`, `provisioner_daemon:delete`, `provisioner_daemon:read`, `provisioner_daemon:update`, `provisioner_jobs:*`, `provisioner_jobs:create`, `provisioner_jobs:read`, `provisioner_jobs:update`, `replicas:*`, `replicas:read`, `system:*`, `system:create`, `system:delete`, `system:read`, `system:update`, `tailnet_coordinator:*`, `tailnet_coordinator:create`, `tailnet_coordinator:delete`, `tailnet_coordinator:read`, `tailnet_coordinator:update`, `task:*`, `task:create`, `task:delete`, `task:read`, `task:update`, `template:*`, `template:create`, `template:delete`, `template:read`, `template:update`, `template:use`, `template:view_insights`, `usage_event:*`, `usage_event:create`, `usage_event:read`, `usage_event:update`, `user:*`, `user:create`, `user:delete`, `user:read`, `user:read_personal`, `user:update`, `user:update_personal`, `user_secret:*`, `user_secret:create`, `user_secret:delete`, `user_secret:read`, `user_secret:update`, `webpush_subscription:*`, `webpush_subscription:create`, `webpush_subscription:delete`, `webpush_subscription:read`, `workspace:*`, `workspace:application_connect`, `workspace:create`, `workspace:create_agent`, `workspace:delete`, `workspace:delete_agent`, `workspace:read`, `workspace:share`, `workspace:ssh`, `workspace:start`, `workspace:stop`, `workspace:update`, `workspace_agent_devcontainers:*`, `workspace_agent_devcontainers:create`, `workspace_agent_resource_monitor:*`, `workspace_agent_resource_monitor:create`, `workspace_agent_resource_monitor:read`, `workspace_agent_resource_monitor:update`, `workspace_dormant:*`, `workspace_dormant:application_connect`, `workspace_dormant:create`, `workspace_dormant:create_agent`, `workspace_dormant:delete`, `workspace_dormant:delete_agent`, `workspace_dormant:read`, `workspace_dormant:share`, `workspace_dormant:ssh`, `workspace_dormant:start`, `workspace_dormant:stop`, `workspace_dormant:update`, `workspace_proxy:*`, `workspace_proxy:create`, `workspace_proxy:delete`, `workspace_proxy:read`, `workspace_proxy:update` |
## codersdk.AddLicenseRequest
@@ -7060,9 +7060,9 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
#### Enumerated Values
| Value(s) |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `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`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
| Value(s) |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `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`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
## codersdk.RateLimitConfig
+5 -5
View File
@@ -810,11 +810,11 @@ Status Code **200**
#### Enumerated Values
| Property | Value(s) |
|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `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`, `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` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `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`, `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).
+5
View File
@@ -36,6 +36,11 @@ export const RBACResourceActions: Partial<
create: "create new audit log entries",
read: "read audit logs",
},
boundary_usage: {
delete: "delete boundary usage statistics",
read: "read boundary usage statistics",
update: "upsert boundary usage statistics",
},
connection_log: {
read: "read connection logs",
update: "upsert connection log entries",
+10
View File
@@ -189,6 +189,10 @@ export type APIKeyScope =
| "audit_log:*"
| "audit_log:create"
| "audit_log:read"
| "boundary_usage:*"
| "boundary_usage:delete"
| "boundary_usage:read"
| "boundary_usage:update"
| "coder:all"
| "coder:apikeys.manage_self"
| "coder:application_connect"
@@ -387,6 +391,10 @@ export const APIKeyScopes: APIKeyScope[] = [
"audit_log:*",
"audit_log:create",
"audit_log:read",
"boundary_usage:*",
"boundary_usage:delete",
"boundary_usage:read",
"boundary_usage:update",
"coder:all",
"coder:apikeys.manage_self",
"coder:application_connect",
@@ -4057,6 +4065,7 @@ export type RBACResource =
| "assign_org_role"
| "assign_role"
| "audit_log"
| "boundary_usage"
| "connection_log"
| "crypto_key"
| "debug_info"
@@ -4101,6 +4110,7 @@ export const RBACResources: RBACResource[] = [
"assign_org_role",
"assign_role",
"audit_log",
"boundary_usage",
"connection_log",
"crypto_key",
"debug_info",