1"use strict";(globalThis.webpackChunkopenchoreo=globalThis.webpackChunkopenchoreo||[]).push([[17065],{95233(e,s,n){n.d(s,{AD:()=>r,e4:()=>i});n(74848);const i={dockerTag:"latest-dev",githubRef:"main",helmChart:"0.0.0-latest-dev",helmSource:"oci://ghcr.io/openchoreo/helm-charts",logsOpensearchModule:"0.0.0-latest-dev",tracingOpensearchModule:"0.0.0-latest-dev",metricsPrometheusModule:"0.0.0-latest-dev",eventsOtelCollectorModule:"0.0.0-latest-dev",finOpsOpenCostModule:"0.0.0-latest-dev"},r={username:"[email protected]",password:"Admin@123"}},5532(e,s,n){n.r(s),n.d(s,{assets:()=>a,auditCollectionEnable:()=>h,auditControlPlaneEnable:()=>x,auditObserverEnable:()=>u,contentTitle:()=>c,default:()=>g,frontMatter:()=>o,metadata:()=>i,toc:()=>j});const i=JSON.parse('{"id":"platform-engineer-guide/audit-logging","title":"Audit Logging","description":"Record who changed what in OpenChoreo, collect the audit trail in the observability plane, and query it.","source":"@site/docs/platform-engineer-guide/audit-logging.mdx","sourceDirName":"platform-engineer-guide","slug":"/platform-engineer-guide/audit-logging","permalink":"/docs/next/platform-engineer-guide/audit-logging","draft":false,"unlisted":false,"editUrl":"https://github.com/openchoreo/openchoreo.github.io/edit/main/docs/platform-engineer-guide/audit-logging.mdx","tags":[],"version":"current","lastUpdatedBy":"Mevan","lastUpdatedAt":1789739144000,"sidebarPosition":9,"frontMatter":{"title":"Audit Logging","description":"Record who changed what in OpenChoreo, collect the audit trail in the observability plane, and query it.","sidebar_position":9},"sidebar":"docsSidebar","previous":{"title":"Secret Management","permalink":"/docs/next/platform-engineer-guide/secret-management"},"next":{"title":"Enabling Local Development","permalink":"/docs/next/platform-engineer-guide/local-development-setup"}}');var r=n(74848),t=n(28453),d=n(88149),l=n(95233);const o={title:"Audit Logging",description:"Record who changed what in OpenChoreo, collect the audit trail in the observability plane, and query it.",sidebar_position:9},c="Audit Logging",a={},h=`helm upgrade observability-logs-opensearch \\\n oci://ghcr.io/openchoreo/helm-charts/observability-logs-opensearch \\\n --namespace openchoreo-observability-plane \\\n --version ${l.e4.logsOpensearchModule} \\\n --reuse-values \\\n --set auditLogs.enabled=true`,u=`helm upgrade openchoreo-observability-plane ${l.e4.helmSource}/openchoreo-observability-plane \\\n --version ${l.e4.helmChart} \\\n --namespace openchoreo-observability-plane \\\n --reuse-values \\\n --set observer.audit.enabled=true`,x=`helm upgrade openchoreo-control-plane ${l.e4.helmSource}/openchoreo-control-plane \\\n --version ${l.e4.helmChart} \\\n --namespace openchoreo-control-plane \\\n --reuse-values \\\n --set openchoreoApi.config.audit.enabled=true \\\n --set openchoreoApi.config.audit.observabilityPlaneRef.kind=ClusterObservabilityPlane \\\n --set openchoreoApi.config.audit.observabilityPlaneRef.name=default`,j=[{value:"Overview",id:"overview",level:2},{value:"Prerequisites",id:"prerequisites",level:2},{value:"Enabling Audit Logging",id:"enabling-audit-logging",level:2},{value:"Step 1: Collect audit records in the logs module",id:"step-1-collect-audit-records-in-the-logs-module",level:3},{value:"Step 2: Emit audit records from the Observer",id:"step-2-emit-audit-records-from-the-observer",level:3},{value:"Step 3: Emit audit records from the control plane",id:"step-3-emit-audit-records-from-the-control-plane",level:3},{value:"Step 4: Verify",id:"step-4-verify",level:3},{value:"Multi-Cluster Setups",id:"multi-cluster-setups",level:2},{value:"Trusted Producers",id:"trusted-producers",level:2},{value:"Configuring What Is Published",id:"configuring-what-is-published",level:2},{value:"Examples",id:"examples",level:3},{value:"Rules Enforced at Startup",id:"rules-enforced-at-startup",level:3},{value:"Actor Identity",id:"actor-identity",level:2},{value:"Access to the Audit Trail",id:"access-to-the-audit-trail",level:2},{value:"Viewing the Audit Trail",id:"viewing-the-audit-trail",level:2},{value:"Logs Modules",id:"logs-modules",level:2}
1,{value:"Configuration Reference",id:"configuration-reference",level:2},{value:"Control Plane (<code>openchoreoApi.config.audit</code>)",id:"control-plane-openchoreoapiconfigaudit",level:3},{value:"Observability Plane (<code>observer.audit</code>)",id:"observability-plane-observeraudit",level:3},{value:"Logs Module",id:"logs-module",level:3},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"<code>openchoreo-api</code> fails to start after enabling audit",id:"openchoreo-api-fails-to-start-after-enabling-audit",level:3},{value:"<code>AUDIT-LOG</code> lines are written, but the trail is empty",id:"audit-log-lines-are-written-but-the-trail-is-empty",level:3},{value:"<code>occ auditlogs</code> cannot find the trail",id:"occ-auditlogs-cannot-find-the-trail",level:3},{value:"<code>403</code> or <code>501</code> when querying",id:"403-or-501-when-querying",level:3},{value:"Related Documentation",id:"related-documentation",level:2}];function p(e){const s={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(s.header,{children:(0,r.jsx)(s.h1,{id:"audit-logging",children:"Audit Logging"})}),"\n",(0,r.jsx)(s.admonition,{title:"Beta",type:"info",children:(0,r.jsx)(s.p,{children:"Audit logging is in beta. The event schema, configuration and query filters may change in a future release."})}),"\n",(0,r.jsxs)(s.p,{children:["OpenChoreo keeps an audit trail of who did what, to which resource, from where, and whether it was allowed. Every state-modifying operation on the OpenChoreo API and the Observer API produces a record, whether the call arrives over REST, from the Backstage portal or ",(0,r.jsx)(s.code,{children:"occ"}),", or as an MCP tool call."]}),"\n",(0,r.jsx)(s.p,{children:"The trail is a separate signal from logs. Records go to their own index or stream with their own retention, so they outlive operational logs and cannot be flooded by application output."}),"\n",(0,r.jsx)(s.h2,{id:"overview",children:"Overview"}),"\n",(0,r.jsx)(s.p,{children:"A record answers:"}),"\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Question"}),(0,r.jsx)(s.th,{children:"Fields"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.strong,{children:"Who"})}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"actor.id"}),", ",(0,r.jsx)(s.code,{children:"actor.type"}),", ",(0,r.jsx)(s.code,{children:"actor.issuer"}),", ",(0,r.jsx)(s.code,{children:"actor.session_id"}),", ",(0,r.jsx)(s.code,{children:"actor.entitlements"})]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.strong,{children:"Did what"})}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"action"})," (e.g. ",(0,r.jsx)(s.code,{children:"create_project"}),"), ",(0,r.jsx)(s.code,{children:"operation_id"})," (e.g. ",(0,r.jsx)(s.code,{children:"CreateProject"}),"), ",(0,r.jsx)(s.code,{children:"category"})]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.strong,{children:"To which resource"})}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"resource.type"}),", ",(0,r.jsx)(s.code,{children:"resource.namespace"}),", ",(0,r.jsx)(s.code,{children:"resource.environment"}),", ",(0,r.jsx)(s.code,{children:"resource.project"}),", ",(0,r.jsx)(s.code,{children:"resource.component"}),", ",(0,r.jsx)(s.code,{children:"resource.name"})]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.strong,{children:"With what outcome"})}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"result"}),": ",(0,r.jsx)(s.code,{children:"success"}),", ",(0,r.jsx)(s.code,{children:"failure"})," or ",(0,r.jsx)(s.code,{children:"denied"})]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.strong,{children:"From where"})}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"source_ip"}),", ",(0,r.jsx)(s.code,{children:"user_agent"}),", ",(0,r.jsx)(s.code,{children:"surface"})," (",(0,r.jsx)(s.code,{children:"rest"})," or ",(0,r.jsx)(s.code,{children:"mcp"}),"), ",(0,r.jsx)(s.code,{children:"producer"})," (",(0,r.jsx)(s.code,{children:"openchoreo-api"})," or ",(0,r.jsx)(s.code,{children:"observer"}),")"]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.strong,{children:"Correlation"})}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"event_id"}),", ",(0,r.jsx)(s.code,{children:"event_time"}),", ",(0,r.jsx)(s.code,{children:"request_id"})]})]})]})]}),"\n",(0,r.jsxs)(s.p,{children:["Every record carries one of three categories. ",(0,r.jsx)(s.code,{children:"management"})," covers create, update and delete on platform resources. ",(0,r.jsx)(s.code,{children:"authorization"})," covers changes to roles and role bindings. ",(0,r.jsx)(s.code,{children:"access"})," covers reads that are worth recording on their own, which today means reads of the audit trail itself."]}),"\n",(0,r.jsx)(s.p,{children:"Other reads are not audited, and neither are requests that never authenticate. Those belong to the access log."}),"\n",(0,r.jsx)(s.h2,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:["An observability plane installed and registered as a ",(0,r.jsx)(s.code,{children:"ClusterObservabilityPlane"})," or ",(0,r.jsx)(s.code,{children:"ObservabilityPlane"}),". See ",(0,r.jsx)(s.a,{href:"/docs/next/getting-started/try-it-out/on-k3d-locally#step-7-setup-observability-plane-optional",children:"Getting Started"})," or ",(0,r.jsx)(s.a,{href:"/docs/next/platform-engineer-guide/multi-cluster-connectivity#observability-plane",children:"Multi-Cluster Connectivity"}),"."]}),"\n",(0,r.jsxs)(s.li,{children:["A logs module that collects and serves audit logs. See ",(0,r.jsx)(s.a,{href:"#logs-modules",children:"Logs Modules"}),"."]}),"\n"]}),"\n",(0,r.jsx)(s.admonition,{title:"Already enabled on quick installs",type:"tip",children:(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.code,{children:"k3d-install.sh --with-observability"}),", the quick-start installer's ",(0,r.jsx)(s.code,{children:"--with-observability"}),", and the guided ",(0,r.jsx)(s.a,{href:"/docs/next/getting-started/try-it-out/on-k3d-locally#step-7-setup-observability-plane-optional",children:"k3d setup"})," all turn audit logging on. Use this page for other installations, or to tune one of those."]})}),"\n",(0,r.jsx)(s.h2,{id:"enabling-audit-logging",children:"Enabling Audit Logging"}),"\n",(0,r.jsxs)(s.p,{children:["Enable collection first, so the first records have somewhere to land, then emission on the observability plane and the control plane. The commands use the OpenSearch logs module in its default namespace. For another module, see ",(0,r.jsx)(s.a,{href:"#logs-modules",children:"Logs Modules"}),"."]}),"\n",(0,r.jsx)(s.admonition,{type:"caution",children:(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.code,{children:"--reuse-values"})," is safe here because the chart version does not change. When upgrading a chart across versions, pass your full values file with the audit settings added instead."]})}),"\n",(0,r.jsx)(s.h3,{id:"step-1-collect-audit-records-in-the-logs-module",children:"Step 1: Collect audit records in the logs module"}),"\n","\n",(0,r.jsx)(d.A,{language:"bash",children:h}),"\n",(0,r.jsx)(s.p,{children:"This assumes log collection is already on, as set up when the observability plane was installed."}),"\n",(0,r.jsx)(s.p,{children:"The upgrade updates the collection agent's configuration, but Fluent Bit reads
1it only at startup and is not restarted by the upgrade. Restart it so it picks up the audit rules:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:"kubectl rollout restart daemonset/fluent-bit -n openchoreo-observability-plane\n"})}),"\n",(0,r.jsx)(s.h3,{id:"step-2-emit-audit-records-from-the-observer",children:"Step 2: Emit audit records from the Observer"}),"\n","\n",(0,r.jsx)(d.A,{language:"bash",children:u}),"\n",(0,r.jsx)(s.h3,{id:"step-3-emit-audit-records-from-the-control-plane",children:"Step 3: Emit audit records from the control plane"}),"\n",(0,r.jsxs)(s.p,{children:["Point ",(0,r.jsx)(s.code,{children:"observabilityPlaneRef"})," at the observability plane whose logs module collects the trail. For a ",(0,r.jsx)(s.code,{children:"ClusterObservabilityPlane"})," named ",(0,r.jsx)(s.code,{children:"default"}),":"]}),"\n","\n",(0,r.jsx)(d.A,{language:"bash",children:x}),"\n",(0,r.jsxs)(s.p,{children:["For a namespace-scoped ",(0,r.jsx)(s.code,{children:"ObservabilityPlane"}),", set ",(0,r.jsx)(s.code,{children:"kind=ObservabilityPlane"})," and add ",(0,r.jsx)(s.code,{children:"openchoreoApi.config.audit.observabilityPlaneRef.namespace"}),"."]}),"\n",(0,r.jsxs)(s.admonition,{type:"warning",children:[(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.code,{children:"openchoreo-api"})," refuses to start when ",(0,r.jsx)(s.code,{children:"audit.enabled"})," is ",(0,r.jsx)(s.code,{children:"true"})," and the reference has no ",(0,r.jsx)(s.code,{children:"kind"})," or ",(0,r.jsx)(s.code,{children:"name"}),"."]}),(0,r.jsx)(s.p,{children:"The plane itself is resolved only when a client asks where the trail lives. Name one that does not exist and the API starts anyway, logs a warning, and clients cannot find the trail. Records are still written, so nothing is lost as long as collection is on."})]}),"\n",(0,r.jsx)(s.h3,{id:"step-4-verify",children:"Step 4: Verify"}),"\n",(0,r.jsx)(s.p,{children:"Create and delete a project, then check the trail end to end:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:"# The API server writes the record\nkubectl logs -n openchoreo-control-plane deployment/openchoreo-api -c api-server | grep AUDIT-LOG\n\n# The Observer serves it back\nocc auditlogs --since 1h\n"})}),"\n",(0,r.jsxs)(s.p,{children:["In the console, ",(0,r.jsx)(s.strong,{children:"Audit Logs"})," in the sidebar shows the same records to anyone with ",(0,r.jsx)(s.code,{children:"auditlogs:view"}),"."]}),"\n",(0,r.jsx)(s.h2,{id:"multi-cluster-setups",children:"Multi-Cluster Setups"}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.code,{children:"openchoreo-api"})," writes records in the control plane cluster and ",(0,r.jsx)(s.code,{children:"observer"})," writes them in the observability plane cluster, so collection has to run in both:"]}),"\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Cluster"}),(0,r.jsx)(s.th,{children:"What to configure"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Control plane"}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"openchoreoApi.config.audit.*"})," as in ",(0,r.jsx)(s.a,{href:"#step-3-emit-audit-records-from-the-control-plane",children:"Step 3"}),". Install the logs module with only its collection agent enabled, pointed at the observability plane, with audit collection on"]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Observability plane"}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"observer.audit.enabled=true"})," as in ",(0,r.jsx)(s.a,{href:"#step-2-emit-audit-records-from-the-observer",children:"Step 2"}),", and audit collection on the logs module as in ",(0,r.jsx)(s.a,{href:"#step-1-collect-audit-records-in-the-logs-module",children:"Step 1"})]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Data plane"}),(0,r.jsx)(s.td,{children:"Nothing. No audit producer runs here"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Workflow plane"}),(0,r.jsx)(s.td,{children:"Nothing"})]})]})]}),"\n",(0,r.jsxs)(s.p,{children:["The control plane cluster's exact settings depend on the module and on how the observability plane exposes its ingestion endpoint, so follow the module's own multi-cluster instructions from ",(0,r.jsx)(s.a,{href:"#logs-modules",children:"Logs Modules"}),". Each record carries the ",(0,r.jsx)(s.code,{children:"openchoreo_cluster_instance"})," of the cluster it was collected in."]}),"\n",(0,r.jsx)(s.h2,{id:"trusted-producers",children:"Trusted Producers"}),"\n",(0,r.jsx)(s.p,{children:"A logs module accepts audit records only from producers you trust. The OpenSearch and OpenObserve modules express that as a container allowlist:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-yaml",children:"auditLogs:\n producers:\n - producer: openchoreo-api\n namespace: openchoreo-control-plane\n container: api-server\n - producer: observer\n namespace: openchoreo-observability-plane\n container: observer\n"})}),"\n",(0,r.jsxs)(s.p,{children:["Entries match the container log file name written by the kubelet, not anything in the log line. A workload outside the list can print something shaped exactly like an audit record and it still lands in the ordinary container logs. Note the container is ",(0,r.jsx)(s.code,{children:"api-server"}),", not ",(0,r.jsx)(s.code,{children:"openchoreo-api"}),"."]}),"\n",(0,r.jsx)(s.admonition,{type:"warning",children:(0,r.jsx)(s.p,{children:"Each entry grants a workload the right to write into the audit trail, so review additions as you would any other grant. Installing the control plane or observability plane into a
1non-default namespace means editing these entries, or collection stops silently: nothing errors, the trail just stays empty."})}),"\n",(0,r.jsx)(s.h2,{id:"configuring-what-is-published",children:"Configuring What Is Published"}),"\n",(0,r.jsx)(s.p,{children:"Every audited operation is published by default. Policies let you drop noise, such as a CI service account that updates workloads hundreds of times a day, or record only a chosen few."}),"\n",(0,r.jsxs)(s.p,{children:["The control plane (",(0,r.jsx)(s.code,{children:"openchoreoApi.config.audit"}),") and the Observer (",(0,r.jsx)(s.code,{children:"observer.audit"}),") are configured separately with the same syntax:"]}),"\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Key"}),(0,r.jsx)(s.th,{children:"Default"}),(0,r.jsx)(s.th,{children:"Description"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"defaults.publish"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"true"})}),(0,r.jsx)(s.td,{children:"Whether an operation that matches no policy is published"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"policies"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"[]"})}),(0,r.jsxs)(s.td,{children:["Ordered list of ",(0,r.jsx)(s.code,{children:"{match, set}"})," rules. The first rule whose ",(0,r.jsx)(s.code,{children:"match"})," fits the operation applies"]})]})]})]}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.code,{children:"set"})," takes one key, ",(0,r.jsx)(s.code,{children:"publish"}),". Every ",(0,r.jsx)(s.code,{children:"match"})," field is a list: a field matches when any of its values does, and a rule matches when all of its non-empty fields do."]}),"\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsxs)(s.th,{children:[(0,r.jsx)(s.code,{children:"match"})," field"]}),(0,r.jsx)(s.th,{children:"Values"}),(0,r.jsx)(s.th,{children:"Checked at startup"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"categories"})}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"management"}),", ",(0,r.jsx)(s.code,{children:"authorization"})]}),(0,r.jsx)(s.td,{children:"Yes"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"resources"})}),(0,r.jsxs)(s.td,{children:["Resource types, e.g. ",(0,r.jsx)(s.code,{children:"project"}),", ",(0,r.jsx)(s.code,{children:"component"}),", ",(0,r.jsx)(s.code,{children:"workload"}),", ",(0,r.jsx)(s.code,{children:"releasebinding"}),", ",(0,r.jsx)(s.code,{children:"authzrolebinding"})]}),(0,r.jsx)(s.td,{children:"Yes"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"operations"})}),(0,r.jsxs)(s.td,{children:["Operation IDs, e.g. ",(0,r.jsx)(s.code,{children:"CreateProject"}),", ",(0,r.jsx)(s.code,{children:"UpdateComponent"})]}),(0,r.jsx)(s.td,{children:"Yes"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"actions"})}),(0,r.jsxs)(s.td,{children:["Action names, e.g. ",(0,r.jsx)(s.code,{children:"create_project"}),", ",(0,r.jsx)(s.code,{children:"delete_project"})]}),(0,r.jsx)(s.td,{children:"Yes"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"surfaces"})}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"rest"}),", ",(0,r.jsx)(s.code,{children:"mcp"})]}),(0,r.jsx)(s.td,{children:"Yes"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"actor_types"})}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"user"}),", ",(0,r.jsx)(s.code,{children:"anonymous"}),", and each subject type configured for the service (e.g. ",(0,r.jsx)(s.code,{children:"service_account"}),")"]}),(0,r.jsx)(s.td,{children:"Yes"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"results"})}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"success"}),", ",(0,r.jsx)(s.code,{children:"failure"}),", ",(0,r.jsx)(s.code,{children:"denied"})]}),(0,r.jsx)(s.td,{children:"Yes"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"actors"})}),(0,r.jsxs)(s.td,{children:["Actor IDs, the claim set by ",(0,r.jsx)(s.code,{children:"actor.idClaim"})]}),(0,r.jsx)(s.td,{children:"No"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"entitlements"})}),(0,r.jsx)(s.td,{children:"Entitlement values such as group names. Matched against the values of every entitlement claim, whatever the claim name"}),(0,r.jsx)(s.td,{children:"No"})]})]})]}),"\n",(0,r.jsx)(s.p,{children:"Resource types, operation IDs and action names differ between the two services. A value the service never produces is rejected at startup, and the error lists what it accepts."}),"\n",(0,r.jsx)(s.admonition,{title:"Policy keys use snake_case",type:"note",children:(0,r.jsxs)(s.p,{children:["Helm passes ",(0,r.jsx)(s.code,{children:"policies"})," through untouched, so write ",(0,r.jsx)(s.code,{children:"actor_types"}),", not ",(0,r.jsx)(s.code,{children:"actorTypes"}),". Lists cannot be set with ",(0,r.jsx)(s.code,{children:"--set"}),"; use a values file or ",(0,r.jsx)(s.code,{children:"--set-json"}),"."]})}),"\n",(0,r.jsx)(s.h3,{id:"examples",children:"Examples"}),"\n",(0,r.jsx)(s.p,{children:"Drop successful workload updates from service accounts, keeping their failures and denials:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-yaml",children:"openchoreoApi:\n config:\n audit:\n policies:\n - match:\n actor_types: [service_account]\n resources: [workload]\n results: [success]\n set:\n publish: false\n"})}),"\n",(0,r.jsx)(s.p,{children:"Record only authorization changes and anything that was refused:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-yaml",children:"openchoreoApi:\n config:\n audit:\n defaults:\n publish: false\n policies:\n - match:\n categories: [authorization]\n set:\n publish: true\n - match:\n results: [denied]\n set:\n publish: true\n"})}),"\n",(0,r.jsx)(s.h3,{id:"rules-enforced-at-startup",children:"Rules Enforced at Startup"}),"\n",(0,r.jsx)(s.p,{children:"Break one of these and the service refuses to start, naming the rule:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:["A rule must set ",(0,r.jsx)(s.code,{children:"publish"}),"."]}),"\n",(0,r.jsxs)(s.li,{children:["A rule with an empty ",(0,r.jsx)(s.code,{children:"match"})," cannot set ",(0,r.jsx)(s.code,{children:"publish: false"}),", since it would silence everything."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"actors"})," and ",(0,r.jsx)(s.code,{children:"entitlements"})," can raise publishing to ",(0,r.jsx)(s.code,{children:"true"})," but never suppress it. Hiding one person's actions is what an audit trail must not allow."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"set.category"})," is rejected. The category comes from the operation."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"match.categories"})," takes ",(0,r.jsx)(s.code,{children:"management"})," and ",(0,r.jsx)(s.code,{children:"authorization"})," only."]}),"\n"]}),"\n",(0,r.jsx)(s.admonition,{title:"Allowlist mode fails closed",type:"warning",children:(0,r.jsxs)(s.p,{children:["With ",(0,r.jsx)(s.code,{children:"defaults.publish: false"}),", only what a ",(0,r.jsx)(s.code,{children:"publish: true"})," rule matches is recorded. Selector values are validated, but a rule that is valid and narrower than you intended drops events silently. Check the trail after changing policies."]})}),"\n",(0,r.jsx)(s.h2,{id:"actor-identity",children:"Actor Identity"}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.code,{children:"actor.id"})," holds one token claim, ",(0,r.jsx)(s.code,{children:"sub"})," by default. Set ",(0,r.jsx)(s.code,{children:"actor.idClaim"})," on both services to record something more readable, such as an email claim your identity provider issues:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-yaml",children:"# Control plane\nopenchoreoApi:\n config:\n audit:\n actor:\n idClaim: email\n\n# Observability plane\nobserver:\n audit:\n actor:\n idClaim: email\n"})}),"\n",(0,r.jsxs)(s.p,{children:["The same value is matched by ",(0,r.jsx)(s.code,{children:"policies[].match.actors"})," and by ",(0,r.jsx)(s.code,{children:"occ auditlogs --actor"}),"."]}),"\n",(0,r.jsxs)(s.p,{children:["An ID is unique only within its issuer, so with more than one identity provider configured, filter on both (",(0,r.jsx)(s.code,{children:"occ auditlogs --actor <id> --issuer <issuer>"}),"). Client credentials tokens carry no session, which leaves ",(0,r.jsx)(s.code,{children:"actor.session_id"})," empty for service accounts."]}),"\n",(0,r.jsx)(s.h2,{id:"access-to-the-audit-trail",children:"Access to the Audit Trail"}),"\n",(0,r.jsxs)(s.p,{children:["Reading the trail requires the ",(0,r.jsx)(s.code,{children:"auditlogs:view"})," action at cluster scope. The portal, ",(0,r.jsx)(s.code,{children:"occ auditlogs"})," and the ",(0,r.jsx)(s.code,{children:"query_audit_logs"})," MCP tool all enforce it."]}),"\n",(0,r.jsxs)(s.p,{children:["Among the default roles, only ",(0,r.jsx)(s.code,{children:"admin"})," and ",(0,r.jsx)(s.code,{children:"platform-engineer"})," have it. Namespace, project and component filters narrow results but grant nothing: managing a project does not confer the right to read its audit trail."]}),"\n",(0,r.jsxs)(s.p,{children:["For a read-only auditor, bind a ",(0,r.jsx)(s.code,{children:"ClusterAuthzRole"})," carrying the action:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-yaml",children:'apiVersion: openchoreo.dev/v1alpha1\nkind: ClusterAuthzRole\nmetadata:\n name: auditor\nspec:\n actions:\n - "auditlogs:view"\n'})}),"\n",(0,r.jsxs)(s.p,{children:["See ",(0,r.jsx)(s.a,{href:"/docs/next/platform-engineer-guide/authorization/custom-roles",children:"Custom Roles and Bindings"})," to bind it to a group, or ",(0,r.jsx)(s.a,{href:"/docs/next/platform-engineer-guide/authorization#customizing-bootstrap-roles-and-bindings",children:"Customizing Bootstrap Roles and Bindings"})," to create it at install time."]}),"\n",(0,r.jsxs)(s.p,{children:["Reads of the trail are themselves recorded, with category ",(0,r.jsx)(s.code,{children:"access"})," and action ",(0,r.jsx)(s.code,{children:"read_audit_log"}),"."]}),"\n",(0,r.jsx)(s.h2,{id:"viewing-the-audit-trail",children:"Viewing the Audit Trail"}),"\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Client"}),(0,r.jsx)(s.th,{children:"How"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.strong,{children:"Backstage portal"})}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.strong,{children:"Audit Logs"})," in the sidebar"]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.strong,{children:"CLI"})}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"occ auditlogs"}),". See the ",(0,r.jsx)(s.a,{href:"/docs/next/reference/cli-reference#auditlogs",children:"CLI reference"})]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.strong,{children:"MCP"})}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"query_audit_logs"})," on the Observability Plane MCP server. See ",(0,r.jsx)(s.a,{href:"/docs/next/reference/mcp-servers#observability-plane-mcp-server",children:"MCP Servers"})]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.strong,{children:"API"})}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"POST /api/v1alpha1/audit-logs/query"})," on the Observer"]})]})]})]}),"\n",(0,r.jsxs)(s.p,{children:["No client needs to be told which Observer holds the trail. They ask the control plane, which resolves ",(0,r.jsx)(s.code,{children:"observabilityPlaneRef"}),"."]}),"\n",(0,r.jsx)(s.p,{children:"A query covers at most 366 days and returns newest first. Values within a filter are OR-ed, separate filters are AND-ed."}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:"# Refused requests in the last 7 days\nocc auditlogs --since 7d --result denied\n\n# Everything one user changed in a namespace\nocc auditlogs --actor [email protected] --namespace acme-corp --category management\n"})}),"\n",(0,r.jsx)(s.h2,{id:"logs-modules",children:"Logs Modules"}),"\n",(0,r.jsx)(s.p,{children:"Collecting, storing and serving the trail is the logs module's job, so the destination, the retention and the way a remote cluster ships records are all module-specific. These modules support audit logs:"}),"\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Module"}),(0,r.jsx)(s.th,{children:"Audit destination"}),(0,r.jsx)(s.th,{children:"Retention setting"}),(0,r.jsx)(s.th,{children:"Default"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://github.com/openchoreo/community-modules/tree/main/observability-logs-opensearch",children:"observability-logs-opensearch"})}),(0,r.jsxs)(s.td,{children:["Daily ",(0,r.jsx)(s.code,{children:"audit-logs-YYYY-MM-DD"})," indices"]}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"openSearchSetup.dataRetentionTime.auditLogs"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"365d"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://github.com/openchoreo/community-modules/tree/main/observability-logs-openobserve",children:"observability-logs-openobserve"})}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"audit_logs"})," stream"]}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"openObserveSetup.auditLogsRetentionDays"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"365"})})]})]})]}),"\n",(0,r.jsxs)(s.p,{children:["Both take the ",(0,r.jsx)(s.code,{children:"auditLogs.*"})," values used on this page, and both can ship records to a separate backend through ",(0,r.jsx)(s.code,{children:"auditLogs.output.*"}
1),", which is how several observability planes can report into one audit store. Their own documentation covers the collection agent, multi-cluster ingestion and credentials."]}),"\n",(0,r.jsxs)(s.p,{children:["A module without audit support answers audit queries with ",(0,r.jsx)(s.code,{children:"501 Not Implemented"}),". See ",(0,r.jsx)(s.a,{href:"/docs/next/platform-engineer-guide/modules/building-a-module#audit-logs",children:"Building a Module"})," for what support involves."]}),"\n",(0,r.jsx)(s.h2,{id:"configuration-reference",children:"Configuration Reference"}),"\n",(0,r.jsxs)(s.h3,{id:"control-plane-openchoreoapiconfigaudit",children:["Control Plane (",(0,r.jsx)(s.code,{children:"openchoreoApi.config.audit"}),")"]}),"\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Value"}),(0,r.jsx)(s.th,{children:"Default"}),(0,r.jsx)(s.th,{children:"Description"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"enabled"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"false"})}),(0,r.jsxs)(s.td,{children:["Emit audit records from ",(0,r.jsx)(s.code,{children:"openchoreo-api"})]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"observabilityPlaneRef.kind"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:'""'})}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"ClusterObservabilityPlane"})," or ",(0,r.jsx)(s.code,{children:"ObservabilityPlane"}),". Required when ",(0,r.jsx)(s.code,{children:"enabled"})," is ",(0,r.jsx)(s.code,{children:"true"})]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"observabilityPlaneRef.name"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:'""'})}),(0,r.jsxs)(s.td,{children:["Name of the plane. Required when ",(0,r.jsx)(s.code,{children:"enabled"})," is ",(0,r.jsx)(s.code,{children:"true"})]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"observabilityPlaneRef.namespace"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:'""'})}),(0,r.jsxs)(s.td,{children:["Namespace of an ",(0,r.jsx)(s.code,{children:"ObservabilityPlane"}),". Not used for ",(0,r.jsx)(s.code,{children:"ClusterObservabilityPlane"})]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"defaults.publish"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"true"})}),(0,r.jsx)(s.td,{children:"Publish operations that match no policy"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"policies"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"[]"})}),(0,r.jsxs)(s.td,{children:["Ordered publishing rules. See ",(0,r.jsx)(s.a,{href:"#configuring-what-is-published",children:"Configuring What Is Published"})]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"actor.idClaim"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"sub"})}),(0,r.jsxs)(s.td,{children:["Token claim recorded as ",(0,r.jsx)(s.code,{children:"actor.id"})]})]})]})]}),"\n",(0,r.jsxs)(s.h3,{id:"observability-plane-observeraudit",children:["Observability Plane (",(0,r.jsx)(s.code,{children:"observer.audit"}),")"]}),"\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Value"}),(0,r.jsx)(s.th,{children:"Default"}),(0,r.jsx)(s.th,{children:"Description"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"enabled"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"false"})}),(0,r.jsxs)(s.td,{children:["Emit audit records from ",(0,r.jsx)(s.code,{children:"observer"})]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"defaults.publish"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"true"})}),(0,r.jsx)(s.td,{children:"Publish operations that match no policy"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"policies"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"[]"})}),(0,r.jsx)(s.td,{children:"Ordered publishing rules"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"actor.idClaim"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"sub"})}),(0,r.jsxs)(s.td,{children:["Token claim recorded as ",(0,r.jsx)(s.code,{children:"actor.id"})]})]})]})]}),"\n",(0,r.jsx)(s.h3,{id:"logs-module",children:"Logs Module"}),"\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Value"}),(0,r.jsx)(s.th,{children:"Default"}),(0,r.jsx)(s.th,{children:"Description"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"auditLogs.enabled"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"false"})}),(0,r.jsx)(s.td,{children:"Route audit records to their own index or stream"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"auditLogs.producers"})}),(0,r.jsx)(s.td,{children:"API and Observer"}),(0,r.jsxs)(s.td,{children:["Trusted-producer allowlist. See ",(0,r.jsx)(s.a,{href:"#trusted-producers",children:"Trusted Producers"})]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"auditLogs.output.*"})}),(0,r.jsx)(s.td,{children:"unset"}),(0,r.jsx)(s.td,{children:"Ship audit records to a separate backend"})]})]})]}),"\n",(0,r.jsx)(s.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,r.jsxs)(s.h3,{id:"openchoreo-api-fails-to-start-after-enabling-audit",children:[(0,r.jsx)(s.code,{children:"openchoreo-api"})," fails to start after enabling audit"]}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.code,{children:"observability_plane_ref.kind"})," or ",(0,r.jsx)(s.code,{children:"observability_plane_ref.name"})," is required, so set ",(0,r.jsx)(s.code,{children:"openchoreoApi.config.audit.observabilityPlaneRef"}),". An error under ",(0,r.jsx)(s.code,{children:"audit.policies[N]"})," means a policy breaks a ",(0,r.jsx)(s.a,{href:"#rules-enforced-at-startup",children:"startup rule"}),", or names a ",(0,r.jsx)(s.code,{children:"match"})," value the service never produces. The message lists what it accepts."]}),"\n",(0,r.jsxs)(s.h3,{id:"audit-log-lines-are-written-but-the-trail-is-empty",children:[(0,r.jsx)(s.code,{children:"AUDIT-LOG"})," lines are written, but the trail is empty"]}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:"Collection is not enabled where the producer runs. In a multi-cluster setup it has to run in the control plane cluster too."}),"\n",(0,r.jsxs)(s.li,{children:["Fluent Bit was not restarted after audit collection was enabled with ",(0,r.jsx)(s.code,{children:"helm upgrade"}),", so it still runs without the audit rules. Restart it as in ",(0,r.jsx)(s.a,{href:"#step-1-collect-audit-records-in-the-logs-module",children:"Step 1"}),", in every cluster where the module was upgraded."]}),"\n",(0,r.jsxs)(s.li,{children:["The producer allowlist still names the default namespaces. See ",(0,r.jsx)(s.a,{href:"#trusted-producers",children:"Trusted Producers"}),"."]}),"\n",(0,r.jsxs)(s.li,{children:["On OpenSearch, an ",(0,r.jsx)(s.code,{children:"audit-logs-*"})," index created before the module applied its index template gets dynamic mappings and answers nothing. See the module's documentation from ",(0,r.jsx)(s.a,{href:"#logs-modules",children:"Logs Modules"}),"."]}),"\n",(0,r.jsxs)(s.li,{children:["With ",(0,r.jsx)(s.code,{children:"auditLogs.output.host"})," set, the audit credentials Secret is missing. The collection agent logs ",(0,r.jsx)(s.code,{children:"is used but not set"}),"."]}),"\n"]}),"\n",(0,r.jsxs)(s.h3,{id:"occ-auditlogs-cannot-find-the-trail",children:[(0,r.jsx)(s.code,{children:"occ auditlogs"})," cannot find the trail"]}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.code,{children:"audit logging is not enabled on this OpenChoreo installation"})," means ",(0,r.jsx)(s.code,{children:"openchoreoApi.config.audit.enabled"})," is ",(0,r.jsx)(s.code,{children:"false"}),". ",(0,r.jsx)(s.code,{children:"audit logging is enabled, but the control plane advertises no observer serving it"})," means ",(0,r.jsx)(s.code,{children:"observabilityPlaneRef"})," names a plane that does not exist. Check ",(0,r.jsx)(s.code,{children:"kubectl get clusterobservabilityplane"}),", and look for ",(0,r.jsx)(s.code,{children:"Audit observability plane not found"})," in the ",(0,r.jsx)(s.code,{children:"openchoreo-api"})," logs."]}),"\n",(0,r.jsxs)(s.h3,{id:"403-or-501-when-querying",children:[(0,r.jsx)(s.code,{children:"403"})," or ",(0,r.jsx)(s.code,{children:"501"})," when querying"]}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.code,{children:"403"})," means the caller lacks ",(0,r.jsx)(s.code,{children:"auditlogs:view"})," at cluster scope; a namespace or project binding is not enough. ",(0,r.jsx)(s.code,{children:"501"})," means the logs module in that observability plane does not serve audit queries."]}),"\n",(0,r.jsx)(s.h2,{id:"related-documentation",children:"Related Documentation"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.a,{href:"/docs/next/platform-engineer-guide/observability-alerting",children:"Observability & Alerting"}),": the observability plane and its modules"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.a,{href:"/docs/next/platform-engineer-guide/multi-cluster-connectivity",children:"Multi-Cluster Connectivity"}),": connecting planes across clusters"]}
1),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.a,{href:"/docs/next/platform-engineer-guide/authorization/overview",children:"Authorization"}),": roles, bindings and actions"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.a,{href:"/docs/next/platform-engineer-guide/modules/building-a-module#audit-logs",children:"Building a Module"}),": implementing audit support in a logs module"]}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"/docs/next/reference/cli-reference#auditlogs",children:"CLI Reference: auditlogs"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"/docs/next/reference/mcp-servers#observability-plane-mcp-server",children:"MCP Servers"})}),"\n"]})]})}function g(e={}){const{wrapper:s}={...(0,t.R)(),...e.components};return s?(0,r.jsx)(s,{...e,children:(0,r.jsx)(p,{...e})}):p(e)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.