diff --git a/README.md b/README.md index ba5f775..85db63d 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,8 @@ deployments// # one folder per stack is, then run the scripts. - **Stand up a stack**: each deployment ships its own `cloud-init.yml` (e.g. [`deployments/pocket-id/cloud-init.yml`](deployments/pocket-id/cloud-init.yml)). + These assume a fresh VM, so they **harden SSH first** (`harden-ssh.sh`, + `HARDEN_SSH=1` by default) and then deploy the stack. Fill in `REPO_URL` and the values at the top of the `runcmd` block, paste as instance user-data, and the host configures itself on first boot. diff --git a/automations.sh b/automations.sh index b5ad668..1da8340 100644 --- a/automations.sh +++ b/automations.sh @@ -74,7 +74,9 @@ ask_deployment_vars() { case "$1" in pocket-id) ask POCKETID_DOMAIN "Public hostname (e.g. id.example.com)" - ask ACME_EMAIL "Let's Encrypt email" ;; + ask ACME_EMAIL "Let's Encrypt email" + ask BASE_DOMAIN "WebFinger base domain (blank = none / use webfinger deployment)" optional + if [[ -n "${BASE_DOMAIN:-}" ]]; then ask REDIRECT_URL "Redirect target for the base domain"; fi ;; beszel) ask BESZEL_DOMAIN "Public hostname (e.g. monitoring.example.com)" ask ACME_EMAIL "Let's Encrypt email" ;; diff --git a/deployments/beszel/cloud-init.yml b/deployments/beszel/cloud-init.yml index 39be8c5..b519f3d 100644 --- a/deployments/beszel/cloud-init.yml +++ b/deployments/beszel/cloud-init.yml @@ -1,6 +1,6 @@ #cloud-config # -# Beszel (monitoring hub) — unattended deploy on a fresh Alpine host. +# Beszel (monitoring hub) — harden SSH, then deploy, on a fresh Alpine host. # # Fill in REPO_URL and the values in the runcmd block, then paste this as the # instance user-data. DNS for BESZEL_DOMAIN must point at this host and ports @@ -16,15 +16,20 @@ runcmd: set -e REPO_URL=https://git.anomalous.dev/57_Wolve/automations.git REPO_BRANCH=main + HARDEN_SSH=1 # harden SSH on this fresh VM (set 0 to skip) + SSH_PORT=22 + ALLOWED_IP= # optional: whitelist your client IP in sshguard git clone --depth 1 --branch "$REPO_BRANCH" "$REPO_URL" /opt/automations cd /opt/automations - # Optional: install shared admin SSH keys from globals/ for root before deploy - # . scripts/lib.sh && load_globals \ - # && install -d -m700 /root/.ssh && resolve_ssh_keys >> /root/.ssh/authorized_keys - - # Optional: harden SSH first (prints a fresh root key — capture it!) - # SKIP_PROMPTS=1 FORCE=1 bash scripts/harden-ssh.sh + # Harden SSH on this fresh VM: PQ KEX, key-only auth, sshguard. Seeds root + # from globals/authorized_keys (or SSH_KEYS_URL). NOTE: harden-ssh also + # prints a generated root key to the serial console — capture it, or rely + # on the seeded keys. + if [ "$HARDEN_SSH" = 1 ]; then + SSH_PORT="$SSH_PORT" ALLOWED_IP="$ALLOWED_IP" SKIP_PROMPTS=1 FORCE=1 \ + bash scripts/harden-ssh.sh + fi BESZEL_DOMAIN=monitoring.example.com \ ACME_EMAIL=admin@example.com \ diff --git a/deployments/headscale/cloud-init.yml b/deployments/headscale/cloud-init.yml index 15f2d3e..9cc5e15 100644 --- a/deployments/headscale/cloud-init.yml +++ b/deployments/headscale/cloud-init.yml @@ -1,6 +1,6 @@ #cloud-config # -# Headscale (Tailscale control server) — unattended deploy on fresh Alpine. +# Headscale (Tailscale control server) — harden SSH, then deploy, on fresh Alpine. # # Fill in REPO_URL and the values in the runcmd block, then paste this as the # instance user-data. DNS for HEADSCALE_DOMAIN must point at this host and @@ -16,15 +16,20 @@ runcmd: set -e REPO_URL=https://git.anomalous.dev/57_Wolve/automations.git REPO_BRANCH=main + HARDEN_SSH=1 # harden SSH on this fresh VM (set 0 to skip) + SSH_PORT=22 + ALLOWED_IP= # optional: whitelist your client IP in sshguard git clone --depth 1 --branch "$REPO_BRANCH" "$REPO_URL" /opt/automations cd /opt/automations - # Optional: install shared admin SSH keys from globals/ for root before deploy - # . scripts/lib.sh && load_globals \ - # && install -d -m700 /root/.ssh && resolve_ssh_keys >> /root/.ssh/authorized_keys - - # Optional: harden SSH first (prints a fresh root key — capture it!) - # SKIP_PROMPTS=1 FORCE=1 bash scripts/harden-ssh.sh + # Harden SSH on this fresh VM: PQ KEX, key-only auth, sshguard. Seeds root + # from globals/authorized_keys (or SSH_KEYS_URL). NOTE: harden-ssh also + # prints a generated root key to the serial console — capture it, or rely + # on the seeded keys. + if [ "$HARDEN_SSH" = 1 ]; then + SSH_PORT="$SSH_PORT" ALLOWED_IP="$ALLOWED_IP" SKIP_PROMPTS=1 FORCE=1 \ + bash scripts/harden-ssh.sh + fi HEADSCALE_DOMAIN=hs.example.com \ ACME_EMAIL=admin@example.com \ diff --git a/deployments/headscale/config.yaml b/deployments/headscale/config.yaml index 8b5b1d4..c9e8849 100644 --- a/deployments/headscale/config.yaml +++ b/deployments/headscale/config.yaml @@ -1,8 +1,8 @@ # headscale config.yaml -- 0.28.0 # -# Placeholders __VAR__ are substituted at deploy time by deploy.sh from the -# values in .env. Don't set them here directly unless you're running -# headscale outside of the deploy script. +# Placeholder tokens (UPPER_SNAKE wrapped in double underscores) are +# substituted at deploy time by deploy.sh from the values in .env. Don't set +# them here directly unless you're running headscale outside the deploy script. server_url: https://__HEADSCALE_DOMAIN__ listen_addr: 0.0.0.0:8080 @@ -56,6 +56,7 @@ dns: - 1.0.0.1 - 2606:4700:4700::1111 - 2606:4700:4700::1001 + split: {} search_domains: [] extra_records: [] @@ -80,6 +81,8 @@ oidc: # - tailscale-admins # allowed_domains: # - example.com - expiry: - from_authentication_path: false - refresh_period: 180d + # How long a node stays authenticated before re-auth is required. This is a + # duration scalar (not a map). use_expiry_from_token overrides it with the + # OIDC token's own expiry when true. + expiry: 180d + use_expiry_from_token: false diff --git a/deployments/headscale/deploy.sh b/deployments/headscale/deploy.sh index f4761df..a802b62 100644 --- a/deployments/headscale/deploy.sh +++ b/deployments/headscale/deploy.sh @@ -225,8 +225,9 @@ sed -i \ -e "s/__OIDC_CLIENT_SECRET__/$(sed_escape "$OIDC_CLIENT_SECRET")/g" \ "$STACK_DIR/config.yaml" -# Final sanity check -grep -q '__.*__' "$STACK_DIR/config.yaml" \ +# Final sanity check (ignore comment lines so documentation tokens don't +# trip a false positive). +grep -v '^[[:space:]]*#' "$STACK_DIR/config.yaml" | grep -q '__[A-Za-z_]*__' \ && warn "config.yaml still has un-substituted __PLACEHOLDER__ markers!" \ || true @@ -312,55 +313,57 @@ EOF # __ARCHIVE_BELOW__ marker is the embedded tar.gz payload (base64). exit 0 __ARCHIVE_BELOW__ -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 diff --git a/deployments/pocket-id/.env.example b/deployments/pocket-id/.env.example index 00deafb..de54a87 100644 --- a/deployments/pocket-id/.env.example +++ b/deployments/pocket-id/.env.example @@ -4,10 +4,10 @@ # ─── Public hostname ──────────────────────────────────────────────────────── # Bare hostname (no scheme) of the pocket-id deployment. Used by Caddy for # TLS issuance, by anubis as the cookie domain, and to derive APP_URL. -POCKETID_DOMAIN=id.example.com +POCKETID_DOMAIN= # Email for Let's Encrypt registration / expiry notifications. -ACME_EMAIL=admin@example.com +ACME_EMAIL= # ─── Pocket-ID ────────────────────────────────────────────────────────────── # APP_URL is set automatically from POCKETID_DOMAIN in compose; no need to @@ -37,8 +37,19 @@ PGID=1000 # Only needed while the anubis-pid service is enabled in compose. ANUBIS_PID_KEY= +# ─── WebFinger (optional) ─────────────────────────────────────────────────── +# Leave BOTH blank unless the base domain has no other web server and you want +# THIS Caddy to also serve /.well-known/webfinger (OIDC discovery for Tailscale +# et al.) at the apex. When set, deploy.sh appends a second site block. +# Otherwise use the dedicated `webfinger` deployment. +# BASE_DOMAIN apex to serve webfinger from (e.g. example.com) +# REDIRECT_URL where non-webfinger requests to the apex are 301'd +# If you set one you must set both. +BASE_DOMAIN= +REDIRECT_URL= + # ─── Image tags ───────────────────────────────────────────────────────────── # Pin for reproducible deploys. -POCKETID_TAG=v2 +POCKETID_TAG=v2.8.0 CADDY_TAG=2-alpine ANUBIS_TAG=latest diff --git a/deployments/pocket-id/Caddyfile b/deployments/pocket-id/Caddyfile index 6b31ddf..b54b9be 100644 --- a/deployments/pocket-id/Caddyfile +++ b/deployments/pocket-id/Caddyfile @@ -1,9 +1,11 @@ -# Caddyfile for pocket-id stack. +# Caddyfile for pocket-id stack (base). # -# Two site blocks: # $POCKETID_DOMAIN pocket-id (via anubis PoW gate) -# $BASE_DOMAIN /.well-known/webfinger for OIDC discovery; everything -# else redirects to $REDIRECT_URL +# +# An optional WebFinger site block (Caddyfile.webfinger) is appended by +# deploy.sh when BASE_DOMAIN is set -- for the case where the base domain has +# no other web server and you want this Caddy to serve OIDC discovery too. +# Otherwise use the dedicated `webfinger` deployment. { email {$ACME_EMAIL} @@ -38,44 +40,3 @@ format console } } - -# ---------------------------------------------------------------------------- -# Base domain: WebFinger for OIDC discovery + redirect everything else. -# Required for Tailscale's custom-OIDC setup -- the email's domain must -# serve /.well-known/webfinger pointing at the issuer. -# ---------------------------------------------------------------------------- -{$BASE_DOMAIN} { - encode zstd gzip - - @webfinger path /.well-known/webfinger - handle @webfinger { - header Content-Type "application/jrd+json" - header Cache-Control "public, max-age=3600" - templates - respond `{ - "subject": "{{.Req.URL.Query.Get "resource"}}", - "links": [ - { - "rel": "http://openid.net/specs/connect/1.0/issuer", - "href": "https://{$POCKETID_DOMAIN}" - } - ] -}` 200 - } - - handle { - redir {$REDIRECT_URL}{uri} permanent - } - - header { - Strict-Transport-Security "max-age=31536000; includeSubDomains" - X-Content-Type-Options "nosniff" - Referrer-Policy "strict-origin-when-cross-origin" - -Server - } - - log { - output stdout - format console - } -} diff --git a/deployments/pocket-id/Caddyfile.webfinger b/deployments/pocket-id/Caddyfile.webfinger new file mode 100644 index 0000000..c104c59 --- /dev/null +++ b/deployments/pocket-id/Caddyfile.webfinger @@ -0,0 +1,42 @@ + +# ---------------------------------------------------------------------------- +# OPTIONAL: appended to the deployed Caddyfile by deploy.sh when BASE_DOMAIN is +# set. Serves /.well-known/webfinger at the base domain (so Tailscale et al. +# can discover the OIDC issuer) and redirects everything else to $REDIRECT_URL. +# Needs BASE_DOMAIN + REDIRECT_URL in .env (passed to caddy via compose). +# ---------------------------------------------------------------------------- +{$BASE_DOMAIN} { + encode zstd gzip + + @webfinger path /.well-known/webfinger + handle @webfinger { + header Content-Type "application/jrd+json" + header Cache-Control "public, max-age=3600" + templates + respond `{ + "subject": "{{.Req.URL.Query.Get "resource"}}", + "links": [ + { + "rel": "http://openid.net/specs/connect/1.0/issuer", + "href": "https://{$POCKETID_DOMAIN}" + } + ] +}` 200 + } + + handle { + redir {$REDIRECT_URL}{uri} permanent + } + + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains" + X-Content-Type-Options "nosniff" + Referrer-Policy "strict-origin-when-cross-origin" + -Server + } + + log { + output stdout + format console + } +} diff --git a/deployments/pocket-id/README.md b/deployments/pocket-id/README.md index 8d98c0c..8c39475 100644 --- a/deployments/pocket-id/README.md +++ b/deployments/pocket-id/README.md @@ -16,6 +16,11 @@ identity provider for the other stacks (headscale, beszel, webfinger). `MAXMIND_LICENSE_KEY` (audit-log geolocation) is optional. See [`.env.example`](.env.example) for the full list. +**Optional WebFinger:** set `BASE_DOMAIN` (and `REDIRECT_URL`) to have this +Caddy also serve `/.well-known/webfinger` at the apex — useful when the base +domain has no other web server. Leave both blank to run pocket-id only and use +the dedicated [`webfinger`](../webfinger/) deployment instead. + ## Deploy From the repo root via the launcher: diff --git a/deployments/pocket-id/build.sh b/deployments/pocket-id/build.sh index 650fb83..8560ae8 100644 --- a/deployments/pocket-id/build.sh +++ b/deployments/pocket-id/build.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash # -# build.sh -- (re)embed docker-compose.yml, Caddyfile, .env.example into -# deploy.sh as a base64-encoded tar.gz payload after the __ARCHIVE_BELOW__ -# marker. Idempotent: strips any existing payload first. +# build.sh -- (re)embed docker-compose.yml, Caddyfile, Caddyfile.webfinger and +# .env.example into deploy.sh as a base64-encoded tar.gz payload after the +# __ARCHIVE_BELOW__ marker. Idempotent: strips any existing payload first. # # Run after editing any of the loose files. The resulting deploy.sh is # self-contained and can be scp'd to the target box on its own. @@ -14,13 +14,13 @@ SCRIPT="$DIR/deploy.sh" MARKER="__ARCHIVE_BELOW__" [[ -f "$SCRIPT" ]] || { echo "deploy.sh not found at $SCRIPT" >&2; exit 1; } -for f in docker-compose.yml Caddyfile .env.example; do +for f in docker-compose.yml Caddyfile Caddyfile.webfinger .env.example; do [[ -f "$DIR/$f" ]] || { echo "Missing $DIR/$f" >&2; exit 1; } done # tar -> gzip -> base64. Files only (no leading ./), wrapped at 76 cols # so the embedded blob is git-friendly. -PAYLOAD=$(tar -czf - -C "$DIR" docker-compose.yml Caddyfile .env.example | base64) +PAYLOAD=$(tar -czf - -C "$DIR" docker-compose.yml Caddyfile Caddyfile.webfinger .env.example | base64) # Strip any existing payload (everything from MARKER to EOF), then append a # fresh one. If MARKER isn't present, sed leaves the file unchanged. diff --git a/deployments/pocket-id/cloud-init.yml b/deployments/pocket-id/cloud-init.yml index 0969abe..be6b15b 100644 --- a/deployments/pocket-id/cloud-init.yml +++ b/deployments/pocket-id/cloud-init.yml @@ -1,6 +1,6 @@ #cloud-config # -# Pocket-ID (OIDC provider) — unattended deploy on a fresh Alpine host. +# Pocket-ID (OIDC provider) — harden SSH, then deploy, on a fresh Alpine host. # # Fill in REPO_URL and the values in the runcmd block, then paste this as the # instance user-data. DNS for POCKETID_DOMAIN must point at this host and @@ -15,15 +15,20 @@ runcmd: set -e REPO_URL=https://git.anomalous.dev/57_Wolve/automations.git REPO_BRANCH=main + HARDEN_SSH=1 # harden SSH on this fresh VM (set 0 to skip) + SSH_PORT=22 + ALLOWED_IP= # optional: whitelist your client IP in sshguard git clone --depth 1 --branch "$REPO_BRANCH" "$REPO_URL" /opt/automations cd /opt/automations - # Optional: install shared admin SSH keys from globals/ for root before deploy - # . scripts/lib.sh && load_globals \ - # && install -d -m700 /root/.ssh && resolve_ssh_keys >> /root/.ssh/authorized_keys - - # Optional: harden SSH first (prints a fresh root key — capture it!) - # SKIP_PROMPTS=1 FORCE=1 bash scripts/harden-ssh.sh + # Harden SSH on this fresh VM: PQ KEX, key-only auth, sshguard. Seeds root + # from globals/authorized_keys (or SSH_KEYS_URL). NOTE: harden-ssh also + # prints a generated root key to the serial console — capture it, or rely + # on the seeded keys. + if [ "$HARDEN_SSH" = 1 ]; then + SSH_PORT="$SSH_PORT" ALLOWED_IP="$ALLOWED_IP" SKIP_PROMPTS=1 FORCE=1 \ + bash scripts/harden-ssh.sh + fi POCKETID_DOMAIN=id.example.com \ ACME_EMAIL=admin@example.com \ diff --git a/deployments/pocket-id/deploy.sh b/deployments/pocket-id/deploy.sh index 8d7c115..77a902c 100644 --- a/deployments/pocket-id/deploy.sh +++ b/deployments/pocket-id/deploy.sh @@ -12,10 +12,11 @@ # 5. Enables docker on boot, runs `docker compose pull && up -d`. # 6. Waits for healthchecks to go green. # -# Self-contained: docker-compose.yml, Caddyfile, and .env.example are -# embedded at the bottom of this file as a base64-encoded tar.gz. The -# script extracts them at runtime, so this single file is all you need -# on the target box. +# Self-contained: docker-compose.yml, Caddyfile, Caddyfile.webfinger and +# .env.example are embedded at the bottom of this file as a base64-encoded +# tar.gz. The script extracts them at runtime, so this single file is all you +# need on the target box. The WebFinger block is appended only when BASE_DOMAIN +# is set in .env. # # To rebuild after editing the loose files: run ./build.sh in this dir. # @@ -39,6 +40,8 @@ set -euo pipefail [[ "$SKIP_PROMPTS" == "1" ]] && FORCE=1 : "${POCKETID_DOMAIN:=}" : "${ACME_EMAIL:=}" +: "${BASE_DOMAIN:=}" # optional: enables the WebFinger block (set with REDIRECT_URL) +: "${REDIRECT_URL:=}" # optional: where the base domain 301s non-webfinger traffic log() { printf '\033[1;32m[+]\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[!]\033[0m %s\n' "$*" >&2; } @@ -121,7 +124,7 @@ else die "No embedded archive found. Run build.sh to embed deployment files." fi -for f in docker-compose.yml Caddyfile .env.example; do +for f in docker-compose.yml Caddyfile Caddyfile.webfinger .env.example; do [[ -f "$SCRIPT_DIR/$f" ]] || die "Embedded archive missing $f" done @@ -154,7 +157,8 @@ open_web_ports log "Setting up $STACK_DIR..." install -d -m 0750 "$STACK_DIR" install -m 0640 "$SCRIPT_DIR/docker-compose.yml" "$STACK_DIR/docker-compose.yml" -install -m 0640 "$SCRIPT_DIR/Caddyfile" "$STACK_DIR/Caddyfile" +# (Caddyfile is assembled below, after .env, so the optional WebFinger block +# can be appended when BASE_DOMAIN is set.) ENV_FILE="$STACK_DIR/.env" if [[ ! -f "$ENV_FILE" ]]; then @@ -165,6 +169,8 @@ if [[ ! -f "$ENV_FILE" ]]; then -e "s|^ACME_EMAIL=.*|ACME_EMAIL=${ACME_EMAIL}|" \ -e "s|^ENCRYPTION_KEY=.*|ENCRYPTION_KEY=$(openssl rand -base64 32)|" \ -e "s|^ANUBIS_PID_KEY=.*|ANUBIS_PID_KEY=$(openssl rand -hex 32)|" \ + -e "s|^BASE_DOMAIN=.*|BASE_DOMAIN=${BASE_DOMAIN}|" \ + -e "s|^REDIRECT_URL=.*|REDIRECT_URL=${REDIRECT_URL}|" \ "$ENV_FILE" else log ".env exists; leaving secrets alone." @@ -177,6 +183,20 @@ for var in POCKETID_DOMAIN ACME_EMAIL ENCRYPTION_KEY ANUBIS_PID_KEY; do done (( ${#missing[@]} == 0 )) || die "Missing values in $ENV_FILE: ${missing[*]}" +# Assemble the deployed Caddyfile each run: base (pocket-id) + the optional +# WebFinger block when BASE_DOMAIN is set in .env. Regenerated every run so +# edits to .env propagate. +install -m 0640 "$SCRIPT_DIR/Caddyfile" "$STACK_DIR/Caddyfile" +bd="$(sed -n 's/^BASE_DOMAIN=//p' "$ENV_FILE")" +ru="$(sed -n 's/^REDIRECT_URL=//p' "$ENV_FILE")" +if [[ -n "$bd" ]]; then + [[ -n "$ru" ]] || die "BASE_DOMAIN is set but REDIRECT_URL is empty in $ENV_FILE; set both or neither." + log "WebFinger enabled -- serving /.well-known/webfinger at ${bd}." + cat "$SCRIPT_DIR/Caddyfile.webfinger" >> "$STACK_DIR/Caddyfile" +else + log "No BASE_DOMAIN -- pocket-id only (use the webfinger deployment for OIDC discovery)." +fi + # ---------------------------------------------------------------------------- # Bring up the stack # ---------------------------------------------------------------------------- @@ -243,50 +263,62 @@ EOF # __ARCHIVE_BELOW__ marker is the embedded tar.gz payload (base64). exit 0 __ARCHIVE_BELOW__ -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 -2B7bY/v29m9v1GZbACgAAA== +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 diff --git a/deployments/pocket-id/docker-compose.yml b/deployments/pocket-id/docker-compose.yml index fec7ad9..8cc1732 100644 --- a/deployments/pocket-id/docker-compose.yml +++ b/deployments/pocket-id/docker-compose.yml @@ -35,6 +35,10 @@ services: environment: POCKETID_DOMAIN: "${POCKETID_DOMAIN}" ACME_EMAIL: "${ACME_EMAIL}" + # Only used by the optional WebFinger block (appended by deploy.sh when + # BASE_DOMAIN is set). Empty otherwise -- harmless. + BASE_DOMAIN: "${BASE_DOMAIN:-}" + REDIRECT_URL: "${REDIRECT_URL:-}" depends_on: - pocket-id healthcheck: diff --git a/deployments/webfinger/cloud-init.yml b/deployments/webfinger/cloud-init.yml index 80d48fe..7267c39 100644 --- a/deployments/webfinger/cloud-init.yml +++ b/deployments/webfinger/cloud-init.yml @@ -1,6 +1,6 @@ #cloud-config # -# WebFinger + redirect — unattended deploy on a fresh Alpine host. +# WebFinger + redirect — harden SSH, then deploy, on a fresh Alpine host. # # Fill in REPO_URL and the values in the runcmd block, then paste this as the # instance user-data. DNS for BASE_DOMAIN must point at this host and ports @@ -15,15 +15,20 @@ runcmd: set -e REPO_URL=https://git.anomalous.dev/57_Wolve/automations.git REPO_BRANCH=main + HARDEN_SSH=1 # harden SSH on this fresh VM (set 0 to skip) + SSH_PORT=22 + ALLOWED_IP= # optional: whitelist your client IP in sshguard git clone --depth 1 --branch "$REPO_BRANCH" "$REPO_URL" /opt/automations cd /opt/automations - # Optional: install shared admin SSH keys from globals/ for root before deploy - # . scripts/lib.sh && load_globals \ - # && install -d -m700 /root/.ssh && resolve_ssh_keys >> /root/.ssh/authorized_keys - - # Optional: harden SSH first (prints a fresh root key — capture it!) - # SKIP_PROMPTS=1 FORCE=1 bash scripts/harden-ssh.sh + # Harden SSH on this fresh VM: PQ KEX, key-only auth, sshguard. Seeds root + # from globals/authorized_keys (or SSH_KEYS_URL). NOTE: harden-ssh also + # prints a generated root key to the serial console — capture it, or rely + # on the seeded keys. + if [ "$HARDEN_SSH" = 1 ]; then + SSH_PORT="$SSH_PORT" ALLOWED_IP="$ALLOWED_IP" SKIP_PROMPTS=1 FORCE=1 \ + bash scripts/harden-ssh.sh + fi BASE_DOMAIN=example.com \ ISSUER_URL=https://auth.example.com \