By the end you'll know
- A repeatable method for mapping any system against a set of controls
- How to give each control an honest status: implemented, partial, inherited, planned or accepted risk
- What a real control map looks like, with evidence and gaps, for a live system
- How gaps turn into a plan of action and milestones
- How to map a system of your own, starting today
Where you are
Lessons 1 to 6 built the pieces: controls and evidence, the frameworks, the six families, evidence from Git and GitOps, policy as code, and the federal process with its SSP and POA&M. This lesson puts them together on one real system.
The system is CivicGrid: the API behind api.civicgrid.org, running on Kubernetes on DigitalOcean, with a Supabase database, Cloudflare in front, and everything deployed from Git. It isn't a federal system, and nobody is asking it for an ATO. That makes it a good subject: there's nothing to pretend about, so the gaps can be shown as plainly as the strengths.
A pre-sale inspection
Before selling a house, careful owners sometimes pay for an inspection themselves, before any buyer asks. They walk room by room with a checklist. What works goes in one column, what needs fixing in another, with a quote and a date for each repair. The point isn't a perfect report. It's knowing the truth first, fixing what matters, and being able to answer any buyer's question with evidence instead of a shrug.
This lesson is that inspection for a software system. In compliance it's called a self-assessment, or a gap assessment, and it's what teams do before an auditor arrives, and what you can do on any system you run.
The method
Five steps, which work for any system and any framework:
- Scope and categorize. Say what's in the system and what isn't, and how bad a failure would be. Lesson 6 categorized CivicGrid as Moderate, driven by integrity.
- Pick the controls. A full Moderate baseline is about 287 controls and enhancements, which is a team's work for months. For a first pass, pick the ones that matter most: here, fourteen controls across the six families from Lesson 3, plus separation of duties from Lesson 4 and boundary protection from Lesson 5.
- For each control, answer three questions. How is it met? What's the evidence? What's the honest status?
- Turn every gap into a POA&M item, with a fix, an owner and a date.
- Repeat on a schedule. Statuses change: evidence ages, new gaps appear, fixes land.
For the status, use one of five words, and be strict about them:
| Status | Means |
|---|---|
| Implemented | In place, working, with evidence you could show today |
| Partial | Some of it is in place; a specific part isn't |
| Inherited | Met by a provider, backed by the provider's own evidence |
| Planned | Not in place yet, with a plan and a date |
| Accepted risk | Not in place, deliberately, with the reason written down |
"Planned" is not "implemented", however good the plan. That one rule is where most self-assessments go wrong.
CivicGrid at a glance
Here's the result first, then the detail. Fourteen controls, as of late October 2026:
The control map
Here's the detail behind each tile: how it's met, the evidence, and what's missing. It's the same shape as the implementation statements in Lesson 6, shortened to fit a table.
| Control | How it's met | Evidence | Status |
|---|---|---|---|
| AC-2 Account management | Unused and test API keys revoked; new sign-ups can only create Free keys; paid keys issued by an admin | Key list with revoked dates; the API test that refuses other tiers | Implemented |
| AC-5 Separation of duties | One person writes and merges every change. Compensating: pull requests required, admins included, with required automated checks | Branch protection settings; Git history | Accepted risk |
| AC-6 Least privilege | The API, the data verifier and the backups each use their own database login with only the rights they need; admin pages limited to an allowlist | Automated tests that prove each login is refused what it shouldn't do | Implemented |
| IA-2 Multi-factor authentication | Two-factor on every account that can change the system: GitHub, DigitalOcean, Cloudflare, Supabase, Vercel | Account security settings | Implemented |
| IA-5 Secrets | Secrets typed in by hand, never stored in Git; secret scanning with push protection on public repositories | Repository settings; Git history with no secrets | Partial: hand-made secrets aren't backed up or encrypted anywhere outside the cluster |
| SC-7 Boundary protection | No public entry point: requests arrive through a Cloudflare Tunnel; Redis accepts connections only from API pods | Service list showing no public load balancer; the Redis NetworkPolicy | Partial: the other workloads have no network policies yet |
| SC-8 Encryption in transit | TLS at the edge; plain HTTP to the API refused; HSTS on the website | Daily-style SC-8 test from Lesson 1; the Cloudflare rule | Partial: certificate checking between the tunnel and the gateway is switched off |
| SC-28 Encryption at rest | The database provider encrypts all data at rest with AES-256 | Supabase's published security statement | Inherited |
| AU-2 Event logging | Application logs and metrics collected; key events like data approvals recorded in the database | Monitoring configuration; review records | Partial: metrics kept 3 days, no long-term log store |
| SI-4 System monitoring | Prometheus alerts to Slack, plus an outside dead-man's switch that alerts if the monitoring itself goes quiet | Alert rules; heartbeat checks; alerts that fired | Implemented |
| CM-2 Baseline configuration | Cluster and infrastructure defined in Terraform and Git; images pinned to exact commits and checked by digest | Terraform and GitOps repositories | Implemented |
| CM-3 Change control | Every change through a pull request with required checks; Argo CD deploys only from Git; data changes proposed by the verifier, approved by a person | Pull requests; Argo CD sync history; the review queue's records | Implemented |
| CP-9, CP-10 Backup and recovery | Weekly database backups, kept 60 days; a full restore tested and matched exactly | Backup logs; the restore test record | Partial: one location, one restore test |
| RA-5, SI-2 Scanning and patching | Dependabot alerts and security updates on every repository; dependencies pinned to exact versions | Dependabot history; merged update pull requests | Partial: no container image or Terraform scanning |
A few of these deserve a closer look, because they show the method working.
SC-8 shows how a gap turns up. When this track began, the API still answered plain HTTP. A test while writing Networking Lesson 9 found it, one Cloudflare rule fixed it, and Compliance Lesson 1's evidence file proves it. And mapping the control carefully turned up a second, smaller gap: inside the cluster, the connection from the tunnel to the gateway doesn't check the gateway's certificate. Low risk, since it never leaves the cluster, but real, and so it's partial, not implemented.
SI-4 shows why evidence of a control isn't enough on its own. CivicGrid had Prometheus alerting for weeks. Then Prometheus itself crashed and stayed down for nine days, and nothing said so, because the thing that sends alerts was the thing that was down. The fix was a dead-man's switch: an outside service that expects a regular ping from the monitoring, and alerts when it stops. Only after that is SI-4 honestly implemented.
SC-28 shows what inherited means. CivicGrid doesn't encrypt its own disks; its database provider does, and says so publicly. That's a legitimate status, but it rests on the provider's word and evidence, which is why it's inherited and not implemented. The first case below looks at the catch.
AC-5 shows what accepted risk means. One person can't separate duties from themselves. Rather than pretend, the map says so, names the compensating controls, and records the decision. If CivicGrid ever had a second engineer, this would become a POA&M item on the first day.
The POA&M
Every partial status becomes a line on the plan of action and milestones. Here's CivicGrid's, with the target dates proposed for the next few months:
| Weakness | Control | Planned fix | Target |
|---|---|---|---|
| Tunnel doesn't verify the gateway's certificate | SC-8 | Give the gateway a Cloudflare origin certificate, then turn verification on | Nov 2026 |
| Backups in one place, restore tested once | CP-9, CP-10 | Add an off-site copy; test a restore every quarter, timed and recorded | Nov 2026 |
| No image or Terraform scanning | RA-5 | Trivy on every image build; Checkov on the infrastructure repository, both as required checks | Nov 2026 |
| Most workloads have no network policy | SC-7 | Default-deny policies per namespace, then allow what's needed | Dec 2026 |
| Hand-made secrets live only in the cluster | IA-5 | Encrypted secrets stored in Git, or an external secret store | Jan 2027 |
| Metrics kept 3 days, no long-term logs | AU-2 | Send logs to a store with retention of a year | Jan 2027 |
Each item has a weakness, the control it belongs to, a specific fix and a date. The owner, for a one-person system, is always the same person, and a real POA&M would still say so. Some fixes have costs worth naming: CivicGrid runs on one small node whose memory is mostly spoken for, so tools like Kyverno or a log store have to fit in the space that's left. Trade-offs like that are exactly what a POA&M is for: written down, with a plan, rather than discovered later.
Map a system of your own
You can do this today, on any system you run: a side project, a home lab, a small app at work. Start with the same fourteen controls, and for each, fill in a block like this:
Control: SC-8, encryption in transit
How: (what's in place, specifically enough for someone to test)
Evidence: (a file, a setting, a test result, with a date)
Status: Implemented / Partial / Inherited / Planned / Accepted risk
Gap: (if not implemented: what's missing)
Fix: (if not implemented: the next step, and a date)
Three tips from doing it on CivicGrid:
- Start with what you can prove, not what you remember. If you can't find the evidence in a couple of minutes, mark the control partial until you can.
- Write the gaps first. It's tempting to fill in the strong controls and skim the weak ones. The weak ones are the point.
- Date it. A control map is true on the day it's made. Put the date at the top, and redo it every few months, which is continuous monitoring on a small scale.
The result is the most useful thing in this track for a job interview: a real system, mapped to real control IDs, with evidence and an honest plan. "I mapped my own production system against NIST 800-53, here's what I found, and here's what I fixed" is a story most candidates can't tell.
In my real setup
This lesson is my real setup. Writing this track changed it: the plain-HTTP gap was found and closed in the middle of it, and mapping the controls turned up two more I hadn't written down. I'm publishing the gaps on purpose. This whole site is me learning in public, and a control map with no gaps would be the least believable thing on it. When the POA&M items close, I'll update the map here, with the date.
Spot the problem
Three mistakes that make a self-assessment look better than the system is. Work out what's wrong before you read the explanation.
"Inherited" on the provider's word. A team marks encryption at rest as inherited from its database provider, citing the provider's security web page. An auditor asks for the provider's own audit report. The team finds the report is only available on a more expensive plan than theirs. Is the control still inherited? Partly. The public statement is something, but it isn't independent evidence. An inherited control needs the provider's evidence, usually its SOC 2 report or FedRAMP authorization, and if you can't get it, the honest status says so: inherited, with the provider's report not reviewed.
Planned, marked implemented. A team's map says "vulnerability scanning: implemented", because adding Trivy to the pipeline is on next sprint's board. The tool isn't running yet. What's the problem? A plan isn't a control, however soon it starts. If an auditor samples it, there's no evidence, and the whole map's credibility drops with it. Mark it planned, with the date, and change it to implemented on the day the first scan runs.
The restore test from last year. A team marks backup and recovery as implemented, citing a full restore test that matched production exactly. The test was fourteen months ago, and the database has doubled in size since. What's weak about the evidence? It was true once. Restores fail in new ways as data grows, schemas change and tools update. Recovery evidence needs to be repeated on a schedule, which is why CivicGrid's map counts one restore test as partial, and the fix is a test every quarter.
Where you are now
You've finished the Compliance for cloud engineers track. You can explain frameworks, controls and evidence; read the frameworks in any job description; talk about the six control families in engineering terms with their NIST IDs; get evidence from Git, Terraform and GitOps; enforce rules with policy as code; walk through the RMF and the ATO; and now, map a real system, honestly, with a plan for what's missing. The best next step is the last section: pick a system you run, take fourteen controls, and start the map.
Check yourself
A team plans to add image scanning next sprint. What status does RA-5 get today?
What makes a control inherited rather than implemented?
When is accepted risk the right status?
Why does every partial status need a POA&M item?
CivicGrid's monitoring was down for nine days without anyone knowing. Which control was weak?
Why date a control map?
Key terms
- Self-assessment (gap assessment): mapping your own system against controls before an auditor does.
- Control map: for each control, how it's met, the evidence, and the status.
- Implemented, partial, inherited, planned, accepted risk: the five honest statuses for a control.
- Inherited control: met by a provider, and backed by the provider's own evidence.
- Accepted risk: a control deliberately not implemented, with the reason recorded.
- Compensating control: something that reduces the risk when a control can't be met directly.
- Dead-man's switch: an outside check that alerts when an expected regular signal stops.
Go deeper
- NIST's SP 800-53 Revision 5 (opens in a new tab), for the full text of every control in this map.
- Security at Supabase (opens in a new tab), the provider statement behind CivicGrid's inherited SC-28.