mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 10:32:40 +08:00
feat(ql3): persist cluster secret transition receipts
This commit is contained in:
@@ -452,6 +452,69 @@ export const pluginPackageSecretBindingApprovalPlans = ql3Schema.table(
|
||||
],
|
||||
);
|
||||
|
||||
export const pluginPackageSecretBindingTransitionReceipts = ql3Schema.table(
|
||||
'plugin_package_secret_binding_transition_receipts',
|
||||
{
|
||||
generationDigest: char('generation_digest', { length: 64 }).primaryKey(),
|
||||
transitionDigest: char('transition_digest', { length: 64 }).notNull(),
|
||||
projectId: varchar('project_id', { length: 128 }).notNull(),
|
||||
packageName: varchar('package_name', { length: 63 }).notNull(),
|
||||
installationId: varchar('installation_id', { length: 128 }).notNull(),
|
||||
lockDigest: char('lock_digest', { length: 64 }).notNull(),
|
||||
generation: integer('generation').notNull(),
|
||||
manifestDigest: char('manifest_digest', { length: 64 }).notNull(),
|
||||
previousActiveLockDigest: char('previous_active_lock_digest', {
|
||||
length: 64,
|
||||
}).notNull(),
|
||||
authorityKind: varchar('authority_kind', { length: 32 }).notNull(),
|
||||
evidenceDigest: char('evidence_digest', { length: 64 }).notNull(),
|
||||
bindingDigest: char('binding_digest', { length: 64 }),
|
||||
committedAtMs: bigint('committed_at_ms', { mode: 'number' }).notNull(),
|
||||
receiptDigest: char('receipt_digest', { length: 64 }).notNull(),
|
||||
receiptJson: jsonb('receipt_json')
|
||||
.$type<Record<string, unknown>>()
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
foreignKey({
|
||||
name: 'ql3_plugin_package_secret_binding_transition_receipt_project_fk',
|
||||
columns: [table.projectId],
|
||||
foreignColumns: [projects.id],
|
||||
})
|
||||
.onDelete('restrict')
|
||||
.onUpdate('restrict'),
|
||||
foreignKey({
|
||||
name: 'ql3_plugin_package_secret_binding_transition_receipt_install_fk',
|
||||
columns: [table.installationId],
|
||||
foreignColumns: [pluginPackageInstalls.installationId],
|
||||
})
|
||||
.onDelete('restrict')
|
||||
.onUpdate('restrict'),
|
||||
check(
|
||||
'ql3_package_secret_transition_receipt_identity_check',
|
||||
sql`${table.projectId} ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' and ${table.packageName} ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$' and ${table.installationId} ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' and ${table.generation} between 2 and 2147483647 and ${table.authorityKind} = 'approved-action-execution' and ${table.committedAtMs} >= 0`,
|
||||
),
|
||||
check(
|
||||
'ql3_package_secret_transition_receipt_digest_check',
|
||||
sql`${table.generationDigest} ~ '^[0-9a-f]{64}$' and ${table.transitionDigest} ~ '^[0-9a-f]{64}$' and ${table.lockDigest} ~ '^[0-9a-f]{64}$' and ${table.manifestDigest} ~ '^[0-9a-f]{64}$' and ${table.previousActiveLockDigest} ~ '^[0-9a-f]{64}$' and ${table.evidenceDigest} ~ '^[0-9a-f]{64}$' and (${table.bindingDigest} is null or ${table.bindingDigest} ~ '^[0-9a-f]{64}$') and ${table.receiptDigest} ~ '^[0-9a-f]{64}$'`,
|
||||
),
|
||||
check(
|
||||
'ql3_package_secret_transition_receipt_json_check',
|
||||
sql`jsonb_typeof(${table.receiptJson}) = 'object' and octet_length(${table.receiptJson}::text) between 2 and 196608 and ${table.receiptJson} @> jsonb_build_object('schema', 'qinglong/plugin-package-secret-binding-transition-receipt@v1', 'transitionPlan', jsonb_build_object('schema', 'qinglong/plugin-package-secret-binding-transition-plan@v1', 'transitionDigest', ${table.transitionDigest}, 'previousActiveLockDigest', ${table.previousActiveLockDigest}, 'nextTarget', jsonb_build_object('generationDigest', ${table.generationDigest}, 'projectId', ${table.projectId}, 'packageName', ${table.packageName}, 'installationId', ${table.installationId}, 'lockDigest', ${table.lockDigest}, 'generation', ${table.generation}, 'manifestDigest', ${table.manifestDigest})), 'authority', jsonb_build_object('kind', ${table.authorityKind}, 'evidenceDigest', ${table.evidenceDigest}), 'bindingDigest', ${table.bindingDigest}, 'committedAtMs', ${table.committedAtMs}, 'receiptDigest', ${table.receiptDigest})`,
|
||||
),
|
||||
uniqueIndex('ql3_package_secret_transition_receipt_transition_uidx').on(
|
||||
table.transitionDigest,
|
||||
),
|
||||
uniqueIndex('ql3_package_secret_transition_receipt_digest_uidx').on(
|
||||
table.receiptDigest,
|
||||
),
|
||||
index('ql3_package_secret_transition_receipt_install_idx').on(
|
||||
table.installationId,
|
||||
table.generationDigest,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
export const projectToolDefinitionSnapshots = ql3Schema.table(
|
||||
'project_tool_definition_snapshots',
|
||||
{
|
||||
@@ -5940,6 +6003,7 @@ export const ql3PostgresTables = [
|
||||
pluginPackageMaterializedRevisions,
|
||||
pluginPackageSecretBindings,
|
||||
pluginPackageSecretBindingApprovalPlans,
|
||||
pluginPackageSecretBindingTransitionReceipts,
|
||||
projectToolDefinitionSnapshots,
|
||||
projectToolDefinitionSnapshotSources,
|
||||
pluginPackageQuarantineEvents,
|
||||
|
||||
@@ -21,8 +21,8 @@ export interface PostgresSchemaContractTrigger {
|
||||
export interface PostgresSchemaContract {
|
||||
readonly schema: 'ql3';
|
||||
readonly contractName: 'control-core';
|
||||
readonly contractVersion: 61;
|
||||
readonly migrationId: 'pg-0062-plugin-package-secret-binding-target-guard';
|
||||
readonly contractVersion: 62;
|
||||
readonly migrationId: 'pg-0063-plugin-package-secret-binding-transition-receipts';
|
||||
readonly minimumServerMajor: 16;
|
||||
readonly maximumServerMajor: 18;
|
||||
readonly capabilities: Readonly<{
|
||||
@@ -64,6 +64,7 @@ export interface PostgresSchemaContract {
|
||||
plugin_package_secret_binding: 1;
|
||||
plugin_package_secret_binding_approval_plan: 1;
|
||||
plugin_package_secret_binding_transition: 1;
|
||||
plugin_package_secret_binding_transition_receipt: 1;
|
||||
plugin_package_secret_materialization: 1;
|
||||
plugin_package_proposal: 1;
|
||||
plugin_package_publisher_provenance: 1;
|
||||
@@ -115,8 +116,8 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
|
||||
Object.freeze({
|
||||
schema: 'ql3',
|
||||
contractName: 'control-core',
|
||||
contractVersion: 61,
|
||||
migrationId: 'pg-0062-plugin-package-secret-binding-target-guard',
|
||||
contractVersion: 62,
|
||||
migrationId: 'pg-0063-plugin-package-secret-binding-transition-receipts',
|
||||
minimumServerMajor: 16,
|
||||
maximumServerMajor: 18,
|
||||
capabilities: Object.freeze({
|
||||
@@ -151,6 +152,7 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
|
||||
plugin_package_secret_binding: 1,
|
||||
plugin_package_secret_binding_approval_plan: 1,
|
||||
plugin_package_secret_binding_transition: 1,
|
||||
plugin_package_secret_binding_transition_receipt: 1,
|
||||
plugin_package_secret_materialization: 1,
|
||||
plugin_package_proposal: 1,
|
||||
plugin_package_publisher_provenance: 1,
|
||||
@@ -288,6 +290,23 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
|
||||
'expires_at_ms',
|
||||
'plan_json',
|
||||
]),
|
||||
table('plugin_package_secret_binding_transition_receipts', [
|
||||
'generation_digest',
|
||||
'transition_digest',
|
||||
'project_id',
|
||||
'package_name',
|
||||
'installation_id',
|
||||
'lock_digest',
|
||||
'generation',
|
||||
'manifest_digest',
|
||||
'previous_active_lock_digest',
|
||||
'authority_kind',
|
||||
'evidence_digest',
|
||||
'binding_digest',
|
||||
'committed_at_ms',
|
||||
'receipt_digest',
|
||||
'receipt_json',
|
||||
]),
|
||||
table('project_tool_definition_snapshots', [
|
||||
'project_id',
|
||||
'active_vector_digest',
|
||||
@@ -1471,6 +1490,10 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
|
||||
'ql3_plugin_package_secret_binding_approval_plan_digest_uidx',
|
||||
'ql3_plugin_package_secret_binding_approval_plan_target_idx',
|
||||
'ql3_plugin_package_secret_binding_approval_plan_expiry_idx',
|
||||
'plugin_package_secret_binding_transition_receipts_pkey',
|
||||
'ql3_package_secret_transition_receipt_transition_uidx',
|
||||
'ql3_package_secret_transition_receipt_digest_uidx',
|
||||
'ql3_package_secret_transition_receipt_install_idx',
|
||||
'project_tool_definition_snapshots_pkey',
|
||||
'ql3_project_tool_snapshot_withdrawal_key',
|
||||
'ql3_project_tool_definition_snapshot_digest_uidx',
|
||||
@@ -1769,6 +1792,9 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
|
||||
'ql3_plugin_package_secret_binding_approval_plan_digest_check',
|
||||
'ql3_plugin_package_secret_binding_approval_plan_time_check',
|
||||
'ql3_plugin_package_secret_binding_approval_plan_json_check',
|
||||
'ql3_package_secret_transition_receipt_identity_check',
|
||||
'ql3_package_secret_transition_receipt_digest_check',
|
||||
'ql3_package_secret_transition_receipt_json_check',
|
||||
'ql3_plugin_package_quarantine_identity_check',
|
||||
'ql3_plugin_package_quarantine_state_check',
|
||||
'ql3_plugin_package_quarantine_subject_check',
|
||||
@@ -2226,6 +2252,8 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
|
||||
'ql3_plugin_package_secret_binding_install_fk',
|
||||
'ql3_plugin_package_secret_binding_approval_plan_project_fk',
|
||||
'ql3_plugin_package_secret_binding_approval_plan_install_fk',
|
||||
'ql3_plugin_package_secret_binding_transition_receipt_project_fk',
|
||||
'ql3_plugin_package_secret_binding_transition_receipt_install_fk',
|
||||
'ql3_project_tool_definition_snapshot_project_fk',
|
||||
'ql3_project_tool_definition_snapshot_source_snapshot_fk',
|
||||
'ql3_project_tool_definition_snapshot_source_install_fk',
|
||||
@@ -2401,6 +2429,14 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
|
||||
volatility: 'volatile',
|
||||
configuration: Object.freeze(['search_path=pg_catalog, ql3']),
|
||||
}),
|
||||
Object.freeze({
|
||||
name: 'enforce_plugin_package_secret_binding_transition_receipt_target',
|
||||
identityArguments: '',
|
||||
owner: 'ql3_migration',
|
||||
securityDefiner: false,
|
||||
volatility: 'volatile',
|
||||
configuration: Object.freeze(['search_path=pg_catalog, ql3']),
|
||||
}),
|
||||
Object.freeze({
|
||||
name: 'enforce_plugin_package_secret_materialization',
|
||||
identityArguments: '',
|
||||
@@ -2539,6 +2575,12 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
|
||||
tableName: 'plugin_package_secret_bindings',
|
||||
functionName: 'enforce_plugin_package_secret_binding_target',
|
||||
}),
|
||||
Object.freeze({
|
||||
name: 'ql3_package_secret_transition_receipt_target_guard',
|
||||
tableName: 'plugin_package_secret_binding_transition_receipts',
|
||||
functionName:
|
||||
'enforce_plugin_package_secret_binding_transition_receipt_target',
|
||||
}),
|
||||
Object.freeze({
|
||||
name: 'ql3_plugin_package_secret_materialization_guard',
|
||||
tableName: 'plugin_package_materialized_revisions',
|
||||
|
||||
@@ -186,6 +186,12 @@ const REQUIRED_RUNTIME_PRIVILEGES = Object.freeze({
|
||||
update: false,
|
||||
delete: false,
|
||||
}),
|
||||
plugin_package_secret_binding_transition_receipts: Object.freeze({
|
||||
select: false,
|
||||
insert: false,
|
||||
update: false,
|
||||
delete: false,
|
||||
}),
|
||||
project_tool_definition_snapshots: Object.freeze({
|
||||
select: false,
|
||||
insert: false,
|
||||
@@ -711,6 +717,12 @@ const REQUIRED_ADMIN_PRIVILEGES = Object.freeze({
|
||||
update: false,
|
||||
delete: false,
|
||||
}),
|
||||
plugin_package_secret_binding_transition_receipts: Object.freeze({
|
||||
select: false,
|
||||
insert: false,
|
||||
update: false,
|
||||
delete: false,
|
||||
}),
|
||||
project_tool_definition_snapshots: Object.freeze({
|
||||
select: false,
|
||||
insert: false,
|
||||
@@ -1264,6 +1276,7 @@ const REQUIRED_PACKAGE_EXECUTOR_PRIVILEGES: RequiredPrivileges = Object.freeze(
|
||||
name === 'plugin_package_install_mutations' ||
|
||||
name === 'plugin_package_materialized_revisions' ||
|
||||
name === 'plugin_package_secret_bindings' ||
|
||||
name === 'plugin_package_secret_binding_transition_receipts' ||
|
||||
name === 'project_tool_definition_snapshots' ||
|
||||
name === 'project_tool_definition_snapshot_sources' ||
|
||||
name === 'plugin_package_lifecycle_plans' ||
|
||||
@@ -1531,6 +1544,7 @@ const REQUIRED_RUNTIME_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges =
|
||||
commit_plugin_package_task_reconciliation: false,
|
||||
enforce_plugin_package_secret_materialization: false,
|
||||
enforce_plugin_package_secret_binding_target: false,
|
||||
enforce_plugin_package_secret_binding_transition_receipt_target: false,
|
||||
enforce_plugin_package_stage_provenance: false,
|
||||
lock_active_plugin_package_project: false,
|
||||
lock_approval_policy_fence: false,
|
||||
@@ -1553,6 +1567,7 @@ const REQUIRED_PACKAGE_MANAGER_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges =
|
||||
commit_plugin_package_task_reconciliation: false,
|
||||
enforce_plugin_package_secret_materialization: false,
|
||||
enforce_plugin_package_secret_binding_target: false,
|
||||
enforce_plugin_package_secret_binding_transition_receipt_target: false,
|
||||
enforce_plugin_package_stage_provenance: false,
|
||||
lock_active_plugin_package_project: false,
|
||||
lock_approval_policy_fence: true,
|
||||
@@ -1575,6 +1590,7 @@ const REQUIRED_PACKAGE_EXECUTOR_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges
|
||||
commit_plugin_package_task_reconciliation: true,
|
||||
enforce_plugin_package_secret_materialization: false,
|
||||
enforce_plugin_package_secret_binding_target: false,
|
||||
enforce_plugin_package_secret_binding_transition_receipt_target: false,
|
||||
enforce_plugin_package_stage_provenance: false,
|
||||
lock_active_plugin_package_project: true,
|
||||
lock_approval_policy_fence: true,
|
||||
@@ -1744,10 +1760,9 @@ async function assertSchemaContract(
|
||||
constraintsResult,
|
||||
functionsResult,
|
||||
triggersResult,
|
||||
] =
|
||||
await Promise.all([
|
||||
queryable.query<ColumnRow>(
|
||||
`
|
||||
] = await Promise.all([
|
||||
queryable.query<ColumnRow>(
|
||||
`
|
||||
SELECT
|
||||
tables.relname AS "tableName",
|
||||
columns.attname AS "columnName"
|
||||
@@ -1760,19 +1775,19 @@ WHERE schemas.nspname = $1
|
||||
AND NOT columns.attisdropped
|
||||
ORDER BY tables.relname, columns.attnum
|
||||
`.trim(),
|
||||
[contract.schema],
|
||||
),
|
||||
queryable.query<IndexRow>(
|
||||
`
|
||||
[contract.schema],
|
||||
),
|
||||
queryable.query<IndexRow>(
|
||||
`
|
||||
SELECT indexname AS "indexName"
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = $1
|
||||
ORDER BY indexname
|
||||
`.trim(),
|
||||
[contract.schema],
|
||||
),
|
||||
queryable.query<ConstraintRow>(
|
||||
`
|
||||
[contract.schema],
|
||||
),
|
||||
queryable.query<ConstraintRow>(
|
||||
`
|
||||
SELECT
|
||||
constraints.conname AS "constraintName",
|
||||
CASE constraints.contype
|
||||
@@ -1786,10 +1801,10 @@ WHERE schemas.nspname = $1
|
||||
AND constraints.contype IN ('c', 'f')
|
||||
ORDER BY constraints.contype, constraints.conname
|
||||
`.trim(),
|
||||
[contract.schema],
|
||||
),
|
||||
queryable.query<FunctionRow>(
|
||||
`
|
||||
[contract.schema],
|
||||
),
|
||||
queryable.query<FunctionRow>(
|
||||
`
|
||||
SELECT
|
||||
routines.proname AS "functionName",
|
||||
pg_get_function_identity_arguments(routines.oid) AS "identityArguments",
|
||||
@@ -1817,10 +1832,10 @@ JOIN pg_namespace schemas ON schemas.oid = routines.pronamespace
|
||||
WHERE schemas.nspname = $1
|
||||
ORDER BY routines.proname, pg_get_function_identity_arguments(routines.oid)
|
||||
`.trim(),
|
||||
[contract.schema],
|
||||
),
|
||||
queryable.query<TriggerRow>(
|
||||
`
|
||||
[contract.schema],
|
||||
),
|
||||
queryable.query<TriggerRow>(
|
||||
`
|
||||
SELECT
|
||||
triggers.tgname AS "triggerName",
|
||||
tables.relname AS "tableName",
|
||||
@@ -1835,12 +1850,9 @@ WHERE schemas.nspname = $1
|
||||
AND triggers.tgname = ANY($2::text[])
|
||||
ORDER BY triggers.tgname
|
||||
`.trim(),
|
||||
[
|
||||
contract.schema,
|
||||
contract.triggers.map(({ name }) => name),
|
||||
],
|
||||
),
|
||||
]);
|
||||
[contract.schema, contract.triggers.map(({ name }) => name)],
|
||||
),
|
||||
]);
|
||||
const actualTables = new Map<string, Set<string>>();
|
||||
for (const row of columnsResult.rows) {
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user