From 55eb37bb470fd6816d06cd1d801187781c21fda4 Mon Sep 17 00:00:00 2001
From: DS <commits@sidhion.com>
Date: Mon, 31 Mar 2025 21:30:46 -0700
Subject: [PATCH] Configure host to use SSH certs on the host and client side.

---
 README.md                                     | 43 +++++++++++++++++
 flake.nix                                     |  2 +-
 host_config/bootstrap_host.sh                 | 47 +++++++++++++++++++
 .../code_server_disk.nix                      |  0
 code_server.nix => host_config/default.nix    |  4 ++
 host_config/sign_user_pub.sh                  | 25 ++++++++++
 host_config/ssh_certs/.gitignore              |  2 +
 host_config/ssh_certs/host_ca.pub             |  1 +
 host_config/ssh_certs/user_ca.pub             |  1 +
 justfile                                      |  7 ++-
 10 files changed, 129 insertions(+), 3 deletions(-)
 create mode 100755 host_config/bootstrap_host.sh
 rename code_server_disk.nix => host_config/code_server_disk.nix (100%)
 rename code_server.nix => host_config/default.nix (96%)
 create mode 100755 host_config/sign_user_pub.sh
 create mode 100644 host_config/ssh_certs/.gitignore
 create mode 100644 host_config/ssh_certs/host_ca.pub
 create mode 100644 host_config/ssh_certs/user_ca.pub

diff --git a/README.md b/README.md
index 39b3ba0..c8f495e 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,41 @@ There's already a `.vscode` directory which should direct VSCode to enable the D
 
 # Infrastructure
 
