Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: implement API key scopes database migration#19861

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
feat: transition API keys from single scope to scopes array with allo…
…w listReplace the single `scope` column with `scopes` array and add `allow_list`column to API keys table. This enables fine-grained authorization controlusing low-level resource:action permissions while maintaining backwardcompatibility.- Add database migration extending api_key_scope enum with all RBAC  permissions- Replace APIKey.Scope with APIKey.Scopes array and AllowList fields- Implement APIKeyScopes.Expand() method for multi-scope authorization- Add AllowList type with database serialization support- Update RBAC scope expansion to support resource:action format- Maintain response compatibility by deriving single scope from arrays- Add comprehensive test coverage for scope expansion logic
  • Loading branch information
@ThomasK33
ThomasK33 committedSep 22, 2025
commitb27f3123077c83af25a53758c5fe6721bb7dce10
3 changes: 2 additions & 1 deletioncoderd/apikey/apikey.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -92,7 +92,8 @@ func Generate(params CreateParams) (database.InsertAPIKeyParams, string, error)
UpdatedAt: dbtime.Now(),
HashedSecret: hashed[:],
LoginType: params.LoginType,
Scope: scope,
Scopes: database.APIKeyScopes{scope},
AllowList: database.AllowList{database.AllowListWildcard()},
TokenName: params.TokenName,
}, token, nil
}
Expand Down
4 changes: 2 additions & 2 deletionscoderd/apikey/apikey_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -159,9 +159,9 @@ func TestGenerate(t *testing.T) {
}

iftc.params.Scope!="" {
assert.Equal(t,tc.params.Scope,key.Scope)
assert.True(t,key.Scopes.Has(tc.params.Scope))
}else {
assert.Equal(t,database.APIKeyScopeAll,key.Scope)
assert.True(t,key.Scopes.Has(database.APIKeyScopeAll))
}

