import { createHash } from "node:crypto"; import { rootPath } from "./config"; import { readYamlRecord } from "./platform-infra-ops-library"; import { materializeYamlComposition } from "./yaml-composition"; const configPath = "config/platform-infra/pipelines-as-code.yaml"; export interface PacAdmissionConsumerContract { readonly id: string; readonly node: string; readonly namespace: string; readonly pipeline: string; readonly pipelineRunPrefix: string; readonly markerValue: string; readonly executionServiceAccountName: string; readonly gitOps: { readonly repoUrl: string; readonly targetRevision: string }; } export interface PacAdmissionContract { readonly version: string; readonly markerAnnotation: string; readonly controllerUsername: string; readonly policyName: string; readonly bindingName: string; readonly failurePolicy: "Fail"; readonly validationActions: readonly ["Deny"]; readonly child: { readonly enabled: false; readonly parentNameAnnotation: string; readonly parentUidAnnotation: string; }; readonly consumers: readonly PacAdmissionConsumerContract[]; } export interface PacAdmissionDesiredIdentity { readonly configSha256: string; readonly policyName: string; readonly bindingName: string; readonly version: string; readonly controllerIdentity: string; } export function readPacAdmissionContract(): PacAdmissionContract { const source = readYamlRecord>(rootPath("config", "platform-infra", "pipelines-as-code.yaml"), "platform-infra-pipelines-as-code"); return parsePacAdmissionContract(source); } export function parsePacAdmissionContract(source: Record): PacAdmissionContract { const root = record(materializeYamlComposition(source, { label: configPath }).value, configPath); const provenance = record(root.deliveryProvenance, `${configPath}#deliveryProvenance`); const child = record(provenance.child, `${configPath}#deliveryProvenance.child`); const controller = record(provenance.controller, `${configPath}#deliveryProvenance.controller`); const admission = record(provenance.admission, `${configPath}#deliveryProvenance.admission`); const controllerNamespace = required(controller.namespace, "deliveryProvenance.controller.namespace"); const controllerServiceAccount = required(controller.serviceAccountName, "deliveryProvenance.controller.serviceAccountName"); const validationActions = stringArray(admission.validationActions, "deliveryProvenance.admission.validationActions"); if (validationActions.length !== 1 || validationActions[0] !== "Deny") throw new Error("deliveryProvenance.admission.validationActions must be exactly [Deny]"); const consumers = recordArray(root.consumers, "consumers").flatMap((consumer, index) => { if (consumer.deliveryProvenance === undefined) return []; const value = record(consumer.deliveryProvenance, `consumers[${index}].deliveryProvenance`); if (value.required === false) return []; if (value.required !== true) throw new Error(`consumers[${index}].deliveryProvenance.required must be boolean true or false`); const gitOps = record(value.gitOps, `consumers[${index}].deliveryProvenance.gitOps`); return [{ id: required(consumer.id, `consumers[${index}].id`), node: required(consumer.node, `consumers[${index}].node`), namespace: required(consumer.namespace, `consumers[${index}].namespace`), pipeline: required(consumer.pipeline, `consumers[${index}].pipeline`), pipelineRunPrefix: required(consumer.pipelineRunPrefix, `consumers[${index}].pipelineRunPrefix`), markerValue: required(value.markerValue, `consumers[${index}].deliveryProvenance.markerValue`), executionServiceAccountName: required(value.executionServiceAccountName, `consumers[${index}].deliveryProvenance.executionServiceAccountName`), gitOps: { repoUrl: required(gitOps.repoUrl, `consumers[${index}].deliveryProvenance.gitOps.repoUrl`), targetRevision: required(gitOps.targetRevision, `consumers[${index}].deliveryProvenance.gitOps.targetRevision`), }, }]; }); const markerValues = new Set(consumers.map((consumer) => consumer.markerValue)); if (markerValues.size !== consumers.length) throw new Error("deliveryProvenance markerValue must be unique per consumer"); const version = required(provenance.version, "deliveryProvenance.version"); const versionMatch = version.match(/-v([1-9][0-9]*)$/u); if (versionMatch === null) throw new Error("deliveryProvenance.version must end with a positive -vN resource epoch"); const resourceEpoch = `-v${versionMatch[1]}`; const policyName = required(admission.policyName, "deliveryProvenance.admission.policyName"); const bindingName = required(admission.bindingName, "deliveryProvenance.admission.bindingName"); if (!policyName.endsWith(resourceEpoch) || !bindingName.endsWith(resourceEpoch)) throw new Error(`deliveryProvenance admission resource names must end with ${resourceEpoch}`); return { version, markerAnnotation: required(provenance.markerAnnotation, "deliveryProvenance.markerAnnotation"), controllerUsername: `system:serviceaccount:${controllerNamespace}:${controllerServiceAccount}`, policyName, bindingName, failurePolicy: literal(admission.failurePolicy, "deliveryProvenance.admission.failurePolicy", "Fail"), validationActions: ["Deny"], child: { enabled: literal(child.enabled, "deliveryProvenance.child.enabled", false), parentNameAnnotation: required(child.parentNameAnnotation, "deliveryProvenance.child.parentNameAnnotation"), parentUidAnnotation: required(child.parentUidAnnotation, "deliveryProvenance.child.parentUidAnnotation"), }, consumers, }; } export function pacAdmissionConsumer(consumerId: string): PacAdmissionConsumerContract | null { return readPacAdmissionContract().consumers.find((consumer) => consumer.id === consumerId) ?? null; } export function renderPacAdmissionDesiredFragment(targetId: string): string { const contract = readPacAdmissionContract(); const consumers = contract.consumers.filter((consumer) => consumer.node.toLowerCase() === targetId.toLowerCase()); if (consumers.length === 0) return ""; const annotation = contract.markerAnnotation; const markerPresent = `has(object.metadata.annotations) && ${cel(annotation)} in object.metadata.annotations`; const oldMarkerPresent = `has(oldObject.metadata.annotations) && ${cel(annotation)} in oldObject.metadata.annotations`; const protectedUpdate = [markerPresent, oldMarkerPresent, ...consumers.flatMap((consumer) => [candidateExpression(consumer, "object"), candidateExpression(consumer, "oldObject")])].map((item) => `(${item})`).join(" || "); const immutableProofKeys = [ ["labels", "app.kubernetes.io/managed-by"], ["labels", "pipelinesascode.tekton.dev/event-type"], ["labels", "pipelinesascode.tekton.dev/repository"], ["labels", "pipelinesascode.tekton.dev/sha"], ["labels", "pipelinesascode.tekton.dev/commit"], ["labels", "pipelinesascode.tekton.dev/branch"], ["labels", "pipelinesascode.tekton.dev/sender"], ["annotations", "pipelinesascode.tekton.dev/controller-info"], ["annotations", "pipelinesascode.tekton.dev/event-type"], ["annotations", "pipelinesascode.tekton.dev/git-provider"], ["annotations", "pipelinesascode.tekton.dev/repository"], ["annotations", "pipelinesascode.tekton.dev/sha"], ["annotations", "pipelinesascode.tekton.dev/commit"], ["annotations", "pipelinesascode.tekton.dev/branch"], ["annotations", "pipelinesascode.tekton.dev/source-branch"], ["annotations", "pipelinesascode.tekton.dev/sender"], ["annotations", contract.child.parentNameAnnotation], ["annotations", contract.child.parentUidAnnotation], ] as const; const validations: Record[] = [ { expression: `request.operation != 'CREATE' || !(${markerPresent}) || request.userInfo.username == ${cel(contract.controllerUsername)}`, message: "reserved PaC provenance marker may only be created by the authenticated PaC controller ServiceAccount", reason: "Forbidden", }, { expression: `request.operation != 'UPDATE' || ((${markerPresent}) == (${oldMarkerPresent}) && (!(${markerPresent}) || object.metadata.annotations[${cel(annotation)}] == oldObject.metadata.annotations[${cel(annotation)}]))`, message: "reserved PaC provenance marker is immutable", reason: "Invalid", }, { expression: `request.operation != 'CREATE' || ((!has(object.metadata.annotations) || !(${cel(contract.child.parentNameAnnotation)} in object.metadata.annotations)) && (!has(object.metadata.annotations) || !(${cel(contract.child.parentUidAnnotation)} in object.metadata.annotations)))`, message: "child PipelineRun provenance is disabled until an admission-verified parent name and UID contract is enabled", reason: "Forbidden", }, { expression: `request.operation != 'UPDATE' || !(${protectedUpdate}) || (${immutableProofKeys.map(([map, key]) => metadataEntryEqual(map, key)).join(" && ")})`, message: "PaC repository, branch, revision, event, controller, sender, and parent identity proof fields are immutable", reason: "Invalid", }, { expression: `request.operation != 'UPDATE' || !(${protectedUpdate}) || ((!has(object.spec.params) && !has(oldObject.spec.params)) || (has(object.spec.params) && has(oldObject.spec.params) && object.spec.params == oldObject.spec.params))`, message: "PaC source revision parameters are immutable", reason: "Invalid", }, { expression: `request.operation != 'UPDATE' || !(${protectedUpdate}) || (${serviceAccountEqual()})`, message: "PaC execution ServiceAccount is immutable", reason: "Invalid", }, { expression: `request.operation != 'UPDATE' || !(${protectedUpdate}) || (${pipelineRefEqual()})`, message: "PaC referenced Pipeline identity is immutable", reason: "Invalid", }, { expression: `request.operation != 'UPDATE' || !(${protectedUpdate}) || object.spec == oldObject.spec`, message: "admission-proven PaC PipelineRun spec is immutable, including embedded pipelineSpec, workspaces, and timeouts", reason: "Invalid", }, ...consumers.flatMap((consumer) => [{ expression: `request.operation != 'CREATE' || !(${candidateExpression(consumer, "object")}) || (request.userInfo.username == ${cel(contract.controllerUsername)} && (${markerPresent}) && object.metadata.annotations[${cel(annotation)}] == ${cel(consumer.markerValue)} && ${usesServiceAccount(consumer, "object")})`, message: `PipelineRun candidates for ${consumer.id} require its exact admission marker and execution ServiceAccount`, reason: "Invalid", }, { expression: `!(${usesServiceAccount(consumer, "object")}) || ((${candidateExpression(consumer, "object")}) && (${markerPresent}) && object.metadata.annotations[${cel(annotation)}] == ${cel(consumer.markerValue)})`, message: `execution ServiceAccount for ${consumer.id} is reserved to its admission-proven PipelineRun candidates`, reason: "Forbidden", }]), ]; const policySpec = { failurePolicy: contract.failurePolicy, matchConstraints: { matchPolicy: "Equivalent", resourceRules: [{ apiGroups: ["tekton.dev"], apiVersions: ["v1"], operations: ["CREATE", "UPDATE"], resources: ["pipelineruns"] }], }, validations, }; const bindingSpec = { policyName: contract.policyName, validationActions: contract.validationActions }; const policyHash = admissionSpecSha256(targetId, contract.policyName, contract.bindingName, policySpec, bindingSpec); const metadata = { labels: { "app.kubernetes.io/managed-by": "unidesk", "app.kubernetes.io/part-of": "platform-infra" }, annotations: { "argocd.argoproj.io/sync-wave": "0", "unidesk.ai/pac-provenance-version": contract.version, "unidesk.ai/pac-controller-identity": contract.controllerUsername, "unidesk.ai/owning-config-ref": `${configPath}#deliveryProvenance`, "unidesk.ai/effective-config-sha256": policyHash, }, }; const policy = { apiVersion: "admissionregistration.k8s.io/v1", kind: "ValidatingAdmissionPolicy", metadata: { name: contract.policyName, ...metadata }, spec: policySpec, }; const binding = { apiVersion: "admissionregistration.k8s.io/v1", kind: "ValidatingAdmissionPolicyBinding", metadata: { name: contract.bindingName, ...metadata, annotations: { ...metadata.annotations, "argocd.argoproj.io/sync-wave": "1" }, }, spec: bindingSpec, }; return [policy, binding].map((item) => Bun.YAML.stringify(item).trim()).join("\n---\n") + "\n"; } export function pacAdmissionDesiredIdentity(targetId: string): PacAdmissionDesiredIdentity { const contract = readPacAdmissionContract(); const manifest = renderPacAdmissionDesiredFragment(targetId); const policy = manifest.length === 0 ? null : record(Bun.YAML.parse(manifest.split(/^---\s*$/mu)[0] ?? ""), "rendered admission policy"); const policyMetadata = policy === null ? {} : record(policy.metadata, "rendered admission policy metadata"); const annotations = policy === null ? {} : record(policyMetadata.annotations, "rendered admission policy annotations"); return { configSha256: policy === null ? `sha256:${createHash("sha256").update(stableCanonicalJson({ targetId, disabled: true })).digest("hex")}` : required(annotations["unidesk.ai/effective-config-sha256"], "rendered admission policy effective config sha256"), policyName: contract.policyName, bindingName: contract.bindingName, version: contract.version, controllerIdentity: contract.controllerUsername, }; } function admissionSpecSha256(targetId: string, policyName: string, bindingName: string, policySpec: Record, bindingSpec: Record): string { const input = { targetId, policyName, bindingName, policySpec, bindingSpec }; return `sha256:${createHash("sha256").update(stableCanonicalJson(input)).digest("hex")}`; } function stableCanonicalJson(value: unknown): string { if (Array.isArray(value)) return `[${value.map(stableCanonicalJson).join(",")}]`; if (typeof value === "object" && value !== null) { return `{${Object.entries(value as Record).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableCanonicalJson(item)}`).join(",")}}`; } return JSON.stringify(value); } function candidateExpression(consumer: PacAdmissionConsumerContract, root: "object" | "oldObject"): string { return `${root}.metadata.namespace == ${cel(consumer.namespace)} && (${root}.metadata.name.startsWith(${cel(consumer.pipelineRunPrefix)}) || (has(${root}.metadata.labels) && ((${cel("tekton.dev/pipeline")} in ${root}.metadata.labels && ${root}.metadata.labels[${cel("tekton.dev/pipeline")}] == ${cel(consumer.pipeline)}) || (${cel("tekton.dev/pipelineName")} in ${root}.metadata.labels && ${root}.metadata.labels[${cel("tekton.dev/pipelineName")}] == ${cel(consumer.pipeline)}))) || (has(${root}.spec.pipelineRef) && has(${root}.spec.pipelineRef.name) && ${root}.spec.pipelineRef.name == ${cel(consumer.pipeline)}))`; } function usesServiceAccount(consumer: PacAdmissionConsumerContract, root: "object" | "oldObject"): string { return `has(${root}.spec.taskRunTemplate) && has(${root}.spec.taskRunTemplate.serviceAccountName) && ${root}.spec.taskRunTemplate.serviceAccountName == ${cel(consumer.executionServiceAccountName)}`; } function metadataEntryEqual(map: "annotations" | "labels", key: string): string { const current = `has(object.metadata.${map}) && ${cel(key)} in object.metadata.${map}`; const previous = `has(oldObject.metadata.${map}) && ${cel(key)} in oldObject.metadata.${map}`; return `((${current}) == (${previous}) && (!(${current}) || object.metadata.${map}[${cel(key)}] == oldObject.metadata.${map}[${cel(key)}]))`; } function serviceAccountEqual(): string { const current = "has(object.spec.taskRunTemplate) && has(object.spec.taskRunTemplate.serviceAccountName)"; const previous = "has(oldObject.spec.taskRunTemplate) && has(oldObject.spec.taskRunTemplate.serviceAccountName)"; return `((${current}) == (${previous}) && (!(${current}) || object.spec.taskRunTemplate.serviceAccountName == oldObject.spec.taskRunTemplate.serviceAccountName))`; } function pipelineRefEqual(): string { const current = "has(object.spec.pipelineRef)"; const previous = "has(oldObject.spec.pipelineRef)"; return `((${current}) == (${previous}) && (!(${current}) || object.spec.pipelineRef == oldObject.spec.pipelineRef))`; } function cel(value: string): string { return JSON.stringify(value); } function record(value: unknown, path: string): Record { if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${path} must be an object`); return value as Record; } function recordArray(value: unknown, path: string): Record[] { if (!Array.isArray(value)) throw new Error(`${path} must be an array`); return value.map((item, index) => record(item, `${path}[${index}]`)); } function required(value: unknown, path: string): string { if (typeof value !== "string" || value.length === 0 || value.includes("\n")) throw new Error(`${path} must be a non-empty single-line string`); return value; } function stringArray(value: unknown, path: string): string[] { if (!Array.isArray(value) || value.length === 0 || value.some((item) => typeof item !== "string" || item.length === 0)) throw new Error(`${path} must be a non-empty string array`); return value as string[]; } function literal(value: unknown, path: string, expected: T): T { if (value !== expected) throw new Error(`${path} must be ${expected}`); return expected; }