at 23.05-pre 6.5 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 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 #! /bin/sh 18 export PATH=${path} 19 20 # For convenience in client scripts, extract the remote domain 21 # name and name server. 22 for var in ''${!foreign_option_*}; do 23 x=(''${!var}) 24 if [ "''${x[0]}" = dhcp-option ]; then 25 if [ "''${x[1]}" = DOMAIN ]; then domain="''${x[2]}" 26 elif [ "''${x[1]}" = DNS ]; then nameserver="''${x[2]}" 27 fi 28 fi 29 done 30 31 ${cfg.up} 32 ${optionalString cfg.updateResolvConf 33 "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"} 34 ''; 35 36 downScript = '' 37 #! /bin/sh 38 export PATH=${path} 39 ${optionalString cfg.updateResolvConf 40 "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"} 41 ${cfg.down} 42 ''; 43 44 configFile = pkgs.writeText "openvpn-config-${name}" 45 '' 46 errors-to-stderr 47 ${optionalString (cfg.up != "" || cfg.down != "" || cfg.updateResolvConf) "script-security 2"} 48 ${cfg.config} 49 ${optionalString (cfg.up != "" || cfg.updateResolvConf) 50 "up ${pkgs.writeScript "openvpn-${name}-up" upScript}"} 51 ${optionalString (cfg.down != "" || cfg.updateResolvConf) 52 "down ${pkgs.writeScript "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.iproute2 pkgs.nettools ]; 67 68 serviceConfig.ExecStart = "@${openvpn}/sbin/openvpn openvpn --suppress-timestamps --config ${configFile}"; 69 serviceConfig.Restart = "always"; 70 serviceConfig.Type = "notify"; 71 }; 72 73in 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 = literalExpression '' 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 = lib.mdDoc '' 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 `openvpn-«name».service`, 123 where «name» 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 = lib.mdDoc '' 134 Configuration of this OpenVPN instance. See 135 {manpage}`openvpn(8)` 136 for details. 137 138 To import an external config file, use the following definition: 139 `config = "config /path/to/config.ovpn"` 140 ''; 141 }; 142 143 up = mkOption { 144 default = ""; 145 type = types.lines; 146 description = lib.mdDoc '' 147 Shell commands executed when the instance is starting. 148 ''; 149 }; 150 151 down = mkOption { 152 default = ""; 153 type = types.lines; 154 description = lib.mdDoc '' 155 Shell commands executed when the instance is shutting down. 156 ''; 157 }; 158 159 autoStart = mkOption { 160 default = true; 161 type = types.bool; 162 description = lib.mdDoc "Whether this OpenVPN instance should be started automatically."; 163 }; 164 165 updateResolvConf = mkOption { 166 default = false; 167 type = types.bool; 168 description = lib.mdDoc '' 169 Use the script from the update-resolv-conf package to automatically 170 update resolv.conf with the DNS information provided by openvpn. The 171 script will be run after the "up" commands and before the "down" commands. 172 ''; 173 }; 174 175 authUserPass = mkOption { 176 default = null; 177 description = lib.mdDoc '' 178 This option can be used to store the username / password credentials 179 with the "auth-user-pass" authentication method. 180 181 WARNING: Using this option will put the credentials WORLD-READABLE in the Nix store! 182 ''; 183 type = types.nullOr (types.submodule { 184 185 options = { 186 username = mkOption { 187 description = lib.mdDoc "The username to store inside the credentials file."; 188 type = types.str; 189 }; 190 191 password = mkOption { 192 description = lib.mdDoc "The password to store inside the credentials file."; 193 type = types.str; 194 }; 195 }; 196 }); 197 }; 198 }; 199 200 }); 201 202 }; 203 204 }; 205 206 207 ###### implementation 208 209 config = mkIf (cfg.servers != {}) { 210 211 systemd.services = listToAttrs (mapAttrsFlatten (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers); 212 213 environment.systemPackages = [ openvpn ]; 214 215 boot.kernelModules = [ "tun" ]; 216 217 }; 218 219}