+## Setting up SSH access
+
+You'll need to trust the SSH certificate authority that generates SSH keys for Epesooj's hosts.
+The CA's public key is in `./host_config/ssh_certs/host_ca.pub`.
+
+This is the template for the SSH `known_hosts` entry:
+
+```
+@cert-authority <dns name or ip address> <CONTENTS OF host_ca.pub>
+```
+
+For example:
+
+```
+@cert-authority code.akols.com ssh-ed25519 AAAA...
+```
+
+## Signing a user's public SSH key to give them host access
+
+Run `just sign_user_key <username> <user_pub_key_path>`.
+This will by default give them `root` access.
+Check the definition of this `just` command to see how to give them access to different user(s).
+
+Once this is done, give them the signed public key (it'll be a file in the same directory as `<user_pub_key_path>` with the `-cert.pub` suffix) and tell them to add the `CertificateFile` option to their SSH config to make sure it'll also present the signed public key.
+For example:
+
+```
+Host epesooj
+  User root
+  HostName code.akols.com
+  IdentityFile ~/.ssh/epesooj_personal.pub
+  CertificateFile ~/.ssh/epesooj_personal-cert.pub
+  IdentitiesOnly yes
+```
+
 ## Nixifying a new host
 
 If you have a bunch of SSH keys in your SSH agent and get errors when trying to SSH into a fresh host, you may need to temporarily add the following config to your SSH config (obviously change the details for your case).
@@ -50,7 +85,15 @@ Host 188.245.194.78
   IdentitiesOnly yes
 ```
 
+Once you can SSH into the host normally, run `just nixify_host <hostname> "code" "<dns name>,<ip address>"`.
+For example: `just nixify_host epesooj-code-0001 code "code.akols.com,188.245.194.78"`.
+
+This command requires you to have the key for the Epesooj Host SSH certificate authority.
+If you don't have it, contact someone who does.
+
 ## Deploying the webring
 
 You should have a `.env` file with the id and deploy key for each script in the webring, as well as a key to deploy the index page to bunny.
 When you have this, run `deno task deploy`.
+You'll need the API keys required to deploy these.
+If you don't have them, contact someone who does.
diff --git a/flake.nix b/flake.nix
index 784d0dc..d8ec428 100644
--- a/flake.nix
+++ b/flake.nix
@@ -21,7 +21,7 @@
       systems = [ "x86_64-linux" ];
 
       imports = [
-        ./code_server.nix
+        ./host_config
       ];
 
       flake =
diff --git a/host_config/bootstrap_host.sh b/host_config/bootstrap_host.sh
new file mode 100755
index 0000000..cbf2341
--- /dev/null
+++ b/host_config/bootstrap_host.sh
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+
+set -euxo pipefail
+
+SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
+host_ca_key="${SCRIPT_DIR}/ssh_certs/host_ca"
+user_ca_pub="${SCRIPT_DIR}/ssh_certs/user_ca.pub"
+
+if [ ! -f "${host_ca_key}" ]
+then
+  echo "Host CA key not found."
+  exit 1
+fi
+
+if [ ! -f "${user_ca_pub}" ]
+then
+  echo "Public User CA key not found."
+  exit 1
+fi
+
+temp=$(mktemp -d)
+
+cleanup() {
+  rm -rf "${temp}"
+}
+# trap cleanup EXIT
+
+host_type=$1
+hostname=$2
+extra_names=${3:-}
+
+principal_names="${hostname}"
+
+if [ ! -z "${extra_names}" ]
+then
+    principal_names="${principal_names},${extra_names}"
+fi
+
+install -d -m755 "${temp}/persisted/etc/ssh"
+ssh-keygen -t ed25519 -f "${temp}/persisted/etc/ssh/ssh_host_ed25519_key" -C '' -N ''
+ssh-keygen -s ${host_ca_key} -I ${hostname} -h -n "${principal_names}" -V +52w "${temp}/persisted/etc/ssh/ssh_host_ed25519_key.pub"
+
+cp "${user_ca_pub}" "${temp}/persisted/etc/ssh/user_cas.pub"
+
+echo "${temp}"
+
+#nix run github:nix-community/nixos-anywhere -- --extra-files "${temp}" --flake .#${host_type} --target-host root@${hostname}
diff --git a/code_server_disk.nix b/host_config/code_server_disk.nix
similarity index 100%
rename from code_server_disk.nix
rename to host_config/code_server_disk.nix
diff --git a/code_server.nix b/host_config/default.nix
similarity index 96%
rename from code_server.nix
rename to host_config/default.nix
index 889ed5e..ea9b4e5 100644
--- a/code_server.nix
+++ b/host_config/default.nix
@@ -96,6 +96,10 @@
         settings = {
           PasswordAuthentication = false;
         };
+        extraConfig = ''
+          HostCertificate /persisted/etc/ssh/ssh_host_ed25519_key-cert.pub
+          TrustedUserCAKeys /persisted/etc/ssh/user_cas.pub
+        '';
       };
 
       users.users.root = {
diff --git a/host_config/sign_user_pub.sh b/host_config/sign_user_pub.sh
new file mode 100755
index 0000000..edf614d
--- /dev/null
+++ b/host_config/sign_user_pub.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+set -euxo pipefail
+
+SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
+user_ca_key="${SCRIPT_DIR}/ssh_certs/user_ca"
+
+if [ ! -f "${user_ca_key}" ]
+then
+  echo "User CA key not found."
+  exit 1
+fi
+
+username=$1
+principals=$2
+user_pub=$3
+
+if [ ! -f "${user_pub}" ]
+then
+    echo "User public key not found."
+    exit 1
+fi
+
+ssh-keygen -s "${user_ca_key}" -I "${username}" -n "${principals}" -V +52w "${user_pub}"
+echo "Done!"
diff --git a/host_config/ssh_certs/.gitignore b/host_config/ssh_certs/.gitignore
new file mode 100644
index 0000000..b8718ec
--- /dev/null
+++ b/host_config/ssh_certs/.gitignore
@@ -0,0 +1,2 @@
+host_ca
+user_ca
diff --git a/host_config/ssh_certs/host_ca.pub b/host_config/ssh_certs/host_ca.pub
new file mode 100644
index 0000000..1714776
--- /dev/null
+++ b/host_config/ssh_certs/host_ca.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJygYxMUdGgApUE3KirRQVgG2X5zWurIBPbwEc10FxDi epesooj host ca
diff --git a/host_config/ssh_certs/user_ca.pub b/host_config/ssh_certs/user_ca.pub
new file mode 100644
index 0000000..248ea65
--- /dev/null
+++ b/host_config/ssh_certs/user_ca.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINdiqYdA3pm9yKgR5hFlL7ZeSV3xeKH9HwyNwaxY6yZZ epesooj user ca
diff --git a/justfile b/justfile
index bd926c2..1fcde48 100644
--- a/justfile
+++ b/justfile
@@ -4,5 +4,8 @@ default:
 build_nixos_config host_type="code":
   nix build .#nixosConfigurations.{{host_type}}.config.system.build.toplevel
 
-nixify_host hostname host_type="code":
-  nix run github:nix-community/nixos-anywhere -- --flake .#{{host_type}} --target-host root@{{hostname}}
+nixify_host hostname host_type="code" extra_names="":
+  ./host_config/bootstrap_host.sh {{host_type}} {{hostname}} {{extra_names}}
+
+sign_user_key username user_pub_key principals="root":
+  ./host_config/sign_user_pub.sh {{username}} {{principals}} {{user_pub_key}}
-- 
2.48.1