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

Commit81b483f

Browse files
committed
feat: transition API keys from single scope to scopes array with allow list
Replace 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
1 parent6fb4cc6 commit81b483f

26 files changed

+1252
-71
lines changed

‎coderd/apikey/apikey.go‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ func Generate(params CreateParams) (database.InsertAPIKeyParams, string, error)
9292
UpdatedAt:dbtime.Now(),
9393
HashedSecret:hashed[:],
9494
LoginType:params.LoginType,
95-
Scope:scope,
95+
Scopes: database.APIKeyScopes{scope},
96+
AllowList: database.AllowList{database.AllowListWildcard()},
9697
TokenName:params.TokenName,
9798
},token,nil
9899
}

‎coderd/apikey/apikey_test.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,9 @@ func TestGenerate(t *testing.T) {
159159
}
160160

161161
iftc.params.Scope!="" {
162-
assert.Equal(t,tc.params.Scope,key.Scope)
162+
assert.True(t,key.Scopes.Has(tc.params.Scope))
163163
}else {
164-
assert.Equal(t,database.APIKeyScopeAll,key.Scope)
164+
assert.True(t,key.Scopes.Has(database.APIKeyScopeAll))
165165
}
166166

167167
iftc.params.TokenName!="" {

‎coderd/coderdtest/authorize.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func AssertRBAC(t *testing.T, api *coderd.API, client *codersdk.Client) RBACAsse
6868
ID:key.UserID.String(),
6969
Roles:rbac.RoleIdentifiers(roleNames),
7070
Groups:roles.Groups,
71-
Scope:rbac.ScopeName(key.Scope),
71+
Scope:key.Scopes,
7272
},
7373
Recorder:recorder,
7474
}

