diff --git a/modules/nixos/programs/forgejo/ssh.nix b/modules/nixos/programs/forgejo/ssh.nix index f2ea4b8..e3735a3 100644 --- a/modules/nixos/programs/forgejo/ssh.nix +++ b/modules/nixos/programs/forgejo/ssh.nix @@ -6,44 +6,78 @@ ... }: let gitUser = config.services.forgejo.settings.server.BUILTIN_SSH_SERVER_USER; + forgejoUser = config.services.forgejo.user; forgejo = config.services.forgejo.package; stateDir = config.services.forgejo.stateDir; + forgejoExe = lib.getExe forgejo; - forgejoKeysScript = pkgs.writeShellScript "forgejo-keys" '' - FORGEJO_WORK_DIR=${stateDir} ${lib.getExe forgejo} keys -e git -u "$1" -t "$2" -k "$3" - ''; + separateUsers = gitUser != forgejoUser; + + forgejoKeysCmd = "FORGEJO_WORK_DIR=${stateDir} ${forgejoExe} keys -e git -u \"$1\" -t \"$2\" -k \"$3\""; + + # When the SSH user differs from the forgejo service user, rewrite + # the command= wrapper to use sudo so forgejo serv runs as the user + # that owns the repository data. + forgejoKeysScript = pkgs.writeShellScript "forgejo-keys" ( + if separateUsers + then '' + ${forgejoKeysCmd} \ + | ${pkgs.gnused}/bin/sed 's|command="${forgejoExe}|command="/run/wrappers/bin/sudo -u ${forgejoUser} --preserve-env=SSH_ORIGINAL_COMMAND ${forgejoExe}|' + '' + else forgejoKeysCmd + ); forgejoKeysPath = "/run/forgejo-keys"; in { - config = lib.mkIf config.services.forgejo.enable { - # sshd rejects connections for users with nologin shell before - # processing authorized_keys, so we need a valid shell even though - # the command= wrapper in Forgejo's keys prevents actual shell access. - users.users.${gitUser}.shell = pkgs.bash; - users.groups.${config.services.forgejo.group}.members = [gitUser]; + config = lib.mkIf config.services.forgejo.enable (lib.mkMerge [ + { + # sshd rejects connections for users with nologin shell before + # processing authorized_keys, so we need a valid shell even though + # the command= wrapper in Forgejo's keys prevents actual shell access. + users.users.${gitUser}.shell = pkgs.bash; - services.openssh.settings.AllowUsers = [gitUser]; + services.openssh.settings.AllowUsers = [gitUser]; - # Copy the key lookup script to a root-owned path outside /nix/store. - # sshd StrictModes requires AuthorizedKeysCommand and all parent dirs - # to be owned by root with no group/world writes. /nix/store and /etc - # symlinks both fail this check. - system.activationScripts.forgejo-ssh-keys = lib.stringAfter ["etc"] '' - install -m 0755 -o root -g root ${forgejoKeysScript} ${forgejoKeysPath} - ''; + # Copy the key lookup script to a root-owned path outside /nix/store. + # sshd StrictModes requires AuthorizedKeysCommand and all parent dirs + # to be owned by root with no group/world writes. /nix/store and /etc + # symlinks both fail this check. + system.activationScripts.forgejo-ssh-keys = lib.stringAfter ["etc"] '' + install -m 0755 -o root -g root ${forgejoKeysScript} ${forgejoKeysPath} + ''; - services.openssh.extraConfig = '' - Match User ${gitUser} - AuthorizedKeysCommandUser ${gitUser} - AuthorizedKeysCommand ${forgejoKeysPath} %u %t %k - AuthenticationMethods publickey - KbdInteractiveAuthentication no - PasswordAuthentication no - AllowAgentForwarding no - AllowTcpForwarding no - X11Forwarding no - PermitTTY no - ''; - }; + services.openssh.extraConfig = '' + Match User ${gitUser} + AuthorizedKeysCommandUser ${gitUser} + AuthorizedKeysCommand ${forgejoKeysPath} %u %t %k + AuthenticationMethods publickey + KbdInteractiveAuthentication no + PasswordAuthentication no + AllowAgentForwarding no + AllowTcpForwarding no + X11Forwarding no + PermitTTY no + ''; + } + + (lib.mkIf separateUsers { + # Give the git user read access to forgejo's config and data + users.groups.${config.services.forgejo.group}.members = [gitUser]; + + # Allow the git user to run forgejo serv as the forgejo user + security.sudo.extraRules = [ + { + users = [gitUser]; + runAs = forgejoUser; + commands = [ + { + command = "${forgejoExe} --config=${stateDir}/custom/conf/app.ini serv *"; + options = ["NOPASSWD" "SETENV"]; + } + ]; + } + ]; + }) + ]); }; }