WIP janus secrets

This commit is contained in:
John Lancaster
2026-07-04 11:49:58 -05:00
parent cddc369687
commit 69f492c7cc
10 changed files with 318 additions and 58 deletions
+59
View File
@@ -144,6 +144,33 @@ in
type = lib.types.str;
default = "/etc/step-ca/certs";
};
bootstrap = {
enable = lib.mkOption {
description = "Enable initial mTLS issuance when cert material is missing or invalid.";
type = lib.types.bool;
default = false;
};
wantedBy = lib.mkOption {
description = "systemd targets that should pull in mtls-bootstrap.service.";
type = with lib.types; listOf str;
default = [ "multi-user.target" ];
};
after = lib.mkOption {
description = "systemd units/targets that mtls-bootstrap.service should run after.";
type = with lib.types; listOf str;
default = [ "network-online.target" ];
};
wants = lib.mkOption {
description = "systemd units/targets that mtls-bootstrap.service should pull in.";
type = with lib.types; listOf str;
default = [ "network-online.target" ];
};
provisionerPasswordFile = lib.mkOption {
description = "Optional path passed to mtls-generate as --provisioner-password-file for noninteractive issuance.";
type = lib.types.nullOr lib.types.str;
default = null;
};
};
certReaders = lib.mkOption {
description = "";
type = lib.types.listOf lib.types.str;
@@ -179,6 +206,38 @@ in
mtlsRenewWrapper.outputs.systemd-system
];
systemd.services.mtls-bootstrap = lib.mkIf cfg.bootstrap.enable {
description = "Issue initial mTLS certificate if missing or invalid";
wantedBy = cfg.bootstrap.wantedBy;
after = cfg.bootstrap.after;
wants = cfg.bootstrap.wants;
path = with pkgs; [ coreutils step-cli ];
serviceConfig = {
Type = "oneshot";
User = cfg.renew.user;
Group = cfg.renew.group;
};
script = ''
set -euo pipefail
if [ -s "${cfg.certFile}" ] \
&& [ -s "${cfg.keyFile}" ] \
&& [ -s "${cfg.bundleFile}" ] \
&& step certificate inspect "${cfg.certFile}" >/dev/null 2>&1; then
echo "mTLS certificate already exists"
exit 0
fi
echo "Issuing initial mTLS certificate"
cmd=(/run/current-system/sw/bin/mtls-generate)
${lib.optionalString (cfg.bootstrap.provisionerPasswordFile != null) ''
cmd+=(--provisioner-password-file "${cfg.bootstrap.provisionerPasswordFile}")
''}
"''${cmd[@]}"
'';
};
systemd.timers.mtls-renew = lib.mkIf cfg.renew.enable {
wantedBy = [ "timers.target" ];
timerConfig = {
+1
View File
@@ -68,6 +68,7 @@ in
systemd.tmpfiles.rules = [
"d ${cfg.certDir} 0750 root root -"
"L+ ${cfg.certDir}/root_ca.crt - - - - ${cfg.root}"
"d /root/.step 0700 root root -"
"d /root/.step/config 0700 root root -"
"d /root/.step/certs 0700 root root -"
+43
View File
@@ -5,6 +5,13 @@ Generate passwords:
mkdir -p /tmp/janus-step-ca-bootstrap && chmod 700 /tmp/janus-step-ca-bootstrap && cd /tmp/janus-step-ca-bootstrap && umask 077 && openssl rand -base64 48 > ca_password.txt && openssl rand -base64 48 > admin_jwk_password.txt
```
Generate the Janus OpenSSH host key that Nix will materialize from SOPS at
`/etc/ssh/ssh_host_ed25519_key`:
```shell
ssh-keygen -t ed25519 -N '' -C [email protected] -f /tmp/janus-step-ca-bootstrap/ssh_host_ed25519_key
```
Bootstrap CA materials with SSH enabled:
```shell
STEPPATH=/tmp/janus-step-ca-bootstrap/step step ca init --name Janus --dns janus.john-stream.com --dns 192.168.1.244 --address :443 --provisioner admin --password-file /tmp/janus-step-ca-bootstrap/ca_password.txt --provisioner-password-file /tmp/janus-step-ca-bootstrap/admin_jwk_password.txt --ssh --deployment-type standalone --with-ca-url https://janus.john-stream.com
@@ -17,9 +24,24 @@ Insert generated runtime CA material into `modules/hosts/janus/secrets.yaml` und
- `/tmp/janus-step-ca-bootstrap/step/secrets/ssh_host_ca_key` -> `ssh_host_ca_key`
- `/tmp/janus-step-ca-bootstrap/step/secrets/ssh_user_ca_key` -> `ssh_user_ca_key`
- `/tmp/janus-step-ca-bootstrap/step/config/ca.json` -> `admin_provisioner_encrypted_key` (copy `authority.provisioners[].encryptedKey` for the `admin` JWK provisioner)
- `/tmp/janus-step-ca-bootstrap/ssh_host_ed25519_key` -> `ssh_host_ed25519_key`
If you are only validating wiring first, `admin_provisioner_encrypted_key` can be an encrypted placeholder and replaced later.
The checked-in `ssh_host_ed25519_key` value is also only an encrypted placeholder.
Replace it before deploying Janus, otherwise OpenSSH and SSH host certificate
issuance will fail because the materialized host key is not a valid private key.
For multiline secret values, avoid putting private key contents in shell history.
One safe non-interactive pattern is to JSON-encode the file content and pass that
temporary JSON file to `sops set --value-file`:
```shell
jq -Rs . /tmp/janus-step-ca-bootstrap/ssh_host_ed25519_key > /tmp/janus-step-ca-bootstrap/ssh_host_ed25519_key.json
sops --config .sops.yaml set --value-file modules/hosts/janus/secrets.yaml '["janus"]["ssh_host_ed25519_key"]' /tmp/janus-step-ca-bootstrap/ssh_host_ed25519_key.json
rm -f /tmp/janus-step-ca-bootstrap/ssh_host_ed25519_key.json
```
If rotating provisioner password, also set:
- `/tmp/janus-step-ca-bootstrap/admin_jwk_password.txt` -> `janus.admin_jwk` in `keys/secrets.yaml`
@@ -27,6 +49,7 @@ If rotating provisioner password, also set:
Secret source-of-truth after this split:
- `modules/hosts/janus/secrets.yaml`: Janus runtime CA secrets (`ca_password`, `intermediate_ca_key`, `ssh_host_ca_key`, `ssh_user_ca_key`, `admin_provisioner_encrypted_key`)
- `modules/hosts/janus/secrets.yaml`: Janus OpenSSH host private key (`ssh_host_ed25519_key`) materialized to `/etc/ssh/ssh_host_ed25519_key`
- `keys/secrets.yaml`: shared Janus provisioner secret (`janus.admin_jwk`) consumed by `step-ssh-host` across hosts
Then update public artifacts in repo from generated output:
@@ -38,6 +61,26 @@ Then update public artifacts in repo from generated output:
step certificate fingerprint /tmp/janus-step-ca-bootstrap/step/certs/root_ca.crt
```
- `modules/hosts/janus/ssh_user_ca_key.pub` from `/tmp/janus-step-ca-bootstrap/step/certs/ssh_user_ca_key.pub`
- `modules/hosts/janus/ssh_host_ca_key.pub` from `/tmp/janus-step-ca-bootstrap/step/certs/ssh_host_ca_key.pub`
## First boot checks
After switching Janus, verify the declarative bootstrap units instead of running
ad hoc issuance commands first:
```shell
systemctl status step-ca.service
systemctl status step-ssh-host-renew.service
systemctl status step-ssh-host-renew.timer
systemctl status mtls-bootstrap.service
ssh-host-cert-check
mtls-check
```
`step-ssh-host-renew.service` issues the SSH host certificate when it is missing
or expiring. `mtls-bootstrap.service` issues the first Janus mTLS bundle
only when the configured certificate files are absent or invalid; recurring mTLS
renewal remains handled by `mtls-renew.timer`.
## Back up the root CA key offline
+18 -1
View File
@@ -16,9 +16,20 @@ in
nixos.docker
nixos.login-text
nixos.mtls
({ lib, pkgs, ... }: {
({ config, lib, pkgs, ... }: {
networking.hostName = hostname;
loginText.extraServiceStatus = {
"Step-CA" = "step-ca";
};
sops.defaultSopsFile = ./secrets.yaml;
sops.secrets."janus/ssh_host_ed25519_key" = {
sopsFile = ./secrets.yaml;
owner = "root";
group = "root";
mode = "0600";
path = "/etc/ssh/ssh_host_ed25519_key";
restartUnits = [ "sshd.service" "step-ssh-host-renew.service" ];
};
step-ssh-host = {
hostname = hostname;
extraPrincipals = [
@@ -49,6 +60,12 @@ in
"${hostname}.john-stream.com"
"192.168.1.244"
];
bootstrap = {
enable = true;
after = [ "network-online.target" "sops-nix.service" "step-ca.service" ];
wants = [ "network-online.target" "step-ca.service" ];
provisionerPasswordFile = config.sops.secrets."janus/admin_jwk".path;
};
};
users.users."${username}" = {
+27 -18
View File
@@ -1,31 +1,40 @@
janus:
ca_password: ENC[AES256_GCM,data:GmuqoePDJd5Cn7+sWbvXPlGoUf3SVgYnHOoq9mnPWNWj,iv:VY/8olA+yu66wW+RbhD58GtA0YUWuAtm1HUXtNIIuLk=,tag:fCz3x6B4UPw+XpJE0F3t/g==,type:str]
intermediate_ca_crt: ENC[AES256_GCM,data:9W6QXGlcPdfas1ea6S3w1OYI/SeF6YDIdF5J7Hv4ype3,iv:H8oVfbMLBRvU0ywovVSB84g0q1wPhHEdzfpIVAY0Bkg=,tag:7/pU0N1SlfC1i8H2IgPGVA==,type:str]
intermediate_ca_key: ENC[AES256_GCM,data:wrzE8pgSZrtMA2jyLuVhMFxr47ICDBUkqOFyFJex9h3g,iv:I3Sz07e+cFDOUunCu7ZGcAjckRJDy3NmTXoB0vtA0HI=,tag:inANSVzyAzWlX4J5pheY8A==,type:str]
ssh_host_ca_key: ENC[AES256_GCM,data:IT3PcnbrexRDvxoVEyyxYBx7+Brm3SzVP3Yr2UGkFjeH,iv:edfMiRR+EVx9veTH+mEAFtEdC/rlW8uU5jMD8UX2Ylo=,tag:KKS/2GQE8V6obbjfMLspug==,type:str]
ssh_user_ca_key: ENC[AES256_GCM,data:w3gc97CYUODNxk0gIyghd9PCksDVvrdvl8j/NQe/tCtw,iv:WbWlVCPV7niVHEJ9sVcO9SAcLSfquE/bvcR5KClxszk=,tag:08uj+lv9MflYOtQh28K6pw==,type:str]
admin_provisioner_encrypted_key: ENC[AES256_GCM,data:/EdlRiPn/LHpQk5sA9VVYTz/U4i6Ag==,iv:r6Rm38L1R4xsnyR97e3J2FdH4KWYewZdADosv9WLAXw=,tag:MnWq9lI7GAXN0FmGTa9zmQ==,type:str]
ca_password: ENC[AES256_GCM,data:z6i0ELrn6XwVbJ7gP1GkpWe0Xw==,iv:3pZs/kzPp6bL8iv8jITZnAHuCycbSjhWswgzuJuLvjE=,tag:3gUftdyVXPVWe5dGxh9dvw==,type:str]
intermediate_ca_key: ENC[AES256_GCM,data:0QVk/coqb4xvf2vVhZS9wpo9qrGIcdwPRuHqORyJlUIC0VAwH8AoZNKM54uarwag7Wpb+ltv6vhi4frcp+RzGOCDQET28pSrgXBKw5B31vA4uNI6z8pbZbsUrIbVuQDCw/QAu2v+xuCyMyYK58r4hGaCR6RZu6LCZ9Gx7JgmBZWQcZ3Li5jxG+Cm0HM9AympBhCcIGyWfRXreTZkGlzOhknO/MsBhjTmEwpNi4XZdTlsh1dF2tJ3l9nd7B260yAAwcI8AFyCFSIxAZOxYiYWwqUOQu/tFnDA3Zl7Q2LCwCjrr4UyYd+f33R43BWJPD/8TkPOeoyC4RHGIcB11UyyPXGm6TISJITYd0Ii0t8jPkPPFT3bwx5BEadI+/ICYte8bb5d5uV0gbAZB1EpWmeOcEVOOIt076dr6Q==,iv:1ygRHwQfyAAhG7dmm6aB2b5VVDv6JY0/SJGyVVoVSiM=,tag:GTeWsVEE912GXXNjkK2dHw==,type:str]
ssh_host_ca_key: ENC[AES256_GCM,data:SA46pzoWXdCLRWPZEHS+n93rspAZ7OQEZTfGF8zNXtuP//522eEocq0CGmmi8AJdST0oWnwbx1EFTyDlz2T8/1eP6ucfNigY8BicLymfdMb57uqJs7lAqN3xakdiM+SqfQcEuGy+SjJjqTjfHw7eqUcFXAuqFcTu6roF+kbVQwqmJyFDhh844aB+YhwIjphBYStqYu6CXGrE4UZBdHtyTHT514I0C1FRXBB6T8QzHr006hh+F7TCCnjVEd+JFNVD9dbf+zfWSdmi5edVRO8/fOrsP0PlDz7LQLocc3YJO+SiLC0m47ihszJJFhE/rYdVfPiagw9L6OmXi2RfDOF1/w2+r2d7Ki9WcyHItnzNsb1FEOFvVICT304s/KbGkesQLpp2JDzyPV3xOIxo/mmSsrJ3kKCahJpxdQ==,iv:rl/Vms9w/8NzaNkSU8kgk2SRV/Ic0DYhdtgT1YFoeEw=,tag:H1GrRqh0CmPN+EfS7+zb3w==,type:str]
ssh_user_ca_key: ENC[AES256_GCM,data:jyMrso0G2KVfvn1/Qdl+A0dcQU9/CIrbj1F6hbPB3KWROiQghe/ytTdIsAgKYb+0xUfH27tVjufLY6zEqviPxczzfN9y0ldk+caxH96VEy7da4UdR9rJa7Rl1bvcobRcVIXUCK7rA1deHtJr2wq0KspK4id2QON3w6ggXvYRCe0IAo16p0eGQUMotHqMO+M0qWRkV8IqMKObmxZePK6SGNUEleUNXfSbL4mbN50MIZt9lVKOxOVNZK1BjETtei6IjeeFhPcitJFRjbQTwFB6+5NYqLhEDEZyO3vMZe3poEFtPw9TFlhtYIwtWlBhl+nbh3mAbDWDsHcTix5AL2VVPuDoNpnwCCE4OFzcXjBlBZr28x40o+ukrCxityCXf8M99K3NJVyqgVMdNzjcmViM6pc6wV20t8t/mQ==,iv:RLv3ltoa8Hl1x5Q5xD8xKvlluGd6VTTwxaiV8KhKJBk=,tag:owD14446JnZWznpY9cG2BA==,type:str]
admin_provisioner_encrypted_key: ENC[AES256_GCM,data:lv85vgW77Qiy4UOcHt5SaVvuGmBsDIdjTxbUUaxGeP2H+XghTVuHZ/oLNXaPYqBFKKFqctG7FeL3/Gnm5wD7DXn9z6Cwc9eSZjNHUEiYcQLThBHDUOArk/CHhWvuAhWbuT5yBwVJkUmBpO5ZR3slRAh8RS/EBhb3iOXGW63i2Bj1kFAyf4XucmWY8lkRRpIj11gkWA1gVZqJkQI5AJDf4ikvYI/5p33wtQFpClsKJsJRzaoCee+m3w2Obq8hYMdS1mGnXm1L3tbkAj0130dl4/eOvOZq7le2S5Fo0WAFFyk1nhNz771fRI3WtiA85IHC9Nd3v6jGek+PVclk0D4rfvlQTF1yOpemtcsX2BbAbbB2j7sJ5a5bM9O5lotBoFlGQP/co5EU7Ki0+sTYdQxsHVSQhvsCTv5IhSMSjrzkUVN0RV7EwSkKdLK65Q8bR1pmOdXVDAPsyjB4tUJlwWKisrkAc5oyM+mI0Lalz7nvbTYvgtiS0vcBnGmRwcxDC5q//BTm4RhAB2e/My0MadVxF3aEn6fMaoK+3ef1zAToBRG8bV1vC+trhz3ucPqvMNPxjrSQpOdxzJyqlPgfKFVRI/DEQhO0XWpnU3h7wA1mV/dyjeosFVydfKN5iOLl+xF3VgRpisV2oE0Fgas6JkOGwy/d+MpkgNz/Amr+9phZA/bXl2lpDYIKj6aBduqizI4jV1jpM2FHtv99Jkg24DKIcPSZVvzVYr6j8FMmgaxOsA3Z72GlleLdwBKtuBC0GvU=,iv:kcJEhE1d4vYLHk+W2ay2rid+KCuarAIxUZSl/0wi5W4=,tag:7VARgV4a1nT+beIYWelqyw==,type:str]
ssh_host_ed25519_key: ENC[AES256_GCM,data:HUL5aKHKXLet6Z1cZKsEgesjT/MoiF2y1MIIdD5YzvUyBBCUuoeZuEPs5TnQwthVITHFsiQP0CJQARLGX2QM0+GpMsw/C7pZYbkuBgVbo0U8qA9fixga3wqqk/2b8bgage8cwjCxg/WCLnfa97b7eeQabSU451VYk7nClLoR1Vh0BSGde0oaXMv0eYmgGPMJaD0JMCZX9wAgX2+VcW2MNP80vsyOJNp/GvkV+9RWa1h3DUh/bpywk3rFm4ftTCcpHjwXNyhXOXWXkheJtKWHAWkPSVzvygODVy45ZZqXke7mOsPZfANy4O2IILic6MLaU/TDQ7FbrNAKq9RPHLAMR8C7XFq7QV2MoLkpZ3fzJOn6tLzfgaGmvHBIekywsHJMgCv3MH1l1Uvw2LYY31dFN6z41I7Mj/bFZGZ4TWDQchuJSAF56aosXSw9DCiTY7gly13HkXNYbTrdKM3BjA5TNyYtJ+KMipket4hBUDAVVQ+fBbk087RFmcuzBSAuCnpkoRvPv767/ht8Q5YQpTY=,iv:m1ZKNB5Ab7pKywHQvxqElkCHokNZUZYbxfXXRF8bDD0=,tag:NPA7ErsJMMmff9XYHrUqbQ==,type:str]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCTEFTSy96QTluaWo0bURH
YlpFSWx3VnYvSHpDYU1PNmZrb0FuVHVTcG00CmJnT09OZEJad01OZk8zUzlPT1Ft
bVpHeW9UMXZYN1JJQVgrTk9OamhZeXcKLS0tIFhXMVNDQjhVcUp2aHhjenlGVW9z
cTNPalJOQVJNQWxzMnVMT1lYQS9SNW8KWapdX8dwxEvcqhKI8RJmCWRrV7sRmS72
sUt6HaUGpQfUQ97MtS01DYaSZjbAvj8t//mXhLubZddDTtTuhbpjAg==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCdFNwM2JQSVlFZXpJZ0VQ
eDFYek1TMnNTM3lLbjh4SG56VDl6MGsvb1drCnpLbGZ1N2FwSmRacitFVDJxaFlD
Vk5XeCtxRGM0OGV2UFNwQmlFV1pLS1EKLS0tIHB5bm1iRG9WWmpjTlBKcWkwWktz
SWVQOVlJU3RBejdpaGpyTTAzVmh0MFkK6nLey1QwSw5J8WeDdUIjJo62pylqOKmP
+GANs3OQkRuwW2+nwG+LJR2pfUS6O6PDOz7ZWGKih90/Bk7EMtjOIQ==
-----END AGE ENCRYPTED FILE-----
recipient: age1n94nkvmzezdj08t4dpwukd7s5k4cx2rldwvx9x5qhx3xhqm48yhq6ejldt
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyeE04L0RvYmlnaWpxUWhm
M01vQVc2bFdNZ2k4TjJBOWZDTlV1V2VkVGd3CnhseTlOZWtqbmpldXUrWnRxSWdR
dFFPTFZtSlZjZVdKMDdYQkVJeG5wZUUKLS0tIGxRbEE3ZjNrUTlLSDMrVVIzbnJ5
cEFnNmErTlhQMEtZM282STVRUGl0dVkKDyyTGVFDm6u+cdGu/PzFUFgj6ewCf0fi
vFhmYA8bTGNg6loThbYxm4uN1u+mJgwOKJ3FRR+u9yYEF6xvqrIbdw==
-----END AGE ENCRYPTED FILE-----
recipient: age1ykcs39e62pz3xu6cedg8ea685kv5d5qsrhgkndygzm8rx30xd5ys5t3qxt
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWcnVWS2lpMDM5VWxLWDIx
R0xWQ3ZOaEVFNTR5bXArNTc4MzFOM2xCQWhjCnBZeUtJQ3RvWDdZeVRpRzBPbTl2
M3FOTlRnTDhIaGg2VzM3MFVEc1FVUmcKLS0tIDAzeDlaZGt5Q2dJbzNWRVB4Qmwr
SWpxcytNdEFBQ2o0Q3lWSHZSNEdCUUEKgHE0j678WIhcQQsZQ5RcQNkQcP++ibvr
ZT3ScL4PAYhH+lxciJK4tit53taevbp5o0jFibaup4ByDvgj7HPclw==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjeHQ4aDlSU2M2bzE1ODFY
Ym1NNEFFaVhrVlR0WUs1ZUVwTFVGN3QwMFZJCmdJWWFaeHByUlB2U3FLbEpwRkJI
ckRTNWx2dVl3Mi9ZNjhwUFNTdjZIaTAKLS0tIDZ0dFpCa2F3aFRCYmh2N3Nwb2dF
ZUJxakM3Wnc3U3JwTklPcllydXZBUXcKlf9C2/Gb9H9PdEKHdAd48dsalm43vYIz
ODYoLItstmJvT/rZ+XijZFALNsRrIj5435CW6V34OsfCkFkjeHz17w==
-----END AGE ENCRYPTED FILE-----
recipient: age1gvplss0ddmyf6vpjy363wu3n057vhm0j6n7tc94cxd8kadapypws5mtaj0
lastmodified: "2026-07-04T04:20:22Z"
mac: ENC[AES256_GCM,data:N0+lAFghaas9fg9PMcsaMPKJbYmnSrgPIFPsEuC749nzMX1IfQVaTLYm6ziZiq8TpscGR1tBqzw2eXiSZG4XfI44naEzO8CvY87aaZAL19/c/j4XE8eBqv1RLsLMbBIYYs8h6aZXCwhWUa3bwFukjueD0iu2X3md6V4T+4RnYSE=,iv:Xd/DCSDymp+KURHjM3Cht8oqbyN/k5+eybdi3pK5eww=,tag:tmDJoo5RKXQTnVjqkXsZ1w==,type:str]
lastmodified: "2026-07-04T16:08:14Z"
mac: ENC[AES256_GCM,data:9cWYDRVot45SO79sR6ETDI2zwMW5EVH2k3pOObQ/FWPhkdZYBkox3uf2UXgzCo9An98ta3bmhcZnfv77jAnZOz088s4MWTvl89ViI0SFtPnm94Ml2NopxmKdRndja5Aj5AOD5MUrqofaSXIhOTxekp/goCGknd986zuUa7yVhhw=,iv:38ycphhpGYKHFhWTQeM5QlfzS8cnaC3Y5yAkO/kxfEk=,tag:ILOV+YyP3kA3e9FJmBC5Gw==,type:str]
unencrypted_suffix: _unencrypted
version: 3.13.1
+3 -1
View File
@@ -1,13 +1,14 @@
{ inputs, ... }:
let
userName = "john";
sshHostCAPubKey = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNug18oLH0vZxnibXJzMJvTWFPZTnSlhCDDVi+rHhgnIum6ZXQ4SF+VHOOAM5BbzZmMKitNJ5lcrGP15Eur7DzQ=";
sshHostCAPubKeyPath = ../hosts/janus/ssh_host_ca_key.pub;
in
{
flake.modules.nixos.ssh = { config, pkgs, lib, ... }:
let
cfg = config.ssh;
configDir = "/etc/ssh";
sshHostCAPubKey = lib.removeSuffix "\n" (builtins.readFile sshHostCAPubKeyPath);
in
{
options.ssh = {
@@ -72,6 +73,7 @@ in
identityFile = cfg.identityFile;
publicKeyFile = "${identityFile}.pub";
certificateFile = "${identityFile}-cert.pub";
sshHostCAPubKey = lib.removeSuffix "\n" (builtins.readFile sshHostCAPubKeyPath);
in
{
options.ssh = with lib; {
+39 -18
View File
@@ -11,6 +11,24 @@
cfg.hostname
"${cfg.hostname}.john-stream.com"
] ++ cfg.extraPrincipals);
sshHostCertRenew = pkgs.writeShellScriptBin "ssh-host-cert-renew" ''
set -euo pipefail
if [ ! -s "${sshKeyPath}.pub" ]; then
${lib.getExe' pkgs.openssh "ssh-keygen"} -y -f "${sshKeyPath}" > "${sshKeyPath}.pub"
chmod 0644 "${sshKeyPath}.pub"
fi
${lib.getExe pkgs.step-cli} ssh certificate \
--host --sign \
--provisioner "${cfg.provisioner}" \
--provisioner-password-file "${provisionerPasswordPath}" \
${principalArgs} \
"${cfg.hostname}" "${sshKeyPath}.pub"
'';
sshHostCertCheck = pkgs.writeShellScriptBin "ssh-host-cert-check" ''
${lib.getExe' pkgs.openssh "ssh-keygen"} -Lf ${sshCertPath}
'';
in
{
# NixOS Options
@@ -45,17 +63,9 @@
};
networking.nameservers = [ "192.168.1.150" ];
networking.dhcpcd.extraConfig = "nohook resolv.conf";
environment.systemPackages = with pkgs; [
# step-cli
(writeShellScriptBin "ssh-host-cert-renew" ''
${lib.getExe pkgs.step-cli} ssh certificate \
--host --sign \
--provisioner "${cfg.provisioner}" \
--provisioner-password-file "${provisionerPasswordPath}" \
${principalArgs} \
"${cfg.hostname}" "${sshKeyPath}.pub"
'')
(writeShellScriptBin "ssh-host-cert-check" "${lib.getExe' pkgs.openssh "ssh-keygen"} -Lf ${sshCertPath}")
environment.systemPackages = [
sshHostCertRenew
sshHostCertCheck
];
systemd.services.step-ssh-host-renew = {
@@ -63,7 +73,7 @@
wantedBy = [ ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
path = with pkgs; [ coreutils systemd step-cli openssh ];
path = with pkgs; [ coreutils systemd step-cli openssh ];
serviceConfig = {
Type = "oneshot";
User = "root";
@@ -71,8 +81,14 @@
};
script = ''
set -euo pipefail
if ${lib.getExe pkgs.step-cli} ssh needs-renewal "${sshCertPath}" --expires-in "4h"; then
echo "Renewing SSH host certificate"
renew=0
if [ ! -s "${sshCertPath}" ]; then
echo "SSH host cert missing: ${sshCertPath}"
renew=1
elif ${lib.getExe pkgs.step-cli} ssh needs-renewal "${sshCertPath}" --expires-in "4h"; then
echo "SSH host cert needs renewal"
renew=1
else
rc=$?
if [ "$rc" -eq 1 ]; then
@@ -81,12 +97,17 @@
fi
if [ "$rc" -eq 2 ]; then
echo "SSH host cert missing: ${sshCertPath}" >&2
exit 1
echo "SSH host cert missing or unreadable: ${sshCertPath}"
renew=1
else
echo "step ssh needs-renewal failed with rc=$rc" >&2
exit "$rc"
fi
fi
echo "step ssh needs-renewal failed with rc=$rc" >&2
exit "$rc"
if [ "$renew" -eq 1 ]; then
${lib.getExe sshHostCertRenew}
${lib.getExe sshHostCertCheck}
fi
'';
};