iftc.params.TokenName!="" {
Expand Down
2 changes: 1 addition & 1 deletioncoderd/coderdtest/authorize.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -68,7 +68,7 @@ func AssertRBAC(t *testing.T, api *coderd.API, client *codersdk.Client) RBACAsse
ID:key.UserID.String(),
Roles:rbac.RoleIdentifiers(roleNames),
Groups:roles.Groups,
Scope:rbac.ScopeName(key.Scope),
Scope:key.Scopes,
},
Recorder:recorder,
}
Expand Down
4 changes: 2 additions & 2 deletionscoderd/database/dbauthz/dbauthz_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -251,7 +251,7 @@ func (s *MethodTestSuite) TestAPIKey() {
}))
s.Run("InsertAPIKey", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
u := testutil.Fake(s.T(), faker, database.User{})
arg := database.InsertAPIKeyParams{UserID: u.ID, LoginType: database.LoginTypePassword,Scope: database.APIKeyScopeAll, IPAddress: defaultIPAddress()}
arg := database.InsertAPIKeyParams{UserID: u.ID, LoginType: database.LoginTypePassword,Scopes: database.APIKeyScopes{database.APIKeyScopeAll}, IPAddress: defaultIPAddress()}
ret := testutil.Fake(s.T(), faker, database.APIKey{UserID: u.ID, LoginType: database.LoginTypePassword})
dbm.EXPECT().InsertAPIKey(gomock.Any(), arg).Return(ret, nil).AnyTimes()
check.Args(arg).Asserts(rbac.ResourceApiKey.WithOwner(u.ID.String()), policy.ActionCreate)
Expand All@@ -265,7 +265,7 @@ func (s *MethodTestSuite) TestAPIKey() {
check.Args(arg).Asserts(a, policy.ActionUpdate).Returns()
}))
s.Run("DeleteApplicationConnectAPIKeysByUserID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
a := testutil.Fake(s.T(), faker, database.APIKey{Scope: database.APIKeyScopeApplicationConnect})
a := testutil.Fake(s.T(), faker, database.APIKey{Scopes: database.APIKeyScopes{database.APIKeyScopeApplicationConnect}})
dbm.EXPECT().DeleteApplicationConnectAPIKeysByUserID(gomock.Any(), a.UserID).Return(nil).AnyTimes()
check.Args(a.UserID).Asserts(rbac.ResourceApiKey.WithOwner(a.UserID.String()), policy.ActionDelete).Returns()
}))
Expand Down
3 changes: 2 additions & 1 deletioncoderd/database/dbgen/dbgen.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,7 +185,8 @@ func APIKey(t testing.TB, db database.Store, seed database.APIKey, munge ...func
CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()),
UpdatedAt: takeFirst(seed.UpdatedAt, dbtime.Now()),
LoginType: takeFirst(seed.LoginType, database.LoginTypePassword),
Scope: takeFirst(seed.Scope, database.APIKeyScopeAll),
Scopes: takeFirstSlice([]database.APIKeyScope(seed.Scopes), []database.APIKeyScope{database.APIKeyScopeAll}),
AllowList: takeFirstSlice(seed.AllowList, database.AllowList{database.AllowListWildcard()}),
TokenName: takeFirst(seed.TokenName),
}
for _, fn := range munge {
Expand Down
145 changes: 142 additions & 3 deletionscoderd/database/dump.sql
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
-- Recreate single-scope column and collapse arrays
ALTER TABLE api_keys ADD COLUMN scope api_key_scope DEFAULT 'all'::api_key_scope NOT NULL;

-- Collapse logic: prefer 'all', else 'application_connect', else 'all'
UPDATE api_keys SET scope =
CASE
WHEN 'all'::api_key_scope = ANY(scopes) THEN 'all'::api_key_scope
WHEN 'application_connect'::api_key_scope = ANY(scopes) THEN 'application_connect'::api_key_scope
ELSE 'all'::api_key_scope
END;

-- Drop new columns
ALTER TABLE api_keys DROP COLUMN allow_list;
ALTER TABLE api_keys DROP COLUMN scopes;

-- Note: We intentionally keep the expanded enum values to avoid dependency churn.
-- If strict narrowing is required, create a new type with only ('all','application_connect'),
-- cast column, drop the new type, and rename.
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
-- Extend api_key_scope enum with low-level <resource>:<action> values derived from RBACPermissions
-- Generated via: go run ./scripts/generate_api_key_scope_enum
-- Begin enum extensions
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'aibridge_interception:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'aibridge_interception:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'aibridge_interception:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'api_key:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'api_key:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'api_key:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'api_key:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'assign_org_role:assign';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'assign_org_role:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'assign_org_role:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'assign_org_role:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'assign_org_role:unassign';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'assign_org_role:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'assign_role:assign';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'assign_role:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'assign_role:unassign';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'audit_log:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'audit_log:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'connection_log:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'connection_log:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'crypto_key:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'crypto_key:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'crypto_key:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'crypto_key:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'debug_info:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'deployment_config:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'deployment_config:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'deployment_stats:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'file:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'file:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'group:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'group:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'group:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'group:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'group_member:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'idpsync_settings:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'idpsync_settings:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'inbox_notification:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'inbox_notification:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'inbox_notification:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'license:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'license:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'license:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'notification_message:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'notification_message:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'notification_message:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'notification_message:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'notification_preference:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'notification_preference:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'notification_template:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'notification_template:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'oauth2_app:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'oauth2_app:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'oauth2_app:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'oauth2_app:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'oauth2_app_code_token:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'oauth2_app_code_token:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'oauth2_app_code_token:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'oauth2_app_secret:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'oauth2_app_secret:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'oauth2_app_secret:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'oauth2_app_secret:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'organization:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'organization:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'organization:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'organization:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'organization_member:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'organization_member:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'organization_member:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'organization_member:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'prebuilt_workspace:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'prebuilt_workspace:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'provisioner_daemon:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'provisioner_daemon:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'provisioner_daemon:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'provisioner_daemon:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'provisioner_jobs:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'provisioner_jobs:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'provisioner_jobs:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'replicas:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'system:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'system:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'system:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'system:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'tailnet_coordinator:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'tailnet_coordinator:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'tailnet_coordinator:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'tailnet_coordinator:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'template:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'template:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'template:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'template:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'template:use';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'template:view_insights';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'usage_event:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'usage_event:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'usage_event:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'user:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'user:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'user:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'user:read_personal';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'user:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'user:update_personal';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'user_secret:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'user_secret:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'user_secret:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'user_secret:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'webpush_subscription:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'webpush_subscription:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'webpush_subscription:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace:application_connect';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace:create_agent';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace:delete_agent';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace:ssh';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace:start';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace:stop';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_agent_devcontainers:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_agent_resource_monitor:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_agent_resource_monitor:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_agent_resource_monitor:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_dormant:application_connect';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_dormant:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_dormant:create_agent';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_dormant:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_dormant:delete_agent';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_dormant:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_dormant:ssh';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_dormant:start';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_dormant:stop';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_dormant:update';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_proxy:create';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_proxy:delete';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_proxy:read';
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'workspace_proxy:update';
-- End enum extensions

-- Add new columns without defaults; backfill; then enforce NOT NULL
ALTER TABLE api_keys ADD COLUMN scopes api_key_scope[];
ALTER TABLE api_keys ADD COLUMN allow_list text[];

-- Backfill existing rows for compatibility
UPDATE api_keys SET scopes = ARRAY[scope::api_key_scope];
UPDATE api_keys SET allow_list = ARRAY['*:*'];

-- Enforce NOT NULL
ALTER TABLE api_keys ALTER COLUMN scopes SET NOT NULL;
ALTER TABLE api_keys ALTER COLUMN allow_list SET NOT NULL;

-- Drop legacy single-scope column
ALTER TABLE api_keys DROP COLUMN scope;
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp