mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add allow list to API keys (#19972)
Add API key allow list to the SDK This PR adds an allow list to API keys in the SDK. The allow list is a list of targets that the API key is allowed to access. If the allow list is empty, a default allow list with a single entry that allows access to all resources is created. The changes include: - Adding a default allow list when generating an API key if none is provided - Adding allow list to the API key response in the SDK - Converting database allow list entries to SDK format in the API response - Adding tests to verify the default allow list behavior Fixes #19854
This commit is contained in:
Generated
+6
@@ -11894,6 +11894,12 @@ const docTemplate = `{
|
||||
"user_id"
|
||||
],
|
||||
"properties": {
|
||||
"allow_list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.APIAllowListTarget"
|
||||
}
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
|
||||
Generated
+6
@@ -10590,6 +10590,12 @@
|
||||
"user_id"
|
||||
],
|
||||
"properties": {
|
||||
"allow_list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.APIAllowListTarget"
|
||||
}
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
|
||||
@@ -51,6 +51,8 @@ func TestTokenCRUD(t *testing.T) {
|
||||
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*6))
|
||||
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*8))
|
||||
require.Equal(t, codersdk.APIKeyScopeAll, keys[0].Scope)
|
||||
require.Len(t, keys[0].AllowList, 1)
|
||||
require.Equal(t, "*:*", keys[0].AllowList[0].String())
|
||||
|
||||
// no update
|
||||
|
||||
@@ -86,6 +88,8 @@ func TestTokenScoped(t *testing.T) {
|
||||
require.EqualValues(t, len(keys), 1)
|
||||
require.Contains(t, res.Key, keys[0].ID)
|
||||
require.Equal(t, keys[0].Scope, codersdk.APIKeyScopeApplicationConnect)
|
||||
require.Len(t, keys[0].AllowList, 1)
|
||||
require.Equal(t, "*:*", keys[0].AllowList[0].String())
|
||||
}
|
||||
|
||||
// Ensure backward-compat: when a token is created using the legacy singular
|
||||
@@ -132,6 +136,8 @@ func TestTokenLegacySingularScopeCompat(t *testing.T) {
|
||||
require.Len(t, keys, 1)
|
||||
require.Equal(t, tc.scope, keys[0].Scope)
|
||||
require.ElementsMatch(t, keys[0].Scopes, tc.scopes)
|
||||
require.Len(t, keys[0].AllowList, 1)
|
||||
require.Equal(t, "*:*", keys[0].AllowList[0].String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ type CheckConstraint string
|
||||
|
||||
// CheckConstraint enums.
|
||||
const (
|
||||
CheckAPIKeysAllowListNotEmpty CheckConstraint = "api_keys_allow_list_not_empty" // api_keys
|
||||
CheckOneTimePasscodeSet CheckConstraint = "one_time_passcode_set" // users
|
||||
CheckUsersUsernameMinLength CheckConstraint = "users_username_min_length" // users
|
||||
CheckMaxProvisionerLogsLength CheckConstraint = "max_provisioner_logs_length" // provisioner_jobs
|
||||
|
||||
@@ -51,6 +51,13 @@ func ListLazy[F any, T any](convert func(F) T) func(list []F) []T {
|
||||
}
|
||||
}
|
||||
|
||||
func APIAllowListTarget(entry rbac.AllowListElement) codersdk.APIAllowListTarget {
|
||||
return codersdk.APIAllowListTarget{
|
||||
Type: codersdk.RBACResource(entry.Type),
|
||||
ID: entry.ID,
|
||||
}
|
||||
}
|
||||
|
||||
type ExternalAuthMeta struct {
|
||||
Authenticated bool
|
||||
ValidateError string
|
||||
|
||||
Generated
+2
-1
@@ -1126,7 +1126,8 @@ CREATE TABLE api_keys (
|
||||
ip_address inet DEFAULT '0.0.0.0'::inet NOT NULL,
|
||||
token_name text DEFAULT ''::text NOT NULL,
|
||||
scopes api_key_scope[] NOT NULL,
|
||||
allow_list text[] NOT NULL
|
||||
allow_list text[] NOT NULL,
|
||||
CONSTRAINT api_keys_allow_list_not_empty CHECK ((array_length(allow_list, 1) > 0))
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN api_keys.hashed_secret IS 'hashed_secret contains a SHA256 hash of the key secret. This is considered a secret and MUST NOT be returned from the API as it is used for API key encryption in app proxying code.';
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Drop all CHECK constraints added in the up migration
|
||||
ALTER TABLE api_keys
|
||||
DROP CONSTRAINT api_keys_allow_list_not_empty;
|
||||
@@ -0,0 +1,10 @@
|
||||
-- Defensively update any API keys with empty allow_list to have default '*:*'
|
||||
-- This ensures all existing keys have at least one entry before adding the constraint
|
||||
UPDATE api_keys
|
||||
SET allow_list = ARRAY['*:*']
|
||||
WHERE allow_list = ARRAY[]::text[] OR array_length(allow_list, 1) IS NULL;
|
||||
|
||||
-- Add CHECK constraint to ensure allow_list array is never empty
|
||||
ALTER TABLE api_keys
|
||||
ADD CONSTRAINT api_keys_allow_list_not_empty
|
||||
CHECK (array_length(allow_list, 1) > 0);
|
||||
@@ -1608,5 +1608,6 @@ func convertAPIKey(k database.APIKey) codersdk.APIKey {
|
||||
Scopes: scopes,
|
||||
LifetimeSeconds: k.LifetimeSeconds,
|
||||
TokenName: k.TokenName,
|
||||
AllowList: db2sdk.List(k.AllowList, db2sdk.APIAllowListTarget),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user