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