1# NixOS module for iodine, ip over dns daemon
2
3{ config, lib, pkgs, ... }:
4
5with lib;
6
7let
8 cfg = config.services.iodine;
9
10 iodinedUser = "iodined";
11
12 /* is this path made unreadable by ProtectHome = true ? */
13 isProtected = x: hasPrefix "/root" x || hasPrefix "/home" x;
14in
15{
16 imports = [
17 (mkRenamedOptionModule [ "services" "iodined" "enable" ] [ "services" "iodine" "server" "enable" ])
18 (mkRenamedOptionModule [ "services" "iodined" "domain" ] [ "services" "iodine" "server" "domain" ])
19 (mkRenamedOptionModule [ "services" "iodined" "ip" ] [ "services" "iodine" "server" "ip" ])
20 (mkRenamedOptionModule [ "services" "iodined" "extraConfig" ] [ "services" "iodine" "server" "extraConfig" ])
21 (mkRemovedOptionModule [ "services" "iodined" "client" ] "")
22 ];
23
24 ### configuration
25
26 options = {
27
28 services.iodine = {
29 clients = mkOption {
30 default = {};
31 description = lib.mdDoc ''
32 Each attribute of this option defines a systemd service that
33 runs iodine. Many or none may be defined.
34 The name of each service is
35 `iodine-«name»`
36 where «name» is the name of the
37 corresponding attribute name.
38 '';
39 example = literalExpression ''
40 {
41 foo = {
42 server = "tunnel.mdomain.com";
43 relay = "8.8.8.8";
44 extraConfig = "-v";
45 }
46 }
47 '';
48 type = types.attrsOf (
49 types.submodule (
50 {
51 options = {
52 server = mkOption {
53 type = types.str;
54 default = "";
55 description = lib.mdDoc "Hostname of server running iodined";
56 example = "tunnel.mydomain.com";
57 };
58
59 relay = mkOption {
60 type = types.str;
61 default = "";
62 description = lib.mdDoc "DNS server to use as an intermediate relay to the iodined server";
63 example = "8.8.8.8";
64 };
65
66 extraConfig = mkOption {
67 type = types.str;
68 default = "";
69 description = lib.mdDoc "Additional command line parameters";
70 example = "-l 192.168.1.10 -p 23";
71 };
72
73 passwordFile = mkOption {
74 type = types.str;
75 default = "";
76 description = lib.mdDoc "Path to a file containing the password.";
77 };
78 };
79 }
80 )
81 );
82 };
83
84 server = {
85 enable = mkOption {
86 type = types.bool;
87 default = false;
88 description = lib.mdDoc "enable iodined server";
89 };
90
91 ip = mkOption {
92 type = types.str;
93 default = "";
94 description = lib.mdDoc "The assigned ip address or ip range";
95 example = "172.16.10.1/24";
96 };
97
98 domain = mkOption {
99 type = types.str;
100 default = "";
101 description = lib.mdDoc "Domain or subdomain of which nameservers point to us";
102 example = "tunnel.mydomain.com";
103 };
104
105 extraConfig = mkOption {
106 type = types.str;
107 default = "";
108 description = lib.mdDoc "Additional command line parameters";
109 example = "-l 192.168.1.10 -p 23";
110 };
111
112 passwordFile = mkOption {
113 type = types.str;
114 default = "";
115 description = lib.mdDoc "File that contains password";
116 };
117 };
118
119 };
120 };
121
122 ### implementation
123
124 config = mkIf (cfg.server.enable || cfg.clients != {}) {
125 environment.systemPackages = [ pkgs.iodine ];
126 boot.kernelModules = [ "tun" ];
127
128 systemd.services =
129 let
130 createIodineClientService = name: cfg:
131 {
132 description = "iodine client - ${name}";
133 after = [ "network.target" ];
134 wantedBy = [ "multi-user.target" ];
135 script = "exec ${pkgs.iodine}/bin/iodine -f -u ${iodinedUser} ${cfg.extraConfig} ${optionalString (cfg.passwordFile != "") "< \"${builtins.toString cfg.passwordFile}\""} ${cfg.relay} ${cfg.server}";
136 serviceConfig = {
137 RestartSec = "30s";
138 Restart = "always";
139
140 # hardening :
141 # Filesystem access
142 ProtectSystem = "strict";
143 ProtectHome = if isProtected cfg.passwordFile then "read-only" else "true" ;
144 PrivateTmp = true;
145 ReadWritePaths = "/dev/net/tun";
146 PrivateDevices = false;
147 ProtectKernelTunables = true;
148 ProtectKernelModules = true;
149 ProtectControlGroups = true;
150 # Caps
151 NoNewPrivileges = true;
152 # Misc.
153 LockPersonality = true;
154 RestrictRealtime = true;
155 PrivateMounts = true;
156 MemoryDenyWriteExecute = true;
157 };
158 };
159 in
160 listToAttrs (
161 mapAttrsToList
162 (name: value: nameValuePair "iodine-${name}" (createIodineClientService name value))
163 cfg.clients
164 ) // {
165 iodined = mkIf (cfg.server.enable) {
166 description = "iodine, ip over dns server daemon";
167 after = [ "network.target" ];
168 wantedBy = [ "multi-user.target" ];
169 script = "exec ${pkgs.iodine}/bin/iodined -f -u ${iodinedUser} ${cfg.server.extraConfig} ${optionalString (cfg.server.passwordFile != "") "< \"${builtins.toString cfg.server.passwordFile}\""} ${cfg.server.ip} ${cfg.server.domain}";
170 serviceConfig = {
171 # Filesystem access
172 ProtectSystem = "strict";
173 ProtectHome = if isProtected cfg.server.passwordFile then "read-only" else "true" ;
174 PrivateTmp = true;
175 ReadWritePaths = "/dev/net/tun";
176 PrivateDevices = false;
177 ProtectKernelTunables = true;
178 ProtectKernelModules = true;
179 ProtectControlGroups = true;
180 # Caps
181 NoNewPrivileges = true;
182 # Misc.
183 LockPersonality = true;
184 RestrictRealtime = true;
185 PrivateMounts = true;
186 MemoryDenyWriteExecute = true;
187 };
188 };
189 };
190
191 users.users.${iodinedUser} = {
192 uid = config.ids.uids.iodined;
193 group = "iodined";
194 description = "Iodine daemon user";
195 };
196 users.groups.iodined.gid = config.ids.gids.iodined;
197 };
198}