mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add ai_gateway_keys table and related RBAC (#25563)
Adds table to store keys that AI Gateway standalone replicas will use to authenticate into Coderd. Also adds RBAC and audit boilerplate.
This commit is contained in:
Generated
+3
@@ -6,6 +6,9 @@ type CheckConstraint string
|
||||
|
||||
// CheckConstraint enums.
|
||||
const (
|
||||
CheckAiGatewayKeysHashedSecretCheck CheckConstraint = "ai_gateway_keys_hashed_secret_check" // ai_gateway_keys
|
||||
CheckAiGatewayKeysNameCheck CheckConstraint = "ai_gateway_keys_name_check" // ai_gateway_keys
|
||||
CheckAiGatewayKeysSecretPrefixCheck CheckConstraint = "ai_gateway_keys_secret_prefix_check" // ai_gateway_keys
|
||||
CheckAiModelPricesCacheReadPriceCheck CheckConstraint = "ai_model_prices_cache_read_price_check" // ai_model_prices
|
||||
CheckAiModelPricesCacheWritePriceCheck CheckConstraint = "ai_model_prices_cache_write_price_check" // ai_model_prices
|
||||
CheckAiModelPricesInputPriceCheck CheckConstraint = "ai_model_prices_input_price_check" // ai_model_prices
|
||||
|
||||
Generated
+32
-2
@@ -253,7 +253,11 @@ CREATE TYPE api_key_scope AS ENUM (
|
||||
'boundary_log:*',
|
||||
'boundary_log:create',
|
||||
'boundary_log:delete',
|
||||
'boundary_log:read'
|
||||
'boundary_log:read',
|
||||
'ai_gateway_key:*',
|
||||
'ai_gateway_key:create',
|
||||
'ai_gateway_key:delete',
|
||||
'ai_gateway_key:read'
|
||||
);
|
||||
|
||||
CREATE TYPE app_sharing_level AS ENUM (
|
||||
@@ -564,7 +568,8 @@ CREATE TYPE resource_type AS ENUM (
|
||||
'ai_provider',
|
||||
'ai_provider_key',
|
||||
'group_ai_budget',
|
||||
'user_skill'
|
||||
'user_skill',
|
||||
'ai_gateway_key'
|
||||
);
|
||||
|
||||
CREATE TYPE shareable_workspace_owners AS ENUM (
|
||||
@@ -1287,6 +1292,22 @@ BEGIN
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TABLE ai_gateway_keys (
|
||||
id uuid NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
name text NOT NULL,
|
||||
secret_prefix character varying(11) NOT NULL,
|
||||
hashed_secret bytea NOT NULL,
|
||||
last_used_at timestamp with time zone,
|
||||
CONSTRAINT ai_gateway_keys_hashed_secret_check CHECK ((length(hashed_secret) > 0)),
|
||||
CONSTRAINT ai_gateway_keys_name_check CHECK (((length(name) <= 64) AND (name ~ '^[a-z0-9]+(-[a-z0-9]+)*$'::text))),
|
||||
CONSTRAINT ai_gateway_keys_secret_prefix_check CHECK ((length((secret_prefix)::text) = 11))
|
||||
);
|
||||
|
||||
COMMENT ON TABLE ai_gateway_keys IS 'Hashed bearer secrets used by AI Gateway standalone replicas to authenticate into coderd.';
|
||||
|
||||
COMMENT ON COLUMN ai_gateway_keys.secret_prefix IS 'Public token prefix for display and audit correlation. Auth uses hashed_secret.';
|
||||
|
||||
CREATE TABLE ai_model_prices (
|
||||
provider text NOT NULL,
|
||||
model text NOT NULL,
|
||||
@@ -3763,6 +3784,9 @@ ALTER TABLE ONLY workspace_resource_metadata ALTER COLUMN id SET DEFAULT nextval
|
||||
ALTER TABLE ONLY workspace_agent_stats
|
||||
ADD CONSTRAINT agent_stats_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY ai_gateway_keys
|
||||
ADD CONSTRAINT ai_gateway_keys_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY ai_model_prices
|
||||
ADD CONSTRAINT ai_model_prices_pkey PRIMARY KEY (provider, model);
|
||||
|
||||
@@ -4147,6 +4171,12 @@ ALTER TABLE ONLY workspace_resources
|
||||
ALTER TABLE ONLY workspaces
|
||||
ADD CONSTRAINT workspaces_pkey PRIMARY KEY (id);
|
||||
|
||||
CREATE UNIQUE INDEX ai_gateway_keys_hashed_secret_idx ON ai_gateway_keys USING btree (hashed_secret);
|
||||
|
||||
CREATE UNIQUE INDEX ai_gateway_keys_name_idx ON ai_gateway_keys USING btree (lower(name));
|
||||
|
||||
CREATE UNIQUE INDEX ai_gateway_keys_secret_prefix_idx ON ai_gateway_keys USING btree (secret_prefix);
|
||||
|
||||
CREATE UNIQUE INDEX ai_providers_name_unique ON ai_providers USING btree (name) WHERE (deleted = false);
|
||||
|
||||
CREATE INDEX api_keys_last_used_idx ON api_keys USING btree (last_used DESC);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Enum additions to resource_type and api_key_scope are intentionally not
|
||||
-- reverted because Postgres cannot drop enum values safely.
|
||||
DROP INDEX IF EXISTS ai_gateway_keys_hashed_secret_idx;
|
||||
DROP INDEX IF EXISTS ai_gateway_keys_secret_prefix_idx;
|
||||
DROP INDEX IF EXISTS ai_gateway_keys_name_idx;
|
||||
DROP TABLE IF EXISTS ai_gateway_keys;
|
||||
@@ -0,0 +1,25 @@
|
||||
CREATE TABLE ai_gateway_keys (
|
||||
id uuid PRIMARY KEY,
|
||||
created_at timestamptz NOT NULL,
|
||||
name text NOT NULL,
|
||||
secret_prefix varchar(11) NOT NULL,
|
||||
hashed_secret bytea NOT NULL,
|
||||
last_used_at timestamptz NULL,
|
||||
CONSTRAINT ai_gateway_keys_name_check CHECK (length(name) <= 64 AND name ~ '^[a-z0-9]+(-[a-z0-9]+)*$'),
|
||||
CONSTRAINT ai_gateway_keys_secret_prefix_check CHECK (length(secret_prefix) = 11),
|
||||
CONSTRAINT ai_gateway_keys_hashed_secret_check CHECK (length(hashed_secret) > 0)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE ai_gateway_keys IS 'Hashed bearer secrets used by AI Gateway standalone replicas to authenticate into coderd.';
|
||||
COMMENT ON COLUMN ai_gateway_keys.secret_prefix IS 'Public token prefix for display and audit correlation. Auth uses hashed_secret.';
|
||||
|
||||
CREATE UNIQUE INDEX ai_gateway_keys_name_idx ON ai_gateway_keys USING btree (lower(name));
|
||||
CREATE UNIQUE INDEX ai_gateway_keys_secret_prefix_idx ON ai_gateway_keys USING btree (secret_prefix);
|
||||
CREATE UNIQUE INDEX ai_gateway_keys_hashed_secret_idx ON ai_gateway_keys USING btree (hashed_secret);
|
||||
|
||||
ALTER TYPE resource_type ADD VALUE IF NOT EXISTS 'ai_gateway_key';
|
||||
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'ai_gateway_key:*';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'ai_gateway_key:create';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'ai_gateway_key:delete';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'ai_gateway_key:read';
|
||||
@@ -0,0 +1,15 @@
|
||||
INSERT INTO ai_gateway_keys (
|
||||
id,
|
||||
created_at,
|
||||
name,
|
||||
secret_prefix,
|
||||
hashed_secret,
|
||||
last_used_at
|
||||
) VALUES (
|
||||
'8b6f0a82-9a3a-4d2e-8c0c-2c9c9b9b1a01',
|
||||
'2026-05-21 00:00:00+00',
|
||||
'example-key',
|
||||
'cdr_1234567',
|
||||
'\x00'::bytea,
|
||||
NULL
|
||||
);
|
||||
Generated
+28
-2
@@ -324,6 +324,10 @@ const (
|
||||
ApiKeyScopeBoundaryLogCreate APIKeyScope = "boundary_log:create"
|
||||
ApiKeyScopeBoundaryLogDelete APIKeyScope = "boundary_log:delete"
|
||||
ApiKeyScopeBoundaryLogRead APIKeyScope = "boundary_log:read"
|
||||
ApiKeyScopeAiGatewayKey APIKeyScope = "ai_gateway_key:*"
|
||||
ApiKeyScopeAiGatewayKeyCreate APIKeyScope = "ai_gateway_key:create"
|
||||
ApiKeyScopeAiGatewayKeyDelete APIKeyScope = "ai_gateway_key:delete"
|
||||
ApiKeyScopeAiGatewayKeyRead APIKeyScope = "ai_gateway_key:read"
|
||||
)
|
||||
|
||||
func (e *APIKeyScope) Scan(src interface{}) error {
|
||||
@@ -588,7 +592,11 @@ func (e APIKeyScope) Valid() bool {
|
||||
ApiKeyScopeBoundaryLog,
|
||||
ApiKeyScopeBoundaryLogCreate,
|
||||
ApiKeyScopeBoundaryLogDelete,
|
||||
ApiKeyScopeBoundaryLogRead:
|
||||
ApiKeyScopeBoundaryLogRead,
|
||||
ApiKeyScopeAiGatewayKey,
|
||||
ApiKeyScopeAiGatewayKeyCreate,
|
||||
ApiKeyScopeAiGatewayKeyDelete,
|
||||
ApiKeyScopeAiGatewayKeyRead:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -822,6 +830,10 @@ func AllAPIKeyScopeValues() []APIKeyScope {
|
||||
ApiKeyScopeBoundaryLogCreate,
|
||||
ApiKeyScopeBoundaryLogDelete,
|
||||
ApiKeyScopeBoundaryLogRead,
|
||||
ApiKeyScopeAiGatewayKey,
|
||||
ApiKeyScopeAiGatewayKeyCreate,
|
||||
ApiKeyScopeAiGatewayKeyDelete,
|
||||
ApiKeyScopeAiGatewayKeyRead,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3353,6 +3365,7 @@ const (
|
||||
ResourceTypeAIProviderKey ResourceType = "ai_provider_key"
|
||||
ResourceTypeGroupAiBudget ResourceType = "group_ai_budget"
|
||||
ResourceTypeUserSkill ResourceType = "user_skill"
|
||||
ResourceTypeAIGatewayKey ResourceType = "ai_gateway_key"
|
||||
)
|
||||
|
||||
func (e *ResourceType) Scan(src interface{}) error {
|
||||
@@ -3424,7 +3437,8 @@ func (e ResourceType) Valid() bool {
|
||||
ResourceTypeAIProvider,
|
||||
ResourceTypeAIProviderKey,
|
||||
ResourceTypeGroupAiBudget,
|
||||
ResourceTypeUserSkill:
|
||||
ResourceTypeUserSkill,
|
||||
ResourceTypeAIGatewayKey:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -3465,6 +3479,7 @@ func AllResourceTypeValues() []ResourceType {
|
||||
ResourceTypeAIProviderKey,
|
||||
ResourceTypeGroupAiBudget,
|
||||
ResourceTypeUserSkill,
|
||||
ResourceTypeAIGatewayKey,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4435,6 +4450,17 @@ type AIBridgeUserPrompt struct {
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
// Hashed bearer secrets used by AI Gateway standalone replicas to authenticate into coderd.
|
||||
type AIGatewayKey struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
Name string `db:"name" json:"name"`
|
||||
// Public token prefix for display and audit correlation. Auth uses hashed_secret.
|
||||
SecretPrefix string `db:"secret_prefix" json:"secret_prefix"`
|
||||
HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"`
|
||||
LastUsedAt sql.NullTime `db:"last_used_at" json:"last_used_at"`
|
||||
}
|
||||
|
||||
// Runtime configuration for AI providers. Authoritative source for the provider set served by aibridged. Replaces deployment-time CODER_AIBRIDGE_* environment variables.
|
||||
type AIProvider struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
|
||||
@@ -261,8 +261,10 @@ sql:
|
||||
ai_provider: AIProvider
|
||||
ai_provider_key: AIProviderKey
|
||||
ai_provider_type: AIProviderType
|
||||
ai_gateway_key: AIGatewayKey
|
||||
resource_type_ai_provider: ResourceTypeAIProvider
|
||||
resource_type_ai_provider_key: ResourceTypeAIProviderKey
|
||||
resource_type_ai_gateway_key: ResourceTypeAIGatewayKey
|
||||
mcp_server_config: MCPServerConfig
|
||||
mcp_server_configs: MCPServerConfigs
|
||||
mcp_server_user_token: MCPServerUserToken
|
||||
|
||||
Generated
+4
@@ -7,6 +7,7 @@ type UniqueConstraint string
|
||||
// UniqueConstraint enums.
|
||||
const (
|
||||
UniqueAgentStatsPkey UniqueConstraint = "agent_stats_pkey" // ALTER TABLE ONLY workspace_agent_stats ADD CONSTRAINT agent_stats_pkey PRIMARY KEY (id);
|
||||
UniqueAiGatewayKeysPkey UniqueConstraint = "ai_gateway_keys_pkey" // ALTER TABLE ONLY ai_gateway_keys ADD CONSTRAINT ai_gateway_keys_pkey PRIMARY KEY (id);
|
||||
UniqueAiModelPricesPkey UniqueConstraint = "ai_model_prices_pkey" // ALTER TABLE ONLY ai_model_prices ADD CONSTRAINT ai_model_prices_pkey PRIMARY KEY (provider, model);
|
||||
UniqueAiProviderKeysPkey UniqueConstraint = "ai_provider_keys_pkey" // ALTER TABLE ONLY ai_provider_keys ADD CONSTRAINT ai_provider_keys_pkey PRIMARY KEY (id);
|
||||
UniqueAiProvidersPkey UniqueConstraint = "ai_providers_pkey" // ALTER TABLE ONLY ai_providers ADD CONSTRAINT ai_providers_pkey PRIMARY KEY (id);
|
||||
@@ -135,6 +136,9 @@ const (
|
||||
UniqueWorkspaceResourceMetadataPkey UniqueConstraint = "workspace_resource_metadata_pkey" // ALTER TABLE ONLY workspace_resource_metadata ADD CONSTRAINT workspace_resource_metadata_pkey PRIMARY KEY (id);
|
||||
UniqueWorkspaceResourcesPkey UniqueConstraint = "workspace_resources_pkey" // ALTER TABLE ONLY workspace_resources ADD CONSTRAINT workspace_resources_pkey PRIMARY KEY (id);
|
||||
UniqueWorkspacesPkey UniqueConstraint = "workspaces_pkey" // ALTER TABLE ONLY workspaces ADD CONSTRAINT workspaces_pkey PRIMARY KEY (id);
|
||||
UniqueAiGatewayKeysHashedSecretIndex UniqueConstraint = "ai_gateway_keys_hashed_secret_idx" // CREATE UNIQUE INDEX ai_gateway_keys_hashed_secret_idx ON ai_gateway_keys USING btree (hashed_secret);
|
||||
UniqueAiGatewayKeysNameIndex UniqueConstraint = "ai_gateway_keys_name_idx" // CREATE UNIQUE INDEX ai_gateway_keys_name_idx ON ai_gateway_keys USING btree (lower(name));
|
||||
UniqueAiGatewayKeysSecretPrefixIndex UniqueConstraint = "ai_gateway_keys_secret_prefix_idx" // CREATE UNIQUE INDEX ai_gateway_keys_secret_prefix_idx ON ai_gateway_keys USING btree (secret_prefix);
|
||||
UniqueAiProvidersNameUnique UniqueConstraint = "ai_providers_name_unique" // CREATE UNIQUE INDEX ai_providers_name_unique ON ai_providers USING btree (name) WHERE (deleted = false);
|
||||
UniqueIndexAPIKeyName UniqueConstraint = "idx_api_key_name" // CREATE UNIQUE INDEX idx_api_key_name ON api_keys USING btree (user_id, token_name) WHERE (login_type = 'token'::login_type);
|
||||
UniqueIndexChatDebugRunsIDChat UniqueConstraint = "idx_chat_debug_runs_id_chat" // CREATE UNIQUE INDEX idx_chat_debug_runs_id_chat ON chat_debug_runs USING btree (id, chat_id);
|
||||
|
||||
Reference in New Issue
Block a user