diff --git a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml index 582b1715d1a..0b59f19a934 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml @@ -320,6 +320,15 @@ services.go-autoconfig. + + + tmate-ssh-server, + server side part of + tmate. Available + as + services.tmate-ssh-server. + + Grafana diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md index 3e38f85b8f1..7201b0e1cc0 100644 --- a/nixos/doc/manual/release-notes/rl-2211.section.md +++ b/nixos/doc/manual/release-notes/rl-2211.section.md @@ -110,6 +110,8 @@ In addition to numerous new and upgraded packages, this release has the followin - [go-autoconfig](https://github.com/L11R/go-autoconfig), IMAP/SMTP autodiscover server. Available as [services.go-autoconfig](#opt-services.go-autoconfig.enable). +- [tmate-ssh-server](https://github.com/tmate-io/tmate-ssh-server), server side part of [tmate](https://tmate.io/). Available as [services.tmate-ssh-server](#opt-services.tmate-ssh-server.enable). + - [Grafana Tempo](https://www.grafana.com/oss/tempo/), a distributed tracing store. Available as [services.tempo](#opt-services.tempo.enable). - [AusweisApp2](https://www.ausweisapp.bund.de/), the authentication software for the German ID card. Available as [programs.ausweisapp](#opt-programs.ausweisapp.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index c2545016707..db07d6312c4 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -960,6 +960,7 @@ ./services/networking/tinc.nix ./services/networking/tinydns.nix ./services/networking/tftpd.nix + ./services/networking/tmate-ssh-server.nix ./services/networking/trickster.nix ./services/networking/tox-bootstrapd.nix ./services/networking/tox-node.nix diff --git a/nixos/modules/services/networking/tmate-ssh-server.nix b/nixos/modules/services/networking/tmate-ssh-server.nix new file mode 100644 index 00000000000..1b8f6662ef4 --- /dev/null +++ b/nixos/modules/services/networking/tmate-ssh-server.nix @@ -0,0 +1,122 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.services.tmate-ssh-server; + + defaultKeysDir = "/etc/tmate-ssh-server-keys"; + edKey = "${defaultKeysDir}/ssh_host_ed25519_key"; + rsaKey = "${defaultKeysDir}/ssh_host_rsa_key"; + + keysDir = + if cfg.keysDir == null + then defaultKeysDir + else cfg.keysDir; + + domain = config.networking.domain; +in +{ + options.services.tmate-ssh-server = { + enable = mkEnableOption (mdDoc "tmate ssh server"); + + package = mkOption { + type = types.package; + description = mdDoc "The package containing tmate-ssh-server"; + defaultText = literalExpression "pkgs.tmate-ssh-server"; + default = pkgs.tmate-ssh-server; + }; + + host = mkOption { + type = types.str; + description = mdDoc "External host name"; + defaultText = lib.literalExpression "config.networking.domain or config.networking.hostName "; + default = + if domain == null then + config.networking.hostName + else + domain; + }; + + port = mkOption { + type = types.port; + description = mdDoc "Listen port for the ssh server"; + default = 2222; + }; + + openFirewall = mkOption { + type = types.bool; + default = true; + description = mdDoc "Whether to automatically open the specified ports in the firewall."; + }; + + advertisedPort = mkOption { + type = types.port; + description = mdDoc "External port advertised to clients"; + }; + + keysDir = mkOption { + type = with types; nullOr str; + description = mdDoc "Directory containing ssh keys, defaulting to auto-generation"; + default = null; + }; + }; + + config = mkIf cfg.enable { + + networking.firewall.allowedTCPPorts = optionals cfg.openFirewall [ cfg.port ]; + + services.tmate-ssh-server = { + advertisedPort = mkDefault cfg.port; + }; + + environment.systemPackages = + let + tmate-config = pkgs.writeText "tmate.conf" + '' + set -g tmate-server-host "${cfg.host}" + set -g tmate-server-port ${toString cfg.port} + set -g tmate-server-ed25519-fingerprint "@ed25519_fingerprint@" + set -g tmate-server-rsa-fingerprint "@rsa_fingerprint@" + ''; + in + [ + (pkgs.writeShellApplication { + name = "tmate-client-config"; + runtimeInputs = with pkgs;[ openssh coreutils sd ]; + text = '' + RSA_SIG="$(ssh-keygen -l -E SHA256 -f "${keysDir}/ssh_host_rsa_key.pub" | cut -d ' ' -f 2)" + ED25519_SIG="$(ssh-keygen -l -E SHA256 -f "${keysDir}/ssh_host_ed25519_key.pub" | cut -d ' ' -f 2)" + sd -sp '@ed25519_fingerprint@' "$ED25519_SIG" ${tmate-config} | \ + sd -sp '@rsa_fingerprint@' "$RSA_SIG" + ''; + }) + ]; + + systemd.services.tmate-ssh-server = { + description = "tmate SSH Server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${cfg.package}/bin/tmate-ssh-server -h ${cfg.host} -p ${toString cfg.port} -q ${toString cfg.advertisedPort} -k ${keysDir}"; + }; + preStart = mkIf (cfg.keysDir == null) '' + if [[ ! -d ${defaultKeysDir} ]] + then + mkdir -p ${defaultKeysDir} + fi + if [[ ! -f ${edKey} ]] + then + ${pkgs.openssh}/bin/ssh-keygen -t ed25519 -f ${edKey} -N "" + fi + if [[ ! -f ${rsaKey} ]] + then + ${pkgs.openssh}/bin/ssh-keygen -t rsa -f ${rsaKey} -N "" + fi + ''; + }; + }; + + meta = { + maintainers = with maintainers; [ jlesquembre ]; + }; + +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 21d8fa59753..131936a87c3 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -626,6 +626,7 @@ in { tinc = handleTest ./tinc {}; tinydns = handleTest ./tinydns.nix {}; tinywl = handleTest ./tinywl.nix {}; + tmate-ssh-server = handleTest ./tmate-ssh-server.nix { }; tomcat = handleTest ./tomcat.nix {}; tor = handleTest ./tor.nix {}; # traefik test relies on docker-containers diff --git a/nixos/tests/tmate-ssh-server.nix b/nixos/tests/tmate-ssh-server.nix new file mode 100644 index 00000000000..e7f94db9bfc --- /dev/null +++ b/nixos/tests/tmate-ssh-server.nix @@ -0,0 +1,73 @@ +import ./make-test-python.nix ({ pkgs, lib, ... }: +let + inherit (import ./ssh-keys.nix pkgs) + snakeOilPrivateKey snakeOilPublicKey; + + setUpPrivateKey = name: '' + ${name}.succeed( + "mkdir -p /root/.ssh", + "chown 700 /root/.ssh", + "cat '${snakeOilPrivateKey}' > /root/.ssh/id_snakeoil", + "chown 600 /root/.ssh/id_snakeoil", + ) + ${name}.wait_for_file("/root/.ssh/id_snakeoil") + ''; + + sshOpts = "-oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oIdentityFile=/root/.ssh/id_snakeoil"; + +in +{ + name = "tmate-ssh-server"; + nodes = + { + server = { ... }: { + services.tmate-ssh-server = { + enable = true; + port = 2223; + }; + }; + client = { ... }: { + environment.systemPackages = [ pkgs.tmate ]; + services.openssh.enable = true; + users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; + }; + client2 = { ... }: { + environment.systemPackages = [ pkgs.openssh ]; + }; + }; + testScript = '' + start_all() + + server.wait_for_unit("tmate-ssh-server.service") + server.wait_for_open_port(2223) + server.wait_for_file("/etc/tmate-ssh-server-keys/ssh_host_ed25519_key.pub") + server.wait_for_file("/etc/tmate-ssh-server-keys/ssh_host_rsa_key.pub") + server.succeed("tmate-client-config > /tmp/tmate.conf") + server.wait_for_file("/tmp/tmate.conf") + + ${setUpPrivateKey "server"} + client.wait_for_unit("sshd.service") + client.wait_for_open_port(22) + server.succeed("scp ${sshOpts} /tmp/tmate.conf client:/tmp/tmate.conf") + + client.wait_for_file("/tmp/tmate.conf") + client.send_chars("root\n") + client.sleep(2) + client.send_chars("tmate -f /tmp/tmate.conf\n") + client.sleep(2) + client.send_chars("q") + client.sleep(2) + client.send_chars("tmate display -p '#{tmate_ssh}' > /tmp/ssh_command\n") + client.wait_for_file("/tmp/ssh_command") + ssh_cmd = client.succeed("cat /tmp/ssh_command") + + client2.succeed("mkdir -p ~/.ssh; ssh-keyscan -p 2223 server > ~/.ssh/known_hosts") + client2.send_chars("root\n") + client2.sleep(2) + client2.send_chars(ssh_cmd.strip() + "\n") + client2.sleep(2) + client2.send_chars("touch /tmp/client_2\n") + + client.wait_for_file("/tmp/client_2") + ''; +}) diff --git a/pkgs/servers/tmate-ssh-server/default.nix b/pkgs/servers/tmate-ssh-server/default.nix index dd1c6ad05c4..539b456c9d7 100644 --- a/pkgs/servers/tmate-ssh-server/default.nix +++ b/pkgs/servers/tmate-ssh-server/default.nix @@ -1,14 +1,28 @@ -{ lib, stdenv, fetchFromGitHub, autoreconfHook, cmake, libtool, pkg-config -, zlib, openssl, libevent, ncurses, ruby, msgpack, libssh }: +{ lib +, stdenv +, fetchFromGitHub +, autoreconfHook +, cmake +, libtool +, pkg-config +, zlib +, openssl +, libevent +, ncurses +, ruby +, msgpack +, libssh +, nixosTests +}: stdenv.mkDerivation rec { pname = "tmate-ssh-server"; version = "unstable-2021-10-17"; src = fetchFromGitHub { - owner = "tmate-io"; - repo = "tmate-ssh-server"; - rev = "1f314123df2bb29cb07427ed8663a81c8d9034fd"; + owner = "tmate-io"; + repo = "tmate-ssh-server"; + rev = "1f314123df2bb29cb07427ed8663a81c8d9034fd"; sha256 = "sha256-9/xlMvtkNWUBRYYnJx20qEgtEcjagH2NtEKZcDOM1BY="; }; @@ -17,12 +31,13 @@ stdenv.mkDerivation rec { buildInputs = [ libtool zlib openssl libevent ncurses ruby msgpack libssh ]; nativeBuildInputs = [ autoreconfHook cmake pkg-config ]; + passthru.tests.tmate-ssh-server = nixosTests.tmate-ssh-server; + meta = with lib; { - homepage = "https://tmate.io/"; + homepage = "https://tmate.io/"; description = "tmate SSH Server"; - license = licenses.mit; - platforms = platforms.unix; + license = licenses.mit; + platforms = platforms.unix; maintainers = with maintainers; [ ck3d ]; }; } -