243 lines
10 KiB
JavaScript
243 lines
10 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { generateKeyPairSync, sign } from "node:crypto";
|
|
|
|
import {
|
|
ENGINE_CREDENTIAL_SINK_AUDIT_SCHEMA_VERSION,
|
|
ENGINE_CREDENTIAL_SINK_PROVISION_SCHEMA_VERSION,
|
|
ENGINE_CREDENTIAL_SINK_RECEIPT_SCHEMA_VERSION,
|
|
ENGINE_CREDENTIAL_SINK_ROLLBACK_RECEIPT_SCHEMA_VERSION,
|
|
ENGINE_CREDENTIAL_SINK_ROLLBACK_SCHEMA_VERSION,
|
|
computeEngineCredentialCapabilityDigest,
|
|
computeEngineCredentialSinkPolicyHash,
|
|
computeEngineCredentialSinkReceiptHash,
|
|
engineCredentialSinkAuditTargets,
|
|
validateEngineCredentialSinkAudit,
|
|
validateEngineCredentialSinkProvision,
|
|
validateEngineCredentialSinkReceipt,
|
|
validateEngineCredentialSinkRollback,
|
|
validateEngineCredentialSinkRollbackReceipt,
|
|
} from "../src/index.mjs";
|
|
|
|
const hash = (character) => `sha256:${character.repeat(64)}`;
|
|
const issuer = { serviceId: "platform.external-data-plane", keyId: "edp-issuer-20260715-001" };
|
|
const { publicKey: issuerPublicKey, privateKey: issuerPrivateKey } = generateKeyPairSync("ed25519");
|
|
const issuerPublicKeys = { [`${issuer.serviceId}:${issuer.keyId}`]: issuerPublicKey };
|
|
const target = (nodeId, nodeType, credentialType) => ({
|
|
workflowId: "WCb62yGL8v",
|
|
workflowRevision: "revision-20260715-001",
|
|
nodeId,
|
|
nodeType,
|
|
credentialType,
|
|
});
|
|
|
|
const request = {
|
|
schemaVersion: ENGINE_CREDENTIAL_SINK_PROVISION_SCHEMA_VERSION,
|
|
transaction: {
|
|
id: "credential-transaction-20260715-001",
|
|
idempotencyKey: "credential-transaction-20260715-001",
|
|
requestedAt: "2026-07-15T18:00:00.000Z",
|
|
requestExpiresAt: "2026-07-15T18:10:00.000Z",
|
|
policyHash: hash("0"),
|
|
failureMode: "rollback-all",
|
|
issuer,
|
|
attestation: { algorithm: "Ed25519", signature: "A".repeat(86) },
|
|
},
|
|
bindings: [
|
|
{
|
|
bindingId: "positions-writer",
|
|
capabilityType: "external-data-plane.writer",
|
|
grantId: "writer-grant-001",
|
|
target: target(
|
|
"publish-node-001",
|
|
"n8n-nodes-ndc.ndcDataProductPublish",
|
|
"ndcDataProductWriterApi",
|
|
),
|
|
expiresAt: "2026-08-15T18:00:00.000Z",
|
|
policyHash: hash("1"),
|
|
material: { format: "opaque-bearer", value: `ndc_edpwb_${"A".repeat(43)}` },
|
|
},
|
|
{
|
|
bindingId: "positions-reader",
|
|
capabilityType: "external-data-plane.reader",
|
|
grantId: "reader-grant-001",
|
|
target: target(
|
|
"read-node-001",
|
|
"n8n-nodes-ndc.ndcDataProductRead",
|
|
"ndcDataProductReaderApi",
|
|
),
|
|
expiresAt: "2026-08-15T18:00:00.000Z",
|
|
policyHash: hash("2"),
|
|
material: { format: "opaque-bearer", value: `ndc_edprb_${"B".repeat(43)}` },
|
|
},
|
|
{
|
|
bindingId: "positions-foundry",
|
|
capabilityType: "foundry.binding",
|
|
grantId: "foundry-grant-001",
|
|
target: target(
|
|
"foundry-node-001",
|
|
"n8n-nodes-ndc.ndcFoundryBinding",
|
|
"ndcFoundryBindingApi",
|
|
),
|
|
expiresAt: "2026-08-15T18:00:00.000Z",
|
|
policyHash: hash("3"),
|
|
material: { format: "opaque-bearer", value: `ndc_fndbg_${"C".repeat(43)}` },
|
|
},
|
|
],
|
|
};
|
|
for (const binding of request.bindings) {
|
|
binding.capabilityDigest = computeEngineCredentialCapabilityDigest(binding.material.value);
|
|
}
|
|
attest(request);
|
|
const provisionValidationNow = "2026-07-15T18:00:01.000Z";
|
|
|
|
assert.equal(validateEngineCredentialSinkProvision(request, { now: provisionValidationNow, issuerPublicKeys }).ok, true);
|
|
assert.equal(validateEngineCredentialSinkProvision(request, {
|
|
now: provisionValidationNow,
|
|
}).errors.includes("transaction.issuer_public_key_required"), true);
|
|
assert.equal(validateEngineCredentialSinkProvision({
|
|
...request,
|
|
transaction: { ...request.transaction, policyHash: hash("f") },
|
|
}, { now: provisionValidationNow, issuerPublicKeys }).errors.includes("transaction.policyHash_mismatch"), true);
|
|
assert.equal(validateEngineCredentialSinkProvision({
|
|
...request,
|
|
bindings: [{
|
|
...request.bindings[0],
|
|
target: { ...request.bindings[0].target, nodeType: "n8n-nodes-ndc.ndcDataProductRead" },
|
|
}],
|
|
}, { now: provisionValidationNow, issuerPublicKeys }).errors.includes("bindings[0].target.nodeType_capability_mismatch"), true);
|
|
assert.equal(validateEngineCredentialSinkProvision({
|
|
...request,
|
|
bindings: [{
|
|
...request.bindings[0],
|
|
material: { format: "opaque-bearer", value: request.bindings[1].material.value },
|
|
}],
|
|
}, { now: provisionValidationNow, issuerPublicKeys }).errors.includes("bindings[0].material.value_invalid_for_capability"), true);
|
|
|
|
const swappedCapabilityRequest = structuredClone(request);
|
|
swappedCapabilityRequest.bindings[0].material.value = `ndc_edpwb_${"D".repeat(43)}`;
|
|
assert.equal(validateEngineCredentialSinkProvision(swappedCapabilityRequest, {
|
|
now: provisionValidationNow,
|
|
issuerPublicKeys,
|
|
}).errors.includes("bindings[0].capabilityDigest_material_mismatch"), true);
|
|
|
|
const resignedByAttackerRequest = structuredClone(swappedCapabilityRequest);
|
|
resignedByAttackerRequest.bindings[0].capabilityDigest = computeEngineCredentialCapabilityDigest(
|
|
resignedByAttackerRequest.bindings[0].material.value,
|
|
);
|
|
resignedByAttackerRequest.transaction.policyHash = computeEngineCredentialSinkPolicyHash(resignedByAttackerRequest);
|
|
assert.equal(validateEngineCredentialSinkProvision(resignedByAttackerRequest, {
|
|
now: provisionValidationNow,
|
|
issuerPublicKeys,
|
|
}).errors.includes("transaction.attestation_invalid"), true);
|
|
|
|
const staleRequest = structuredClone(request);
|
|
staleRequest.transaction.requestedAt = "2020-01-01T00:00:00.000Z";
|
|
staleRequest.transaction.requestExpiresAt = "2020-01-01T00:10:00.000Z";
|
|
attest(staleRequest);
|
|
assert.equal(validateEngineCredentialSinkProvision(staleRequest, { now: provisionValidationNow, issuerPublicKeys }).errors.includes("request_expired"), true);
|
|
|
|
const futureRequest = structuredClone(request);
|
|
futureRequest.transaction.requestedAt = "2026-07-15T18:02:00.000Z";
|
|
futureRequest.transaction.requestExpiresAt = "2026-07-15T18:12:00.000Z";
|
|
attest(futureRequest);
|
|
assert.equal(validateEngineCredentialSinkProvision(futureRequest, { now: provisionValidationNow, issuerPublicKeys }).errors.includes("requestedAt_exceeds_clock_skew"), true);
|
|
|
|
const credentials = request.bindings.map((binding, index) => ({
|
|
bindingId: binding.bindingId,
|
|
capabilityType: binding.capabilityType,
|
|
grantId: binding.grantId,
|
|
target: binding.target,
|
|
credentialRef: `engcred_${index + 1}_opaque_reference`,
|
|
expiresAt: binding.expiresAt,
|
|
policyHash: binding.policyHash,
|
|
capabilityDigest: binding.capabilityDigest,
|
|
disposition: "created",
|
|
}));
|
|
const receipt = {
|
|
schemaVersion: ENGINE_CREDENTIAL_SINK_RECEIPT_SCHEMA_VERSION,
|
|
transactionId: request.transaction.id,
|
|
idempotencyKey: request.transaction.idempotencyKey,
|
|
outcome: "committed",
|
|
policyHash: request.transaction.policyHash,
|
|
processedAt: "2026-07-15T18:00:02.000Z",
|
|
credentials,
|
|
rollback: { status: "not-required" },
|
|
};
|
|
assert.equal(validateEngineCredentialSinkReceipt(receipt, { request, issuerPublicKeys }).ok, true);
|
|
assert.equal(validateEngineCredentialSinkReceipt({
|
|
...receipt,
|
|
credentials: [{ ...credentials[0], target: { ...credentials[0].target, nodeId: "wrong-node" } }, ...credentials.slice(1)],
|
|
}, { request, issuerPublicKeys }).errors.includes("credentials_request_target_mismatch"), true);
|
|
assert.equal(validateEngineCredentialSinkReceipt({
|
|
...receipt,
|
|
capability: request.bindings[0].material.value,
|
|
}).errors.includes("credentialSinkReceipt.capability_not_allowed"), true);
|
|
assert.equal(validateEngineCredentialSinkReceipt({
|
|
...receipt,
|
|
outcome: "rolled-back",
|
|
credentials: credentials.slice(0, 1),
|
|
rollback: { status: "complete", completedAt: "2026-07-15T18:00:02.000Z" },
|
|
errorCode: "engine_binding_failed",
|
|
}).errors.includes("noncommitted_credentials_must_be_empty"), true);
|
|
|
|
const committedReceiptHash = computeEngineCredentialSinkReceiptHash(receipt);
|
|
const rollbackRequest = {
|
|
schemaVersion: ENGINE_CREDENTIAL_SINK_ROLLBACK_SCHEMA_VERSION,
|
|
rollback: {
|
|
id: "credential-rollback-20260715-001",
|
|
idempotencyKey: "credential-rollback-20260715-001",
|
|
transactionId: request.transaction.id,
|
|
requestedAt: "2026-07-15T18:05:00.000Z",
|
|
requestExpiresAt: "2026-07-15T18:10:00.000Z",
|
|
policyHash: request.transaction.policyHash,
|
|
committedReceiptHash,
|
|
reasonCode: "operator_requested",
|
|
},
|
|
};
|
|
assert.equal(validateEngineCredentialSinkRollback(rollbackRequest, { now: "2026-07-15T18:05:01.000Z" }).ok, true);
|
|
const staleRollbackRequest = structuredClone(rollbackRequest);
|
|
staleRollbackRequest.rollback.requestedAt = "2020-01-01T00:00:00.000Z";
|
|
staleRollbackRequest.rollback.requestExpiresAt = "2020-01-01T00:10:00.000Z";
|
|
assert.equal(validateEngineCredentialSinkRollback(staleRollbackRequest, { now: "2026-07-15T18:05:01.000Z" }).errors.includes("request_expired"), true);
|
|
const rollbackReceipt = {
|
|
schemaVersion: ENGINE_CREDENTIAL_SINK_ROLLBACK_RECEIPT_SCHEMA_VERSION,
|
|
rollbackId: rollbackRequest.rollback.id,
|
|
transactionId: rollbackRequest.rollback.transactionId,
|
|
outcome: "rolled-back",
|
|
policyHash: rollbackRequest.rollback.policyHash,
|
|
committedReceiptHash,
|
|
processedAt: "2026-07-15T18:05:01.000Z",
|
|
};
|
|
assert.equal(validateEngineCredentialSinkRollbackReceipt(rollbackReceipt, { request: rollbackRequest }).ok, true);
|
|
|
|
const audit = {
|
|
schemaVersion: ENGINE_CREDENTIAL_SINK_AUDIT_SCHEMA_VERSION,
|
|
eventId: "credential-audit-20260715-001",
|
|
transactionId: request.transaction.id,
|
|
operationId: request.transaction.id,
|
|
operation: "provision",
|
|
outcome: "committed",
|
|
occurredAt: receipt.processedAt,
|
|
policyHash: request.transaction.policyHash,
|
|
principal: { serviceId: "platform.credential-provisioner", fingerprint: hash("a") },
|
|
targets: engineCredentialSinkAuditTargets(request, receipt),
|
|
};
|
|
assert.equal(validateEngineCredentialSinkAudit(audit).ok, true);
|
|
assert.equal(JSON.stringify(audit).includes("ndc_edpwb_"), false);
|
|
assert.equal(JSON.stringify(audit).includes("engcred_1_opaque_reference"), false);
|
|
assert.equal(validateEngineCredentialSinkAudit({
|
|
...audit,
|
|
authorization: `Bearer ${request.bindings[0].material.value}`,
|
|
}).errors.includes("audit_must_not_contain_secret_material"), true);
|
|
|
|
function attest(value) {
|
|
value.transaction.policyHash = computeEngineCredentialSinkPolicyHash(value);
|
|
value.transaction.attestation.signature = sign(
|
|
null,
|
|
Buffer.from(value.transaction.policyHash, "utf8"),
|
|
issuerPrivateKey,
|
|
).toString("base64url");
|
|
}
|
|
|
|
console.log("external-provider credential sink contract: ok");
|