mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 19:29:13 +08:00
317 lines
11 KiB
TypeScript
317 lines
11 KiB
TypeScript
import { definePostgresSqlMigration } from './sqlMigration';
|
|
|
|
export const POSTGRESQL_IDENTITY_SUBJECT_MUTATION_TABLE =
|
|
'identity_subject_mutations';
|
|
export const POSTGRESQL_API_CREDENTIAL_MUTATION_TABLE =
|
|
'api_credential_mutations';
|
|
|
|
export const pg0006IdentityCredentialAdministrationMigration =
|
|
definePostgresSqlMigration({
|
|
id: 'pg-0006-identity-credential-administration',
|
|
statements: [
|
|
`
|
|
CREATE TABLE "ql3"."${POSTGRESQL_IDENTITY_SUBJECT_MUTATION_TABLE}" (
|
|
mutation_id uuid PRIMARY KEY,
|
|
operation varchar(16) NOT NULL
|
|
CONSTRAINT ql3_identity_subject_mutations_operation_check
|
|
CHECK (operation IN ('import', 'register', 'enable', 'disable')),
|
|
subject_type varchar(32) NOT NULL
|
|
CONSTRAINT ql3_identity_subject_mutations_subject_type_check
|
|
CHECK (subject_type IN ('user', 'api_app', 'mcp_client', 'agent', 'system', 'worker')),
|
|
subject_id varchar(255) NOT NULL
|
|
CONSTRAINT ql3_identity_subject_mutations_subject_id_check
|
|
CHECK (char_length(subject_id) >= 1),
|
|
subject_version integer NOT NULL
|
|
CONSTRAINT ql3_identity_subject_mutations_version_check
|
|
CHECK (subject_version BETWEEN 1 AND 2147483647),
|
|
expected_previous_version integer NOT NULL
|
|
CONSTRAINT ql3_identity_subject_mutations_previous_version_check
|
|
CHECK (expected_previous_version BETWEEN 0 AND 2147483646),
|
|
status varchar(16) NOT NULL
|
|
CONSTRAINT ql3_identity_subject_mutations_status_check
|
|
CHECK (status IN ('active', 'disabled')),
|
|
changed_by_type varchar(32) NOT NULL
|
|
CONSTRAINT ql3_identity_subject_mutations_changed_by_type_check
|
|
CHECK (changed_by_type IN ('user', 'system')),
|
|
changed_by_id varchar(255) NOT NULL
|
|
CONSTRAINT ql3_identity_subject_mutations_changed_by_id_check
|
|
CHECK (char_length(changed_by_id) >= 1),
|
|
audit_event_id uuid NOT NULL,
|
|
identity_created_at_ms bigint NOT NULL
|
|
CONSTRAINT ql3_identity_subject_mutations_identity_created_at_check
|
|
CHECK (identity_created_at_ms >= 0),
|
|
created_at_ms bigint NOT NULL
|
|
CONSTRAINT ql3_identity_subject_mutations_created_at_check
|
|
CHECK (created_at_ms >= 0),
|
|
CONSTRAINT ql3_identity_subject_mutations_version_fence_check
|
|
CHECK (subject_version = expected_previous_version + 1),
|
|
CONSTRAINT ql3_identity_subject_mutations_transition_check
|
|
CHECK (
|
|
operation = 'import'
|
|
OR (operation = 'register' AND expected_previous_version = 0 AND status = 'active')
|
|
OR (operation = 'enable' AND expected_previous_version >= 1 AND status = 'active')
|
|
OR (operation = 'disable' AND expected_previous_version >= 1 AND status = 'disabled')
|
|
),
|
|
CONSTRAINT ql3_identity_subject_mutations_audit_identity_check
|
|
CHECK (mutation_id = audit_event_id),
|
|
CONSTRAINT ql3_identity_subject_mutations_subject_fk
|
|
FOREIGN KEY (subject_type, subject_id)
|
|
REFERENCES "ql3"."identity_subjects" (subject_type, subject_id)
|
|
ON DELETE RESTRICT,
|
|
CONSTRAINT ql3_identity_subject_mutations_audit_fk
|
|
FOREIGN KEY (audit_event_id)
|
|
REFERENCES "ql3"."security_audit_events" (event_id)
|
|
ON DELETE RESTRICT,
|
|
CONSTRAINT ql3_identity_subject_mutations_subject_version_uidx
|
|
UNIQUE (subject_type, subject_id, subject_version)
|
|
)
|
|
`.trim(),
|
|
`
|
|
CREATE INDEX ql3_identity_subject_mutations_actor_idx
|
|
ON "ql3"."${POSTGRESQL_IDENTITY_SUBJECT_MUTATION_TABLE}"
|
|
(changed_by_type, changed_by_id, created_at_ms DESC, mutation_id)
|
|
`.trim(),
|
|
`
|
|
WITH source AS (
|
|
SELECT
|
|
subject.*,
|
|
md5('ql3-identity-import:' || subject.subject_type || ':' || subject.subject_id) AS digest
|
|
FROM "ql3"."identity_subjects" AS subject
|
|
), imported AS (
|
|
SELECT
|
|
source.*,
|
|
(
|
|
substr(digest, 1, 8) || '-' || substr(digest, 9, 4) || '-4' ||
|
|
substr(digest, 14, 3) || '-8' || substr(digest, 18, 3) || '-' ||
|
|
substr(digest, 21, 12)
|
|
)::uuid AS mutation_id
|
|
FROM source
|
|
)
|
|
INSERT INTO "ql3"."security_audit_events" (
|
|
event_id, request_id, operation_id, project_id, subject_type, subject_id,
|
|
authentication_id, outcome, reasons, project_version, binding_version,
|
|
occurred_at_ms
|
|
)
|
|
SELECT
|
|
mutation_id,
|
|
'migration:' || mutation_id::text,
|
|
'identity.import',
|
|
NULL,
|
|
'system',
|
|
'pg-0006',
|
|
'migration:pg-0006',
|
|
'allowed',
|
|
'["identity_admin"]'::jsonb,
|
|
NULL,
|
|
NULL,
|
|
created_at_ms
|
|
FROM imported
|
|
`.trim(),
|
|
`
|
|
WITH source AS (
|
|
SELECT
|
|
subject.*,
|
|
md5('ql3-identity-import:' || subject.subject_type || ':' || subject.subject_id) AS digest
|
|
FROM "ql3"."identity_subjects" AS subject
|
|
), imported AS (
|
|
SELECT
|
|
source.*,
|
|
(
|
|
substr(digest, 1, 8) || '-' || substr(digest, 9, 4) || '-4' ||
|
|
substr(digest, 14, 3) || '-8' || substr(digest, 18, 3) || '-' ||
|
|
substr(digest, 21, 12)
|
|
)::uuid AS mutation_id
|
|
FROM source
|
|
)
|
|
INSERT INTO "ql3"."${POSTGRESQL_IDENTITY_SUBJECT_MUTATION_TABLE}" (
|
|
mutation_id, operation, subject_type, subject_id, subject_version,
|
|
expected_previous_version, status, changed_by_type, changed_by_id,
|
|
audit_event_id, identity_created_at_ms, created_at_ms
|
|
)
|
|
SELECT
|
|
mutation_id,
|
|
'import',
|
|
subject_type,
|
|
subject_id,
|
|
version,
|
|
version - 1,
|
|
status,
|
|
'system',
|
|
'pg-0006',
|
|
mutation_id,
|
|
created_at_ms,
|
|
created_at_ms
|
|
FROM imported
|
|
`.trim(),
|
|
`
|
|
CREATE TABLE "ql3"."${POSTGRESQL_API_CREDENTIAL_MUTATION_TABLE}" (
|
|
mutation_id uuid PRIMARY KEY,
|
|
operation varchar(16) NOT NULL
|
|
CONSTRAINT ql3_api_credential_mutations_operation_check
|
|
CHECK (operation IN ('import', 'issue', 'rotate', 'revoke')),
|
|
credential_id varchar(64) NOT NULL
|
|
CONSTRAINT ql3_api_credential_mutations_id_check
|
|
CHECK (credential_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$'),
|
|
credential_version integer NOT NULL
|
|
CONSTRAINT ql3_api_credential_mutations_version_check
|
|
CHECK (credential_version BETWEEN 1 AND 2147483647),
|
|
expected_previous_version integer NOT NULL
|
|
CONSTRAINT ql3_api_credential_mutations_previous_version_check
|
|
CHECK (expected_previous_version BETWEEN 0 AND 2147483646),
|
|
state varchar(16) NOT NULL
|
|
CONSTRAINT ql3_api_credential_mutations_state_check
|
|
CHECK (state IN ('active', 'revoked')),
|
|
subject_type varchar(32) NOT NULL
|
|
CONSTRAINT ql3_api_credential_mutations_subject_type_check
|
|
CHECK (subject_type IN ('user', 'api_app', 'mcp_client', 'agent')),
|
|
subject_id varchar(255) NOT NULL
|
|
CONSTRAINT ql3_api_credential_mutations_subject_id_check
|
|
CHECK (char_length(subject_id) >= 1),
|
|
subject_status varchar(16) NOT NULL
|
|
CONSTRAINT ql3_api_credential_mutations_subject_status_check
|
|
CHECK (subject_status IN ('active', 'disabled')),
|
|
changed_by_type varchar(32) NOT NULL
|
|
CONSTRAINT ql3_api_credential_mutations_changed_by_type_check
|
|
CHECK (changed_by_type IN ('user', 'system')),
|
|
changed_by_id varchar(255) NOT NULL
|
|
CONSTRAINT ql3_api_credential_mutations_changed_by_id_check
|
|
CHECK (char_length(changed_by_id) >= 1),
|
|
audit_event_id uuid NOT NULL,
|
|
created_at_ms bigint NOT NULL
|
|
CONSTRAINT ql3_api_credential_mutations_created_at_check
|
|
CHECK (created_at_ms >= 0),
|
|
CONSTRAINT ql3_api_credential_mutations_version_fence_check
|
|
CHECK (credential_version = expected_previous_version + 1),
|
|
CONSTRAINT ql3_api_credential_mutations_transition_check
|
|
CHECK (
|
|
operation = 'import'
|
|
OR (operation = 'issue' AND expected_previous_version = 0 AND state = 'active')
|
|
OR (operation = 'rotate' AND expected_previous_version >= 1 AND state = 'active')
|
|
OR (operation = 'revoke' AND expected_previous_version >= 1 AND state = 'revoked')
|
|
),
|
|
CONSTRAINT ql3_api_credential_mutations_audit_identity_check
|
|
CHECK (mutation_id = audit_event_id),
|
|
CONSTRAINT ql3_api_credential_mutations_credential_fk
|
|
FOREIGN KEY (credential_id, credential_version)
|
|
REFERENCES "ql3"."api_credentials" (credential_id, version)
|
|
ON DELETE RESTRICT,
|
|
CONSTRAINT ql3_api_credential_mutations_subject_fk
|
|
FOREIGN KEY (subject_type, subject_id)
|
|
REFERENCES "ql3"."identity_subjects" (subject_type, subject_id)
|
|
ON DELETE RESTRICT,
|
|
CONSTRAINT ql3_api_credential_mutations_audit_fk
|
|
FOREIGN KEY (audit_event_id)
|
|
REFERENCES "ql3"."security_audit_events" (event_id)
|
|
ON DELETE RESTRICT,
|
|
CONSTRAINT ql3_api_credential_mutations_credential_version_uidx
|
|
UNIQUE (credential_id, credential_version)
|
|
)
|
|
`.trim(),
|
|
`
|
|
CREATE INDEX ql3_api_credential_mutations_actor_idx
|
|
ON "ql3"."${POSTGRESQL_API_CREDENTIAL_MUTATION_TABLE}"
|
|
(changed_by_type, changed_by_id, created_at_ms DESC, mutation_id)
|
|
`.trim(),
|
|
`
|
|
WITH source AS (
|
|
SELECT
|
|
credential.*,
|
|
md5('ql3-credential-import:' || credential.credential_id || ':' || credential.version::text) AS digest
|
|
FROM "ql3"."api_credentials" AS credential
|
|
), imported AS (
|
|
SELECT
|
|
source.*,
|
|
(
|
|
substr(digest, 1, 8) || '-' || substr(digest, 9, 4) || '-4' ||
|
|
substr(digest, 14, 3) || '-8' || substr(digest, 18, 3) || '-' ||
|
|
substr(digest, 21, 12)
|
|
)::uuid AS mutation_id
|
|
FROM source
|
|
)
|
|
INSERT INTO "ql3"."security_audit_events" (
|
|
event_id, request_id, operation_id, project_id, subject_type, subject_id,
|
|
authentication_id, outcome, reasons, project_version, binding_version,
|
|
occurred_at_ms
|
|
)
|
|
SELECT
|
|
mutation_id,
|
|
'migration:' || mutation_id::text,
|
|
'credential.import',
|
|
NULL,
|
|
'system',
|
|
'pg-0006',
|
|
'migration:pg-0006',
|
|
'allowed',
|
|
'["credential_admin"]'::jsonb,
|
|
NULL,
|
|
NULL,
|
|
created_at_ms
|
|
FROM imported
|
|
`.trim(),
|
|
`
|
|
WITH source AS (
|
|
SELECT
|
|
credential.*,
|
|
md5('ql3-credential-import:' || credential.credential_id || ':' || credential.version::text) AS digest
|
|
FROM "ql3"."api_credentials" AS credential
|
|
), imported AS (
|
|
SELECT
|
|
source.*,
|
|
(
|
|
substr(digest, 1, 8) || '-' || substr(digest, 9, 4) || '-4' ||
|
|
substr(digest, 14, 3) || '-8' || substr(digest, 18, 3) || '-' ||
|
|
substr(digest, 21, 12)
|
|
)::uuid AS mutation_id
|
|
FROM source
|
|
)
|
|
INSERT INTO "ql3"."${POSTGRESQL_API_CREDENTIAL_MUTATION_TABLE}" (
|
|
mutation_id, operation, credential_id, credential_version,
|
|
expected_previous_version, state, subject_type, subject_id,
|
|
subject_status, changed_by_type, changed_by_id, audit_event_id, created_at_ms
|
|
)
|
|
SELECT
|
|
mutation_id,
|
|
'import',
|
|
credential_id,
|
|
version,
|
|
version - 1,
|
|
state,
|
|
subject_type,
|
|
subject_id,
|
|
(
|
|
SELECT identity.status
|
|
FROM "ql3"."identity_subjects" AS identity
|
|
WHERE identity.subject_type = imported.subject_type
|
|
AND identity.subject_id = imported.subject_id
|
|
),
|
|
'system',
|
|
'pg-0006',
|
|
mutation_id,
|
|
created_at_ms
|
|
FROM imported
|
|
`.trim(),
|
|
`
|
|
DO $ql3$
|
|
BEGIN
|
|
UPDATE "ql3"."schema_capabilities"
|
|
SET contract_version = 5,
|
|
migration_id = 'pg-0006-identity-credential-administration',
|
|
capabilities = '{"api_credential":1,"api_credential_admin":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1}'::jsonb,
|
|
updated_at_ms = floor(
|
|
extract(epoch FROM transaction_timestamp()) * 1000
|
|
)::bigint
|
|
WHERE contract_name = 'control-core'
|
|
AND contract_version = 4
|
|
AND migration_id = 'pg-0005-api-credential-security-audit'
|
|
AND capabilities = '{"api_credential":1,"project_policy":1,"run_core":1,"run_retry_policy":1,"security_audit":1}'::jsonb;
|
|
|
|
IF NOT FOUND THEN
|
|
RAISE EXCEPTION 'control-core capability is not at version 4'
|
|
USING ERRCODE = 'check_violation';
|
|
END IF;
|
|
END
|
|
$ql3$
|
|
`.trim(),
|
|
],
|
|
});
|