‎coderd/database/dbauthz/dbauthz_test.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ func (s *MethodTestSuite) TestAPIKey() {
251251
}))
252252
s.Run("InsertAPIKey",s.Mocked(func(dbm*dbmock.MockStore,faker*gofakeit.Faker,check*expects) {
253253
u:=testutil.Fake(s.T(),faker, database.User{})
254-
arg:= database.InsertAPIKeyParams{UserID:u.ID,LoginType:database.LoginTypePassword,Scope:database.APIKeyScopeAll,IPAddress:defaultIPAddress()}
254+
arg:= database.InsertAPIKeyParams{UserID:u.ID,LoginType:database.LoginTypePassword,Scopes: database.APIKeyScopes{database.APIKeyScopeAll},IPAddress:defaultIPAddress()}
255255
ret:=testutil.Fake(s.T(),faker, database.APIKey{UserID:u.ID,LoginType:database.LoginTypePassword})
256256
dbm.EXPECT().InsertAPIKey(gomock.Any(),arg).Return(ret,nil).AnyTimes()
257257
check.Args(arg).Asserts(rbac.ResourceApiKey.WithOwner(u.ID.String()),policy.ActionCreate)
@@ -265,7 +265,7 @@ func (s *MethodTestSuite) TestAPIKey() {
265265
check.Args(arg).Asserts(a,policy.ActionUpdate).Returns()
266266
}))
267267
s.Run("DeleteApplicationConnectAPIKeysByUserID",s.Mocked(func(dbm*dbmock.MockStore,faker*gofakeit.Faker,check*expects) {
268-
a:=testutil.Fake(s.T(),faker, database.APIKey{Scope:database.APIKeyScopeApplicationConnect})
268+
a:=testutil.Fake(s.T(),faker, database.APIKey{Scopes: database.APIKeyScopes{database.APIKeyScopeApplicationConnect}})
269269
dbm.EXPECT().DeleteApplicationConnectAPIKeysByUserID(gomock.Any(),a.UserID).Return(nil).AnyTimes()
270270
check.Args(a.UserID).Asserts(rbac.ResourceApiKey.WithOwner(a.UserID.String()),policy.ActionDelete).Returns()
271271
}))

‎coderd/database/dbgen/dbgen.go‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ func APIKey(t testing.TB, db database.Store, seed database.APIKey, munge ...func
185185
CreatedAt:takeFirst(seed.CreatedAt,dbtime.Now()),
186186
UpdatedAt:takeFirst(seed.UpdatedAt,dbtime.Now()),
187187
LoginType:takeFirst(seed.LoginType,database.LoginTypePassword),
188-
Scope:takeFirst(seed.Scope,database.APIKeyScopeAll),
188+
Scopes:takeFirstSlice([]database.APIKeyScope(seed.Scopes), []database.APIKeyScope{database.APIKeyScopeAll}),
189+
AllowList:takeFirstSlice(seed.AllowList, database.AllowList{database.AllowListWildcard()}),
189190
TokenName:takeFirst(seed.TokenName),
190191
}
191192
for_,fn:=rangemunge {

‎coderd/database/dump.sql‎

Lines changed: 142 additions & 3 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-- Recreate single-scope column and collapse arrays
2+
ALTERTABLE api_keys ADD COLUMN scope api_key_scope DEFAULT'all'::api_key_scopeNOT NULL;
3+
4+
-- Collapse logic: prefer 'all', else 'application_connect', else 'all'
5+
UPDATE api_keysSET scope=
6+
CASE
7+
WHEN'all'::api_key_scope= ANY(scopes) THEN'all'::api_key_scope
8+
WHEN'application_connect'::api_key_scope= ANY(scopes) THEN'application_connect'::api_key_scope
9+
ELSE'all'::api_key_scope
10+
END;
11+
12+
-- Drop new columns
13+
ALTERTABLE api_keys DROP COLUMN allow_list;
14+
ALTERTABLE api_keys DROP COLUMN scopes;
15+
16+
-- Note: We intentionally keep the expanded enum values to avoid dependency churn.
17+
-- If strict narrowing is required, create a new type with only ('all','application_connect'),
18+
-- cast column, drop the new type, and rename.
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
-- Extend api_key_scope enum with low-level <resource>:<action> values derived from RBACPermissions
2+
-- Generated via: go run ./scripts/generate_api_key_scope_enum
3+
-- Begin enum extensions
4+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'aibridge_interception:create';
5+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'aibridge_interception:read';
6+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'aibridge_interception:update';
7+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'api_key:create';
8+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'api_key:delete';
9+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'api_key:read';
10+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'api_key:update';
11+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'assign_org_role:assign';
12+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'assign_org_role:create';
13+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'assign_org_role:delete';
14+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'assign_org_role:read';
15+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'assign_org_role:unassign';
16+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'assign_org_role:update';
17+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'assign_role:assign';
18+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'assign_role:read';
19+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'assign_role:unassign';
20+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'audit_log:create';
21+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'audit_log:read';
22+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'connection_log:read';
23+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'connection_log:update';
24+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'crypto_key:create';
25+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'crypto_key:delete';
26+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'crypto_key:read';
27+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'crypto_key:update';
28+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'debug_info:read';
29+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'deployment_config:read';
30+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'deployment_config:update';
31+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'deployment_stats:read';
32+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'file:create';
33+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'file:read';
34+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'group:create';
35+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'group:delete';
36+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'group:read';
37+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'group:update';
38+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'group_member:read';
39+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'idpsync_settings:read';
40+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'idpsync_settings:update';
41+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'inbox_notification:create';
42+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'inbox_notification:read';
43+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'inbox_notification:update';
44+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'license:create';
45+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'license:delete';
46+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'license:read';
47+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'notification_message:create';
48+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'notification_message:delete';
49+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'notification_message:read';
50+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'notification_message:update';
51+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'notification_preference:read';
52+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'notification_preference:update';
53+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'notification_template:read';
54+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'notification_template:update';
55+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'oauth2_app:create';
56+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'oauth2_app:delete';
57+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'oauth2_app:read';
58+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'oauth2_app:update';
59+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'oauth2_app_code_token:create';
60+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'oauth2_app_code_token:delete';
61+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'oauth2_app_code_token:read';
62+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'oauth2_app_secret:create';
63+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'oauth2_app_secret:delete';
64+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'oauth2_app_secret:read';
65+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'oauth2_app_secret:update';
66+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'organization:create';
67+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'organization:delete';
68+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'organization:read';
69+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'organization:update';
70+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'organization_member:create';
71+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'organization_member:delete';
72+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'organization_member:read';
73+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'organization_member:update';
74+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'prebuilt_workspace:delete';
75+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'prebuilt_workspace:update';
76+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'provisioner_daemon:create';
77+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'provisioner_daemon:delete';
78+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'provisioner_daemon:read';
79+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'provisioner_daemon:update';
80+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'provisioner_jobs:create';
81+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'provisioner_jobs:read';
82+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'provisioner_jobs:update';
83+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'replicas:read';
84+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'system:create';
85+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'system:delete';
86+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'system:read';
87+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'system:update';
88+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'tailnet_coordinator:create';
89+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'tailnet_coordinator:delete';
90+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'tailnet_coordinator:read';
91+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'tailnet_coordinator:update';
92+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'template:create';
93+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'template:delete';
94+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'template:read';
95+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'template:update';
96+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'template:use';
97+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'template:view_insights';
98+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'usage_event:create';
99+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'usage_event:read';
100+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'usage_event:update';
101+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'user:create';
102+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'user:delete';
103+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'user:read';
104+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'user:read_personal';
105+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'user:update';
106+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'user:update_personal';
107+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'user_secret:create';
108+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'user_secret:delete';
109+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'user_secret:read';
110+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'user_secret:update';
111+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'webpush_subscription:create';
112+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'webpush_subscription:delete';
113+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'webpush_subscription:read';
114+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace:application_connect';
115+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace:create';
116+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace:create_agent';
117+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace:delete';
118+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace:delete_agent';
119+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace:read';
120+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace:ssh';
121+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace:start';
122+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace:stop';
123+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace:update';
124+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_agent_devcontainers:create';
125+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_agent_resource_monitor:create';
126+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_agent_resource_monitor:read';
127+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_agent_resource_monitor:update';
128+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_dormant:application_connect';
129+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_dormant:create';
130+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_dormant:create_agent';
131+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_dormant:delete';
132+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_dormant:delete_agent';
133+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_dormant:read';
134+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_dormant:ssh';
135+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_dormant:start';
136+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_dormant:stop';
137+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_dormant:update';
138+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_proxy:create';
139+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_proxy:delete';
140+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_proxy:read';
141+
ALTERTYPE api_key_scope ADD VALUE IF NOT EXISTS'workspace_proxy:update';
142+
-- End enum extensions
143+
144+
-- Add new columns without defaults; backfill; then enforce NOT NULL
145+
ALTERTABLE api_keys ADD COLUMN scopes api_key_scope[];
146+
ALTERTABLE api_keys ADD COLUMN allow_listtext[];
147+
148+
-- Backfill existing rows for compatibility
149+
UPDATE api_keysSET scopes= ARRAY[scope::api_key_scope];
150+
UPDATE api_keysSET allow_list= ARRAY['*:*'];
151+
152+
-- Enforce NOT NULL
153+
ALTERTABLE api_keys ALTER COLUMN scopesSETNOT NULL;
154+
ALTERTABLE api_keys ALTER COLUMN allow_listSETNOT NULL;
155+
156+
-- Drop legacy single-scope column
157+
ALTERTABLE api_keys DROP COLUMN scope;

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp