import { createHash } from "node:crypto"; import { readPacAdmissionContract } from "./platform-infra-pac-provenance"; export interface PacConsumerRbacDesiredIdentity { readonly configSha256: string; readonly namespaces: readonly string[]; } const pacConsumerReadOnlyRules = [ { apiGroups: ["tekton.dev"], resources: ["pipelineruns", "taskruns"], verbs: ["get", "list", "watch"] }, { apiGroups: [""], resources: ["pods", "pods/log"], verbs: ["get", "list", "watch"] }, ] as const; const pacConsumerDefaultRoleRef = { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "unidesk-pac-consumer-runner" } as const; export function renderPacConsumerRbacDesiredFragment(targetId: string): string { const consumers = readPacAdmissionContract().consumers.filter((consumer) => consumer.node.toLowerCase() === targetId.toLowerCase()); const namespaces = [...new Set(consumers.map((consumer) => consumer.namespace))]; if (namespaces.length === 0) return ""; const configSha256 = pacConsumerRbacDesiredIdentity(targetId).configSha256; const objects = namespaces.flatMap((namespace) => { const labels = { "app.kubernetes.io/managed-by": "unidesk", "app.kubernetes.io/part-of": "platform-infra", "unidesk.ai/pac-rbac-contract": "admission-provenance-required", }; const annotations = { "argocd.argoproj.io/sync-wave": "-1", "unidesk.ai/effective-config-sha256": configSha256, }; return [ { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: "unidesk-pac-consumer-runner", namespace, labels, annotations }, rules: pacConsumerReadOnlyRules, }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: "unidesk-pac-consumer-runner-default", namespace, labels, annotations }, subjects: [{ kind: "ServiceAccount", name: "default", namespace }], roleRef: pacConsumerDefaultRoleRef, }, ]; }); return `${objects.map((item) => Bun.YAML.stringify(item).trim()).join("\n---\n")}\n`; } export function pacConsumerRbacDesiredIdentity(targetId: string): PacConsumerRbacDesiredIdentity { const consumers = readPacAdmissionContract().consumers.filter((consumer) => consumer.node.toLowerCase() === targetId.toLowerCase()); const namespaces = [...new Set(consumers.map((consumer) => consumer.namespace))].sort(); const desiredSpec = namespaces.map((namespace) => ({ namespace, role: { name: "unidesk-pac-consumer-runner", rules: pacConsumerReadOnlyRules }, roleBinding: { name: "unidesk-pac-consumer-runner-default", subjects: [{ kind: "ServiceAccount", name: "default", namespace }], roleRef: pacConsumerDefaultRoleRef, }, })); return { configSha256: `sha256:${createHash("sha256").update(JSON.stringify({ targetId, desiredSpec, contract: "admission-provenance-required-v1" })).digest("hex")}`, namespaces, }; }