This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

configs.crit: public https://<id>.review.lassul.us per review

The crit wrapper writes a fresh random 10-char id per daemon port into
/var/lib/crit-routes; a path unit turns that into an nginx map on
coaxmetal:7720 (retiolum only). neoprism proxies *.review.lassul.us there
with a wildcard cert obtained via DNS-01 (RFC2136) against a delegated
_acme-challenge.review.lassul.us zone on knot, so ids never hit CT logs.

+193 -27
+35
configs/crit-proxy.nix
··· 1 + { config, ... }: 2 + let 3 + cert = "wildcard.review.lassul.us"; 4 + in 5 + { 6 + # Public door to crit reviews on coaxmetal (configs/crit.nix): every review 7 + # gets a random https://<id>.review.lassul.us, routed to its daemon by the 8 + # nginx on coaxmetal:7720. 9 + # 10 + # Wildcard cert only: a per-name cert would publish every id in the 11 + # Certificate Transparency logs. DNS-01 against our own knot, see the 12 + # _acme-challenge.review.lassul.us zone in configs/dns/knot.nix. 13 + security.acme.certs.${cert} = { 14 + domain = "*.review.lassul.us"; 15 + dnsProvider = "rfc2136"; 16 + environmentFile = config.clan.core.vars.generators.knot-acme-review.files."lego.env".path; 17 + group = config.services.nginx.group; 18 + }; 19 + 20 + services.nginx.virtualHosts.crit-review = { 21 + serverName = ''"~^[a-z0-9]{10}\.review\.lassul\.us$"''; 22 + useACMEHost = cert; 23 + forceSSL = true; 24 + # crit streams review updates over SSE 25 + locations."/" = { 26 + recommendedProxySettings = true; 27 + proxyPass = "http://coaxmetal.r:7720"; 28 + extraConfig = '' 29 + proxy_buffering off; 30 + proxy_read_timeout 1h; 31 + proxy_connect_timeout 5s; 32 + ''; 33 + }; 34 + }; 35 + }
+109 -3
configs/crit.nix
··· 1 1 # crit (https://crit.md): agents run `crit` to open their diff/plan for review 2 2 # in the browser and block until it is finished. Each review session is its 3 3 # own daemon on its own port, reachable over retiolum as 4 - # http://<host>.r:<port>. 4 + # http://<host>.r:<port>, and publicly as https://<id>.review.lassul.us 5 + # through neoprism (configs/crit-proxy.nix) and the router nginx below. 5 6 # 6 7 # SECURITY: crit has no authentication, and comments left in a review are fed 7 8 # to the agent as instructions. Whoever can reach a daemon can steer an agent 8 - # running here, so the ports are only open on retiolum. 9 + # running here, so the ports are only open on retiolum. On the public side the 10 + # random 10-char hostname (~51 bits, fresh per review, only on a wildcard cert 11 + # so it never shows up in CT logs) is the only protection. 9 12 { 10 13 self, 11 14 config, ··· 15 18 let 16 19 firstPort = 7700; 17 20 lastPort = 7719; 21 + # router nginx, neoprism proxies *.review.lassul.us here 22 + routerPort = 7720; 23 + domain = "review.lassul.us"; 24 + # one file per port, "<id> <port>", written by the wrapper 25 + routesDir = "/var/lib/crit-routes"; 26 + mapDir = "/run/crit-routes"; 18 27 crit-unwrapped = self.inputs.llm-agents.packages.${pkgs.system}.crit; 19 28 20 29 # crit binds 127.0.0.1 on a random port by default. Bind all interfaces on a 21 - # free port from the firewalled range instead and advertise the retiolum URL. 30 + # free port from the firewalled range instead and advertise the public URL. 22 31 # Only the invocation that starts a daemon binds the port; reconnecting to a 23 32 # running session uses the port and URL recorded in ~/.crit/sessions. 24 33 crit = pkgs.symlinkJoin { ··· 29 38 for p in $(${pkgs.coreutils}/bin/seq ${toString firstPort} ${toString lastPort}); do 30 39 if [ -z "$(${pkgs.iproute2}/bin/ss -Htln "sport = :$p")" ]; then 31 40 export CRIT_PORT=$p 41 + # Fresh unguessable hostname per daemon. Replacing the port's 42 + # route kills the link of whichever review used the port before. 43 + id=$(LC_ALL=C ${pkgs.coreutils}/bin/tr -dc a-z0-9 </dev/urandom | ${pkgs.coreutils}/bin/head -c 10) 44 + tmp=${routesDir}/.$p.$$ 45 + if printf '%s %s\n' "$id" "$p" >"$tmp" && ${pkgs.coreutils}/bin/mv -f "$tmp" ${routesDir}/"$p"; then 46 + export CRIT_PUBLIC_URL="''${CRIT_PUBLIC_URL:-https://$id.${domain}}" 47 + else 48 + ${pkgs.coreutils}/bin/rm -f "$tmp" 49 + echo "crit: cannot write ${routesDir} (not in group crit?), no public URL" >&2 50 + fi 32 51 break 33 52 fi 34 53 done 35 54 fi 36 55 if [ -n "''${CRIT_PORT:-}" ]; then 56 + echo "crit: retiolum: http://${config.networking.hostName}.r:$CRIT_PORT" >&2 37 57 export CRIT_PUBLIC_URL="''${CRIT_PUBLIC_URL:-http://${config.networking.hostName}.r:$CRIT_PORT}" 38 58 else 39 59 echo "crit: ports ${toString firstPort}-${toString lastPort} all busy, using a random port (not reachable over retiolum)" >&2 ··· 45 65 crit-unwrapped 46 66 ]; 47 67 }; 68 + 69 + # Only well-formed routes make it into the nginx config: the routes dir is 70 + # writable by the agent. 71 + crit-routes-map = pkgs.writeShellApplication { 72 + name = "crit-routes-map"; 73 + runtimeInputs = [ pkgs.coreutils ]; 74 + text = '' 75 + routes=$1 76 + out=$2 77 + tmp=$(mktemp "$out.XXXXXX") 78 + for f in "$routes"/*; do 79 + [ -f "$f" ] || continue 80 + id=''' port=''' 81 + read -r id port _ <"$f" || [ -n "$id" ] || continue 82 + if [[ $id =~ ^[a-z0-9]{10}$ && $port =~ ^[0-9]{4}$ ]] && 83 + ((port >= ${toString firstPort} && port <= ${toString lastPort})); then 84 + printf '%s.${domain} %s;\n' "$id" "$port" 85 + fi 86 + done >"$tmp" 87 + chmod 644 "$tmp" 88 + mv -f "$tmp" "$out" 89 + ''; 90 + }; 48 91 in 49 92 { 50 93 environment.systemPackages = [ crit ]; 51 94 95 + users.groups.crit.members = [ 96 + config.users.users.mainUser.name 97 + "hermes" 98 + ]; 99 + # no sticky bit: any crit user must be able to replace another's route 100 + systemd.tmpfiles.rules = [ "d ${routesDir} 2770 root crit -" ]; 101 + 102 + systemd.paths.crit-routes = { 103 + wantedBy = [ "paths.target" ]; 104 + pathConfig.PathChanged = routesDir; 105 + }; 106 + systemd.services.crit-routes = { 107 + description = "Generate the nginx map of crit review hostnames"; 108 + wantedBy = [ "multi-user.target" ]; 109 + before = [ "nginx.service" ]; 110 + serviceConfig.Type = "oneshot"; 111 + script = '' 112 + mkdir -p ${mapDir} 113 + ${pkgs.lib.getExe crit-routes-map} ${routesDir} ${mapDir}/routes.map 114 + systemctl try-reload-or-restart nginx.service 115 + ''; 116 + }; 117 + 118 + # Routes <id>.review.lassul.us to the daemon's port. A daemon that has gone 119 + # away gives 502, unknown ids 404. 120 + services.nginx = { 121 + enable = true; 122 + # the glob tolerates a missing file before crit-routes ran 123 + appendHttpConfig = '' 124 + map $host $crit_port { 125 + default ""; 126 + include ${mapDir}/*.map; 127 + } 128 + ''; 129 + virtualHosts.crit-routes = { 130 + listen = [ 131 + { 132 + addr = "0.0.0.0"; 133 + port = routerPort; 134 + } 135 + { 136 + addr = "[::]"; 137 + port = routerPort; 138 + } 139 + ]; 140 + extraConfig = '' 141 + if ($crit_port = "") { 142 + return 404; 143 + } 144 + ''; 145 + # the UI follows the review over SSE 146 + locations."/" = { 147 + recommendedProxySettings = true; 148 + proxyPass = "http://127.0.0.1:$crit_port"; 149 + extraConfig = '' 150 + proxy_buffering off; 151 + proxy_read_timeout 1h; 152 + ''; 153 + }; 154 + }; 155 + }; 156 + 157 + networking.firewall.interfaces.retiolum.allowedTCPPorts = [ routerPort ]; 52 158 networking.firewall.interfaces.retiolum.allowedTCPPortRanges = [ 53 159 { 54 160 from = firstPort;
+43 -23
configs/dns/knot.nix
··· 1 1 { 2 + config, 2 3 lib, 4 + pkgs, 3 5 ... 4 6 }: 5 7 let 6 8 ip4 = "95.217.192.59"; 7 9 ip6 = "2a01:4f9:4a:4f1a::2"; 8 - # acmeChallenge = domain: 9 - # pkgs.writeText "_acme-challenge.${domain}.zone" '' 10 - # @ 3600 IN SOA _acme-challenge.${domain}. ns1.lassul.us. 2023101213 7200 3600 86400 3600 11 - 12 - # $TTL 600 13 - 14 - # @ IN NS ns1.lassul.us. 15 - # ''; 10 + # Delegated from lassul.us (NS ns1 only, the Hetzner secondaries never see 11 + # it) so lego can do DNS-01 for *.review.lassul.us (configs/crit-proxy.nix) 12 + # with RFC2136 updates, without touching the signed main zone. 13 + acmeReviewZone = pkgs.writeText "_acme-challenge.review.lassul.us.zone" '' 14 + @ 3600 IN SOA ns1.lassul.us. acme.lassul.us. 1 7200 3600 86400 60 15 + @ 3600 IN NS ns1.lassul.us. 16 + ''; 16 17 # dyndns = domain: 17 18 # pkgs.writeText "${domain}.zone" '' 18 19 # @ 3600 IN SOA ${domain}. ns1.lassul.us. 2023101213 7200 3600 86400 3600 ··· 23 24 # @ IN NS ns1.lassul.us. 24 25 # ''; 25 26 { 26 - #content of the secret 27 - #key: 28 - #- id: acme 29 - # algorithm: hmac-sha256 30 - # secret: 00000000000000000000000000000000000000000000 31 - #nix-shell -p knot-dns --run 'keymgr -t my_name hmac-sha256' 32 - # clanCore.vars.generators.knot = { 33 - # files."knot-keys.conf" = {}; 34 - # generator = '' 35 - # ${pkgs.knot-dns}/bin/keymgr -t hmac-sha256 36 - # ''; 37 - # }; 27 + # TSIG key for the acme-review updates, for knot and for lego 28 + clan.core.vars.generators.knot-acme-review = { 29 + files."knot-key.conf".owner = "knot"; 30 + files."lego.env".owner = "acme"; 31 + runtimeInputs = [ pkgs.openssl ]; 32 + script = '' 33 + secret=$(openssl rand -base64 32) 34 + printf 'key:\n - id: acme-review\n algorithm: hmac-sha256\n secret: %s\n' "$secret" \ 35 + > "$out/knot-key.conf" 36 + printf 'RFC2136_NAMESERVER=%s\nRFC2136_TSIG_ALGORITHM=hmac-sha256.\nRFC2136_TSIG_KEY=acme-review\nRFC2136_TSIG_SECRET=%s\n' \ 37 + ${ip4} "$secret" > "$out/lego.env" 38 + ''; 39 + }; 38 40 39 41 services.knot = { 40 42 enable = true; 41 - # keyFiles = [ 42 - # config.sops.secrets."knot-keys.conf".path 43 - # ]; 43 + keyFiles = [ 44 + config.clan.core.vars.generators.knot-acme-review.files."knot-key.conf".path 45 + ]; 44 46 settings = { 45 47 server = { 46 48 listen = [ ··· 108 110 id = "hetzner_ip6_3"; 109 111 address = "2001:67c:192c::add:a3"; 110 112 action = "transfer"; 113 + } 114 + { 115 + id = "acme_review"; 116 + key = "acme-review"; 117 + address = [ 118 + ip4 119 + ip6 120 + ]; 121 + action = "update"; 122 + update-type = [ "TXT" ]; 111 123 } 112 124 ]; 113 125 ··· 191 203 # Use difference-no-serial to auto-increment serial based on serial-policy 192 204 zonefile-load = "difference-no-serial"; 193 205 serial-policy = "dateserial"; 206 + journal-content = "all"; 207 + } 208 + { 209 + domain = "_acme-challenge.review.lassul.us"; 210 + file = "${acmeReviewZone}"; 211 + acl = [ "acme_review" ]; 212 + zonefile-sync = "-1"; 213 + zonefile-load = "difference-no-serial"; 194 214 journal-content = "all"; 195 215 } 196 216 ];
+5 -1
configs/dns/lassul.us.zone
··· 1 - @ 3600 IN SOA lassul.us. ns1.lassul.us. 2026100200 7200 3600 86400 3600 1 + ; serial is ignored: knot loads this file with zonefile-load = difference-no-serial and serial-policy = dateserial (configs/dns/knot.nix) and bumps the served serial itself, so never edit it 2 + @ 3600 IN SOA lassul.us. ns1.lassul.us. 1 7200 3600 86400 3600 2 3 3 4 ;;@ 3600 IN NS ns1 4 5 @ 3600 IN NS ns1 ··· 60 61 where.is IN CNAME neoprism 61 62 parley IN CNAME neoprism 62 63 review IN CNAME neoprism 64 + *.review IN CNAME neoprism 65 + ;; DNS-01 for the *.review wildcard cert, dynamic zone in knot.nix 66 + _acme-challenge.review IN NS ns1 63 67 when IN CNAME neoprism 64 68 65 69 ;; Mail
+1
machines/neoprism/config.nix
··· 28 28 29 29 ../../configs/hass/proxy.nix 30 30 ../../configs/hermes-proxy.nix 31 + ../../configs/crit-proxy.nix 31 32 32 33 # sync-containers 33 34 ../../configs/consul.nix