Secrets, PKI & CertificatesCapstone · Production CapstoneCapstone
Capstone — build and defend a production secrets and PKI architecture
What you'll learn
- Design a two-tier trust hierarchy and prove each constraint is set correctly
- Automate issuance and renewal and prove the running process serves the new certificate
- Operate a secret manager under least privilege and prove both an allow and a deny
- Rehearse rotation, compromise response and restore, and record the evidence for each
Prerequisites
None — start here.
Verified against OpenSSL 3.5.x teaching target; 3.0+ minimum · OpenSSH 10.x teaching target; 8.2+ minimum for certificate workflows · OpenBao 2.6.x · Smallstep step-ca 0.30.x · Certbot / Pebble Certbot current release; Pebble 2.10.x ACME test server · Kubernetes (cross-course target) 1.36.x · PostgreSQL 17.x · 2026-08-26
Every control in this course has so far been examined on its own. The capstone puts them in one estate and asks whether they hold together. You will build a trust hierarchy, an automated issuance path, a secret manager and the credentials it hands out, all on containers you can destroy, and then you will attack your own build and prove that each control does what you claim it does.
What you are building, and the diagram you owe
The estate is deliberately small: one root, one issuing authority, one ACME server, one TLS service, one SSH certificate authority, one secret manager, one database, one workload and one monitor. Nothing here needs a cloud account or a spare machine. Every component runs as a container on the same host, using the same fixtures the labs use, so that stage thirteen can destroy half of it without consequence.
Before you build anything, redraw the diagram below as your own. Use your own host names, your own validity windows and your own arrows, and add the two flows this version deliberately leaves incomplete: how a failure reaches a human, and how each authority is itself replaced. Your diagram is a graded deliverable, and it is the artefact a reviewer will read first.
flowchart TD
ROOT["Offline root CA\npowered off between ceremonies"]
ISS["Issuing CA\nonline, constrained by the root"]
ACME["Internal ACME server"]
TRUST["Trust anchor distribution\nOS store and runtime store"]
SVC["TLS service\nrenewed and reloaded"]
SSHCA["SSH certificate authority\nbounded principals and validity"]
ADMIN["Operator session\ncertificate, not a key file"]
BAO["Secret manager\nsealed until quorum"]
WL["Workload\nno credential at rest"]
DB["Database\nshort-lived dynamic roles"]
MON["Probes\nexpiry and availability"]
ROOT --> ISS
ROOT --> TRUST
ISS --> ACME
ACME --> SVC
TRUST --> SVC
SSHCA --> ADMIN
WL --> BAO
BAO --> DB
SVC --> MON
BAO --> MON
Read the diagram as two independent trust systems that meet only at the host. On the left, an offline root vouches for an online issuing authority, which sits behind an enrolment service, and a separate distribution path carries the anchor to every client that must recognise it. On the right, a secret manager holds a barrier that stays closed until a quorum opens it, and hands out credentials that expire on their own. The SSH authority is a third, smaller system with no intermediates at all. A weakness in any one of the three is contained by the other two only if you can say, out loud, where the boundary between them runs.
Stages 1 to 3 — the hierarchy and the certificates it issues
The first three stages produce a chain that validates, a leaf whose identity is unambiguous, and an automated renewal that actually reaches the running process. Nothing later in the capstone works if these are wrong, and the third one is where most real estates fail.
| Stage | What you build | Acceptance criterion you can verify yourself |
|---|---|---|
| 1 | A root authority and a separate issuing authority, each with its own key | The root carries basicConstraints=critical,CA:TRUE,pathlen:1 and keyUsage=critical,keyCertSign,cRLSign; the issuing authority carries CA:TRUE,pathlen:0; openssl verify -CAfile root.crt srv-ca.crt returns srv-ca.crt: OK; the root private key is not present on the host that runs the issuing authority, and you can demonstrate that by listing the filesystem it does not appear on |
| 2 | A server certificate with a subject alternative name and a server-authentication extended key usage | Verification succeeds for a name in the SAN and fails with error 62 for a name that is not; verification succeeds for sslserver and fails with error 26 for sslclient; a certificate whose common name disagrees with its SAN is still accepted or rejected purely on the SAN |
| 3 | Automated issuance and renewal against an ACME server, with a reload | The serial the process serves over the network changes after renewal; and, in a separate deliberate run, renewing without reloading leaves the old serial on the wire while the new one sits on disk |
Stage 1 repeats
secrets-pki-lab-04-build-two-tier-certificate-authority and
stage 2 repeats
secrets-pki-lab-05-issue-server-certificate-san-eku; revisit
either if a constraint does not come out as you expected. The
path length on the root is not decoration. A pathLenConstraint
may only appear where the certificate is a certificate authority
and asserts keyCertSign, and setting it to zero on the issuing
authority is what stops that authority minting a third tier you
never approved.
PKI="$HOME/rbpki-capstone/pki"
cd "$PKI" || exit 1
# Identity is whatever the SAN says, and nothing else.
openssl verify -CAfile root.crt -untrusted srv-ca.crt \
-verify_hostname app.lab.example app.crt
openssl verify -CAfile root.crt -untrusted srv-ca.crt \
-verify_hostname wrong.lab.example app.crt
openssl verify -CAfile root.crt -untrusted srv-ca.crt \
-purpose sslclient app.crt
Those three calls are your stage 2 evidence, and they must produce three different results:
app.crt: OK
error 62 at 0 depth lookup: hostname mismatch
error 26 at 0 depth lookup: unsuitable certificate purpose
Stage 3 is the one to take seriously. Renewal writes a file; serving reads memory. Prove the gap exists before you prove you have closed it, by renewing once with the reload hook removed and capturing both serials.
HOST=app.lab.example
PORT=8443
LIVE=/etc/ssl/certs/app.lab.example.pem
# What the process is serving, read over the network.
openssl s_client -connect "$HOST:$PORT" -servername "$HOST" </dev/null 2>/dev/null |
openssl x509 -noout -serial
# What the renewal wrote to disk.
openssl x509 -in "$LIVE" -noout -serial
Two identical serials after a renewal and a reload is a pass. Two
different serials, with the older one on the wire, is the failure
this stage exists to make visible. Labs
secrets-pki-lab-13-automate-issuance-with-acme,
secrets-pki-lab-14-prove-renewal-and-reload and
secrets-pki-lab-15-run-internal-acme-ca cover the mechanics.
Stages 4 to 6 — distribution, SSH identity and the barrier
| Stage | What you build | Acceptance criterion you can verify yourself |
|---|---|---|
| 4 | The private anchor installed into an operating system trust store and into at least one language runtime | A container without the anchor fails the connection with curl: (60) SSL certificate problem: unable to get local issuer certificate; after installing the anchor and refreshing the store, the same call returns the page body; a runtime that ships its own bundle, such as the Python certifi bundle, still fails until it is told about the anchor separately, and you capture both failures |
| 5 | An SSH certificate authority issuing host and user certificates | ssh-keygen -L -f on the user certificate shows a non-empty principals list and a validity window measured in hours rather than years; a login attempt as an unlisted principal is refused, with the server log recording Certificate invalid: name is not a listed principal; an expired certificate is refused with Certificate invalid: expired |
| 6 | A secret manager running as a real server, initialised and unsealed | The server starts uninitialised and sealed; initialisation produces three shares with a threshold of two; the first share reports Unseal Progress 1/2 and the second reports Sealed false; the audit device is declared in the configuration file, because this release refuses to enable one through the API; the shares are recorded as held by different people and the bootstrap root token is revoked once the policies exist |
Stage 4 is the stage engineers skip because it works on their
laptop. It works on their laptop because their laptop has the
anchor. The acceptance criterion demands two separate failures
before the two successes, because the operating system store and
a language runtime bundle are different stores with different
refresh mechanisms, and a fleet that has one but not the other
produces the report that a service works from the shell and fails
from the application on the same host. Revisit
secrets-pki-lab-11-install-private-trust-anchor.
Stage 5 has two traps worth naming before you meet them. Signing
a key without a validity flag produces a certificate valid from
the Unix epoch to the distant future, so pair the signing flag
with an explicit validity interval every single time. And from
OpenSSH 10.3, an empty valid-principals list matches nothing
rather than matching everything, which reverses the advice in a
great deal of published material. Labs
secrets-pki-lab-17-build-ssh-certificate-authority and
secrets-pki-lab-18-issue-and-revoke-ssh-certificates cover
issuance and the key revocation list.
Stage 6 is a ceremony, not a command, and the wording the tool gives you states the obligation plainly:
Vault initialized with 3 key shares and a key threshold of 2. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 2 of these keys to unseal it
before it can start servicing requests.
That text still says Vault, because OpenBao inherited the
strings; quote it as it is rather than tidying it. Your
deliverable is not the transcript but the written answer to the
question it raises: who holds share one, who holds share two, who
holds share three, and which two of those people can be in the
same room within your recovery objective. Rehearse it in
secrets-pki-lab-19-deploy-secret-manager-and-unseal.
Stages 7 to 9 — policy, dynamic credentials and workload identity
| Stage | What you build | Acceptance criterion you can verify yourself |
|---|---|---|
| 7 | A least-privilege policy and a token bound to it | Reading the one permitted path succeeds; reading a neighbouring path returns Code: 403 with permission denied; writing to the permitted path returns 403; listing the parent returns 403, and you can explain that last one by naming the two different paths a versioned key-value store uses for data and for metadata |
| 8 | A dynamic database credential with a short lease | The lease has a stated expiry time; the generated database user authenticates successfully; after an explicit lease revocation the same credential is refused by the database itself, not merely by the secret manager; a count of leftover dynamic roles in the database returns zero |
| 9 | A workload that authenticates for its credentials rather than reading them | No file on the workload filesystem contains a credential that still works after the workload exits; the authentication response carries a bounded lease duration; copying the workload’s on-disk material into a second container does not obtain a usable token after the bounded window has passed |
Stage 7 must produce a deny as carefully as it produces an allow.
A policy that has only ever been tested against the path it
permits has not been tested. The list denial is the instructive
one: a policy granting read on the data path of a versioned
key-value store does not grant list, because listing reads the
metadata path, and the refusal names a path the policy author
never wrote down. Work it through in
secrets-pki-lab-20-write-least-privilege-secret-policy.
Stage 8 is where the phrase short-lived stops being a slogan. The
credential must stop working at the database, and the way you
demonstrate that is by connecting as the dynamic user after
revocation and capturing the database’s own refusal. Anything
observed only at the secret manager proves that the manager
forgot the credential, not that the database did. Lab
secrets-pki-lab-21-issue-dynamic-database-credentials walks the
lease lifecycle.
Stage 9 will not eliminate the bootstrap problem, and your
writeup should say so. An application role identifier plus a
secret identifier is still a credential the workload must obtain
from somewhere, so the honest acceptance criterion is not that no
secret exists but that nothing at rest remains useful: the
identifier is single-use or short-lived, the token it buys has a
lease, and a copy taken from disk after the window is worthless.
State plainly which component you have chosen to trust as the
first link, because that component is now the whole chain. Lab
secrets-pki-lab-22-authenticate-workload-with-approle is the
starting point.
Stages 10 to 13 — operate it, rotate it, break it, restore it
| Stage | What you build | Acceptance criterion you can verify yourself |
|---|---|---|
| 10 | Monitoring for certificate expiry and for secret-manager availability | A probe reports the served certificate entering a renewal window; a probe reports the secret manager as unavailable when it is sealed, which returns Code: 503 with Vault is sealed; and, critically, deleting the certificate the probe reads produces an alert rather than a silent success |
| 11 | A credential rotation and a certificate rotation with no downtime | A request loop running throughout both rotations records zero failed requests; the old credential is retired only after the new one has served real traffic; you can state, from your own timings, the overlap window each rotation required |
| 12 | A response to one injected compromise of your choosing | A timeline with clock times; a containment action that names the component enforcing it; evidence from an independent channel that the old credential no longer works; and an explicit statement of what your response did not contain |
| 13 | A backup and a restore that has actually been performed | The issuing authority is destroyed and rebuilt from backup, and the rebuilt authority issues a certificate that validates against the untouched root; the secret manager is restored and unsealed with the same shares; both restores are timed |
For stage 10, a three-state probe is the smallest honest one:
CERT=/etc/ssl/certs/app.lab.example.pem
WINDOW=$((30 * 24 * 3600))
if openssl x509 -in "$CERT" -noout -checkend "$WINDOW"; then
echo "ok"
elif [ -r "$CERT" ]; then
echo "expiring"
else
echo "probe broken"
fi
Two of those three states must reach a human, and the third state
is the one every estate forgets. A monitor that cannot read its
input is not reporting good news, and a check whose failures are
swallowed reports good news for exactly as long as it takes for
the certificate behind it to expire. Build the probe in
secrets-pki-lab-24-monitor-certificate-expiry, then break it on
purpose and confirm you hear about it.
Stage 11 wants overlap rather than replacement: add the new
credential, move consumers, remove the old one, in that order,
which is the same shape for a database password and for a
certificate. Rehearse in
secrets-pki-lab-23-rotate-database-password-zero-downtime.
Stage 12 gives you three choices, and they are not equivalent: a
leaked interface token is contained by revoking it at the issuer,
a compromised server key requires reissue with a fresh key rather
than reissue alone, and a compromised issuing authority requires
you to decide what every certificate it ever signed is now worth.
Whichever you pick, the deliverable that carries the marks is the
sentence describing what you did not contain. Stage 13 closes the
loop with
secrets-pki-lab-26-restore-pki-from-backup and
secrets-pki-lab-25-recover-from-secret-manager-outage; a backup
whose restore has never been executed is an untested assumption
with a filename.
How the build is assessed
Score each row on evidence, not on intent. A stage with a working build and no captured proof scores zero on that row, because the proof is the deliverable.
| Weight | Assessed element |
|---|---|
| 14 | Trust hierarchy and service certificates, stages 1 and 2 |
| 10 | Renewal with a proven reload, stage 3 |
| 7 | Trust anchor distribution to two kinds of store, stage 4 |
| 7 | SSH identity with bounded principals and validity, stage 5 |
| 8 | Secret manager deployment and unseal ceremony, stage 6 |
| 9 | Least privilege proven by an allow and a deny, stage 7 |
| 8 | Dynamic credential revoked at the database, stage 8 |
| 7 | Workload identity with nothing usable at rest, stage 9 |
| 8 | Monitoring proven to fail loudly, stage 10 |
| 6 | Rotation with no failed requests, stage 11 |
| 6 | Compromise response, including what was not contained, stage 12 |
| 5 | Backup with an executed restore, stage 13 |
| 5 | Your architecture diagram and the six-question analysis |
Seventy is a pass. Three rows are gating regardless of the total: stage 3, stage 7 and stage 13. A renewal you cannot prove reached the process, a policy that has never denied anything, and a backup that has never been restored are the three failures that turn a well-built estate into an outage on a date nobody has written down.
The six questions, answered three times
Close the capstone by returning to the analysis the course opened
with, in secrets-pki-i-02-six-questions-that-govern-every-credential.
Answer all six, in order, for three credentials from your own
build: one certificate-based credential, one bearer credential
and one dynamic credential. The questions are:
- Which principal is being trusted?
- Which credential proves that identity?
- Who issued it, and who checks the issuer?
- Where does the private material live?
- How long does it live?
- What revokes it, and what enforces that?
Two rules make this exercise worth doing rather than pleasant to write. First, questions four and six are never answered by the artefact itself, so write them from your build rather than from the credential, and enumerate the copies of the private material by hand until the count is exact. Second, question six is answered by naming a component and saying what that component does when the revocation record is unavailable. If no component consults it, write that down, and then justify the lifetime you chose in question five as the only containment control you actually have.
Production discipline
- Capture the evidence at the moment it exists. A serial, a log line or an exit status recorded during the stage is worth more than a reconstruction afterwards, and reconstruction is how honest people end up documenting a state that never was.
- Test the deny before you trust the allow. Every policy, principal list and trust store in this build has a negative case, and the negative case is the one that carries the security property.
- Write down what you did not contain. The scope of an incident response is a claim, and the unqualified version of that claim is the one that gets repeated in a review six months later by somebody who was not there.
- Time every restore. A recovery objective is a number somebody promised; your measured restore is the number that is true, and the gap between them belongs in the design document.
- Let lifetime absorb whatever enforcement cannot. Where you cannot name the component that reads a revocation record, shorten the credential and automate its reissuance instead of documenting an intention.
Cross-course references
- Kubernetes for Production Sysadmins - Part LXXVI (Certificates) covers a cluster whose authorities and leaves renew by entirely different mechanisms, which is the same two-deadline problem stage 13 asks you to rehearse.
- Observability for Production Sysadmins - Part LXIV (TLS Monitoring) covers probing from the consumer’s network position and alerting on an absent series, which is stage 10 implemented the way a real estate implements it.
- Linux for Production Sysadmins - Part XXXI (Systemd) covers the unit reload that carries a renewed certificate into the running process, the mechanism stage 3 exists to prove.
Quiz
Knowledge check · 4 questions
Q1. A student reports stage 3 complete: the renewal ran, the new certificate is in the expected directory, and the file modification time is minutes old. What is missing from that evidence?
Q2. In stage 8, demonstrating that the secret manager no longer lists the lease is insufficient evidence that the dynamic credential has been revoked.
Q3. Stage 4 requires two separate failures before its two successes. Name the two stores involved and explain what estate-level symptom the stage is teaching you to predict.
Q4. Decide what the reviewer should do with this submission, and what the student must produce before it can pass.
A capstone submission arrives on 2026-09-10. Stages 1, 2, 4, 5, 6, 8, 9, 11 and 12 are complete with captured evidence and would score 71 marks on their own. Stage 3 shows a renewal transcript and a directory listing but no handshake capture. Stage 7 shows a successful read against the permitted path and no denied operation. Stage 13 shows a backup script, a cron entry and a note that the restore was not attempted because the environment was needed for stage 12.
Passing score: 75%. Answers are checked in this browser.