Files
dendritic/.github/instructions/janus-host.instructions.md
T
2026-07-04 11:49:58 -05:00

9.5 KiB

description, name, applyTo
description name applyTo
Use when modifying the Janus host, Janus step-ca deployment, Step SSH CA, mTLS issuance, CA bootstrap artifacts, or Janus SOPS secrets. Covers idiomatic Nix, secret placement, and troubleshooting. Janus Host Instructions modules/hosts/janus/**, modules/services/step-ca/step-ca.nix, modules/features/step-client.nix

Janus Host Instructions

Use this instruction when changing Janus host wiring, Janus CA material, or the shared Step CA module that Janus consumes.

Host Intent And Boundaries

  • Treat Janus as the homelab certificate authority host. It runs step-ca for X.509 issuance, SSH user and host certificates, and mTLS certificates.
  • Assume Janus CA endpoints are private/LAN-only unless the task explicitly changes the exposure model.
  • Keep Janus as a concrete host under modules/hosts/janus; keep reusable behavior in shared modules such as modules/services/step-ca/step-ca.nix, modules/services/step-ca/ssh-host.nix, modules/features/step-client.nix, and modules/features/mtls.nix.
  • Preserve host identity unless the task explicitly renames the deployed CA: hostname = "janus", flake.nixosConfigurations."${hostname}", and networking.hostName = hostname.
  • Keep Janus-specific paths, DNS names, IP SANs, and CA artifacts in the Janus host area or in Janus-specific option values. Do not move them into generic shared modules unless they become a deliberate module interface.

Required Module Composition

When editing modules/hosts/janus/default.nix, preserve this composition unless the task explicitly changes Janus architecture:

  • nixos.lxc for the container host shape.
  • nixos.mysops with sops.defaultSopsFile = ./secrets.yaml for host-local encrypted CA runtime material.
  • nixos.step-ca to run the CA daemon and render /etc/smallstep/ca.json from Nix plus SOPS paths.
  • nixos.step-client to install Step trust bootstrap defaults and root CA material.
  • nixos.step-ssh-host so Janus itself participates in SSH host certificate flow.
  • nixos.mtls so Janus can issue and renew its own mTLS certificate bundle.

Step CA Deployment Pattern

  • Prefer structured Nix values rendered with builtins.toJSON over hand-written JSON strings for ca.json.
  • Prefer the NixOS services.step-ca.settings interface for normal ca.json settings. Use a sops.templates-rendered config only when the rendered JSON needs sops-nix placeholders or runtime secret paths.
  • Keep secret-bearing ca.json content rendered through sops.templates when it includes values from config.sops.placeholder; do not put decrypted private keys, passwords, or provisioner encrypted private-key values directly in the Nix store.
  • Continue using services.step-ca for the daemon contract: enable, settings, address, port, openFirewall, package, and intermediatePasswordFile.
  • Remember that the NixOS module overrides services.step-ca.settings.address with services.step-ca.address plus services.step-ca.port. Change the high-level address/port options when changing the listener.
  • Keep the intermediate password as a runtime string path from config.sops.secrets.<name>.path; do not use a Nix path literal or the password file can be copied to the globally readable Nix store. Do not pass passwords through command-line arguments or environment variables.
  • Account for upstream NixOS service behavior when debugging: the module writes /etc/smallstep/ca.json, starts step-ca.service as Type=notify, sets DynamicUser = true, uses StateDirectory = "step-ca", and passes intermediatePasswordFile through systemd LoadCredential.
  • If step-ca cannot read key files referenced by ca.json, check sops-nix owner/group/mode and systemd credential handling before loosening permissions. The password file path and CA signing key paths are separate contracts.
  • Keep Step CA runtime state in /var/lib/step-ca/db unless the task explicitly migrates storage. If moving away from embedded Badger, document database backup and HA implications.
  • Keep root, crt, key, ssh.hostKey, and ssh.userKey in ca.json pointing at public cert paths or sops-nix materialized secret paths as appropriate.
  • Use an ACME provisioner for automated X.509 issuance, an SSHPOP provisioner for SSH certificate renewal/rekey behavior, and the admin JWK provisioner for explicit administrative or scripted issuance. Ensure JWK claims include enableSSHCA = true when it is expected to sign SSH certificates.
  • Treat Nix and SOPS as the source of truth. If step ca provisioner add, remove, or update is used for investigation, copy the intended result back into Nix/SOPS and reload or restart step-ca; do not leave live ca.json drift on the host.
  • Use authority.claims or provisioner-level claims for certificate lifetimes, renewal behavior, and SSH CA enablement. Avoid allowRenewalAfterExpiry unless the task explicitly accepts the added risk for intermittently connected clients.
  • Add authority.policy when constraining which X.509 SANs or SSH principals Janus may issue. This is mandatory for any move away from private/LAN-only operation.

CA Material And Secret Placement

Store each kind of material in the narrowest place that matches who needs it:

  • modules/hosts/janus/secrets.yaml: Janus runtime CA secrets used by step-ca, including janus/ca_password, janus/intermediate_ca_key, janus/ssh_host_ca_key, janus/ssh_user_ca_key, and janus/admin_provisioner_encrypted_key.
  • keys/secrets.yaml: shared provisioner credential janus/admin_jwk, because client and host certificate modules outside Janus consume it to request certs.
  • modules/hosts/janus/root_ca.crt, intermediate_ca.crt, fingerprint, ssh_user_ca_key.pub, and ssh_host_ca_key.pub: public trust artifacts that may be committed and reviewed.
  • Offline secret storage only: root_ca_key and the root key password. The running CA should not need the root private key for normal operation.

When editing secrets:

  • Use SOPS commands (sops, edit-secrets, or sops set) rather than editing ENC[...] payloads.
  • Use YAML literal blocks for PEM or OpenSSH private keys so newlines are preserved.
  • After changing Janus recipient rules, run sops --config .sops.yaml updatekeys -y modules/hosts/janus/secrets.yaml so the host-local file is actually rewrapped.
  • Keep .sops.yaml scoped: Janus host runtime secrets should match modules/hosts/janus/secrets.yaml; shared credentials should match keys/secrets.yaml.

Public Trust Artifacts

  • If root_ca.crt changes, update fingerprint from step certificate fingerprint <root_ca.crt> and update modules/features/step-client.nix defaults in the same change.
  • If intermediate_ca.crt changes, confirm it still chains to the committed root and that the running CA uses the matching intermediate_ca_key from SOPS.
  • If SSH CA keys rotate, update the matching public key files and every SSH trust consumer, including modules/services/ssh.nix known-host CA values and TrustedUserCAKeys behavior.
  • Treat README.md bootstrap commands as part of the operational contract. Keep them aligned with secret names, public artifact paths, CA URL, DNS names, and IP SANs.

mTLS And Client Trust

  • Janus mTLS values should stay aligned with Step CA identity: update step-ca.dnsNames, mtls.subject, mtls.san, and Step client defaults together when CA names or addresses change.
  • modules/features/step-client.nix is the shared bootstrap surface. Its default CA URL, root certificate, and fingerprint should describe Janus unless the repo intentionally migrates to another CA.
  • mTLS certificate files are runtime outputs under the configured cert directory, not tracked source files. Do not store mTLS private keys in Git or the Nix store.

Step/Nix Troubleshooting Checklist

Start with the smallest check that matches the change:

  1. Host evaluation: nix eval .#nixosConfigurations.janus.config.system.build.toplevel.drvPath
  2. Build planning: nix build .#nixosConfigurations.janus.config.system.build.toplevel --no-link --dry-run
  3. Secret contract: confirm every step-ca.secrets.* value has a matching sops.secrets declaration and a matching key in modules/hosts/janus/secrets.yaml.
  4. Rendered config: on the host, inspect /etc/smallstep/ca.json or /etc/step-ca paths without copying secret content into logs.
  5. Service health: check systemctl status step-ca.service, journalctl -u step-ca.service, and step ca health --ca-url https://janus.john-stream.com after trust is bootstrapped.
  6. Provisioners: use step ca provisioner list to confirm ACME, SSHPOP, and admin JWK availability.
  7. SSH cert flow: use ssh-host-cert-check, systemctl status step-ssh-host-renew.service, and systemctl status step-ssh-host-renew.timer.
  8. mTLS flow: use mtls-check, mtls-generate, and systemctl status mtls-renew.timer for host certificate renewal issues.

Change Safety Rules

  • Do not deploy a root private key to Janus unless the task explicitly changes the trust model.
  • Do not rename janus/admin_jwk without updating all consumers in the same change.
  • Do not expose ACME or /provisioners endpoints publicly without an explicit migration away from the private/LAN-only model plus policy and endpoint exposure review.
  • Do not use certificate templates casually; if templates are added, keep them narrow and use toJson for user-controlled values.
  • If changing certificate durations, prefer short-lived leaf certs with automated renewal over long-lived leaf certs.
  • If changing service ports or proxying, account for Step renewal behavior: mTLS renewal is sensitive to layer-7 proxies unless configured for token-based renewal.