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 +``` + +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 `. +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 `` 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 "code" ","`. +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.lock b/flake.lock index 32a38dd..ec46993 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1739841949, - "narHash": "sha256-lSOXdgW/1zi/SSu7xp71v+55D5Egz8ACv0STkj7fhbs=", + "lastModified": 1750903843, + "narHash": "sha256-Ng9+f0H5/dW+mq/XOKvB9uwvGbsuiiO6HrPdAcVglCs=", "owner": "nix-community", "repo": "disko", - "rev": "15dbf8cebd8e2655a883b74547108e089f051bf0", + "rev": "83c4da299c1d7d300f8c6fd3a72ac46cb0d59aae", "type": "github" }, "original": { @@ -25,11 +25,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1738453229, - "narHash": "sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm+zmZ7vxbJdo=", + "lastModified": 1749398372, + "narHash": "sha256-tYBdgS56eXYaWVW3fsnPQ/nFlgWi/Z2Ymhyu21zVM98=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "32ea77a06711b758da0ad9bd6a844c5740a87abd", + "rev": "9305fe4e5c2a6fcf5ba6a3ff155720fbe4076569", "type": "github" }, "original": { @@ -38,38 +38,18 @@ "type": "github" } }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "nil": { "inputs": { - "flake-utils": "flake-utils", "nixpkgs": [ "nixpkgs" - ], - "rust-overlay": "rust-overlay" + ] }, "locked": { - "lastModified": 1732053863, - "narHash": "sha256-DCIVdlb81Fct2uwzbtnawLBC/U03U2hqx8trqTJB7WA=", + "lastModified": 1751341694, + "narHash": "sha256-zXag1+8iZC3H5yVFP7KhIi4ps9z8xKrFIkyaeXlZ7Uo=", "owner": "oxalica", "repo": "nil", - "rev": "2e24c9834e3bb5aa2a3701d3713b43a6fb106362", + "rev": "b043bfe1f3f4c4be4b688e24c5ae96e81f525805", "type": "github" }, "original": { @@ -80,11 +60,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1740126099, - "narHash": "sha256-ozoOtE2hGsqh4XkTJFsrTkNxkRgShxpQxDynaPZUGxk=", + "lastModified": 1751271578, + "narHash": "sha256-P/SQmKDu06x8yv7i0s8bvnnuJYkxVGBWLWHaU+tt4YY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "32fb99ba93fea2798be0e997ea331dd78167f814", + "rev": "3016b4b15d13f3089db8a41ef937b13a9e33a8df", "type": "github" }, "original": { @@ -96,14 +76,17 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1738452942, - "narHash": "sha256-vJzFZGaCpnmo7I6i416HaBLpC+hvcURh/BQwROcGIp8=", - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz" + "lastModified": 1748740939, + "narHash": "sha256-rQaysilft1aVMwF14xIdGS3sj1yHlI6oKQNBRTF40cc=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "656a64127e9d791a334452c6b6606d17539476e2", + "type": "github" }, "original": { - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz" + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" } }, "root": { @@ -113,42 +96,6 @@ "nil": "nil", "nixpkgs": "nixpkgs" } - }, - "rust-overlay": { - "inputs": { - "nixpkgs": [ - "nil", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1731983527, - "narHash": "sha256-JECaBgC0pQ91Hq3W4unH6K9to8s2Zl2sPNu7bLOv4ek=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "71287228d96e9568e1e70c6bbfa3f992d145947b", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "type": "github" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", 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}} diff --git a/scripts/bunny_api/main.ts b/scripts/bunny_api/main.ts index 0603140..1d6133c 100644 --- a/scripts/bunny_api/main.ts +++ b/scripts/bunny_api/main.ts @@ -90,21 +90,21 @@ export async function uploadFile(filepath: string, contents: Uint8Array) { } } -export async function purgePath(filepath: string) { - const cdnBaseUrlEnvName = 'BUNNY_CDN_BASE_URL'; - const cdnBaseUrl = Deno.env.get(cdnBaseUrlEnvName); +export async function purgeCDNCache() { + const pullZoneIdEnvName = 'BUNNY_PULL_ZONE_ID'; + const pullZoneId = Deno.env.get(pullZoneIdEnvName); const accessKeyEnvName = 'BUNNY_ACCESS_KEY'; const accessKey = Deno.env.get(accessKeyEnvName); - if (cdnBaseUrl === undefined) { + if (pullZoneId === undefined) { throw new Error( - `Can't purge cache for '${filepath}' because we don't know the CDN base URL. Please set it by setting the environment variable '${cdnBaseUrlEnvName}'.` + `Can't purge CDN cache for because we don't know the pull zone ID. Please set it by setting the environment variable '${pullZoneIdEnvName}'.` ); } if (accessKey === undefined) { throw new Error( - `Can't purge cache for '${filepath}' because we don't have an API key. Please set it by setting the environment variable '${accessKeyEnvName}'.` + `Can't purge CDN cache because we don't have an API key. Please set it by setting the environment variable '${accessKeyEnvName}'.` ); } @@ -114,9 +114,7 @@ export async function purgePath(filepath: string) { accesskey: accessKey, }; - const fetchUrl = new URL(`https://api.bunny.net/purge`); - fetchUrl.searchParams.append('async', 'false'); - fetchUrl.searchParams.append('url', `${cdnBaseUrl}/${filepath}`); + const fetchUrl = `https://api.bunny.net/pullzone/${pullZoneId}/purgeCache`; const res = await fetch(fetchUrl.toString(), { method: 'POST', @@ -125,6 +123,6 @@ export async function purgePath(filepath: string) { if (!res.ok) { console.error(await res.text()); - throw new Error(`Failed to purge cache: ${res.statusText}`); + throw new Error(`Failed to purge CDN cache: ${res.statusText}`); } } diff --git a/scripts/deploy_bunny.ts b/scripts/deploy_bunny.ts index 3d1fb39..29367b3 100644 --- a/scripts/deploy_bunny.ts +++ b/scripts/deploy_bunny.ts @@ -1,5 +1,5 @@ import { join } from '@std/path'; -import { deployScript, purgePath, uploadFile } from './bunny_api/main.ts'; +import { deployScript, purgeCDNCache, uploadFile } from './bunny_api/main.ts'; if (import.meta.main) { console.log(`Attempting to upload index.html`); @@ -8,8 +8,8 @@ if (import.meta.main) { await uploadFile('index.html', indexContents); console.log(`Done!`); - console.log(`Attempting to purge the cache for index.html`); - await purgePath('index.html'); + console.log(`Attempting to purge the CDN cache.`); + await purgeCDNCache(); console.log(`Done!`); for (const dirEntry of Deno.readDirSync( diff --git a/templates/index.html b/templates/index.html index 38d405e..cca30ae 100644 --- a/templates/index.html +++ b/templates/index.html @@ -5,7 +5,6 @@