Skip to content

Commit 86f7de9

Browse files
committed
feat: add allow_list field to API key responses for resource scoping
Add allow_list field to API key data structures and ensure proper JSON serialization across backend and frontend. Initialize with default wildcard entry (*:*) for backward compatibility with existing API keys that don't have explicit resource restrictions. Fixes #19854
1 parent 49af2b4 commit 86f7de9

10 files changed

Lines changed: 161 additions & 46 deletions

File tree

coderd/apidoc/docs.go

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apikey/apikey.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/coder/coder/v2/coderd/database"
1414
"github.com/coder/coder/v2/coderd/database/dbtime"
15+
"github.com/coder/coder/v2/coderd/rbac"
1516
"github.com/coder/coder/v2/coderd/rbac/policy"
1617
"github.com/coder/coder/v2/cryptorand"
1718
)
@@ -102,6 +103,12 @@ func Generate(params CreateParams) (database.InsertAPIKeyParams, string, error)
102103
}
103104
}
104105

106+
if len(params.AllowList) == 0 {
107+
params.AllowList = database.AllowList{
108+
rbac.AllowListElement{Type: rbac.ResourceWildcard.Type, ID: policy.WildcardSymbol},
109+
}
110+
}
111+
105112
token := fmt.Sprintf("%s-%s", keyID, keySecret)
106113

107114
return database.InsertAPIKeyParams{

coderd/apikey_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ func TestTokenCRUD(t *testing.T) {
5151
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*6))
5252
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*8))
5353
require.Equal(t, codersdk.APIKeyScopeAll, keys[0].Scope)
54+
require.Len(t, keys[0].AllowList, 1)
55+
require.Equal(t, "*:*", keys[0].AllowList[0].String())
5456

5557
// no update
5658

@@ -86,6 +88,8 @@ func TestTokenScoped(t *testing.T) {
8688
require.EqualValues(t, len(keys), 1)
8789
require.Contains(t, res.Key, keys[0].ID)
8890
require.Equal(t, keys[0].Scope, codersdk.APIKeyScopeApplicationConnect)
91+
require.Len(t, keys[0].AllowList, 1)
92+
require.Equal(t, "*:*", keys[0].AllowList[0].String())
8993
}
9094

9195
// Ensure backward-compat: when a token is created using the legacy singular
@@ -132,6 +136,8 @@ func TestTokenLegacySingularScopeCompat(t *testing.T) {
132136
require.Len(t, keys, 1)
133137
require.Equal(t, tc.scope, keys[0].Scope)
134138
require.ElementsMatch(t, keys[0].Scopes, tc.scopes)
139+
require.Len(t, keys[0].AllowList, 1)
140+
require.Equal(t, "*:*", keys[0].AllowList[0].String())
135141
})
136142
}
137143
}

coderd/users.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,22 @@ func convertAPIKey(k database.APIKey) codersdk.APIKey {
15961596
scopes = append(scopes, codersdk.APIKeyScope(s))
15971597
}
15981598

1599+
allowList := make([]codersdk.APIAllowListTarget, 0, len(k.AllowList))
1600+
if len(k.AllowList) == 0 {
1601+
allowList = append(allowList, codersdk.AllowAllTarget())
1602+
} else {
1603+
for _, entry := range k.AllowList {
1604+
target := codersdk.AllowAllTarget()
1605+
if entry.Type != "" {
1606+
target.Type = codersdk.RBACResource(entry.Type)
1607+
}
1608+
if entry.ID != "" {
1609+
target.ID = entry.ID
1610+
}
1611+
allowList = append(allowList, target)
1612+
}
1613+
}
1614+
15991615
return codersdk.APIKey{
16001616
ID: k.ID,
16011617
UserID: k.UserID,
@@ -1608,5 +1624,6 @@ func convertAPIKey(k database.APIKey) codersdk.APIKey {
16081624
Scopes: scopes,
16091625
LifetimeSeconds: k.LifetimeSeconds,
16101626
TokenName: k.TokenName,
1627+
AllowList: allowList,
16111628
}
16121629
}

codersdk/apikey.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,18 @@ import (
1212

1313
// APIKey: do not ever return the HashedSecret
1414
type APIKey struct {
15-
ID string `json:"id" validate:"required"`
16-
UserID uuid.UUID `json:"user_id" validate:"required" format:"uuid"`
17-
LastUsed time.Time `json:"last_used" validate:"required" format:"date-time"`
18-
ExpiresAt time.Time `json:"expires_at" validate:"required" format:"date-time"`
19-
CreatedAt time.Time `json:"created_at" validate:"required" format:"date-time"`
20-
UpdatedAt time.Time `json:"updated_at" validate:"required" format:"date-time"`
21-
LoginType LoginType `json:"login_type" validate:"required" enums:"password,github,oidc,token"`
22-
Scope APIKeyScope `json:"scope" enums:"all,application_connect"` // Deprecated: use Scopes instead.
23-
Scopes []APIKeyScope `json:"scopes"`
24-
TokenName string `json:"token_name" validate:"required"`
25-
LifetimeSeconds int64 `json:"lifetime_seconds" validate:"required"`
15+
ID string `json:"id" validate:"required"`
16+
UserID uuid.UUID `json:"user_id" validate:"required" format:"uuid"`
17+
LastUsed time.Time `json:"last_used" validate:"required" format:"date-time"`
18+
ExpiresAt time.Time `json:"expires_at" validate:"required" format:"date-time"`
19+
CreatedAt time.Time `json:"created_at" validate:"required" format:"date-time"`
20+
UpdatedAt time.Time `json:"updated_at" validate:"required" format:"date-time"`
21+
LoginType LoginType `json:"login_type" validate:"required" enums:"password,github,oidc,token"`
22+
Scope APIKeyScope `json:"scope" enums:"all,application_connect"` // Deprecated: use Scopes instead.
23+
Scopes []APIKeyScope `json:"scopes"`
24+
TokenName string `json:"token_name" validate:"required"`
25+
LifetimeSeconds int64 `json:"lifetime_seconds" validate:"required"`
26+
AllowList []APIAllowListTarget `json:"allow_list"`
2627
}
2728

2829
// LoginType is the type of login used to create the API key.

docs/reference/api/schemas.md

Lines changed: 20 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/api/users.md

Lines changed: 84 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/src/api/typesGenerated.ts

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/src/testHelpers/entities.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export const MockToken: TypesGen.APIKeyWithOwner = {
8585
login_type: "token",
8686
scope: "all",
8787
scopes: ["coder:all"],
88+
allow_list: [{ type: "*", id: "*" }],
8889
lifetime_seconds: 2592000,
8990
token_name: "token-one",
9091
username: "admin",
@@ -102,6 +103,7 @@ export const MockTokens: TypesGen.APIKeyWithOwner[] = [
102103
login_type: "token",
103104
scope: "all",
104105
scopes: ["coder:all"],
106+
allow_list: [{ type: "*", id: "*" }],
105107
lifetime_seconds: 2592000,
106108
token_name: "token-two",
107109
username: "admin",

0 commit comments

Comments
 (0)