Wrapper to restrict builder access through ssh, worth upstreaming?

Huh, now that I’m going through everything again I somehow managed to absolutely miss the script at the very top of the thread.

There’s only so many ways to write that script anyway, and below is my take on it, in the form of a nixpkgs patch (nixos-24.11, but can be cleanly cherry-picked to master), which I’ll be maintaining out of tree for a bit.
Feel free to copy, modify, move it upstream.… whatever.
If you do move it upstream, it might be worth making some of this configurable though, and of course fix the formatting and general code quality.
In terms of functionality though, I’m running Lix main and this works (at least with the ssh:// protocol, but ssh-ng:// should too) both locally as well as from within a Hydra instance.

nixpkgs patch for ForceCommand
From c7e73502898afc540a824689a5e93081cf0d3b7e Mon Sep 17 00:00:00 2001
From: benaryorg <binary@benary.org>
Date: Wed, 14 May 2025 23:10:00 +0000
Subject: [PATCH] nixos/ssh-serve: wrapper for SSH ForceCommand

Due to complications of SSH handling some implementations require a "sanity check" before operating via the ssh protocol.
By providing a wrapper script which enforces one of the corresponding access modes the sanity check can be included.
For maximum compatibility, a command passed directly via SSH is also allowed.

Note that this is not a filter on which commands are allowed to run, or some sort of sandbox, instead this is a non-interactive shell providing only the specified commands.
In that sense there is no risk of using escape sequences or special shell characters, and there is no way to run malicious commands such as a fork bomb or similar.

Signed-off-by: benaryorg <binary@benary.org>
---
 nixos/modules/services/misc/nix-ssh-serve.nix | 42 ++++++++++++++++---
 1 file changed, 36 insertions(+), 6 deletions(-)

diff --git a/nixos/modules/services/misc/nix-ssh-serve.nix b/nixos/modules/services/misc/nix-ssh-serve.nix
index b27ef03dae6f..3b869c4d191f 100644
--- a/nixos/modules/services/misc/nix-ssh-serve.nix
+++ b/nixos/modules/services/misc/nix-ssh-serve.nix
@@ -6,11 +6,41 @@
 }:
 let
   cfg = config.nix.sshServe;
-  command =
-    if cfg.protocol == "ssh" then
-      "nix-store --serve ${lib.optionalString cfg.write "--write"}"
-    else
-      "nix-daemon --stdio";
+  template = input: command: "    ${lib.escapeShellArg input})\n      ${lib.escapeShellArgs command}\n      ;;";
+  buildCommand = commands: pkgs.writeShellApplication {
+    name = "nix-serve-forcecommand";
+    text = ''
+      runCommand() {
+        case "$1" in
+      ${builtins.concatStringsSep "\n" (builtins.map ({ input, command }: template input command) commands)}
+          # some implementations use "exec", emulate this
+          "exec "*)
+            runCommand "''${1##"exec "}"
+            exit
+            ;;
+          # other commands are prohibited
+          *)
+            printf "not running unknown command '%s'\\n" "$1" >&2
+            exit 1
+        esac
+      }
+
+      # implementations may pass a command directly, however some implementations pass "bash" as the command to run interactively
+      if test -n "''${SSH_ORIGINAL_COMMAND:+x}" && test "$SSH_ORIGINAL_COMMAND" != bash; then
+        runCommand "$SSH_ORIGINAL_COMMAND"
+      else
+        while read -r line; do
+          runCommand "$line"
+        done
+      fi
+    '';
+    runtimeInputs = [ config.nix.package ];
+  };
+  command = buildCommand ([ { input = "echo started"; command = [ "echo" "started" ]; } ]
+    ++ lib.optional (cfg.protocol == "ssh") { input = "nix-store --serve"; command = [ "nix-store" "--serve" ]; }
+    ++ lib.optional (cfg.protocol == "ssh" && cfg.write) { input = "nix-store --serve --write"; command = [ "nix-store" "--serve" "--write" ]; }
+    ++ lib.optional (cfg.protocol == "ssh-ng") { input = "nix-daemon --stdio"; command = [ "nix-daemon" "--stdio" ]; }
+  );
 in
 {
   options = {
@@ -68,7 +98,7 @@ in
         PermitTTY no
         PermitTunnel no
         X11Forwarding no
-        ForceCommand ${config.nix.package.out}/bin/${command}
+        ForceCommand ${command.out}/bin/nix-serve-forcecommand
       Match All
     '';
 
-- 
2.47.2
1 Like