# CCAT CA — Provisioner set and reference tables This page is **reference**: lookup tables for the CCAT step-ca provisioner set, the SSH access tiers, the Ansible role tags exposed by `ca_trust` and `hsm_host`, and the lifetime flags accepted by `step ca provisioner update`. For the *why* behind these numbers, see {doc}`ca-architecture`. For the operational procedures (adding, rotating, removing), see {doc}`../ca-provisioner-management`. ```{contents} :local: :depth: 2 ``` ## The CCAT provisioner set Provisioners are the entry points through which clients request certs from step-ca. Each provisioner has a type (OIDC, JWK, ACME, SSHPOP, ...), an authentication mechanism, and a set of **claims** that govern what kinds of certs it can issue and with what lifetimes. They live in `ca.json` inside the `step-ca-data` volume. CCAT runs six provisioners. They are installed by the script `step-ca/provisioners-add.sh`. | Name | Type | Purpose | Default lifetime | Max lifetime | |---|---|---|---|---| | `CCAT-GitHub` | OIDC | Interactive SSH user certs via Dex + GitHub (team-gated) | 16 h | 16 h | | `prod-services` | JWK | Production x509 / TLS certs | 90 d (2160 h) | 90 d | | `staging-services` | JWK | Staging x509 / TLS certs | 30 d (720 h) | 30 d | | `service-accounts` | JWK | SSH certs for automated services (Jenkins, ccat_transfer/bbcp, CI) | 24 h | 24 h | | `acme` | ACME | Auto-TLS for internal services via ACME protocol | 90 d | 90 d | | `sshpop` | SSHPOP | SSH host cert auto-renewal (host re-proves by signing with old cert) | 7 d (168 h) | 7 d | For the rationale behind each lifetime, see {doc}`ca-architecture` § "Why these lifetimes" and § "What SSHPOP is and why it's clever". ## SSH access tiers | Tier | Primary auth | Backup auth | Linux account | Sudo | |---|---|---|---|---| | 1 — Admin | `step ssh login` (Dex → GitHub → step-ca cert) | Nitrokey FIDO2 key in static `authorized_keys` | Personal user, `wheel` | Yes | | 2 — Staff | `step ssh login` → cert | *(none — off by design)* | Personal user | No, unless opted in case-by-case | | 3 — Break-glass | *(none — not reachable via SSH)* | iLO console + password | `breakglass` local user | Yes | The narrative for what each tier means and how the tiers compose is in {doc}`ca-architecture` § "SSH access tiers — narrative". ## Ansible role: `ca_trust` — distribute public trust material Applied to: all managed hosts (`input_ccat`, `input_staging`, `ccat`, eventually `travel_hosts`). Responsibilities: - Copy `root_ca.crt` into the RHEL (`/etc/pki/ca-trust/source/anchors/`) or Debian (`/usr/local/share/ca-certificates/`) trust store and run `update-ca-trust` / `update-ca-certificates`. - Copy `ssh_user_ca.pub` to `/etc/ssh/trusted_user_ca_keys` and set `TrustedUserCAKeys` in `sshd_config`. - Register `ssh_host_ca.pub` in `/etc/ssh/ssh_known_hosts` with a `@cert-authority *.data.ccat.uni-koeln.de` line. - Render `/etc/ssh/auth_principals/` for every user in `group_vars/all/users.yml` with a `github:` field, and set `AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u` in `sshd_config`. This closes the loop between the GitHub-team authorization gate (enforced at Dex) and which Linux user each certificate is permitted to become on the target host. Source files for the CA artifacts live in `ansible/roles/ca_trust/files/`. The role **is a safe no-op until those files exist** — it guards every task on file presence on the controller and warns if nothing is found. This means the role can be merged and wired into `playbook_setup_vms.yml` before the ceremony without affecting any running host. Principal-file rendering is gated on `ssh_user_ca.pub` being present for the same reason: a principals file without a trusted user CA is meaningless. ### Tags Run the full role: ```bash cd ansible ansible-playbook playbook_setup_vms.yml --tags ca_trust ``` | Sub-tag | What it does | |---|---| | `ca_trust_x509` | Just the system x509 trust store | | `ca_trust_ssh` | SSH user CA + host CA + principal files + sshd_config | | `ca_trust_principals` | Only the `auth_principals/%u` rendering and sshd_config line (useful when adding a new operator to `users.yml` and you want to push their mapping without touching anything else) | ### How the `github:` field becomes a working cert path The full chain, top to bottom: 1. **github.com**: you add the operator to `ccatobs/datacenter`. 2. **`users.yml`** (this repo): the same operator has a Linux user entry with `github: `. 3. **`ca_trust` run**: `make play-input-ccat T=ca_trust` (or the full playbook) renders `/etc/ssh/auth_principals/` on every target, containing exactly their GitHub login on one line, and ensures `sshd_config` has `AuthorizedPrincipalsFile`. 4. **Operator runs `step ssh login`**: step-cli talks to Dex, Dex checks the GitHub team, step-ca issues a cert whose first principal is their GitHub login (via `step-ca/ssh-user-template.tpl`). 5. **Operator runs `ssh @target`**: sshd on target sees the cert, reads `/etc/ssh/auth_principals/`, matches the cert's principal list against that file, accepts. Login succeeds. If any step in the chain is missing, the attempt fails cleanly: - Step 1 missing → Dex rejects at auth time - Step 2 missing → no principals file → sshd falls back to the default "principal must equal username" rule - Step 3 not run → principals file doesn't exist → same default fallback - Step 4 cert expired → step-cli offers a key, sshd falls back to the key-based path - Step 5 mismatch → permission denied The chain is designed so a missing link never *accidentally* grants access: the failure mode is always "cleanly denied," never "silently promoted." ### Onboarding a new operator 1. Confirm they're in `ccatobs/datacenter` on github.com. 2. Add a new user entry to `group_vars/all/users.yml`: ```yaml - user: alice uid: 1002 comment: "Alice Example" groups: "docker,ccat_deploy" password: "{{ vault_user_alice_password }}" github: alice-github ``` 3. Add `vault_user_alice_password` to the Ansible vault. 4. Run `make play-input-ccat T=users,ca_trust_principals` to create the Linux user and drop their principals file on all prod hosts. (Or drop `T=` to apply everything.) 5. Tell them to run `step ca bootstrap` + `step ssh login`, then `ssh alice@`. The full client onboarding procedure lives in {doc}`../ca-client-onboarding`. ### Off-boarding Fastest path: remove them from the `ccatobs/datacenter` GitHub team. Any new `step ssh login` fails at Dex. Their current cert expires within 16h. That's usually enough. Belt-and-braces path: also delete the `github:` field from their user entry in `users.yml` and re-run `make play-input-ccat T=ca_trust_principals`. The role explicitly scrubs the stale `auth_principals/` file on the next run, so the cert path is closed even if GitHub membership somehow persists. Add them to `users_removed` if you also want their Linux account deleted. ## Ansible role: `hsm_host` — prepare input-b for the HSM Applied to: `input-b` only (inside the `- hosts: input-b` play in `playbook_setup_vms.yml`). Responsibilities: - Install `opensc` and `opensc-tools` packages on the host. - Deploy `99-nitrokey-hsm.rules` udev rule granting the `plugdev` group access to the Nitrokey HSM 2 device node. - Run `pkcs11-tool --list-slots` and either warn (default) or fail (when `_hsm_enforce_verify: true` in host_vars) if the HSM is not detected. Run standalone: ```bash cd ansible ansible-playbook playbook_setup_vms.yml --tags hsm_host -l input-b ``` | Sub-tag | What it does | |---|---| | `hsm_host_pkg` | Install `opensc` and `opensc-tools` packages | | `hsm_host_udev` | Deploy `99-nitrokey-hsm.rules` udev rule | | `hsm_host_verify` | Run `pkcs11-tool --list-slots` to confirm HSM is visible | To make verification strict once commissioning is done (so that an accidentally-unplugged HSM fails the playbook loudly), create `ansible/host_vars/input-b/hsm.yml` with: ```yaml _hsm_enforce_verify: true ``` ## Lifetime flags for `step ca provisioner update` When updating an existing provisioner's lifetimes (see {doc}`../ca-provisioner-management` § "Updating lifetimes on existing provisioners"), pass only the flags you want to change: | Flag | Applies to | Example | |---|---|---| | `--x509-default-dur` | x509 certs | `2160h` (90 days) | | `--x509-max-dur` | x509 certs | `4320h` (180 days) | | `--ssh-user-default-dur` | SSH user certs | `16h` | | `--ssh-user-max-dur` | SSH user certs | `24h` | | `--ssh-host-default-dur` | SSH host certs | `168h` (7 days) | | `--ssh-host-max-dur` | SSH host certs | `336h` (14 days) | All durations are passed as Go time.Duration strings — `h` for hours, `m` for minutes. `d` and `w` are not supported. ## Cross-references - {doc}`tls-and-pki` — PKI fundamentals: what a cert is, TLS handshake, root vs intermediate explained from first principles. - {doc}`ca-architecture` — the design narrative this reference page summarises (provisioner set, lifetimes, GitHub gate, SSHPOP, Pattern A). - {doc}`certificate-authority-threat-model` — what the cert auth model defends against. - {doc}`../secrets-management` — how the vault pipeline works; `STEP_CA_HSM_PIN` and the Dex client secrets flow through this. - {doc}`../deployment` — compose file layering; where `docker-compose.ca.yml` fits alongside the application stack. - {doc}`../backup-restore` — backup of the step-ca Docker volume. - {doc}`../ca-client-onboarding` — how-to: laptop setup. - {doc}`../ca-day-to-day` — how-to: routine ops. - {doc}`../ca-provisioner-management` — how-to: add/update/remove provisioners; rotate JWK passwords. - {doc}`../ca-rotation-and-recovery` — how-to: intermediate / root rotation; disaster recovery. - [pki-security-roadmap.md](https://github.com/ccatobs/system-integration/blob/main/docs/pki-security-roadmap.md) (repo root) — full roadmap including threat analysis and phase plan. - [step-ca documentation](https://smallstep.com/docs/step-ca/) — upstream docs for provisioner syntax, config options, KMS backends.