
Compliance
NIST SP 800-53 on Kubernetes You Own: AC, AU, CM, SC, SI
NIST SP 800-53 Release 5.2.0 on self-hosted Kubernetes: which controls stop being inheritable, which get cheaper to evidence, and an error in NIST's OSCAL.
Most conversations about moving off a hyperscaler treat compliance as pure cost: you give up the provider's attestations and take on work. That is half true. SP 800-53 does not describe controls in the abstract — it names who is responsible for developing, implementing, assessing, authorising and monitoring each one. Change who operates the stack and you change that answer, in both directions.
This article walks the AC, AU, CM, SC and SI families against a cluster you run yourself, naming the Kubernetes object we hold each control against and the artefact an assessor can read. It also does what an article about a control catalogue ought to: it checks the catalogue. NIST's published OSCAL file carries the wrong normative statement for one of the three controls Release 5.2.0 added.
The catalogue moved; the PDF did not
The SP 800-53 Rev. 5 publication CSRC serves is dated "September 2020 (includes updates as of Dec. 10, 2020)". That PDF is what most explainers and templates still work from, and it is not the current catalogue. On 27 August 2025 NIST issued Release 5.2.0, adding SA-15(13), SA-24 and SI-02(07). NIST's summary of changes attributes it to Executive Order 14306 and describes it as focused on "improving the security and reliability of software updates and patches". Release 5.2.0 is distributed through CPRT and "can be viewed in a browser or downloaded in OSCAL, JSON, and spreadsheet formats" — the OSCAL representation in JSON, XML and YAML.
SP 800-53B was not revised alongside it: NIST states the baselines document "has no changes; the version number was updated for consistency". So the three new controls landed in the catalogue and in none of the impact baselines. The structure is unchanged — twenty control families, of which seventeen "are aligned with the minimum security requirements in [FIPS 200]".
The number worth carrying is not the headline count but the split inside it. Parsing the published OSCAL 5.2.0 catalogue gives 1,196 entries across those twenty groups — 324 base controls and 872 enhancements — of which 182 carry a withdrawn status, leaving 1,014 live. Quoting 1,196 without saying so quotes a number including controls NIST has retired. The same walk over the baseline profiles gives 149 control ids in LOW, 287 in MODERATE and 370 in HIGH. These are our counts, not NIST-published figures, so here is the command:
CAT=https://raw.githubusercontent.com/usnistgov/oscal-content/main/nist.gov/SP800-53/rev5/json
# Pin to the commit this article read if you want exactly these bytes:
# CAT=https://raw.githubusercontent.com/usnistgov/oscal-content/78650f02ad9321bb7b817846f8fbd4f2bcd620de/nist.gov/SP800-53/rev5/json
curl -sL "$CAT/NIST_SP-800-53_rev5_catalog.json" -o catalog.json
# Walk groups[] -> controls[] -> nested controls[]. A recursive-descent id scan
# also collects part and parameter ids, and returns a much larger wrong number.
jq '[.catalog.groups[].controls[] | ., (.controls // [])[]]' catalog.json > entries.json
jq 'length' entries.json
jq '[.[] | select((.props // []) | any(.name == "status" and .value == "withdrawn"))] | length' entries.json
jq '[.[] | select(((.props // []) | any(.name == "status" and .value == "withdrawn")) | not)] | length' entries.json
for B in LOW MODERATE HIGH; do
curl -sL "$CAT/NIST_SP-800-53_rev5_${B}-baseline_profile.json" |
jq --arg b "$B" '[.profile.imports[]."include-controls"[]."with-ids"[]] | unique | length | "\($b): \(.)"'
done
jq -r '.. | objects | select(.id == "sa-15.12" or .id == "sa-15.13")
| "\(.id) \(.title)\n \(.parts[] | select(.name == "statement") | .prose)"' catalog.jsonWhat inheritance buys, and what it costs
NIST's definition of a common control is precise about ownership: a control is inheritable when "the system or program receives protection from the implemented control, but the control is developed, implemented, assessed, authorized, and monitored by an internal or external entity other than the entity responsible for the system". Inheritance is not a discount on the control but a transfer of who holds it — and NIST attaches a warning: "Implementing controls as common controls can introduce the risk of a single point of failure."
The families NIST names as candidates for common implementation are the ones this article is about: "identification and authentication controls, boundary protection controls, audit and accountability controls, and access controls". Some of that inheritance is real and worth having. AWS states it "operates, manages and controls the components from the host operating system and virtualization layer down to the physical security of the facilities" — the PE family, and much of SC below the network overlay, genuinely stops being your problem. Other controls are split rather than transferred: AWS calls patch management shared, "responsible for patching and fixing flaws within the infrastructure, but customers are responsible for patching their guest OS and applications".
Inheritance and reach move together. Baselines are "predefined sets of controls" NIST expects to be tailored — but you cannot tailor a control you do not implement. Inherit the AU family and you inherit its configuration.
AC: the RoleBinding is the control, the audit event is the evidence
We implement AC-2, AC-3 and AC-6 as RBAC objects held in Git: the Role or ClusterRole is the enforcement, the RoleBinding the account-to-privilege assignment, the commit history the change record. That mapping is ours, not NIST's — the catalogue names no Kubernetes primitives — but an assessor can follow it, because every object is declarative, diffable and attributable to a person.
Two Kubernetes facts break a naive reading of that mapping, and an assessor who knows them will ask. Kubernetes documents that any member of the system:masters group "bypasses all RBAC rights checks and will always have unrestricted superuser access, which cannot be revoked by removing RoleBindings or ClusterRoleBindings". A manifest review never surfaces that membership, so an AC-6 assertion resting on manifests alone is unfalsifiable. The second is subtler: because pods can run as any ServiceAccount, "granting permission to create workloads also implicitly grants the API access levels of any service account in that namespace". Least privilege is not a property of the verbs in a Role but of what those verbs let a subject become.
Which is why the AC family's evidence is not the manifest. AC-6(9) asks you to "log the execution of privileged functions", and on a cluster you operate that log comes from an API server you configure. The manifest states intent; the audit event records what happened.
AU: the flag that is off until you set it
The AU family is where self-hosting changes the economics most sharply, and it starts from an uncomfortable default. Kubernetes API server auditing is off until you turn it on: the policy is passed with --audit-policy-file, and the documentation is blunt — "if the flag is omitted, no events are logged". A default cluster generates nothing your AU controls could rest on.
Once on, the policy is unusually well shaped for control mapping. Rules are evaluated in order, "the first matching rule sets the audit level of the event", and the four levels — None, Metadata, Request and RequestResponse — decide per resource how much of the request and response body is retained. That maps onto the family almost line for line: which events are logged is AU-2, how much of each is retained is AU-3, the catch-all rule is AU-12, and a RequestResponse rule over RBAC objects satisfies AC-6(9). We write the control ID beside each rule, so the file reads as an implementation statement rather than as tuning.
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages: ["RequestReceived"]
rules:
# AC-6(9) — privileged functions. Full bodies: who was granted what.
- level: RequestResponse
resources:
- group: rbac.authorization.k8s.io
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
# AU-3 — who, what, when. Metadata only: never write a Secret payload.
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"]
# AU-2 — control-plane reads carry no accountability value. Dropped on purpose.
- level: None
verbs: ["get", "list", "watch"]
users: ["system:kube-scheduler", "system:kube-controller-manager"]
# AU-12 — everything else still generates a record.
- level: MetadataAU-9(3) then asks for "cryptographic mechanisms to protect the integrity of audit information and audit tools" — a question about the pipeline rather than the policy, covered end to end in Kubernetes audit logs into a SIEM you operate. What matters here is that on a self-operated control plane both ends of that chain are yours. On a managed one they are not: Amazon EKS exposes five selectable control-plane log types — api, audit, authenticator, controllerManager and scheduler — and AWS documents delivery plainly, into CloudWatch Logs "within a few minutes. However, log delivery is best effort." An assessor asking whether your record is complete for the period is then asking about somebody else's pipeline.
CM: settings you can diff, admission decisions you can prove
CM-6 is about configuration settings; CM-7(5) is specific, requiring "a deny-all, permit-by-exception policy to allow the execution of authorized software programs on the system". We hold CM-6 against the manifests in Git and CM-7(5) against an admission policy, because admission is the only place in a cluster where an unauthorised workload is stopped rather than noticed.
ValidatingAdmissionPolicy has been stable since Kubernetes v1.30 and is served by admissionregistration.k8s.io/v1, with rules written in CEL — so allow-by-exception can be enforced in-tree, no third-party admission controller in the trust path. We will not explain admission control here; our comparison of Kyverno and OPA Gatekeeper covers the mechanism. What matters for CM-7(5) is the pairing: the policy declares the exception, the binding decides where it is enforced, and validationActions decides whether a violation is denied, audited, or both.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: cm-7-5-authorised-registries
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: >-
object.spec.template.spec.containers.all(c,
c.image.startsWith('registry.internal.example/'))
message: "CM-7(5): image is not from an authorised registry"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: cm-7-5-authorised-registries-binding
spec:
policyName: cm-7-5-authorised-registries
validationActions: ["Deny", "Audit"]
matchResources:
namespaceSelector:
matchLabels:
compliance: in-scopeSC: boundary protection an assessor can read
SC-7 requires you to "monitor and control communications at the external managed interfaces to the system and at key internal managed interfaces within the system". The phrase doing the work is key internal managed interfaces. An external firewall answers the first half and nothing of the second, and on a cluster the internal interfaces are where the interesting failures live: a compromised front-end reaching a database it was never meant to address is not a perimeter problem.
We express those interfaces as NetworkPolicy objects, served by networking.k8s.io/v1, in the shape SC-7(5) calls deny by default and allow by exception. Two documents: one removing ambient reachability from the namespace, one granting the single path that should exist. The second is what an assessor reads — the complete enumeration of permitted internal communication for that workload, a list they can compare against your architecture rather than reverse-engineer from a firewall export.
# SC-7 — the boundary. Nothing reaches this namespace by default.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: payments
spec:
podSelector: {}
policyTypes: ["Ingress"]
---
# SC-7(5) — deny by default, allow by exception. This is the whole exception list.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-edge-gateway-to-payments-api
namespace: payments
spec:
podSelector:
matchLabels:
app: payments-api
policyTypes: ["Ingress"]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: edge
podSelector:
matchLabels:
app: gateway
ports:
- protocol: TCP
port: 8443One caveat, which gets its full treatment in Default deny, actually: a NetworkPolicy object is a statement of intent a CNI plugin may or may not be enforcing. Filing one as SC-7 evidence without testing that traffic is actually refused is the most common way this control is evidenced falsely.
SI: integrity, flaw remediation, and what 5.2.0 added
SI-7 asks you to "employ integrity verification tools to detect unauthorized changes", which we hold against signature verification at admission: the image that runs is the one whose signature resolves to a key you control. SI-2, flaw remediation, is where the shared-responsibility line moves most visibly — patching stops being a control you split with a provider and becomes one you hold end to end, kernel and kubelet included. That cost is real and bounded by a published schedule: Kubernetes 1.36 is the current minor with 1.37 upcoming, and 1.36 carries an end-of-life date of 2027-06-28. Pin evidence to the minor, never to a patch release.
Release 5.2.0's addition here is easy to under-implement. SI-02(07), Root Cause Analysis, is not one requirement but three: conduct root cause analysis to identify underlying causes; develop actions to address the root cause; and implement those actions and monitor the implementation for effectiveness. A post-incident document satisfies the first only. The third — monitoring that the corrective action kept working — needs an artefact dated after the incident closed, usually a policy or an alert rule that would fire if the condition recurred.
Where the machine-readable catalogue is wrong
Everything above assumes the catalogue your tooling resolves control ids against is correct. For one entry it is not. In NIST's published OSCAL 5.2.0 catalogue the enhancement sa-15.13 is titled "Logging Syntax", its guidance is about "secure logging", and its three organisation-defined parameters read "secure logging format(s)", "events types to log" and "level of detail to log". Its normative statement — the part telling an implementer what to do — reads: "Require the developer of the system or system component to minimize the use of personally identifiable information in development and test environments." That is byte-identical to the statement of sa-15.12, separately titled "Minimize Personally Identifiable Information" in the same file. Two differently-titled enhancements, one normative sentence, in the JSON and the XML alike.
This is not our discovery, and claiming it would be easy to disprove. It was reported as usnistgov/oscal-content issue #304, "Incorrect statement on SA-15(13)", on 26 September 2025, and a NIST maintainer replied the same day: "We will align the OSCAL version with the CPRT one, and issue a patch as soon as feasible." So the correct wording exists — in CPRT — and the divergence is in the machine-readable representation, not the control. We are deliberately not reproducing that sentence: CPRT serves 5.2.0 from a single-page application whose JSON endpoints return 404, and reconstructing a normative NIST sentence from a title and three parameter labels would be inventing one. Read it from CPRT yourself.
The half-life is what should change your process. The issue is still open, and the catalogue at main still carried the wrong prose when we checked on 23 August 2026 — eleven months on. The file has not sat untouched: GitHub's history for that path shows its newest commit, 78650f02, "Publishing auto-converted artifacts", committed 13 May 2026. The wrong sentence survived a full republication, so automation ingesting this file on a schedule re-ingested the defect.
The lesson generalises. A machine-readable catalogue feels authoritative in a way a PDF does not, because a program read it and did not complain. Treat it as a dependency: pin the commit, diff on update, run a cheap invariant over it — two controls with different titles sharing a statement string is a three-line check — and read its issue tracker before you trust it.
Exit ramp and the long game: OSCAL is the portable artefact
The reason to hold control implementations in OSCAL rather than a spreadsheet is that OSCAL survives the regime changing. A component definition names the control id, the component implementing it and the procedure that collects the evidence — in the format the catalogue already uses.
{
"component-definition": {
"uuid": "2f9d6d0e-6b1a-4a1e-9f4a-1c2b3d4e5f60",
"metadata": {
"title": "Kubernetes control plane — AU family implementation",
"last-modified": "2026-08-23T00:00:00.000000-00:00",
"version": "1.0.0",
"oscal-version": "1.2.2"
},
"components": [
{
"uuid": "b4c1f0a2-3d55-4d3e-8a77-9e0b1c2d3e4f",
"type": "service",
"title": "kube-apiserver",
"description": "API server with an audit policy file under version control.",
"control-implementations": [
{
"uuid": "c7e2a1b3-4f66-4e2d-9b88-0a1b2c3d4e5f",
"source": "https://raw.githubusercontent.com/usnistgov/oscal-content/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_catalog.json",
"description": "AU-2 and AU-12 implemented by audit-policy.yaml.",
"implemented-requirements": [
{
"uuid": "d1a2b3c4-5e77-4f1a-8c99-1b2c3d4e5f60",
"control-id": "au-2",
"description": "Event selection is the ordered rule list in audit-policy.yaml."
},
{
"uuid": "e2b3c4d5-6f88-4a2b-9daa-2c3d4e5f6071",
"control-id": "au-12",
"description": "The terminal Metadata rule generates records for all remaining event types."
}
]
}
]
}
]
}
}That portability is not theoretical. NIST publishes an official crosswalk from SP 800-53 Rev. 5 to ISO/IEC 27001:2022 through its OLIR programme, listed as supplemental material on the Rev. 5 publication page — so an implementation expressed against control ids has a documented route to the Annex A controls an ISO auditor works from. The evidence set does not change when the regime does; only the mapping in front of it does. ISO 27001 without inherited controls is where the ISMS scope and Statement of Applicability get built, and SOC 2 on self-hosted Kubernetes does the same for the Trust Services Criteria. One audit policy file answers all three.
Tooling is arriving to match: OSCAL-COMPASS, the toolchain for managing OSCAL compliance artefacts as code, was accepted to the CNCF at Sandbox maturity on 21 June 2024. Early, but the right shape — an open format with an open toolchain, neither of which can be withdrawn from you. Regimes change on a five-year cycle and jurisdictions add their own, as anyone who has worked through MeitY empanelment will recognise. A control implementation held as a machine-readable artefact, over infrastructure whose audit record you hold, is the part of this work still standing when the framework above it is replaced.
§FAQ/Common questions
Frequently asked
Is NIST SP 800-53 mandatory if I am not a US federal agency?
No. SP 800-53 is the control catalogue for US federal information systems, and outside that context it is voluntary. It is worth using anyway as a control vocabulary: it is free, it is versioned, it is published in a machine-readable format, and NIST publishes an official crosswalk from Rev. 5 to ISO/IEC 27001:2022 through its OLIR programme — so an implementation expressed against 800-53 control ids maps onto the regime your auditor actually works from.
Which SP 800-53 artefact is current — the Rev 5 PDF or Release 5.2.0?
Release 5.2.0, issued on 27 August 2025. The Rev. 5 PDF that CSRC serves is dated September 2020 with updates as of 10 December 2020, and it does not contain SA-15(13), SA-24 or SI-02(07), the three controls Release 5.2.0 added. Release 5.2.0 is distributed through CPRT and downloadable in OSCAL, JSON and spreadsheet formats; the OSCAL representation is available in JSON, XML and YAML.
How many controls are in NIST 800-53 Rev 5?
It depends on whether you count withdrawn controls. Parsing the published OSCAL 5.2.0 catalogue on 23 August 2026 gives 1,196 entries across 20 families — 324 base controls and 872 enhancements — of which 182 carry a withdrawn status, leaving 1,014 live. The impact baselines select far fewer: 149 control ids in LOW, 287 in MODERATE and 370 in HIGH. Those are our counts from the published files, not NIST-published headline figures, and the article ships the command that reproduces them.
Which 800-53 controls change most when you self-host Kubernetes?
The AU family changes most. On a managed control plane you select from the log types the provider exposes and accept its delivery guarantee — AWS documents EKS control-plane log delivery to CloudWatch Logs as best effort. On a cluster you operate, the audit policy is a file you write, with ordered rules and four retention levels, so AU-2, AU-3 and AU-12 become configuration decisions you can evidence directly. The trade is that PE and the lower SC controls stop being inheritable, and SI-2 flaw remediation becomes yours end to end.
Can I trust NIST's OSCAL catalogue as the source of truth for automation?
Verify it first. In the published OSCAL 5.2.0 catalogue, the enhancement sa-15.13 is titled Logging Syntax and its guidance and parameters describe logging, but its normative statement is byte-identical to sa-15.12's requirement about personally identifiable information. It was reported as usnistgov/oscal-content issue #304 on 26 September 2025, a NIST maintainer committed the same day to align OSCAL with the CPRT version, and the defect was still present at main on 23 August 2026 — having survived a republication of the file in May 2026. Pin the commit you ingest, diff on update, and check the issue tracker.
Further reading
- Air-Gapped Patching with Katello: Mirrors and Evidence
- NIST CSF 2.0 Govern: A Profile for Infrastructure You Own
- SOC 2 on Self-Hosted Kubernetes: Own the Evidence
- ISO 27001 Without Inherited Controls: Annex A You Operate
- Kubernetes Audit Logs Into a SIEM You Operate: Wazuh
- Default Deny, Actually: Auditing Kubernetes Network Policy
- Kyverno vs OPA Gatekeeper: Policy as Code at Cluster Scale
- MeitY Empanelment: Architecting for Indian Government Cloud
Executive Briefing
Thirty minutes to clarify your infrastructure risk
Walk us through your vendor footprint and regulatory constraints. We will tell you honestly where sovereignty creates leverage — and where it does not. No pitch deck. No obligation.