1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11 cfg = config.services.uptermd;
12in
13{
14 options = {
15 services.uptermd = {
16 enable = mkEnableOption "uptermd";
17
18 openFirewall = mkOption {
19 type = types.bool;
20 default = false;
21 description = ''
22 Whether to open the firewall for the port in {option}`services.uptermd.port`.
23 '';
24 };
25
26 port = mkOption {
27 type = types.port;
28 default = 2222;
29 description = ''
30 Port the server will listen on.
31 '';
32 };
33
34 listenAddress = mkOption {
35 type = types.str;
36 default = "[::]";
37 example = "127.0.0.1";
38 description = ''
39 Address the server will listen on.
40 '';
41 };
42
43 hostKey = mkOption {
44 type = types.nullOr types.path;
45 default = null;
46 example = "/run/keys/upterm_host_ed25519_key";
47 description = ''
48 Path to SSH host key. If not defined, an ed25519 keypair is generated automatically.
49 '';
50 };
51
52 extraFlags = mkOption {
53 type = types.listOf types.str;
54 default = [ ];
55 example = [ "--debug" ];
56 description = ''
57 Extra flags passed to the uptermd command.
58 '';
59 };
60 };
61 };
62
63 config = mkIf cfg.enable {
64 networking.firewall = mkIf cfg.openFirewall {
65 allowedTCPPorts = [ cfg.port ];
66 };
67
68 systemd.services.uptermd = {
69 description = "Upterm Daemon";
70 wantedBy = [ "multi-user.target" ];
71 after = [ "network.target" ];
72
73 path = [ pkgs.openssh ];
74
75 preStart = mkIf (cfg.hostKey == null) ''
76 if ! [ -f ssh_host_ed25519_key ]; then
77 ssh-keygen \
78 -t ed25519 \
79 -f ssh_host_ed25519_key \
80 -N ""
81 fi
82 '';
83
84 serviceConfig = {
85 StateDirectory = "uptermd";
86 WorkingDirectory = "/var/lib/uptermd";
87 ExecStart = "${pkgs.upterm}/bin/uptermd --ssh-addr ${cfg.listenAddress}:${toString cfg.port} --private-key ${
88 if cfg.hostKey == null then "ssh_host_ed25519_key" else cfg.hostKey
89 } ${concatStringsSep " " cfg.extraFlags}";
90
91 # Hardening
92 AmbientCapabilities = mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
93 CapabilityBoundingSet = mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
94 PrivateUsers = cfg.port >= 1024;
95 DynamicUser = true;
96 LockPersonality = true;
97 MemoryDenyWriteExecute = true;
98 PrivateDevices = true;
99 ProtectClock = true;
100 ProtectControlGroups = true;
101 ProtectHome = true;
102 ProtectHostname = true;
103 ProtectKernelLogs = true;
104 ProtectKernelModules = true;
105 ProtectKernelTunables = true;
106 ProtectProc = "invisible";
107 # AF_UNIX is for ssh-keygen, which relies on nscd to resolve the uid to a user
108 RestrictAddressFamilies = [
109 "AF_INET"
110 "AF_INET6"
111 "AF_UNIX"
112 ];
113 RestrictNamespaces = true;
114 RestrictRealtime = true;
115 SystemCallArchitectures = "native";
116 SystemCallFilter = "@system-service";
117 };
118 };
119 };
120}