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