mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 19:29:13 +08:00
feat(ql3): persist package secret approval plans
This commit is contained in:
+310
@@ -0,0 +1,310 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const { test } = require('node:test');
|
||||
|
||||
const {
|
||||
PLUGIN_PACKAGE_API_VERSION,
|
||||
PLUGIN_PACKAGE_KIND,
|
||||
planPluginPackageInstall,
|
||||
} = require('@qinglong/runtime-core/plugin-package');
|
||||
const {
|
||||
createPluginPackageInstall,
|
||||
createPluginPackageLock,
|
||||
pluginPackageActivationIntentDigest,
|
||||
pluginPackageInstallActionDigest,
|
||||
pluginPackageInstallPlanDigest,
|
||||
transitionPluginPackageInstall,
|
||||
} = require('@qinglong/runtime-core/plugin-package-install');
|
||||
const {
|
||||
createPluginPackageInstallProposal,
|
||||
} = require('@qinglong/runtime-core/plugin-package-proposal');
|
||||
const {
|
||||
createPluginPackageResourceGeneration,
|
||||
} = require('@qinglong/runtime-core/plugin-package-resource-generation');
|
||||
const {
|
||||
createPluginPackageSecretBindingPlan,
|
||||
} = require('@qinglong/runtime-core/plugin-package-secret-binding-plan');
|
||||
const {
|
||||
PluginPackageSecretBindingApprovalPlanConflictError,
|
||||
PluginPackageSecretBindingApprovalPlanUnavailableError,
|
||||
createPluginPackageSecretBindingApprovalPlan,
|
||||
} = require('@qinglong/runtime-core/plugin-package-secret-binding-approval-plan');
|
||||
const { createSecretRef } = require('@qinglong/runtime-core/secret-reference');
|
||||
const {
|
||||
PostgresPluginPackageSecretBindingApprovalPlanReader,
|
||||
PostgresPluginPackageSecretBindingApprovalPlanRepository,
|
||||
} = require('../dist/plugin-package/secret-binding/pluginPackageSecretBindingApprovalPlanRepository');
|
||||
|
||||
function fixture() {
|
||||
const manifest = {
|
||||
apiVersion: PLUGIN_PACKAGE_API_VERSION,
|
||||
kind: PLUGIN_PACKAGE_KIND,
|
||||
metadata: {
|
||||
name: 'example-monitor',
|
||||
displayName: 'Example Monitor',
|
||||
version: '1.0.0',
|
||||
description: 'PostgreSQL Secret binding plan fixture',
|
||||
license: 'Apache-2.0',
|
||||
},
|
||||
spec: {
|
||||
compatibility: {
|
||||
qinglong: '>=3.0.0-0 <4.0.0',
|
||||
architectures: ['arm64'],
|
||||
deploymentProfiles: ['cluster-control'],
|
||||
},
|
||||
runtimes: [],
|
||||
resources: {
|
||||
memory: { recommended: '32Mi' },
|
||||
disk: { install: '4Mi', working: '8Mi' },
|
||||
},
|
||||
permissions: {
|
||||
network: { allowedHosts: [] },
|
||||
secrets: [{ name: 'TOKEN', required: true }],
|
||||
tools: ['secret.use'],
|
||||
},
|
||||
contents: { tasks: [], workflows: [], prompts: [], tools: [] },
|
||||
},
|
||||
};
|
||||
const environment = {
|
||||
qinglongVersion: '3.0.0-alpha.0',
|
||||
architecture: 'arm64',
|
||||
deploymentProfile: 'cluster-control',
|
||||
runtimes: [],
|
||||
availableMemoryBytes: 128 * 1024 * 1024,
|
||||
availableDiskBytes: 256 * 1024 * 1024,
|
||||
};
|
||||
const installPlan = planPluginPackageInstall(manifest, environment);
|
||||
const actionInput = {
|
||||
lockId: 'lock-secret-binding-1',
|
||||
projectId: 'project-1',
|
||||
manifest,
|
||||
plan: installPlan,
|
||||
environment,
|
||||
source: {
|
||||
kind: 'offline',
|
||||
locator: `offline:sha256:${'a'.repeat(64)}`,
|
||||
artifactDigest: 'a'.repeat(64),
|
||||
artifactBytes: 2048,
|
||||
contentDigest: 'b'.repeat(64),
|
||||
},
|
||||
architecture: 'arm64',
|
||||
deploymentProfile: 'cluster-control',
|
||||
targetGeneration: 1,
|
||||
};
|
||||
const proposal = createPluginPackageInstallProposal({
|
||||
actionRef: 'proposal:secret-binding-install-v1',
|
||||
actionInput,
|
||||
proposedBy: { type: 'user', id: 'cluster-owner' },
|
||||
proposalFence: { projectVersion: 1, bindingVersion: 1 },
|
||||
createdAtMs: 90,
|
||||
});
|
||||
const lock = createPluginPackageLock({
|
||||
...actionInput,
|
||||
approval: {
|
||||
requestId: 'approval-secret-binding-install-v1',
|
||||
requestVersion: 1,
|
||||
dispatchId: 'dispatch-secret-binding-install-v1',
|
||||
actionDigest: pluginPackageInstallActionDigest(actionInput),
|
||||
previewDigest: pluginPackageInstallPlanDigest(installPlan),
|
||||
approvedBy: { type: 'user', id: 'install-reviewer' },
|
||||
approvedAtMs: 100,
|
||||
expiresAtMs: 1_000,
|
||||
fence: { projectVersion: 1, bindingVersion: 1 },
|
||||
},
|
||||
createdAtMs: 200,
|
||||
});
|
||||
const queued = createPluginPackageInstall(lock, {
|
||||
installationId: 'install-secret-binding-1',
|
||||
mutationId: 'mutation-secret-binding-create',
|
||||
occurredAtMs: 201,
|
||||
});
|
||||
const staged = transitionPluginPackageInstall(lock, queued, {
|
||||
type: 'stage_completed',
|
||||
mutationId: 'mutation-secret-binding-stage',
|
||||
occurredAtMs: 202,
|
||||
stageRef: 'stage-secret-binding-1',
|
||||
artifactDigest: lock.source.artifactDigest,
|
||||
manifestDigest: lock.manifestDigest,
|
||||
contentDigest: lock.source.contentDigest,
|
||||
evidenceDigest: 'c'.repeat(64),
|
||||
});
|
||||
const activating = transitionPluginPackageInstall(lock, staged, {
|
||||
type: 'activation_started',
|
||||
mutationId: 'mutation-secret-binding-activate',
|
||||
occurredAtMs: 203,
|
||||
});
|
||||
const record = transitionPluginPackageInstall(lock, activating, {
|
||||
type: 'activation_committed',
|
||||
mutationId: 'mutation-secret-binding-commit',
|
||||
occurredAtMs: 204,
|
||||
activationRef: 'activation-secret-binding-1',
|
||||
intentDigest: pluginPackageActivationIntentDigest(lock, activating),
|
||||
generation: 1,
|
||||
contentDigest: lock.source.contentDigest,
|
||||
});
|
||||
const generation = createPluginPackageResourceGeneration({
|
||||
installationId: record.installationId,
|
||||
projectId: record.projectId,
|
||||
packageName: record.packageName,
|
||||
lockDigest: record.lockDigest,
|
||||
generation: record.targetGeneration,
|
||||
previousActiveLockDigest: record.previousActiveLockDigest,
|
||||
contentDigest: lock.source.contentDigest,
|
||||
contents: manifest.spec.contents,
|
||||
});
|
||||
const bindingPlan = createPluginPackageSecretBindingPlan({
|
||||
generation,
|
||||
manifest,
|
||||
assignments: [
|
||||
{
|
||||
name: 'TOKEN',
|
||||
secretRef: createSecretRef({
|
||||
projectId: 'project-1',
|
||||
name: 'runtime-token',
|
||||
version: 2,
|
||||
}),
|
||||
},
|
||||
],
|
||||
plannedAtMs: 300,
|
||||
});
|
||||
const approvalPlan = createPluginPackageSecretBindingApprovalPlan({
|
||||
actionRef: 'secret-binding:example-monitor-v1',
|
||||
bindingPlan,
|
||||
requestedBy: { type: 'user', id: 'cluster-owner' },
|
||||
expiresAtMs: 900,
|
||||
});
|
||||
return { approvalPlan, lock, proposal, record };
|
||||
}
|
||||
|
||||
test('reads and creates one exact approval plan only through the narrow database function', async () => {
|
||||
const { approvalPlan } = fixture();
|
||||
const calls = [];
|
||||
const repository =
|
||||
new PostgresPluginPackageSecretBindingApprovalPlanRepository({
|
||||
async query(text, parameters) {
|
||||
calls.push({ text, parameters });
|
||||
if (
|
||||
text.includes('create_plugin_package_secret_binding_approval_plan')
|
||||
) {
|
||||
return { rows: [{ status: 'created' }] };
|
||||
}
|
||||
return { rows: [{ planJson: approvalPlan }] };
|
||||
},
|
||||
});
|
||||
|
||||
const result = await repository.create(approvalPlan);
|
||||
assert.deepEqual(result, { status: 'created', plan: approvalPlan });
|
||||
assert.equal(calls.length, 2);
|
||||
assert.match(
|
||||
calls[0].text,
|
||||
/create_plugin_package_secret_binding_approval_plan/,
|
||||
);
|
||||
assert.deepEqual(calls[0].parameters, [JSON.stringify(approvalPlan)]);
|
||||
assert.doesNotMatch(calls[0].text, /INSERT INTO/);
|
||||
assert.match(calls[1].text, /WHERE action_ref = \$1/);
|
||||
});
|
||||
|
||||
test('loads one current unbound generation snapshot and rejects provenance drift', async () => {
|
||||
const { lock, proposal, record } = fixture();
|
||||
const pool = {
|
||||
async query(text, parameters) {
|
||||
assert.match(text, /plugin_package_secret_binding_planning_snapshot/);
|
||||
assert.deepEqual(parameters, ['project-1', 'example-monitor']);
|
||||
return {
|
||||
rows: [
|
||||
{
|
||||
recordJson: record,
|
||||
lockJson: lock,
|
||||
proposalJson: proposal,
|
||||
observedAtMs: '500',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
const reader = new PostgresPluginPackageSecretBindingApprovalPlanReader(pool);
|
||||
assert.deepEqual(
|
||||
await reader.loadPlanningSnapshot('project-1', 'example-monitor'),
|
||||
{ record, lock, proposal, observedAtMs: 500 },
|
||||
);
|
||||
|
||||
const drifted = new PostgresPluginPackageSecretBindingApprovalPlanReader({
|
||||
async query() {
|
||||
return {
|
||||
rows: [
|
||||
{
|
||||
recordJson: record,
|
||||
lockJson: lock,
|
||||
proposalJson: { ...proposal, previewDigest: 'f'.repeat(64) },
|
||||
observedAtMs: 500,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
await assert.rejects(
|
||||
drifted.loadPlanningSnapshot('project-1', 'example-monitor'),
|
||||
PluginPackageSecretBindingApprovalPlanUnavailableError,
|
||||
);
|
||||
});
|
||||
|
||||
test('returns absence and fails closed on replay drift or storage conflict', async () => {
|
||||
const { approvalPlan } = fixture();
|
||||
const reader = new PostgresPluginPackageSecretBindingApprovalPlanReader({
|
||||
async query() {
|
||||
return { rows: [] };
|
||||
},
|
||||
});
|
||||
assert.equal(await reader.findByActionRef(approvalPlan.actionRef), null);
|
||||
|
||||
const otherPlan = createPluginPackageSecretBindingApprovalPlan({
|
||||
actionRef: approvalPlan.actionRef,
|
||||
bindingPlan: approvalPlan.bindingPlan,
|
||||
requestedBy: { type: 'user', id: 'another-owner' },
|
||||
expiresAtMs: approvalPlan.expiresAtMs,
|
||||
});
|
||||
|
||||
const drifted = new PostgresPluginPackageSecretBindingApprovalPlanRepository({
|
||||
async query(text) {
|
||||
return text.includes('create_plugin_package_secret_binding_approval_plan')
|
||||
? { rows: [{ status: 'existing' }] }
|
||||
: { rows: [{ planJson: otherPlan }] };
|
||||
},
|
||||
});
|
||||
await assert.rejects(
|
||||
drifted.create(approvalPlan),
|
||||
PluginPackageSecretBindingApprovalPlanConflictError,
|
||||
);
|
||||
|
||||
const conflict = new PostgresPluginPackageSecretBindingApprovalPlanRepository(
|
||||
{
|
||||
async query() {
|
||||
const error = new Error('unique violation');
|
||||
error.code = '23505';
|
||||
throw error;
|
||||
},
|
||||
},
|
||||
);
|
||||
await assert.rejects(
|
||||
conflict.create(approvalPlan),
|
||||
PluginPackageSecretBindingApprovalPlanConflictError,
|
||||
);
|
||||
});
|
||||
|
||||
test('exports plan creation only to the Package manager and readback to the executor', () => {
|
||||
const manager = require('@qinglong/cluster-postgres/package-manager');
|
||||
const executor = require('@qinglong/cluster-postgres/package-executor');
|
||||
assert.equal(
|
||||
manager.PostgresPluginPackageSecretBindingApprovalPlanRepository,
|
||||
PostgresPluginPackageSecretBindingApprovalPlanRepository,
|
||||
);
|
||||
assert.equal(
|
||||
executor.PostgresPluginPackageSecretBindingApprovalPlanRepository,
|
||||
undefined,
|
||||
);
|
||||
assert.equal(
|
||||
executor.PostgresPluginPackageSecretBindingApprovalPlanReader,
|
||||
PostgresPluginPackageSecretBindingApprovalPlanReader,
|
||||
);
|
||||
});
|
||||
@@ -108,7 +108,10 @@ const {
|
||||
} = require('@qinglong/runtime-core/plugin-package-admission');
|
||||
const {
|
||||
pluginPackageInstallActionDigest,
|
||||
pluginPackageActivationIntentDigest,
|
||||
pluginPackageInstallCommit,
|
||||
pluginPackageInstallPlanDigest,
|
||||
transitionPluginPackageInstall,
|
||||
} = require('@qinglong/runtime-core/plugin-package-install');
|
||||
const {
|
||||
createPluginPackageInstallProposal,
|
||||
@@ -183,6 +186,17 @@ const {
|
||||
const {
|
||||
createPluginPackageResourceGenerationFromReferences,
|
||||
} = require('@qinglong/runtime-core/plugin-package-resource-generation');
|
||||
const {
|
||||
createPluginPackageSecretBindingPlan,
|
||||
} = require('@qinglong/runtime-core/plugin-package-secret-binding-plan');
|
||||
const {
|
||||
createPluginPackageSecretBindingApprovalPlan,
|
||||
} = require('@qinglong/runtime-core/plugin-package-secret-binding-approval-plan');
|
||||
const { createSecretRef } = require('@qinglong/runtime-core/secret-reference');
|
||||
const {
|
||||
PostgresPluginPackageSecretBindingApprovalPlanReader,
|
||||
PostgresPluginPackageSecretBindingApprovalPlanRepository,
|
||||
} = require('../dist/plugin-package/secret-binding/pluginPackageSecretBindingApprovalPlanRepository');
|
||||
const {
|
||||
createProjectToolDefinitionSnapshot,
|
||||
projectToolDefinitionRegistry,
|
||||
@@ -291,8 +305,8 @@ function admissionPackageAction() {
|
||||
},
|
||||
permissions: {
|
||||
network: { allowedHosts: [] },
|
||||
secrets: [],
|
||||
tools: [],
|
||||
secrets: [{ name: 'TOKEN', required: true }],
|
||||
tools: ['secret.use'],
|
||||
},
|
||||
contents: { tasks: [], workflows: [], prompts: [], tools: [] },
|
||||
},
|
||||
@@ -882,14 +896,16 @@ if (!migrationConnectionString) {
|
||||
GROUP BY run.id, attempt.id`,
|
||||
[accepted.runId],
|
||||
);
|
||||
assert.deepEqual(durable.rows, [{
|
||||
status: 'queued',
|
||||
version: 2,
|
||||
eventSequence: 2,
|
||||
trigger_type: 'task_start',
|
||||
attemptStatus: 'claimed',
|
||||
eventCount: 2,
|
||||
}]);
|
||||
assert.deepEqual(durable.rows, [
|
||||
{
|
||||
status: 'queued',
|
||||
version: 2,
|
||||
eventSequence: 2,
|
||||
trigger_type: 'task_start',
|
||||
attemptStatus: 'claimed',
|
||||
eventCount: 2,
|
||||
},
|
||||
]);
|
||||
} finally {
|
||||
if (runtimeDatabase && runtimeDatabase !== migrationDatabase) {
|
||||
await runtimeDatabase.close();
|
||||
@@ -2087,12 +2103,14 @@ if (!migrationConnectionString) {
|
||||
runtimeDatabase.pool,
|
||||
);
|
||||
assert.deepEqual(await taskAttempts.listCandidates({ limit: 8 }), {
|
||||
candidates: [{
|
||||
runId: plan.runId,
|
||||
stepRunId: plan.steps[0].stepRunId,
|
||||
readyAtMs: plan.plannedAtMs,
|
||||
planDigest: plan.planDigest,
|
||||
}],
|
||||
candidates: [
|
||||
{
|
||||
runId: plan.runId,
|
||||
stepRunId: plan.steps[0].stepRunId,
|
||||
readyAtMs: plan.plannedAtMs,
|
||||
planDigest: plan.planDigest,
|
||||
},
|
||||
],
|
||||
truncated: false,
|
||||
});
|
||||
const taskAttempt = await taskAttempts.admit(
|
||||
@@ -2167,29 +2185,34 @@ if (!migrationConnectionString) {
|
||||
GROUP BY run.id, attempt.id, step.id`,
|
||||
[plan.runId, taskAttempt.receipt.attemptId],
|
||||
);
|
||||
assert.deepEqual(recovered.rows, [{
|
||||
runStatus: 'running',
|
||||
runVersion: 6,
|
||||
eventSequence: 6,
|
||||
attemptStatus: 'lost',
|
||||
stepStatus: 'ready',
|
||||
stepVersion: 2,
|
||||
stepAttemptCount: 0,
|
||||
events: [
|
||||
'workflow.task_attempt_admitted',
|
||||
'workflow.task_attempt.lost',
|
||||
'step.ready',
|
||||
],
|
||||
}]);
|
||||
assert.deepEqual(recovered.rows, [
|
||||
{
|
||||
runStatus: 'running',
|
||||
runVersion: 6,
|
||||
eventSequence: 6,
|
||||
attemptStatus: 'lost',
|
||||
stepStatus: 'ready',
|
||||
stepVersion: 2,
|
||||
stepAttemptCount: 0,
|
||||
events: [
|
||||
'workflow.task_attempt_admitted',
|
||||
'workflow.task_attempt.lost',
|
||||
'step.ready',
|
||||
],
|
||||
},
|
||||
]);
|
||||
const requeued = await taskAttempts.listCandidates({ limit: 8 });
|
||||
assert.deepEqual(
|
||||
requeued.candidates.map(
|
||||
({ runId, stepRunId }) => ({ runId, stepRunId }),
|
||||
),
|
||||
[{
|
||||
runId: plan.runId,
|
||||
stepRunId: plan.steps[0].stepRunId,
|
||||
}],
|
||||
requeued.candidates.map(({ runId, stepRunId }) => ({
|
||||
runId,
|
||||
stepRunId,
|
||||
})),
|
||||
[
|
||||
{
|
||||
runId: plan.runId,
|
||||
stepRunId: plan.steps[0].stepRunId,
|
||||
},
|
||||
],
|
||||
);
|
||||
const secondTaskAttempt = await taskAttempts.admit(
|
||||
plan.runId,
|
||||
@@ -2242,20 +2265,22 @@ if (!migrationConnectionString) {
|
||||
WHERE run.id = $1`,
|
||||
[plan.runId],
|
||||
);
|
||||
assert.deepEqual(cancelled.rows, [{
|
||||
runStatus: 'cancelled',
|
||||
runVersion: 11,
|
||||
eventSequence: 11,
|
||||
attemptStatuses: ['cancelled', 'lost'],
|
||||
stepStatuses: ['cancelled'],
|
||||
events: [
|
||||
'workflow.task_attempt_admitted',
|
||||
'workflow.task_attempt.cancelled',
|
||||
'step.cancelled',
|
||||
'step.cancelled',
|
||||
'workflow.cancelled',
|
||||
],
|
||||
}]);
|
||||
assert.deepEqual(cancelled.rows, [
|
||||
{
|
||||
runStatus: 'cancelled',
|
||||
runVersion: 11,
|
||||
eventSequence: 11,
|
||||
attemptStatuses: ['cancelled', 'lost'],
|
||||
stepStatuses: ['cancelled'],
|
||||
events: [
|
||||
'workflow.task_attempt_admitted',
|
||||
'workflow.task_attempt.cancelled',
|
||||
'step.cancelled',
|
||||
'step.cancelled',
|
||||
'workflow.cancelled',
|
||||
],
|
||||
},
|
||||
]);
|
||||
} finally {
|
||||
await runtimeDatabase.close();
|
||||
}
|
||||
@@ -3607,10 +3632,181 @@ if (!migrationConnectionString) {
|
||||
await repository.findAdmissionReceipt(consumed.dispatch.id),
|
||||
admitted.receipt,
|
||||
);
|
||||
|
||||
const staged = transitionPluginPackageInstall(lock, admitted.record, {
|
||||
type: 'stage_completed',
|
||||
mutationId: 'stage-package-secret-binding-integration-1',
|
||||
occurredAtMs: admittedAtMs + 1,
|
||||
stageRef: `stage:${lock.lockDigest}`,
|
||||
artifactDigest: lock.source.artifactDigest,
|
||||
manifestDigest: lock.manifestDigest,
|
||||
contentDigest: lock.source.contentDigest,
|
||||
evidenceDigest: 'd'.repeat(64),
|
||||
});
|
||||
const stageProvenance = createPluginPackagePublisherProvenance({
|
||||
projectId: staged.projectId,
|
||||
packageName: staged.packageName,
|
||||
installationId: staged.installationId,
|
||||
lockDigest: staged.lockDigest,
|
||||
artifactDigest: staged.stageReceipt.artifactDigest,
|
||||
manifestDigest: staged.stageReceipt.manifestDigest,
|
||||
contentDigest: staged.stageReceipt.contentDigest,
|
||||
stageEvidenceDigest: staged.stageReceipt.evidenceDigest,
|
||||
signature: {
|
||||
publisher: 'integration.qinglong.dev',
|
||||
keyId: 'integration-key-1',
|
||||
signatureDigest: 'e'.repeat(64),
|
||||
keyNotBeforeMs: 0,
|
||||
keyNotAfterMs: admittedAtMs + 60_000,
|
||||
verifiedAtMs: admittedAtMs,
|
||||
},
|
||||
});
|
||||
await executorDatabase.pool.query(
|
||||
`INSERT INTO "ql3"."plugin_package_publisher_provenance" (
|
||||
installation_id, project_id, package_name, lock_digest,
|
||||
artifact_digest, manifest_digest, content_digest,
|
||||
stage_evidence_digest, publisher, key_id, signature_digest,
|
||||
key_not_before_ms, key_not_after_ms, verified_at_ms,
|
||||
provenance_digest, provenance_json
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11,
|
||||
$12, $13, $14, $15, $16::jsonb
|
||||
)`,
|
||||
[
|
||||
stageProvenance.installationId,
|
||||
stageProvenance.projectId,
|
||||
stageProvenance.packageName,
|
||||
stageProvenance.lockDigest,
|
||||
stageProvenance.artifactDigest,
|
||||
stageProvenance.manifestDigest,
|
||||
stageProvenance.contentDigest,
|
||||
stageProvenance.stageEvidenceDigest,
|
||||
stageProvenance.publisher,
|
||||
stageProvenance.keyId,
|
||||
stageProvenance.signatureDigest,
|
||||
stageProvenance.keyNotBeforeMs,
|
||||
stageProvenance.keyNotAfterMs,
|
||||
stageProvenance.verifiedAtMs,
|
||||
stageProvenance.provenanceDigest,
|
||||
JSON.stringify(stageProvenance),
|
||||
],
|
||||
);
|
||||
await repository.commit(
|
||||
pluginPackageInstallCommit(admitted.record, staged),
|
||||
);
|
||||
const activating = transitionPluginPackageInstall(lock, staged, {
|
||||
type: 'activation_started',
|
||||
mutationId: 'activate-package-secret-binding-integration-1',
|
||||
occurredAtMs: admittedAtMs + 2,
|
||||
});
|
||||
await repository.commit(pluginPackageInstallCommit(staged, activating));
|
||||
const active = transitionPluginPackageInstall(lock, activating, {
|
||||
type: 'activation_committed',
|
||||
mutationId: 'commit-package-secret-binding-integration-1',
|
||||
occurredAtMs: admittedAtMs + 3,
|
||||
activationRef: `active:${lock.lockDigest}`,
|
||||
intentDigest: pluginPackageActivationIntentDigest(lock, activating),
|
||||
generation: lock.targetGeneration,
|
||||
contentDigest: lock.source.contentDigest,
|
||||
});
|
||||
await repository.commit(pluginPackageInstallCommit(activating, active));
|
||||
} finally {
|
||||
await executorDatabase.close();
|
||||
}
|
||||
|
||||
const bindingManagerDatabase = await open('package-manager');
|
||||
let approvalPlan;
|
||||
try {
|
||||
const plans =
|
||||
new PostgresPluginPackageSecretBindingApprovalPlanRepository(
|
||||
bindingManagerDatabase.pool,
|
||||
);
|
||||
const snapshot = await plans.loadPlanningSnapshot(
|
||||
'default',
|
||||
'postgres-monitor',
|
||||
);
|
||||
assert.ok(snapshot);
|
||||
const generation = createPluginPackageResourceGenerationFromReferences({
|
||||
installationId: snapshot.record.installationId,
|
||||
projectId: snapshot.record.projectId,
|
||||
packageName: snapshot.record.packageName,
|
||||
lockDigest: snapshot.record.lockDigest,
|
||||
generation: snapshot.record.targetGeneration,
|
||||
previousActiveLockDigest: snapshot.record.previousActiveLockDigest,
|
||||
contentDigest: snapshot.lock.source.contentDigest,
|
||||
resources: [],
|
||||
});
|
||||
const bindingPlan = createPluginPackageSecretBindingPlan({
|
||||
generation,
|
||||
manifest: snapshot.proposal.actionInput.manifest,
|
||||
assignments: [
|
||||
{
|
||||
name: 'TOKEN',
|
||||
secretRef: createSecretRef({
|
||||
projectId: 'default',
|
||||
name: 'postgres-monitor-token',
|
||||
version: 1,
|
||||
}),
|
||||
},
|
||||
],
|
||||
plannedAtMs: snapshot.observedAtMs,
|
||||
});
|
||||
approvalPlan = createPluginPackageSecretBindingApprovalPlan({
|
||||
actionRef: 'secret-binding:postgres-monitor-v1',
|
||||
bindingPlan,
|
||||
requestedBy: requester,
|
||||
expiresAtMs: snapshot.observedAtMs + 60_000,
|
||||
});
|
||||
assert.equal((await plans.create(approvalPlan)).status, 'created');
|
||||
assert.equal((await plans.create(approvalPlan)).status, 'existing');
|
||||
await assert.rejects(
|
||||
bindingManagerDatabase.pool.query(
|
||||
`INSERT INTO "ql3"."plugin_package_secret_binding_approval_plans" (
|
||||
action_ref
|
||||
) VALUES ('forbidden-direct-manager-insert')`,
|
||||
),
|
||||
(error) => error?.code === '42501',
|
||||
);
|
||||
} finally {
|
||||
await bindingManagerDatabase.close();
|
||||
}
|
||||
|
||||
const bindingExecutorDatabase = await open('package-executor');
|
||||
try {
|
||||
assert.deepEqual(
|
||||
await new PostgresPluginPackageSecretBindingApprovalPlanReader(
|
||||
bindingExecutorDatabase.pool,
|
||||
).findByActionRef(approvalPlan.actionRef),
|
||||
approvalPlan,
|
||||
);
|
||||
await assert.rejects(
|
||||
bindingExecutorDatabase.pool.query(
|
||||
`SELECT "ql3"."create_plugin_package_secret_binding_approval_plan"(
|
||||
$1::jsonb
|
||||
)`,
|
||||
[JSON.stringify(approvalPlan)],
|
||||
),
|
||||
(error) => error?.code === '42501',
|
||||
);
|
||||
} finally {
|
||||
await bindingExecutorDatabase.close();
|
||||
}
|
||||
|
||||
const runtimeDatabase = await open('runtime');
|
||||
try {
|
||||
await assert.rejects(
|
||||
runtimeDatabase.pool.query(
|
||||
`SELECT *
|
||||
FROM "ql3"."plugin_package_secret_binding_planning_snapshot"(
|
||||
'default', 'postgres-monitor'
|
||||
)`,
|
||||
),
|
||||
(error) => error?.code === '42501',
|
||||
);
|
||||
} finally {
|
||||
await runtimeDatabase.close();
|
||||
}
|
||||
|
||||
const verificationDatabase = await open('migration');
|
||||
try {
|
||||
const facts = await verificationDatabase.pool.query(
|
||||
@@ -3623,13 +3819,17 @@ if (!migrationConnectionString) {
|
||||
FROM "ql3"."plugin_package_admission_receipts") AS receipts,
|
||||
(SELECT count(*)::integer
|
||||
FROM "ql3"."security_audit_events"
|
||||
WHERE operation_id = 'plugin_package.admit') AS audits`,
|
||||
WHERE operation_id = 'plugin_package.admit') AS audits,
|
||||
(SELECT count(*)::integer
|
||||
FROM "ql3"."plugin_package_secret_binding_approval_plans")
|
||||
AS "approvalPlans"`,
|
||||
);
|
||||
assert.deepEqual(facts.rows[0], {
|
||||
installs: 1,
|
||||
mutations: 1,
|
||||
mutations: 4,
|
||||
receipts: 1,
|
||||
audits: 1,
|
||||
approvalPlans: 1,
|
||||
});
|
||||
} finally {
|
||||
await verificationDatabase.close();
|
||||
@@ -4718,10 +4918,9 @@ if (!migrationConnectionString) {
|
||||
new PostgresTaskDefinitionAdministrationRepository(
|
||||
automationDatabase.pool,
|
||||
);
|
||||
const triggerMutations =
|
||||
new PostgresTriggerAdministrationRepository(
|
||||
automationDatabase.pool,
|
||||
);
|
||||
const triggerMutations = new PostgresTriggerAdministrationRepository(
|
||||
automationDatabase.pool,
|
||||
);
|
||||
const taskBase = {
|
||||
projectId,
|
||||
taskId: 'automation-task',
|
||||
|
||||
@@ -111,6 +111,7 @@ test('defines the immutable PostgreSQL capability and Run core stream', async ()
|
||||
'pg-0058-plugin-package-automation-disposition-events',
|
||||
'pg-0059-plugin-package-secret-bindings',
|
||||
'pg-0060-plugin-package-secret-materialization-guard',
|
||||
'pg-0061-plugin-package-secret-binding-approval-plans',
|
||||
],
|
||||
);
|
||||
for (const migration of postgresqlMainMigrationStream.migrations) {
|
||||
@@ -163,6 +164,7 @@ test('keeps local-only and legacy tables out of the cluster baseline', async ()
|
||||
'plugin_package_install_mutations',
|
||||
'plugin_package_materialized_revisions',
|
||||
'plugin_package_secret_bindings',
|
||||
'plugin_package_secret_binding_approval_plans',
|
||||
'plugin_package_lifecycle_events',
|
||||
'plugin_package_lifecycle_heads',
|
||||
'plugin_package_lifecycle_receipts',
|
||||
@@ -547,6 +549,11 @@ test('freezes every published PostgreSQL migration checksum', () => {
|
||||
checksum:
|
||||
'28284ca860b39ff9de5b2aa1a2a60ef2c463fd6a72798d237040174272b64b1e',
|
||||
},
|
||||
{
|
||||
id: 'pg-0061-plugin-package-secret-binding-approval-plans',
|
||||
checksum:
|
||||
'c995b7846ae8a57d3abb4b5523961e81aeba890e7405a030bcb505dfc6be3d25',
|
||||
},
|
||||
];
|
||||
assert.deepEqual(
|
||||
postgresqlMainMigrationStream.migrations.map(({ id, checksum }) => ({
|
||||
@@ -2030,3 +2037,49 @@ test('advances capability v59 with fail-closed Package Secret materialization',
|
||||
assert.match(sql, /contract_version = 58/);
|
||||
assert.match(sql, /migration_id = 'pg-0059-plugin-package-secret-bindings'/);
|
||||
});
|
||||
|
||||
test('advances capability v60 with least-privilege Package Secret binding approval plans', async () => {
|
||||
const migration = migrationById(
|
||||
'pg-0061-plugin-package-secret-binding-approval-plans',
|
||||
);
|
||||
const statements = [];
|
||||
await migration.up({
|
||||
async query(statement) {
|
||||
statements.push(statement);
|
||||
return { rows: [] };
|
||||
},
|
||||
});
|
||||
const sql = statements.join('\n');
|
||||
assert.match(
|
||||
sql,
|
||||
/CREATE TABLE "ql3"\."plugin_package_secret_binding_approval_plans"/,
|
||||
);
|
||||
assert.match(
|
||||
sql,
|
||||
/CREATE FUNCTION "ql3"\."plugin_package_secret_binding_planning_snapshot"\([\s\S]+p_project_id varchar,[\s\S]+p_package_name varchar[\s\S]+\)/,
|
||||
);
|
||||
assert.match(
|
||||
sql,
|
||||
/CREATE FUNCTION "ql3"\."create_plugin_package_secret_binding_approval_plan"\([\s\S]+p_plan_json jsonb[\s\S]+\)/,
|
||||
);
|
||||
assert.match(sql, /SECURITY DEFINER/);
|
||||
assert.match(
|
||||
sql,
|
||||
/GRANT SELECT ON "ql3"\."plugin_package_secret_binding_approval_plans" TO ql3_package_manager/,
|
||||
);
|
||||
assert.match(
|
||||
sql,
|
||||
/GRANT SELECT ON "ql3"\."plugin_package_secret_binding_approval_plans" TO ql3_package_executor/,
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
sql,
|
||||
/GRANT (?:INSERT|UPDATE|DELETE)[^;]+plugin_package_secret_binding_approval_plans[^;]+ql3_package_manager/,
|
||||
);
|
||||
assert.match(sql, /contract_version = 60/);
|
||||
assert.match(sql, /"plugin_package_secret_binding_approval_plan":1/);
|
||||
assert.match(sql, /contract_version = 59/);
|
||||
assert.match(
|
||||
sql,
|
||||
/migration_id = 'pg-0060-plugin-package-secret-materialization-guard'/,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -75,6 +75,7 @@ function validPrivileges() {
|
||||
plugin_package_install_mutations: [false, false, false, false],
|
||||
plugin_package_materialized_revisions: [false, false, false, false],
|
||||
plugin_package_secret_bindings: [false, false, false, false],
|
||||
plugin_package_secret_binding_approval_plans: [false, false, false, false],
|
||||
project_tool_definition_snapshots: [false, false, false, false],
|
||||
project_tool_definition_snapshot_sources: [false, false, false, false],
|
||||
plugin_package_quarantine_events: [false, false, false, false],
|
||||
@@ -194,6 +195,7 @@ function validAdminPrivileges() {
|
||||
plugin_package_install_mutations: [false, false, false, false],
|
||||
plugin_package_materialized_revisions: [false, false, false, false],
|
||||
plugin_package_secret_bindings: [false, false, false, false],
|
||||
plugin_package_secret_binding_approval_plans: [false, false, false, false],
|
||||
project_tool_definition_snapshots: [false, false, false, false],
|
||||
project_tool_definition_snapshot_sources: [false, false, false, false],
|
||||
plugin_package_quarantine_events: [false, false, false, false],
|
||||
@@ -287,6 +289,7 @@ function packagePrivileges(kind) {
|
||||
'plugin_package_publisher_trust_transition_proposals',
|
||||
'plugin_package_publisher_trust_transition_receipts',
|
||||
'plugin_package_lifecycle_plans',
|
||||
'plugin_package_secret_binding_approval_plans',
|
||||
'plugin_package_automation_publications',
|
||||
'plugin_package_automation_publication_heads',
|
||||
...(manager
|
||||
@@ -667,7 +670,13 @@ function queryable(overrides = {}) {
|
||||
contract.functions.map(({ name: functionName }) => ({
|
||||
functionName,
|
||||
executeAllowed:
|
||||
overrides.functionMode === 'manager'
|
||||
overrides.functionMode === 'package-manager'
|
||||
? [
|
||||
'create_plugin_package_secret_binding_approval_plan',
|
||||
'lock_approval_policy_fence',
|
||||
'plugin_package_secret_binding_planning_snapshot',
|
||||
].includes(functionName)
|
||||
: overrides.functionMode === 'manager'
|
||||
? functionName === 'lock_approval_policy_fence'
|
||||
: overrides.functionMode === 'run-manager'
|
||||
? functionName === 'lock_run_management_policy_fence'
|
||||
@@ -754,7 +763,7 @@ test('accepts the exact PostgreSQL control schema and least-privilege runtime ro
|
||||
serverMajor: 16,
|
||||
currentUser: 'ql3_runtime',
|
||||
contractName: 'control-core',
|
||||
contractVersion: 59,
|
||||
contractVersion: 60,
|
||||
migrationIds: [
|
||||
'pg-0001-schema-capability',
|
||||
'pg-0002-run-core',
|
||||
@@ -816,6 +825,7 @@ test('accepts the exact PostgreSQL control schema and least-privilege runtime ro
|
||||
'pg-0058-plugin-package-automation-disposition-events',
|
||||
'pg-0059-plugin-package-secret-bindings',
|
||||
'pg-0060-plugin-package-secret-materialization-guard',
|
||||
'pg-0061-plugin-package-secret-binding-approval-plans',
|
||||
],
|
||||
});
|
||||
});
|
||||
@@ -846,10 +856,10 @@ test('accepts the exact schema and isolated least-privilege admin role', async (
|
||||
}),
|
||||
);
|
||||
assert.equal(report.currentUser, 'ql3_admin');
|
||||
assert.equal(report.contractVersion, 59);
|
||||
assert.equal(report.contractVersion, 60);
|
||||
assert.equal(
|
||||
report.migrationIds.at(-1),
|
||||
'pg-0060-plugin-package-secret-materialization-guard',
|
||||
'pg-0061-plugin-package-secret-binding-approval-plans',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -862,10 +872,10 @@ test('accepts the isolated least-privilege automation manager role', async () =>
|
||||
}),
|
||||
);
|
||||
assert.equal(report.currentUser, 'ql3_automation_manager');
|
||||
assert.equal(report.contractVersion, 59);
|
||||
assert.equal(report.contractVersion, 60);
|
||||
assert.equal(
|
||||
report.migrationIds.at(-1),
|
||||
'pg-0060-plugin-package-secret-materialization-guard',
|
||||
'pg-0061-plugin-package-secret-binding-approval-plans',
|
||||
);
|
||||
|
||||
const widened = automationManagerPrivileges();
|
||||
@@ -894,10 +904,10 @@ test('accepts the isolated least-privilege human Approval manager role', async (
|
||||
}),
|
||||
);
|
||||
assert.equal(report.currentUser, 'ql3_approval_manager');
|
||||
assert.equal(report.contractVersion, 59);
|
||||
assert.equal(report.contractVersion, 60);
|
||||
assert.equal(
|
||||
report.migrationIds.at(-1),
|
||||
'pg-0060-plugin-package-secret-materialization-guard',
|
||||
'pg-0061-plugin-package-secret-binding-approval-plans',
|
||||
);
|
||||
|
||||
const widened = approvalManagerPrivileges();
|
||||
@@ -928,10 +938,10 @@ test('accepts the isolated least-privilege Run manager role', async () => {
|
||||
}),
|
||||
);
|
||||
assert.equal(report.currentUser, 'ql3_run_manager');
|
||||
assert.equal(report.contractVersion, 59);
|
||||
assert.equal(report.contractVersion, 60);
|
||||
assert.equal(
|
||||
report.migrationIds.at(-1),
|
||||
'pg-0060-plugin-package-secret-materialization-guard',
|
||||
'pg-0061-plugin-package-secret-binding-approval-plans',
|
||||
);
|
||||
|
||||
const widened = runManagerPrivileges();
|
||||
@@ -983,7 +993,7 @@ test('accepts isolated Package manager and executor roles', async () => {
|
||||
queryable({
|
||||
currentUser: 'ql3_package_manager',
|
||||
privileges: packagePrivileges('manager'),
|
||||
functionMode: 'manager',
|
||||
functionMode: 'package-manager',
|
||||
}),
|
||||
);
|
||||
assert.equal(manager.currentUser, 'ql3_package_manager');
|
||||
@@ -1063,10 +1073,10 @@ test('accepts the exact schema and isolated Worker ingress role', async () => {
|
||||
}),
|
||||
);
|
||||
assert.equal(report.currentUser, 'ql3_worker_ingress');
|
||||
assert.equal(report.contractVersion, 59);
|
||||
assert.equal(report.contractVersion, 60);
|
||||
assert.equal(
|
||||
report.migrationIds.at(-1),
|
||||
'pg-0060-plugin-package-secret-materialization-guard',
|
||||
'pg-0061-plugin-package-secret-binding-approval-plans',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user