]> Git — Sourcephile - sourcephile-nix.git/blob - nixos/modules/services/networking/openvpn.nix
netns: improve the service
[sourcephile-nix.git] / nixos / modules / services / networking / openvpn.nix
1 { config, lib, pkgs, ... }:
2
3 with lib;
4
5 let
6
7 cfg = config.services.openvpn;
8
9 inherit (pkgs) openvpn;
10
11 makeOpenVPNJob = cfg: name:
12 let
13
14 path = makeBinPath (getAttr "openvpn-${name}" config.systemd.services).path;
15
16 upScript = ''
17 export PATH=${path}
18
19 # For convenience in client scripts, extract the remote domain
20 # name and name server.
21 for var in ''${!foreign_option_*}; do
22 x=(''${!var})
23 if [ "''${x[0]}" = dhcp-option ]; then
24 if [ "''${x[1]}" = DOMAIN ]; then domain="''${x[2]}"
25 elif [ "''${x[1]}" = DNS ]; then nameserver="''${x[2]}"
26 fi
27 fi
28 done
29
30 ${cfg.up}
31 ${optionalString cfg.updateResolvConf
32 "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
33 '';
34
35 downScript = ''
36 export PATH=${path}
37 ${optionalString cfg.updateResolvConf
38 "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
39 ${cfg.down}
40 '';
41
42 configFile = pkgs.writeText "openvpn-config-${name}"
43 ''
44 errors-to-stderr
45 ${optionalString (cfg.up != "" || cfg.down != "" || cfg.updateResolvConf) "script-security 2"}
46 ${cfg.config}
47 ${optionalString (cfg.up != "" || cfg.updateResolvConf)
48 "up ${pkgs.writeShellScript "openvpn-${name}-up" upScript}"}
49 ${optionalString (cfg.routeUp != "")
50 "route-up ${pkgs.writeShellScript "openvpn-${name}-up" cfg.routeUp}"}
51 ${optionalString (cfg.down != "" || cfg.updateResolvConf)
52 "down ${pkgs.writeShellScript "openvpn-${name}-down" downScript}"}
53 ${optionalString (cfg.authUserPass != null)
54 "auth-user-pass ${pkgs.writeText "openvpn-credentials-${name}" ''
55 ${cfg.authUserPass.username}
56 ${cfg.authUserPass.password}
57 ''}"}
58 '';
59
60 in {
61 description = "OpenVPN instance ‘${name}’";
62
63 wantedBy = optional cfg.autoStart "multi-user.target";
64 after = [ "network.target" ];
65
66 path = [ pkgs.iptables pkgs.iproute pkgs.nettools ];
67
68 serviceConfig.ExecStart = "@${openvpn}/sbin/openvpn openvpn --suppress-timestamps --config ${configFile}";
69 serviceConfig.Restart = "always";
70 serviceConfig.Type = "notify";
71 };
72
73 in
74
75 {
76 imports = [
77 (mkRemovedOptionModule [ "services" "openvpn" "enable" ] "")
78 ];
79
80 ###### interface
81
82 options = {
83
84 services.openvpn.servers = mkOption {
85 default = {};
86
87 example = literalExample ''
88 {
89 server = {
90 config = '''
91 # Simplest server configuration: https://community.openvpn.net/openvpn/wiki/StaticKeyMiniHowto
92 # server :
93 dev tun
94 ifconfig 10.8.0.1 10.8.0.2
95 secret /root/static.key
96 ''';
97 up = "ip route add ...";
98 down = "ip route del ...";
99 };
100
101 client = {
102 config = '''
103 client
104 remote vpn.example.org
105 dev tun
106 proto tcp-client
107 port 8080
108 ca /root/.vpn/ca.crt
109 cert /root/.vpn/alice.crt
110 key /root/.vpn/alice.key
111 ''';
112 up = "echo nameserver $nameserver | ''${pkgs.openresolv}/sbin/resolvconf -m 0 -a $dev";
113 down = "''${pkgs.openresolv}/sbin/resolvconf -d $dev";
114 };
115 }
116 '';
117
118 description = ''
119 Each attribute of this option defines a systemd service that
120 runs an OpenVPN instance. These can be OpenVPN servers or
121 clients. The name of each systemd service is
122 <literal>openvpn-<replaceable>name</replaceable>.service</literal>,
123 where <replaceable>name</replaceable> is the corresponding
124 attribute name.
125 '';
126
127 type = with types; attrsOf (submodule {
128
129 options = {
130
131 config = mkOption {
132 type = types.lines;
133 description = ''
134 Configuration of this OpenVPN instance. See
135 <citerefentry><refentrytitle>openvpn</refentrytitle><manvolnum>8</manvolnum></citerefentry>
136 for details.
137
138 To import an external config file, use the following definition:
139 <literal>config = "config /path/to/config.ovpn"</literal>
140 '';
141 };
142
143 up = mkOption {
144 default = "";
145 type = types.lines;
146 description = ''
147 Shell commands executed when the instance is starting.
148 '';
149 };
150
151 routeUp = mkOption {
152 default = "";
153 type = types.lines;
154 description = ''
155 '';
156 };
157
158 down = mkOption {
159 default = "";
160 type = types.lines;
161 description = ''
162 Shell commands executed when the instance is shutting down.
163 '';
164 };
165
166 nftables = mkOption {
167 default = "";
168 type = types.lines;
169 description = ''
170 Nftables rules
171 '';
172 };
173
174 autoStart = mkOption {
175 default = true;
176 type = types.bool;
177 description = "Whether this OpenVPN instance should be started automatically.";
178 };
179
180 updateResolvConf = mkOption {
181 default = false;
182 type = types.bool;
183 description = ''
184 Use the script from the update-resolv-conf package to automatically
185 update resolv.conf with the DNS information provided by openvpn. The
186 script will be run after the "up" commands and before the "down" commands.
187 '';
188 };
189
190 authUserPass = mkOption {
191 default = null;
192 description = ''
193 This option can be used to store the username / password credentials
194 with the "auth-user-pass" authentication method.
195
196 WARNING: Using this option will put the credentials WORLD-READABLE in the Nix store!
197 '';
198 type = types.nullOr (types.submodule {
199
200 options = {
201 username = mkOption {
202 description = "The username to store inside the credentials file.";
203 type = types.str;
204 };
205
206 password = mkOption {
207 description = "The password to store inside the credentials file.";
208 type = types.str;
209 };
210 };
211 });
212 };
213 };
214
215 });
216
217 };
218
219 };
220
221
222 ###### implementation
223
224 config = mkIf (cfg.servers != {}) {
225
226 systemd.services = listToAttrs (mapAttrsFlatten (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers);
227
228 environment.systemPackages = [ openvpn ];
229
230 boot.kernelModules = [ "tun" ];
231
232 };
233
234 }