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 5de7e79bb08..e9f4f707f52 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
@@ -314,6 +314,13 @@
services.alps.
+
+
+ endlessh,
+ an SSH tarpit. Available as
+ services.endlessh.
+
+
endlessh-go,
diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md
index 541deabc205..9b5257ca8e1 100644
--- a/nixos/doc/manual/release-notes/rl-2211.section.md
+++ b/nixos/doc/manual/release-notes/rl-2211.section.md
@@ -109,6 +109,8 @@ In addition to numerous new and upgraded packages, this release has the followin
- [alps](https://git.sr.ht/~migadu/alps), a simple and extensible webmail. Available as [services.alps](#opt-services.alps.enable).
+- [endlessh](https://github.com/skeeto/endlessh), an SSH tarpit. Available as [services.endlessh](#opt-services.endlessh.enable).
+
- [endlessh-go](https://github.com/shizunge/endlessh-go), an SSH tarpit that exposes Prometheus metrics. Available as [services.endlessh-go](#opt-services.endlessh-go.enable).
- [Garage](https://garagehq.deuxfleurs.fr/), a simple object storage server for geodistributed deployments, alternative to MinIO. Available as [services.garage](#opt-services.garage.enable).
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index ba9f67f87fd..1e9298a6571 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1012,6 +1012,7 @@
./services/security/certmgr.nix
./services/security/cfssl.nix
./services/security/clamav.nix
+ ./services/security/endlessh.nix
./services/security/endlessh-go.nix
./services/security/fail2ban.nix
./services/security/fprintd.nix
diff --git a/nixos/modules/services/security/endlessh.nix b/nixos/modules/services/security/endlessh.nix
new file mode 100644
index 00000000000..e99b4dadcd5
--- /dev/null
+++ b/nixos/modules/services/security/endlessh.nix
@@ -0,0 +1,99 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.endlessh;
+in
+{
+ options.services.endlessh = {
+ enable = mkEnableOption (mdDoc "endlessh service");
+
+ port = mkOption {
+ type = types.port;
+ default = 2222;
+ example = 22;
+ description = mdDoc ''
+ Specifies on which port the endlessh daemon listens for SSH
+ connections.
+
+ Setting this to `22` may conflict with {option}`services.openssh`.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = with types; listOf str;
+ default = [ ];
+ example = [ "-6" "-d 9000" "-v" ];
+ description = mdDoc ''
+ Additional command line options to pass to the endlessh daemon.
+ '';
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = lib.mdDoc ''
+ Whether to open a firewall port for the SSH listener.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.endlessh = {
+ description = "SSH tarpit";
+ requires = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig =
+ let
+ needsPrivileges = cfg.port < 1024;
+ capabilities = [ "" ] ++ optionals needsPrivileges [ "CAP_NET_BIND_SERVICE" ];
+ rootDirectory = "/run/endlessh";
+ in
+ {
+ Restart = "always";
+ ExecStart = with cfg; concatStringsSep " " ([
+ "${pkgs.endlessh}/bin/endlessh"
+ "-p ${toString port}"
+ ] ++ extraOptions);
+ DynamicUser = true;
+ RootDirectory = rootDirectory;
+ BindReadOnlyPaths = [ builtins.storeDir ];
+ InaccessiblePaths = [ "-+${rootDirectory}" ];
+ RuntimeDirectory = baseNameOf rootDirectory;
+ RuntimeDirectoryMode = "700";
+ AmbientCapabilities = capabilities;
+ CapabilityBoundingSet = capabilities;
+ UMask = "0077";
+ LockPersonality = true;
+ MemoryDenyWriteExecute = true;
+ NoNewPrivileges = true;
+ PrivateDevices = true;
+ PrivateTmp = true;
+ PrivateUsers = !needsPrivileges;
+ ProtectClock = true;
+ ProtectControlGroups = true;
+ ProtectHome = true;
+ ProtectHostname = true;
+ ProtectKernelLogs = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ ProtectSystem = "strict";
+ ProtectProc = "noaccess";
+ ProcSubset = "pid";
+ RemoveIPC = true;
+ RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+ RestrictNamespaces = true;
+ RestrictRealtime = true;
+ RestrictSUIDSGID = true;
+ SystemCallArchitectures = "native";
+ SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ];
+ };
+ };
+
+ networking.firewall.allowedTCPPorts = with cfg;
+ optionals openFirewall [ port ];
+ };
+
+ meta.maintainers = with maintainers; [ azahi ];
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 06ee955668f..a3253339c03 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -183,6 +183,7 @@ in {
ejabberd = handleTest ./xmpp/ejabberd.nix {};
elk = handleTestOn ["x86_64-linux"] ./elk.nix {};
emacs-daemon = handleTest ./emacs-daemon.nix {};
+ endlessh = handleTest ./endlessh.nix {};
endlessh-go = handleTest ./endlessh-go.nix {};
engelsystem = handleTest ./engelsystem.nix {};
enlightenment = handleTest ./enlightenment.nix {};
diff --git a/nixos/tests/endlessh.nix b/nixos/tests/endlessh.nix
new file mode 100644
index 00000000000..be742a749fd
--- /dev/null
+++ b/nixos/tests/endlessh.nix
@@ -0,0 +1,43 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+ name = "endlessh";
+ meta.maintainers = with lib.maintainers; [ azahi ];
+
+ nodes = {
+ server = { ... }: {
+ services.endlessh = {
+ enable = true;
+ openFirewall = true;
+ };
+
+ specialisation = {
+ unprivileged.configuration.services.endlessh.port = 2222;
+
+ privileged.configuration.services.endlessh.port = 22;
+ };
+ };
+
+ client = { pkgs, ... }: {
+ environment.systemPackages = with pkgs; [ curl netcat ];
+ };
+ };
+
+ testScript = ''
+ def activate_specialisation(name: str):
+ server.succeed(f"/run/booted-system/specialisation/{name}/bin/switch-to-configuration test >&2")
+
+ start_all()
+
+ with subtest("Unprivileged"):
+ activate_specialisation("unprivileged")
+ server.wait_for_unit("endlessh.service")
+ server.wait_for_open_port(2222)
+ client.succeed("nc -dvW5 server 2222")
+
+ with subtest("Privileged"):
+ activate_specialisation("privileged")
+ server.wait_for_unit("endlessh.service")
+ server.wait_for_open_port(22)
+ client.succeed("nc -dvW5 server 22")
+ '';
+})
diff --git a/pkgs/servers/endlessh/default.nix b/pkgs/servers/endlessh/default.nix
index fc05ec033d4..e408c764939 100644
--- a/pkgs/servers/endlessh/default.nix
+++ b/pkgs/servers/endlessh/default.nix
@@ -1,4 +1,10 @@
-{ lib, stdenv, fetchFromGitHub }:
+{ lib
+, stdenv
+, fetchFromGitHub
+, testers
+, endlessh
+, nixosTests
+}:
stdenv.mkDerivation rec {
pname = "endlessh";
@@ -8,17 +14,25 @@ stdenv.mkDerivation rec {
owner = "skeeto";
repo = pname;
rev = version;
- sha256 = "0ziwr8j1frsp3dajr8h5glkm1dn5cci404kazz5w1jfrp0736x68";
+ hash = "sha256-yHQzDrjZycDL/2oSQCJjxbZQJ30FoixVG1dnFyTKPH4=";
};
makeFlags = [ "PREFIX=$(out)" ];
+ passthru.tests = {
+ inherit (nixosTests) endlessh;
+ version = testers.testVersion {
+ package = endlessh;
+ command = "endlessh -V";
+ };
+ };
+
meta = with lib; {
description = "SSH tarpit that slowly sends an endless banner";
homepage = "https://github.com/skeeto/endlessh";
changelog = "https://github.com/skeeto/endlessh/releases/tag/${version}";
license = licenses.unlicense;
- maintainers = [ maintainers.marsam ];
+ maintainers = with maintainers; [ azahi marsam ];
platforms = platforms.unix;
};
}