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:*",
"audit_log:create", "audit_log:create",
"audit_log:read", "audit_log:read",
"boundary_usage:*",
"boundary_usage:delete",
"boundary_usage:read",
"boundary_usage:update",
"coder:all", "coder:all",
"coder:apikeys.manage_self", "coder:apikeys.manage_self",
"coder:application_connect", "coder:application_connect",
@@ -12665,6 +12669,10 @@ const docTemplate = `{
"APIKeyScopeAuditLogAll", "APIKeyScopeAuditLogAll",
"APIKeyScopeAuditLogCreate", "APIKeyScopeAuditLogCreate",
"APIKeyScopeAuditLogRead", "APIKeyScopeAuditLogRead",
"APIKeyScopeBoundaryUsageAll",
"APIKeyScopeBoundaryUsageDelete",
"APIKeyScopeBoundaryUsageRead",
"APIKeyScopeBoundaryUsageUpdate",
"APIKeyScopeCoderAll", "APIKeyScopeCoderAll",
"APIKeyScopeCoderApikeysManageSelf", "APIKeyScopeCoderApikeysManageSelf",
"APIKeyScopeCoderApplicationConnect", "APIKeyScopeCoderApplicationConnect",
@@ -17740,6 +17748,7 @@ const docTemplate = `{
"assign_org_role", "assign_org_role",
"assign_role", "assign_role",
"audit_log", "audit_log",
"boundary_usage",
"connection_log", "connection_log",
"crypto_key", "crypto_key",
"debug_info", "debug_info",
@@ -17784,6 +17793,7 @@ const docTemplate = `{
"ResourceAssignOrgRole", "ResourceAssignOrgRole",
"ResourceAssignRole", "ResourceAssignRole",
"ResourceAuditLog", "ResourceAuditLog",
"ResourceBoundaryUsage",
"ResourceConnectionLog", "ResourceConnectionLog",
"ResourceCryptoKey", "ResourceCryptoKey",
"ResourceDebugInfo", "ResourceDebugInfo",
+10
View File
@@ -11105,6 +11105,10 @@
"audit_log:*", "audit_log:*",
"audit_log:create", "audit_log:create",
"audit_log:read", "audit_log:read",
"boundary_usage:*",
"boundary_usage:delete",
"boundary_usage:read",
"boundary_usage:update",
"coder:all", "coder:all",
"coder:apikeys.manage_self", "coder:apikeys.manage_self",
"coder:application_connect", "coder:application_connect",
@@ -11303,6 +11307,10 @@
"APIKeyScopeAuditLogAll", "APIKeyScopeAuditLogAll",
"APIKeyScopeAuditLogCreate", "APIKeyScopeAuditLogCreate",
"APIKeyScopeAuditLogRead", "APIKeyScopeAuditLogRead",
"APIKeyScopeBoundaryUsageAll",
"APIKeyScopeBoundaryUsageDelete",
"APIKeyScopeBoundaryUsageRead",
"APIKeyScopeBoundaryUsageUpdate",
"APIKeyScopeCoderAll", "APIKeyScopeCoderAll",
"APIKeyScopeCoderApikeysManageSelf", "APIKeyScopeCoderApikeysManageSelf",
"APIKeyScopeCoderApplicationConnect", "APIKeyScopeCoderApplicationConnect",
@@ -16182,6 +16190,7 @@
"assign_org_role", "assign_org_role",
"assign_role", "assign_role",
"audit_log", "audit_log",
"boundary_usage",
"connection_log", "connection_log",
"crypto_key", "crypto_key",
"debug_info", "debug_info",
@@ -16226,6 +16235,7 @@
"ResourceAssignOrgRole", "ResourceAssignOrgRole",
"ResourceAssignRole", "ResourceAssignRole",
"ResourceAuditLog", "ResourceAuditLog",
"ResourceBoundaryUsage",
"ResourceConnectionLog", "ResourceConnectionLog",
"ResourceCryptoKey", "ResourceCryptoKey",
"ResourceDebugInfo", "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, Scope: rbac.ScopeAll,
}.WithCachedASTValue() }.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 // 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) 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{ var AsRemoveActor = rbac.Subject{
ID: "remove-actor", ID: "remove-actor",
} }
@@ -1678,6 +1703,13 @@ func (q *querier) DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, u
return q.db.DeleteApplicationConnectAPIKeysByUserID(ctx, userID) 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) { func (q *querier) DeleteCryptoKey(ctx context.Context, arg database.DeleteCryptoKeyParams) (database.CryptoKey, error) {
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceCryptoKey); err != nil { if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceCryptoKey); err != nil {
return database.CryptoKey{}, err return database.CryptoKey{}, err
@@ -2239,6 +2271,13 @@ func (q *querier) GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUI
return q.db.GetAuthorizationUserRoles(ctx, userID) 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) { 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. // Just like with the audit logs query, shortcut if the user is an owner.
err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceConnectionLog) 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) 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 { func (q *querier) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceSystem); err != nil { if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceSystem); err != nil {
return err return err
@@ -5943,6 +5989,13 @@ func (q *querier) UpsertApplicationName(ctx context.Context, value string) error
return q.db.UpsertApplicationName(ctx, value) 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) { func (q *querier) UpsertConnectionLog(ctx context.Context, arg database.UpsertConnectionLogParams) (database.ConnectionLog, error) {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceConnectionLog); err != nil { if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceConnectionLog); err != nil {
return database.ConnectionLog{}, err 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() dbm.EXPECT().DeleteApplicationConnectAPIKeysByUserID(gomock.Any(), a.UserID).Return(nil).AnyTimes()
check.Args(a.UserID).Asserts(rbac.ResourceApiKey.WithOwner(a.UserID.String()), policy.ActionDelete).Returns() 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) { s.Run("DeleteExternalAuthLink", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
a := testutil.Fake(s.T(), faker, database.ExternalAuthLink{}) 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() 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() 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)) 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) { s.Run("UpdateGroupByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
g := testutil.Fake(s.T(), faker, database.Group{}) 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() dbm.EXPECT().GetAuthorizationUserRoles(gomock.Any(), u.ID).Return(database.GetAuthorizationUserRolesRow{}, nil).AnyTimes()
check.Args(u.ID).Asserts(rbac.ResourceSystem, policy.ActionRead) 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) { s.Run("GetDERPMeshKey", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
dbm.EXPECT().GetDERPMeshKey(gomock.Any()).Return("testing", nil).AnyTimes() dbm.EXPECT().GetDERPMeshKey(gomock.Any()).Return("testing", nil).AnyTimes()
check.Args().Asserts(rbac.ResourceSystem, policy.ActionRead) check.Args().Asserts(rbac.ResourceSystem, policy.ActionRead)
@@ -3342,6 +3355,11 @@ func (s *MethodTestSuite) TestSystemFunctions() {
dbm.EXPECT().UpsertApplicationName(gomock.Any(), "").Return(nil).AnyTimes() dbm.EXPECT().UpsertApplicationName(gomock.Any(), "").Return(nil).AnyTimes()
check.Args("").Asserts(rbac.ResourceDeploymentConfig, policy.ActionUpdate) 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) { s.Run("GetHealthSettings", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
dbm.EXPECT().GetHealthSettings(gomock.Any()).Return("{}", nil).AnyTimes() dbm.EXPECT().GetHealthSettings(gomock.Any()).Return("{}", nil).AnyTimes()
check.Args().Asserts() check.Args().Asserts()
+32
View File
@@ -335,6 +335,14 @@ func (m queryMetricsStore) DeleteApplicationConnectAPIKeysByUserID(ctx context.C
return r0 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) { func (m queryMetricsStore) DeleteCryptoKey(ctx context.Context, arg database.DeleteCryptoKeyParams) (database.CryptoKey, error) {
start := time.Now() start := time.Now()
r0, r1 := m.s.DeleteCryptoKey(ctx, arg) r0, r1 := m.s.DeleteCryptoKey(ctx, arg)
@@ -894,6 +902,14 @@ func (m queryMetricsStore) GetAuthorizationUserRoles(ctx context.Context, userID
return r0, r1 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) { func (m queryMetricsStore) GetConnectionLogsOffset(ctx context.Context, arg database.GetConnectionLogsOffsetParams) ([]database.GetConnectionLogsOffsetRow, error) {
start := time.Now() start := time.Now()
r0, r1 := m.s.GetConnectionLogsOffset(ctx, arg) r0, r1 := m.s.GetConnectionLogsOffset(ctx, arg)
@@ -3318,6 +3334,14 @@ func (m queryMetricsStore) RemoveUserFromGroups(ctx context.Context, arg databas
return r0, r1 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 { func (m queryMetricsStore) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error {
start := time.Now() start := time.Now()
r0 := m.s.RevokeDBCryptKey(ctx, activeKeyDigest) r0 := m.s.RevokeDBCryptKey(ctx, activeKeyDigest)
@@ -4069,6 +4093,14 @@ func (m queryMetricsStore) UpsertApplicationName(ctx context.Context, value stri
return r0 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) { func (m queryMetricsStore) UpsertConnectionLog(ctx context.Context, arg database.UpsertConnectionLogParams) (database.ConnectionLog, error) {
start := time.Now() start := time.Now()
r0, r1 := m.s.UpsertConnectionLog(ctx, arg) 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) 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. // DeleteCryptoKey mocks base method.
func (m *MockStore) DeleteCryptoKey(ctx context.Context, arg database.DeleteCryptoKeyParams) (database.CryptoKey, error) { func (m *MockStore) DeleteCryptoKey(ctx context.Context, arg database.DeleteCryptoKeyParams) (database.CryptoKey, error) {
m.ctrl.T.Helper() 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) 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. // GetConnectionLogsOffset mocks base method.
func (m *MockStore) GetConnectionLogsOffset(ctx context.Context, arg database.GetConnectionLogsOffsetParams) ([]database.GetConnectionLogsOffsetRow, error) { func (m *MockStore) GetConnectionLogsOffset(ctx context.Context, arg database.GetConnectionLogsOffsetParams) ([]database.GetConnectionLogsOffsetRow, error) {
m.ctrl.T.Helper() 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) 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. // RevokeDBCryptKey mocks base method.
func (m *MockStore) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error { func (m *MockStore) RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error {
m.ctrl.T.Helper() 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) 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. // UpsertConnectionLog mocks base method.
func (m *MockStore) UpsertConnectionLog(ctx context.Context, arg database.UpsertConnectionLogParams) (database.ConnectionLog, error) { func (m *MockStore) UpsertConnectionLog(ctx context.Context, arg database.UpsertConnectionLogParams) (database.ConnectionLog, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
+35 -2
View File
@@ -204,7 +204,11 @@ CREATE TYPE api_key_scope AS ENUM (
'task:delete', 'task:delete',
'task:*', 'task:*',
'workspace:share', '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 ( CREATE TYPE app_sharing_level AS ENUM (
@@ -1111,6 +1115,32 @@ CREATE TABLE audit_logs (
resource_icon text NOT NULL 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 ( CREATE TABLE connection_logs (
id uuid NOT NULL, id uuid NOT NULL,
connect_time timestamp with time zone NOT NULL, connect_time timestamp with time zone NOT NULL,
@@ -2002,7 +2032,7 @@ CREATE TABLE telemetry_items (
CREATE TABLE telemetry_locks ( CREATE TABLE telemetry_locks (
event_type text NOT NULL, event_type text NOT NULL,
period_ending_at timestamp with time zone 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.'; 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 ALTER TABLE ONLY audit_logs
ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id); 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 ALTER TABLE ONLY connection_logs
ADD CONSTRAINT connection_logs_pkey PRIMARY KEY (id); 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:*" ApiKeyScopeTask APIKeyScope = "task:*"
ApiKeyScopeWorkspaceShare APIKeyScope = "workspace:share" ApiKeyScopeWorkspaceShare APIKeyScope = "workspace:share"
ApiKeyScopeWorkspaceDormantShare APIKeyScope = "workspace_dormant: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 { func (e *APIKeyScope) Scan(src interface{}) error {
@@ -445,7 +449,11 @@ func (e APIKeyScope) Valid() bool {
ApiKeyScopeTaskDelete, ApiKeyScopeTaskDelete,
ApiKeyScopeTask, ApiKeyScopeTask,
ApiKeyScopeWorkspaceShare, ApiKeyScopeWorkspaceShare,
ApiKeyScopeWorkspaceDormantShare: ApiKeyScopeWorkspaceDormantShare,
ApiKeyScopeBoundaryUsage,
ApiKeyScopeBoundaryUsageDelete,
ApiKeyScopeBoundaryUsageRead,
ApiKeyScopeBoundaryUsageUpdate:
return true return true
} }
return false return false
@@ -647,6 +655,10 @@ func AllAPIKeyScopeValues() []APIKeyScope {
ApiKeyScopeTask, ApiKeyScopeTask,
ApiKeyScopeWorkspaceShare, ApiKeyScopeWorkspaceShare,
ApiKeyScopeWorkspaceDormantShare, ApiKeyScopeWorkspaceDormantShare,
ApiKeyScopeBoundaryUsage,
ApiKeyScopeBoundaryUsageDelete,
ApiKeyScopeBoundaryUsageRead,
ApiKeyScopeBoundaryUsageUpdate,
} }
} }
@@ -3702,6 +3714,24 @@ type AuditLog struct {
ResourceIcon string `db:"resource_icon" json:"resource_icon"` 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 { type ConnectionLog struct {
ID uuid.UUID `db:"id" json:"id"` ID uuid.UUID `db:"id" json:"id"`
ConnectTime time.Time `db:"connect_time" json:"connect_time"` ConnectTime time.Time `db:"connect_time" json:"connect_time"`
+13
View File
@@ -88,6 +88,8 @@ type sqlcQuerier interface {
// be recreated. // be recreated.
DeleteAllWebpushSubscriptions(ctx context.Context) error DeleteAllWebpushSubscriptions(ctx context.Context) error
DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, userID uuid.UUID) 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) DeleteCryptoKey(ctx context.Context, arg DeleteCryptoKeyParams) (CryptoKey, error)
DeleteCustomRole(ctx context.Context, arg DeleteCustomRoleParams) error DeleteCustomRole(ctx context.Context, arg DeleteCustomRoleParams) error
DeleteExpiredAPIKeys(ctx context.Context, arg DeleteExpiredAPIKeysParams) (int64, 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 // This function returns roles for authorization purposes. Implied member roles
// are included. // are included.
GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUID) (GetAuthorizationUserRolesRow, error) 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) GetConnectionLogsOffset(ctx context.Context, arg GetConnectionLogsOffsetParams) ([]GetConnectionLogsOffsetRow, error)
GetCoordinatorResumeTokenSigningKey(ctx context.Context) (string, error) GetCoordinatorResumeTokenSigningKey(ctx context.Context) (string, error)
GetCryptoKeyByFeatureAndSequence(ctx context.Context, arg GetCryptoKeyByFeatureAndSequenceParams) (CryptoKey, error) GetCryptoKeyByFeatureAndSequence(ctx context.Context, arg GetCryptoKeyByFeatureAndSequenceParams) (CryptoKey, error)
@@ -646,6 +652,9 @@ type sqlcQuerier interface {
RegisterWorkspaceProxy(ctx context.Context, arg RegisterWorkspaceProxyParams) (WorkspaceProxy, error) RegisterWorkspaceProxy(ctx context.Context, arg RegisterWorkspaceProxyParams) (WorkspaceProxy, error)
RemoveUserFromAllGroups(ctx context.Context, userID uuid.UUID) error RemoveUserFromAllGroups(ctx context.Context, userID uuid.UUID) error
RemoveUserFromGroups(ctx context.Context, arg RemoveUserFromGroupsParams) ([]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 RevokeDBCryptKey(ctx context.Context, activeKeyDigest string) error
// Note that this selects from the CTE, not the original table. The CTE is named // 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 // 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 UpsertAnnouncementBanners(ctx context.Context, value string) error
UpsertAppSecurityKey(ctx context.Context, value string) error UpsertAppSecurityKey(ctx context.Context, value string) error
UpsertApplicationName(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) UpsertConnectionLog(ctx context.Context, arg UpsertConnectionLogParams) (ConnectionLog, error)
UpsertCoordinatorResumeTokenSigningKey(ctx context.Context, value string) error UpsertCoordinatorResumeTokenSigningKey(ctx context.Context, value string) error
// The default proxy is implied and not actually stored in the database. // 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 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 const countConnectionLogs = `-- name: CountConnectionLogs :one
SELECT SELECT
COUNT(*) AS count 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); 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); 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); 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); 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); 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); 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" SubjectTypeUsagePublisher SubjectType = "usage_publisher"
SubjectAibridged SubjectType = "aibridged" SubjectAibridged SubjectType = "aibridged"
SubjectTypeDBPurge SubjectType = "dbpurge" SubjectTypeDBPurge SubjectType = "dbpurge"
SubjectTypeBoundaryUsageTracker SubjectType = "boundary_usage_tracker"
) )
const ( const (
+10
View File
@@ -63,6 +63,15 @@ var (
Type: "audit_log", 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 // ResourceConnectionLog
// Valid Actions // Valid Actions
// - "ActionRead" :: read connection logs // - "ActionRead" :: read connection logs
@@ -417,6 +426,7 @@ func AllResources() []Objecter {
ResourceAssignOrgRole, ResourceAssignOrgRole,
ResourceAssignRole, ResourceAssignRole,
ResourceAuditLog, ResourceAuditLog,
ResourceBoundaryUsage,
ResourceConnectionLog, ResourceConnectionLog,
ResourceCryptoKey, ResourceCryptoKey,
ResourceDebugInfo, ResourceDebugInfo,
+7
View File
@@ -380,4 +380,11 @@ var RBACPermissions = map[string]PermissionDefinition{
ActionCreate: "create aibridge interceptions & related records", 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 dormancy and workspace are omitted.
// Workspace is specifically handled based on the opts.NoOwnerWorkspaceExec. // Workspace is specifically handled based on the opts.NoOwnerWorkspaceExec.
// Owners cannot access other users' secrets. // 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. // This adds back in the Workspace permissions.
Permissions(map[string][]policy.Action{ Permissions(map[string][]policy.Action{
ResourceWorkspace.Type: ownerWorkspaceActions, ResourceWorkspace.Type: ownerWorkspaceActions,
@@ -309,7 +309,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
ResourceOauth2App.Type: {policy.ActionRead}, ResourceOauth2App.Type: {policy.ActionRead},
ResourceWorkspaceProxy.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{ Permissions(map[string][]policy.Action{
// Users cannot do create/update/delete on themselves, but they // Users cannot do create/update/delete on themselves, but they
// can read their own details. // can read their own details.
@@ -433,7 +433,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
ByOrgID: map[string]OrgPermissions{ ByOrgID: map[string]OrgPermissions{
// Org admins should not have workspace exec perms. // Org admins should not have workspace exec perms.
organizationID.String(): { 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}, 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), ResourceWorkspace.Type: slice.Omit(ResourceWorkspace.AvailableActions(), policy.ActionApplicationConnect, policy.ActionSSH),
// PrebuiltWorkspaces are a subset of Workspaces. // 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. // We expect every permission to be tested above.
+9
View File
@@ -25,6 +25,9 @@ const (
ScopeAssignRoleUnassign ScopeName = "assign_role:unassign" ScopeAssignRoleUnassign ScopeName = "assign_role:unassign"
ScopeAuditLogCreate ScopeName = "audit_log:create" ScopeAuditLogCreate ScopeName = "audit_log:create"
ScopeAuditLogRead ScopeName = "audit_log:read" 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" ScopeConnectionLogRead ScopeName = "connection_log:read"
ScopeConnectionLogUpdate ScopeName = "connection_log:update" ScopeConnectionLogUpdate ScopeName = "connection_log:update"
ScopeCryptoKeyCreate ScopeName = "crypto_key:create" ScopeCryptoKeyCreate ScopeName = "crypto_key:create"
@@ -180,6 +183,9 @@ func (e ScopeName) Valid() bool {
ScopeAssignRoleUnassign, ScopeAssignRoleUnassign,
ScopeAuditLogCreate, ScopeAuditLogCreate,
ScopeAuditLogRead, ScopeAuditLogRead,
ScopeBoundaryUsageDelete,
ScopeBoundaryUsageRead,
ScopeBoundaryUsageUpdate,
ScopeConnectionLogRead, ScopeConnectionLogRead,
ScopeConnectionLogUpdate, ScopeConnectionLogUpdate,
ScopeCryptoKeyCreate, ScopeCryptoKeyCreate,
@@ -336,6 +342,9 @@ func AllScopeNameValues() []ScopeName {
ScopeAssignRoleUnassign, ScopeAssignRoleUnassign,
ScopeAuditLogCreate, ScopeAuditLogCreate,
ScopeAuditLogRead, ScopeAuditLogRead,
ScopeBoundaryUsageDelete,
ScopeBoundaryUsageRead,
ScopeBoundaryUsageUpdate,
ScopeConnectionLogRead, ScopeConnectionLogRead,
ScopeConnectionLogUpdate, ScopeConnectionLogUpdate,
ScopeCryptoKeyCreate, ScopeCryptoKeyCreate,
+4
View File
@@ -29,6 +29,10 @@ const (
APIKeyScopeAuditLogAll APIKeyScope = "audit_log:*" APIKeyScopeAuditLogAll APIKeyScope = "audit_log:*"
APIKeyScopeAuditLogCreate APIKeyScope = "audit_log:create" APIKeyScopeAuditLogCreate APIKeyScope = "audit_log:create"
APIKeyScopeAuditLogRead APIKeyScope = "audit_log:read" 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" APIKeyScopeCoderAll APIKeyScope = "coder:all"
APIKeyScopeCoderApikeysManageSelf APIKeyScope = "coder:apikeys.manage_self" APIKeyScopeCoderApikeysManageSelf APIKeyScope = "coder:apikeys.manage_self"
APIKeyScopeCoderApplicationConnect APIKeyScope = "coder:application_connect" APIKeyScopeCoderApplicationConnect APIKeyScope = "coder:application_connect"
+2
View File
@@ -10,6 +10,7 @@ const (
ResourceAssignOrgRole RBACResource = "assign_org_role" ResourceAssignOrgRole RBACResource = "assign_org_role"
ResourceAssignRole RBACResource = "assign_role" ResourceAssignRole RBACResource = "assign_role"
ResourceAuditLog RBACResource = "audit_log" ResourceAuditLog RBACResource = "audit_log"
ResourceBoundaryUsage RBACResource = "boundary_usage"
ResourceConnectionLog RBACResource = "connection_log" ResourceConnectionLog RBACResource = "connection_log"
ResourceCryptoKey RBACResource = "crypto_key" ResourceCryptoKey RBACResource = "crypto_key"
ResourceDebugInfo RBACResource = "debug_info" ResourceDebugInfo RBACResource = "debug_info"
@@ -79,6 +80,7 @@ var RBACResourceActions = map[RBACResource][]RBACAction{
ResourceAssignOrgRole: {ActionAssign, ActionCreate, ActionDelete, ActionRead, ActionUnassign, ActionUpdate}, ResourceAssignOrgRole: {ActionAssign, ActionCreate, ActionDelete, ActionRead, ActionUnassign, ActionUpdate},
ResourceAssignRole: {ActionAssign, ActionRead, ActionUnassign}, ResourceAssignRole: {ActionAssign, ActionRead, ActionUnassign},
ResourceAuditLog: {ActionCreate, ActionRead}, ResourceAuditLog: {ActionCreate, ActionRead},
ResourceBoundaryUsage: {ActionDelete, ActionRead, ActionUpdate},
ResourceConnectionLog: {ActionRead, ActionUpdate}, ResourceConnectionLog: {ActionRead, ActionUpdate},
ResourceCryptoKey: {ActionCreate, ActionDelete, ActionRead, ActionUpdate}, ResourceCryptoKey: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
ResourceDebugInfo: {ActionRead}, ResourceDebugInfo: {ActionRead},
+20 -20
View File
@@ -172,10 +172,10 @@ Status Code **200**
#### Enumerated Values #### Enumerated Values
| Property | Value(s) | | 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` | | `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` | | `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). To perform this operation, you must be authenticated. [Learn more](authentication.md).
@@ -305,10 +305,10 @@ Status Code **200**
#### Enumerated Values #### Enumerated Values
| Property | Value(s) | | 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` | | `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` | | `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). To perform this operation, you must be authenticated. [Learn more](authentication.md).
@@ -438,10 +438,10 @@ Status Code **200**
#### Enumerated Values #### Enumerated Values
| Property | Value(s) | | 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` | | `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` | | `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). To perform this operation, you must be authenticated. [Learn more](authentication.md).
@@ -533,10 +533,10 @@ Status Code **200**
#### Enumerated Values #### Enumerated Values
| Property | Value(s) | | 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` | | `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` | | `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). To perform this operation, you must be authenticated. [Learn more](authentication.md).
@@ -850,9 +850,9 @@ Status Code **200**
#### Enumerated Values #### Enumerated Values
| Property | Value(s) | | 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` | | `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` | | `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). To perform this operation, you must be authenticated. [Learn more](authentication.md).
+6 -6
View File
@@ -862,9 +862,9 @@
#### Enumerated Values #### Enumerated Values
| Value(s) | | 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` | | `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 ## codersdk.AddLicenseRequest
@@ -7060,9 +7060,9 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
#### Enumerated Values #### Enumerated Values
| Value(s) | | 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` | | `*`, `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 ## codersdk.RateLimitConfig
+5 -5
View File
@@ -810,11 +810,11 @@ Status Code **200**
#### Enumerated Values #### Enumerated Values
| Property | Value(s) | | 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` | | `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` | | `login_type` | `github`, `oidc`, `password`, `token` |
| `scope` | `all`, `application_connect` | | `scope` | `all`, `application_connect` |
To perform this operation, you must be authenticated. [Learn more](authentication.md). 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", create: "create new audit log entries",
read: "read audit logs", read: "read audit logs",
}, },
boundary_usage: {
delete: "delete boundary usage statistics",
read: "read boundary usage statistics",
update: "upsert boundary usage statistics",
},
connection_log: { connection_log: {
read: "read connection logs", read: "read connection logs",
update: "upsert connection log entries", update: "upsert connection log entries",
+10
View File
@@ -189,6 +189,10 @@ export type APIKeyScope =
| "audit_log:*" | "audit_log:*"
| "audit_log:create" | "audit_log:create"
| "audit_log:read" | "audit_log:read"
| "boundary_usage:*"
| "boundary_usage:delete"
| "boundary_usage:read"
| "boundary_usage:update"
| "coder:all" | "coder:all"
| "coder:apikeys.manage_self" | "coder:apikeys.manage_self"
| "coder:application_connect" | "coder:application_connect"
@@ -387,6 +391,10 @@ export const APIKeyScopes: APIKeyScope[] = [
"audit_log:*", "audit_log:*",
"audit_log:create", "audit_log:create",
"audit_log:read", "audit_log:read",
"boundary_usage:*",
"boundary_usage:delete",
"boundary_usage:read",
"boundary_usage:update",
"coder:all", "coder:all",
"coder:apikeys.manage_self", "coder:apikeys.manage_self",
"coder:application_connect", "coder:application_connect",
@@ -4057,6 +4065,7 @@ export type RBACResource =
| "assign_org_role" | "assign_org_role"
| "assign_role" | "assign_role"
| "audit_log" | "audit_log"
| "boundary_usage"
| "connection_log" | "connection_log"
| "crypto_key" | "crypto_key"
| "debug_info" | "debug_info"
@@ -4101,6 +4110,7 @@ export const RBACResources: RBACResource[] = [
"assign_org_role", "assign_org_role",
"assign_role", "assign_role",
"audit_log", "audit_log",
"boundary_usage",
"connection_log", "connection_log",
"crypto_key", "crypto_key",
"debug_info", "debug_info",