diff --git a/README.md b/README.md index 862ff31..4440a16 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ -# Ansible playbook for remote OpenVPN deployment +# Ansible playbook for remote Wireguard deployment -The purpose of this playbook, affectionately referred to as "VPNator", is to help with the remote installation, deployment, stoppage and removal of OpenVPN on Debian based systems. Currently, only Debian 9 is sure to be supported. +The purpose of this playbook, affectionately referred to as "VPNator", is to help with the remote installation, deployment, stoppage and removal of WireGuard on Debian based systems. Currently, only up-to-date Debian 10 is sure to be supported. When run naively, ```bash -ansible-playbook vpNator.yml +ansible-playbook vpNator-wireguard.yml ``` this playbook prompts you for an action. You can : - 1. : Install OpenVPN's package, copy the configuration files required over to the server, launch and mount the tunnels. - 2. : Remove OpenVPN and wipe the configuration clean. - 3. : Stop, remove, install, and restart OpenVPN (doing step 2 then 3). - 4. : Stop OpenVPN without uninstall it. - 5. : Start OpenVPN (requires prior installation) + 1. : Install WireGuard's package (usually, to compile the associated kernel module), set up and configure + 2. : Start WireGuard and FireQOS + 3. : Stop WireGuard and FireQOS + 4. : Remove WireGuard and the other tools. + 5. : Uninstall and install everything (do a complete round of purge, install and launch) ## Technical Details @@ -20,12 +20,12 @@ This part of the document contains more explicit and detailed explanations of wh ### Technical generalities -The way VPNator is typically run at INSALan, a simple call to `ansible-playbook`, passing any flags if needed, is enough. However, if your remote user happens to need a sudo password, or the remote SSH connection requires a password (or keys you haven't loaded), then : +The way VPNator is typically run at INSALan, a simple call to `ansible-playbook` is enough. However, if your remote user happens to need a sudo password, or the remote SSH connection requires a password (or keys you haven't loaded), then : - Remember to load any keys you need to before you start the playbook ```bash ssh-add $PATH_TO_KEY/my_key ``` - - Launch Ansible with the `-k` or `--ask-pass` option. Ansible will prompt you for the password to use for the privilege ascension mechanism used (here, that is sudo). + - Launch Ansible with the `-K` or `--ask-pass` option. Ansible will prompt you for the password to use for the privilege ascension mechanism used (here, that is sudo). #### Adding remote hosts @@ -34,30 +34,29 @@ Our version of VPNator connects to any remote machine under the `vpn` inventory ``` [vpn] -vpn1 subnet=$SUBNET1 fournisseur=vpn1 ansible_user=debian -vpn2 subnet=$SUBNET2 fournisseur=vpn2 ansible_user=debian -ovh1 subnet=$SUBNET3 fournisseur=ovh1 ansible_user=debian +vpn1 id=1 +vpn2 id=3 +ovh1 id=100 ansible_user=bidule ``` -where `$SUBNETX` is a subnet prefix in the likes of `10.8.2.0`. The local host will take ip address 1 on that subnet on the interface corresponding with the VPN connection, and the VPN will take address 2. +The subnet used within a tunnel is computed using that id. It must be an integer between `1` and `254`. The local host will take ip address 1 on that subnet on the interface corresponding with the VPN connection, and the VPN will take address 2. -Note that, using Ansible's terminology, `vpn1` and so on are host names (which should ideally be aliased to ip addresses/resolvable domain names) list under the same "inventory". +This file can be generated beforehand by [vpn_register.py](vpn_register.py), which will also register the VPNs to other useful locations, like in `/etc/hosts`. -The `fournisseur` key is later only referred to as `ovpnNumber` in the playbook or "VPN number" in this document. +Note that, using Ansible's terminology, `vpn1` and so on are host names (which should ideally be aliased to ip addresses/resolvable domain names) list under the same "inventory". The `ansible_user` host variable is a documented ansible variable that indicates what user to log in as on that specific host. It is typically set to "debian", and when undefined that is the value taken. #### Modifying specific variables Various aspects of the playbook can be modified on a global level. - - The port on which OpenVPN should listen for incoming connections is modified around line 35 in the `port` variable - - The protocol used by OpenVPN to transmit data is determined by the variable `protocol` - - The variable `ovpnNumber` contains a unique identifier used to distinguish files from a specific VPS and its associated VPN - - Default arguments passed to OpenVPN are defined by the variable `default_args`. Additional arguments should be written in `extra_args`. + - The port on which WireGuard should listen for incoming connections is modified around line 37 in the `port` variable + - The address of the remote server on the tunnel is computed using their peer id in the pattern `10.8.id.1`. The local address on that tunnel is also computed around line 35. It is `10.8.id.2`. + - The local WireGuard interface is called `tunid` where `id` is the host id, and that can be changed in the `localhost_wg_interface` interface. -All of these variables can be set either by modifying the playbook, or by providing extra arguments (which will override those set in the playbook) using the `-e` or `--extra-vars` option of `ansible-playbook`, followed by pairs of `key=val` strings. For example, you can change the `extra_args` variable to provide additional parameters to openvpn at launch by using +All of these variables can be set either by modifying the playbook, or by providing extra arguments (which will override those set in the playbook) using the `-e` or `--extra-vars` option of `ansible-playbook`, followed by pairs of `key=val` strings. For example, you can change the `extra_args` variable to change the port WireGuard listens on ```bash -ansible-playbook vpNator.yml -e extra_args='--local 127.0.0.1' +ansible-playbook vpNator-wireguard.yml -e port=5010 ``` Using additional external arguments is recommended for one-time uses. When long-term persistent modifications are needed, modify the playbook/host inventory files themselves. @@ -66,32 +65,33 @@ Using additional external arguments is recommended for one-time uses. When long- It is also possible to limit the playbook's run to a particular machine among the group `vpn` (or any other you have chosen to use instead), using the `-l` option and providing individual hostnames or any pattern. Ansible will match individuals in the original pool of hosts and their data against that pattern, and only run the playbook for those successfully matched. -***BE CAREFUL : When limiting ansible to a set of hosts that does not contain localhost, the menu asking you which operation you want to execute will be bypassed. This can be fixed by adding 'localhost' in the list of restricted hosts.*** +***BE CAREFUL : When limiting ansible to a set of hosts that does not contain localhost, the menu asking you which operation you want to execute will be bypassed. This can be fixed by adding 'localhost' in the list of restricted hosts, or by providing activity tags.*** ### Structure of the Playbook This Ansible playbook is designed such that actions requested by the user will always follow the logical order they should be executed in. -When run without flags, the playbook launches a local play that prompts the user for an action to take (i.e. what flags to set). This play sits almost at the top of the playbook in order to be executed first when needed. +When run without tags, the playbook launches a local play that prompts the user for an action to take (i.e. what flags to set). This play sits almost at the top of the playbook in order to be executed first when needed. Once appropriate flags are set, the playbook begins. In order, it can - 1. Kill OpenVPN on both remote and local hosts - 2. Stop FireQOS from running - 3. Uninstall tools and their configuration files (mostly FireQOS) - 4. Remove the iptable rules that masquerades outbound traffic - 5. Uninstalling OpenVPN and purging its configuration - 6. Remove more directories in which we store OpenVPN files (.ovpn) or the certificate generation script, or more configuration files + 1. Bring remote and local WireGuard interfaces down, destroying all connections + 2. Stop FireQOS + 3. Uninstall and purge FireQOS + 4. Remove the forwarding networking rules + 5. Uninstalling WireGuard and purging its configuration + 6. Remove the interfaces we created for WireGuard, destroying their configuration, and removing the copies of the private keys we generated 7. Install additional packages typically used in production to probe and operate on the VPS - 8. Create a folder dedicated to storing VPN files - 9. Copy and run a certificate generation script - 10. Customize and copy OpenVPN configuration files - 11. Repatriate the customized OpenVPN files from the VPS - 12. Enable IP forwarding on the system - 13. Add IP table rules to masquerade outbound traffic - 14. Start OpenVPN on the VPS, and locally - 15. Start FireQOS - -You may notice that operations 1-2 correspond to a typical "stop", operations 3-6 correspond to an uninstallation, 7-13 to an installation, and 14-15 to a start. This way, when the user requests one of these operations, Ansible simply examines the conditions between the groups of actions above and skips over those that do not correspond. When an operation like a "stop & purge" is requested, everything beyond operation 6 is skipped. This way, none of the typical operations needed to handle VPN deployment and maintenance requires more than a single run of the playbook. + 8. Generating and storing local and remote encryption keys on the hard drive + 9. Installing the linux headers and DKMS modules for WireGuard, along with the utility called `wg` + 10. Creating and setting up network interfaces on both ends + 11. Set up addresses on both ends + 12. Let both ends know about each other + 13. Enable IP forwarding on the system + 14. Add IP table rules to masquerade outbound traffic + 15. Bring the WireGuard interfaces up on the VPS, and locally + 16. Start FireQOS + +You may notice that operations 1-2 correspond to a typical "stop", operations 3-6 correspond to an uninstallation, 7-14 to an installation, and 15-16 to a start. This way, when the user requests one of these operations, Ansible simply examines the conditions between the groups of actions above and skips over those that do not correspond. When an operation like a "stop & purge" is requested, everything beyond operation 6 is skipped. This way, none of the typical operations needed to handle VPN deployment and maintenance requires more than a single run of the playbook. In the next subsection, we go over the technical details of each of these actions the user can take and how they are realized. @@ -99,106 +99,87 @@ In the next subsection, we go over the technical details of each of these action #### Stopping - - **What does it do?** To be quite explicit, this action kills any process called `openvpn` on the VPS and local host, and disables FireQOS. - - - **How is it done?** The killing of openvpn on remote hosts is simply done using `pkill openvpn`. It sens a `SIGTERM` signal to the openvpn process, which handles it as a shutdown request. - - Locally, since a single openvpn process deals with a single VPN connection, we restrict the killing process by matching a pattern against the full command line that launched openvpn, containing the name (and VPN number) of the configuration file for the specific connection we want to terminate. + - **What does it do?** To be quite explicit, this action brings the remote and local WireGuard networking interfaces on the VPS and local host, and disables FireQOS. + - **How is it done?** After calling `ip link set X down`, the interfaces still exist, and their configuration remains, but all connections are suddenly killed. FireQOS has a command called `stop` that simply disables it. - - **How do I know it worked?** On both the VPS and the local host, you should observe that the virtual network devices `tunX` associated with your VPN are no longer present. A `pgrep openvpn` on the VPS should yield nothing. There is no specific way to detect whether FireQOS is stopped or not (since it's not a running process), but we have never observed any failure with `fireqos stop`. + - **How do I know it worked?** On both the VPS and the local host, you should observe that the virtual network devices `tunX` (or whatever you're calling it) associated with your VPN are no longer 'Up'. You can check that with `ip -c a` or `ip link`. There is no specific way to detect whether FireQOS is stopped or not (since it's not a running process), but we have never observed any failure with `fireqos stop` unless FireQOS wasn't started in the first place. #### Uninstallation - - **What does it do?** This action removes the `openvpn` package, configuration files, additional tools and any other file we may have transferred to run our VPN. + - **What does it do?** This action removes the `wireguard` package, additional tools and any other file we may have transferred to run our VPN. - **How is it done?** Most of the file deletion operations are run using Ansible's `file` module. FireQOS' configuration file is the first to go. Then the IP rule we added in order to enable Native Address Translation (or NAT) is removed using `iptables -t nat -D ...` (since our rule is present in the `nat` table; the omitted part contains the exact copy of a rule explained in details in the Installation operation). - Until recently, most of the tools typically used by people at INSALan were removed and purged alongside OpenVPN's packet using the `apt` module. This has caused issues in testing since some of these are dependencies for other programs, and caused massive interference (especially since a purge of configuration and data was requested). Notably, `openssl` is listed in the `packages` variable, and is a dependency for many IM server daemons. That being said, OpenVPN is still removed (but not purged), and later more files we sent over to run it are removed : - - `/etc/openvpn` which contains all of the configuration OpenVPN uses - - `/etc/sysctl.d/30-openvpn-forward.conf` which contains (we assume) system rules related to the forwarding of packets to and from openvpn. - - `/home/vpn` where the certificate generation script is copied and run - - `/home/client-{{ ovpnNumber }}` which is the OpenVPN configuration file for the specific VPN with number `ovpnNumber` + Until recently, most of the tools typically used by people at INSALan were removed and purged alongside WireGuard's packet using the `apt` module. This has caused issues in testing since some of these are dependencies for other programs, and caused massive interference (especially since a purge of configuration and data was requested). Notably, `openssl` is listed in the `packages` variable, and is a dependency for many IM server daemons. - - **How do I know it worked?** If this operation worked, `apt search openvpn` should show you that the package `openvpn` is not installed. You should see none of the nodes listed above in the file system. Running `sudo iptables-save` should not show you the iptable line used to enable NAT (although if it does it is not a problem). + - **How do I know it worked?** If this operation worked, `apt search wireguard` should show you that the package `wireguard` is no longer installed. You should see none of the nodes listed above in the file system. Running `sudo iptables-save` should not show you the iptable line used to enable NAT. #### Installation - - **What does it do?** This action installs our typically useful programs, then OpenVPN. It copies a lot of configuration files over to the VPS, and a bash script to generate a certificate. Some network rules are set as well. + - **What does it do?** This action installs our typically useful programs, then WireGuard. It copies some configuration files over to the VPS, for FireQOS. Some network rules are set as well. - **How is it done?** First and foremost the `apt` module is used to install all of the packages in the `packages` variable, after an update of the package list is done. This is typically very slow, especially if it is run for the first time on a VPS. - ***An important note on our call to the apt module*** : We pass the option `install_recommends` and set it to `false` since one of the recommended packages of `fireqos`, `firehol` (but not `firehol-common`), has a habit of ***locking us outside of the VPS***. We prevent this package from being installed, and especially run, by asking ansible not to install recommended packages alongside the ones we ask it to install. It also makes things run a little faster. + ***An important note on our call to the apt module*** : We pass the option `install_recommends` and set it to `false` since one of the recommended packages of `fireqos`, `firehol` (but not `firehol-common`), has a habit of ***locking us outside of the VPS***. We prevent this package from being installed, and especially run, by asking ansible not to install recommended packages alongside the ones we ask it to install. It also makes things run a little faster. - A directory called `vpn` is created in the `home` directory using the `file` module. Then, a certificate generation script is customized and copied over to the VPS where it is run. More files are copied as well : - - `server.conf.j2` is customized and copied to `/etc/openvpn` to act as the configuration file for the openvpn server - - `client.conf.j2` is customized and copied to `/home/vpn` to act as the configuration file for a specific openvpn client - - `dh.pem` is copied to `/etc/openvpn`. It is a typical Diffie-Hellman parameters file which seems to contain a certificate. For our purposes, we do not care about its strength or privacy. + The configuration file `template/fireqos_vpn.conf` is sent over to the server in `/etc/firehold/`. Note that the remote interface name is hardcoded in order to be compatible with the OpenVPN version of this playbook. - When we talk about "customizing", the reader should understand that specific fields delineated by `{{...}}` tags are detected in the files mentioned for the value stored in the variable `...` by Ansible. This way, for example, the `client.conf.j2` file can take the name `client-?.ovpn` where `?` is the VPN number corresponding to a specific instance. + ***Building the kernel module*** + WireGuard works using a kernel module that is responsible for handling very specific types of network interfaces. One can create these interfaces with + ```sh + ip link add wg0 type wireguard + ``` + And this what we do. But in order for this to work, the type `wireguard` must be known to the Linux kernel at run-time. A kernel module called `wireguard.ko` must be loaded. You can check whether that is the case using + ```sh + lsmod | grep wireguard + ``` + Installing the `wireguard` apt package does not necessarily install said module. With versions of Debian that have a linux kernel above version `5.6.0`, the module is shipped along with your installation of the kernel. If that is not the case (and currently for Debian 10 stable without backports it isn't), the `wireguard` package depends on another one called `wireguard-dkms`. When it is unpacked, it compiles the source code for the `wireguard` kernel module, installs it and loads it. Note that `bc` is a required dependency, that is why it is listed among the needed tools. - Once all of this is in place, the `.ovpn` client configuration file is modified once more so that the `tun` device name takes on a unique number at the end. That number corresponds to the place of a specific instance in the inventory of hosts listed in the `vpn` group. When this modification is done, that `.ovpn` file is copied back onto the local host into a folder called `openvpn_files` (which is created when it does not exist), located above the directory where Ansible is run. + Since kernel module compilation requires a version of the linux kernel headers that matches the kernel currently running, it is your job when deploying WireGuard to know whether the headers corresponding to the currently running version can be installed. Our playbook does attempt to use `uname -r` to figure out the kernel version and install the appropriate headers. If you cannot, it will be impossible to run WireGuard without excruciating pain. - IP forwarding is enabled by the kernel after the value 1 is "cat-ed" into `/proc/sys/net/ipv4/ip_forward`. Only IPv4 forwarding is enabled, but this is not (yet) an issue. - Finally, an IP table rule is added to the IP table `nat` in the `POSTROUTING` chain to enable masquerading (i.e. substitution of actual sender address in IP headers with the machine's IP and the opposite for inbound traffic) whenever a packet leaves through the external network interface (usually called `en*` in Debian). + ***Network Forwarding Rules*** - - **How do I know it worked?** Typically when everything here worked out, Ansible will not show any error, and you'll notice that every file mentioned above is at the right place. Running `iptables-save` will show the IP table rule line mentioned (at least once), and the `.ovpn` files will all be present in `openvpn_files` above the directory where you ran the playbook. + IP forwarding is enabled by the kernel after the value 1 is "cat-ed" into `/proc/sys/net/ipv4/ip_forward`. Only IPv4 forwarding is enabled, but this is not (yet) an issue. + + Finally, an IP table rule is added to the IP table `nat` in the `POSTROUTING` chain to enable masquerading (i.e. substitution of actual sender address in IP headers with the machine's IP and the opposite for inbound traffic) whenever a packet leaves through the external network interface (usually called `en*` or `eth*` in Debian). + + - **How do I know it worked?** Typically when everything here worked out, Ansible will not show any error, and you'll notice that every file mentioned above is at the right place. Running `iptables-save` will show the IP table rule line mentioned (at least once). #### Launching - - **What does it do?** The last action launches OpenVPN and FireQOS on both the local and remote hosts. - - **How is it done?** On the remote host, OpenVPN is started by running the OpenVPN binary. The only argument provided is the path to the server configuration file uploaded during installation. + - **What does it do?** The last action brings both WireGuard interfaces up and starts FireQOS on both the local and remote hosts. + - **How is it done?** The following command brings the interface `wg69` up ```bash - /usr/sbin/openvpn --config /etc/openvpn/server.conf --daemon + ip link set wg69 up ``` - The `daemon` option ensures that `openvpn` forks and runs in the background, detached from the shell access granted to the Ansible 'command' module. - - Locally, OpenVPN is launched with a couple of different options. Among the default arguments are : - - The path to the client configuration file `client-{{ ovpnNumber }}.ovpn` for obvious reasons - - `route-noexec` is a flag that prevents OpenVPN from setting IP routes automatically on the client to redirect network traffic. Other scripts in the deployment procedure at INSALan handle IP rules and IP routes in exactly the way we want to. - - `daemon` such that OpenVPN runs in the background. - - Supplementary arguments can be added by writing them in the `extra_args` variable in the beginning of the playbook. + This is done on both local and remote hosts, such that connections can be initiated using both interfaces. And finally FireQOS is started by simply using `fireqos start` on the remote host. Remember that FireQOS does not actually run a process, so `fireqos start` simply enables the tools we use for monitoring on remote systems. - - **How do I know it worked?** A `pgrep` on both the remote and local hosts should yield at least one process ID on each machine (and hopefully only one). The network interfaces `tunX` should be visible on both ends of the tunnel using `ip -c a` (or any command to display the list of network interfaces). You should be able to log onto the remote host by using the following command where `
` stands for the IPv4 address assigned to the `tunX` network interface of said remote host - ```bash - ssh debian@ - ``` - If this works you have successfully logged onto the remote host through the VPN tunnel. Further testing is usually done by trying to access the global internet (typically websites such as your favorite search engine) in order to check that the VPN actually forwards network traffic. + + - **How do I know it worked?** The network interfaces `tunX` (or whatever you called it) should be 'UP' on both ends of the tunnel. You can check using `ip -c a` (or any command to display the list of network interfaces). You should be able to log onto the remote host by using the following command where `` stands for the IPv4 address assigned to the `tunX` network interface of said remote host + ```bash + ssh debian@ + ``` + If this works you have successfully logged onto the remote host through the VPN tunnel. Further testing is usually done by trying to access the global internet (typically websites such as your favorite search engine) in order to check that the VPN actually forwards network traffic. ### An explanation of the IP table rule As described in the documentation of `iptables`, the `nat` table is consulted whenever a packet creates a new connection. When NAT is enabled at that stage, the remainder of the connection is done under the impression of the external peer (i.e. not our VPS) that it is legitimately talking to the VPS itself, when, in actuality, some packages are emitted by players in the LAN, rise into our VPN tunnels, exit at the VPS, undergo masquerade, and then leave. -Specifically, the `POSTROUTING` chain is called just as packets are about to leave. This makes sense, since we only wish to masquerade packets that openvpn, running on the VPS, emits to the outside world, stemming from data circulating in the tunnels. Moreover, those packets should only be masqueraded whenever they leave the VPS and try and contact the outside world. Since *we are always supposed to make first contact in any connection that transits through OpenVPN*, it makes sense to only masquerade those packets that will leave for the `en+` interface. +Specifically, the `POSTROUTING` chain is called just as packets are about to leave. This makes sense, since we only wish to masquerade packets that WireGuard, running on the VPS, emits to the outside world, stemming from data circulating in the tunnels. Moreover, those packets should only be masqueraded whenever they leave the VPS and try and contact the outside world. Since *we are always supposed to make first contact in any connection that transits through WireGuard*, it makes sense to only masquerade those packets that will leave for the `e+` interface. All of these requirements explain the following line of IP table rule : ```bash -iptables -t nat -A POSTROUTING -o en+ -j MASQUERADE +iptables -t nat -A POSTROUTING -o e+ -j MASQUERADE ``` What is not explained here is simple `iptables` syntax : the `-t` parameter gives the table (here it's `nat`), the `-A` parameter indicates that we should append to the chain provided (here `POSTROUTING`) and the rest is the rule. Whenever the requirements (that a packet establishing a connection be about to leave for the outside world, the action listed after `-j` is -### The certificate generation script - -During the installation procedure, a shell script called `certgen.sh` is copied remotely, and later on executed. Its inner workings are detailed in this section. - -This script is the last standing piece of shell script left from the original "Roadwarrior" OpenVPN script from which we created the ansible playbook. Both authors of the playbook decided to keep all of the operations related to certificate generation in that script, and to launch it remotely, in order to keep the playbook relatively clear. - -The script starts by downloading and inflating an archive from OpenVPN's GitHub repository for the `easy-rsa` program. The contents of that archive are then copied over to `/etc/openvpn/easyrsa` where we operate. - -Easy-RSA is used at first to generate what is called a 'PKI', or 'Public Key Infrastructure'. This is a system that wherein a Certificate Authority (CA) can receive certificate signing requests (CSRs) and generate certificates and certificate revocation lists (CRLs). These certificates are what interest us here. - -A CA is built before the script generates two certificates (one for the server and one for the client), along with a CRL. Once the CA and both certificates have been built, they are copied into `/etc/openvpn` and their content is copied into the `.ovpn` client configuration file (between appropriate tags), so that the client has copies of these certificates too. - ## Common troubleshooting The following is a short list of typical errors that are encountered while using VPNator. - - Skipped errors during stop operation : VPNator tries to `pkill openvpn` on the remote host. If OpenVPN is not running (either because you haven't started it yet or because you manually killed it, or maybe it crashed), this will fail, and Ansible will catch that failure. To prevent the playbook from failing when that happens, this error is ignored. The same thing is done for `fireqos stop`. + - Skipped errors during stop operation : VPNator tries to `ip link set down` on the remote host when they don't exist. If WireGuard is not running (either because you haven't started it yet or because you manually killed it, or maybe it crashed but we've never seen that yet), this will fail, and Ansible will catch that failure. To prevent the playbook from failing when that happens, this error is ignored. The same thing is done for `fireqos stop`. - No traffic goes through one or more of the VPNs but the tunnels work : this should not happen any more, but do check that both the IP table rule and IP forwarding are set as expected. - -## To-do list - - Upgrade Easy-RSA in the certificate generation script to version 3.0.6. diff --git a/ansible_scripts/vpNator.yml b/ansible_scripts/vpNator.yml index 5f734f8..acbc611 100644 --- a/ansible_scripts/vpNator.yml +++ b/ansible_scripts/vpNator.yml @@ -13,58 +13,57 @@ gather_facts: no tasks: - name: Check args - pause: + pause: prompt: "WARNING: It looks like you didn't used any tag.\n You can use the argument '--tags tag1,tag2,...' to sepcify one or more action to run\n What do you want to do now?\n\n - [1]: Install OpenVPN (and configure it) (--tags install)\n - [2]: Remove OpenVPN (--tags uninstall)\n - [3]: Uninstall and reinstall OpenVPN (--tags install,uninstall)\n - [4]: Stop OpenVPN (--tags stop)\n - [5]: Start the tunnels and FireqOS (--tags start)\n - [6]: Just change current config (--tags reconf)" + \t[1]: Install Wireguard and other tools (--tags install)\n + \t[2]: Start Wireguard and FireQOS (--tags start)\n + \t[3]: Stop Wireguard and FireQOS (--tags stop)\n + \t[4]: Remove Wireguard and other tools (--tags uninstall)\n + \t[5]: Uninstall & Install (--tags install,uninstall)\n + Please select exactly one" register: pause_result - - - # Et maintenant on s'occupe vraiment des VPNs - hosts: vpn remote_user: debian + gather_facts: no vars: - subnet: "{{ hostvars[inventory_hostname].subnet }}" + peer_number: "{{ hostvars[inventory_hostname].id }}" + tunnel_address: "10.8.{{ peer_number }}.1" + local_address: "10.8.{{ peer_number }}.2" + localhost_wg_interface: "tun{{ peer_number }}" port: 5010 - protocol: tcp - ovpnNumber: "{{ hostvars[inventory_hostname].fournisseur }}" - packages: ['openvpn', 'iptables', 'openssl', 'htop', 'tmux', 'screen', 'neovim', 'hexedit', 'vim', 'emacs', 'fireqos', 'iftop', 'bmon', 'curl', 'ca-certificates', 'iperf3'] - # OPENVPN - default_args: --route-noexec --daemon - extra_args: + # bc is required to build wireguard-dkms, but not an apt dependency + packages: ['bc', 'nftables', 'openssl', 'htop', 'tmux', 'screen', 'neovim', 'hexedit', 'vim', 'emacs', 'fireqos', 'iftop', 'bmon', 'curl', 'ca-certificates', 'iperf3'] + localhost_wireguard_public: "{{ hostvars.localhost.localhost_wireguard_public.stdout }}" tasks: # ****************************** UNINSTALL ************************************* - + - name: === STOP === block: - - name: Kill OpenVPN on remote + - name: Bring remote wireguard interface down become: true become_method: sudo - command: pkill openvpn + command: ip link set tun0 down ignore_errors: true - - - name: Kill OpenVPN on localhost # FIXME : Figure out whether you can only kill for one tunnel + + - name: Stop FireQOS on remote become: true become_method: sudo - command: pkill -f "openvpn --config ../openvpn_files/client-{{ ovpnNumber }}.ovpn" - delegate_to: localhost + command: fireqos stop ignore_errors: true - - - name: Stop FireQOS on remote + + - name: Bring local wg interface down become: true become_method: sudo - command: fireqos stop - - when: (hostvars.localhost.pause_result is defined and (hostvars.localhost.pause_result.user_input == "2" or hostvars.localhost.pause_result.user_input == "3" or hostvars.localhost.pause_result.user_input == "4" or hostvars.localhost.pause_result.user_input == "6") ) or hostvars.localhost.pause_result is not defined - tags: [ uninstall, stop, reconf ] - + command: ip link set "{{ localhost_wg_interface }}" down + delegate_to: localhost + ignore_errors: true + + when: hostvars.localhost.pause_result is not defined or hostvars.localhost.pause_result.user_input in ["3", "4", "5"] + tags: [ uninstall, stop ] + - name: === Uninstall === block: - name: Remove Additional Tools Config (FireQOS) @@ -77,7 +76,7 @@ - name: Remove IP Table rule for NAT become: true become_method: sudo - command: iptables -t nat -D POSTROUTING -o en+ -j MASQUERADE + command: nft delete chain insalan masquerading #FIXME catch both eth+ and en+, but better ignore_errors: true #- name: Uninstall additional tools @@ -94,106 +93,174 @@ # machines where these other packages are dependencies of # working services, which configuration will get wiped. # Source : the apache2 server I accidentally wiped clean from my VPS - + # Uninstall script begins here - - name: Uninstall and purge OpenVPN + # Note: destroying the interface removes the peer remotely + - name: Delete remote wireguard interface + become: true + become_method: sudo + shell: ip link del tun0 + ignore_errors: true + # We don't care if it doesn't exist + + - name: Delete local wireguard interface + become: true + become_method: sudo + shell: "ip link del {{ localhost_wg_interface }}" + ignore_errors: true + delegate_to: localhost + + - name: Remove local wireguad private key + become: true + become_method: sudo + file: + state: absent + path: "/tmp/wireguard_private_{{ peer_number }}" + ignore_errors: true + delegate_to: localhost + + - name: Uninstall and purge wireguard become: true become_method: sudo apt: - name: openvpn - purge: True + name: wireguard + purge: true state: absent - - name: Remove OVPN config files and VPN directory + - name: Remove wireguard private key file become: true become_method: sudo file: state: absent - path: "{{ item }}" - loop: - - /etc/openvpn - - /etc/sysctl.d/30-openvpn-forward.conf - - /home/vpn - - /home/client-{{ ovpnNumber }} - - when: (hostvars.localhost.pause_result is defined and (hostvars.localhost.pause_result.user_input == "2" or hostvars.localhost.pause_result.user_input == "3")) or hostvars.localhost.pause_result is not defined + path: /root/wireguard_private + + when: hostvars.localhost.pause_result is not defined or hostvars.localhost.pause_result.user_input in ["4", "5"] tags: [ uninstall ] - - + + # ************************ INSTALL ***************************************** - - name: Install packages and utilities - become: true - become_method: sudo - apt: - name: "{{ packages }}" - state: present - install_recommends: false # DO NOT LET IT INSTALL FIREHOL, DO NOT. (cf. Readme) - update_cache: yes - when: (hostvars.localhost.pause_result is defined and (hostvars.localhost.pause_result.user_input == "1" or hostvars.localhost.pause_result.user_input == "3" )) or hostvars.localhost.pause_result is not defined - tags: [ install ] - - name: === Install === block: - - name: Create a folder for the VPN software + - name: === Localhost Install === + block: + - name: Generate localhost wireguard key + become: true + become_method: sudo + shell: "wg genkey > /tmp/wireguard_private_{{ peer_number }}" + + - name: Create localhost interface + become: true + become_method: sudo + shell: "ip link add {{ localhost_wg_interface }} type wireguard" + + - name: Give localhost wireguard interface address 2 + become: true + become_method: sudo + shell: "ip addr add {{ local_address }}/24 dev {{ localhost_wg_interface }}" + + - name: Set localhost private key + become: true + become_method: sudo + shell: "wg set {{ localhost_wg_interface }} private-key /tmp/wireguard_private_{{ peer_number }}" + + - name: Obtain localhost public key + become: true + become_method: sudo + shell: "wg pubkey < /tmp/wireguard_private_{{ peer_number }}" + register: localhost_wireguard_public + delegate_to: localhost + + - name: Install packages and utilities become: true become_method: sudo - file: - path: /home/vpn - state: directory + apt: + name: "{{ packages }}" + state: present + install_recommends: false # DO NOT LET IT INSTALL FIREHOL, DO NOT. (cf. Readme) + update_cache: yes - - name: Copy the key generation script + - name: Apt full upgrade become: true become_method: sudo - template: src=templates/certgen.sh.j2 dest=/home/vpn/certgen.sh - - - name: Copy the server.conf to the remote server + apt: + upgrade: full + state: latest + install_recommends: false + + - name: Figure out kernel version + shell: uname -r + register: remote_kernel_version + + # For some reason this makes ansible crash + - name: Install appropriate linux headers become: true become_method: sudo - template: src=templates/server.conf.j2 dest=/etc/openvpn/server.conf - #owner=root - #mode=0777 - - - name: Copy the client.ovpn file to the server to apply the template + apt: + name: "linux-headers-{{ remote_kernel_version.stdout }}" + state: present + install_recommends: false + + - name: Install wireguard and compile DKMS module become: true become_method: sudo - template: - src: templates/client.conf.j2 - dest: "/home/vpn/client-{{ ovpnNumber }}.ovpn" + apt: + name: "wireguard" + state: present + install_recommends: false - - name: Copy FireQoS config + - name: Create remote interface become: true become_method: sudo - template: - src: templates/fireqos_vpn.conf - dest: /etc/firehol/fireqos.conf + shell: ip link add tun0 type wireguard - - name: Generate the keys + - name: Give remote interface an address become: true become_method: sudo - command: bash /home/vpn/certgen.sh - - - name: Send dh.pem + shell: ip addr add "{{ tunnel_address }}/24" dev tun0 + + - name: Set remote wireguard port become: true become_method: sudo - template: - src: templates/dh.pem - dest: "/etc/openvpn/dh.pem" + shell: wg set tun0 listen-port "{{ port}}" + + - name: Create wireguard private key + become: true + become_method: sudo + shell: wg genkey > /root/wireguard_private + + - name: Set wireguard private key + become: true + become_method: sudo + shell: wg set tun0 private-key /root/wireguard_private + + - name: Add localhost as peer + become: true + become_method: sudo + shell: wg set tun0 peer "{{ localhost_wireguard_public.stdout }}" allowed-ips 10.0.1.100/0 # Only allow server + + - name: Obtain peer public key + become: true + become_method: sudo + shell: wg pubkey < /root/wireguard_private + register: remote_public_wireguard - - name: Rename dev tun + - name: Add peer to localhost become: true become_method: sudo - lineinfile: - path: "/home/vpn/client-{{ ovpnNumber }}.ovpn" - regexp: '^dev tun' - line: "dev tun{{groups['vpn'].index(inventory_hostname) + 1}}" + shell: wg set "{{ localhost_wg_interface }}" peer "{{ remote_public_wireguard.stdout }}" persistent-keepalive 30 allowed-ips 0.0.0.0/0 endpoint "{{ inventory_hostname }}:{{ port }}" # Allow to route everything + delegate_to: localhost - - name: Repatriate .ovpn files + - name: Copy FireQoS config become: true become_method: sudo - fetch: - src: "/home/vpn/client-{{ ovpnNumber }}.ovpn" - dest: ../openvpn_files/ - flat: true + template: + src: templates/fireqos_vpn.conf + dest: /etc/firehol/fireqos.conf + + #- name: Substitute 'tun0' for 'wg0' + #become: true + #become_method: sudo + #shell: sed -i "s/tun0/wg0/" /etc/firehol/fireqos.conf - name: Enable IP Forward become: true @@ -203,38 +270,37 @@ - name: IP Table Rule become: true become_method: sudo - command: iptables -t nat -A POSTROUTING -o en+ -j MASQUERADE + shell: | + nft add table ip insalan + nft -- add chain insalan masquerading "{ type nat hook postrouting priority 100; }" + nft add rule insalan masquerading oifname "e*" masquerade + args: + executable: /bin/bash + + when: hostvars.localhost.pause_result is not defined or hostvars.localhost.pause_result.user_input in ["1", "5"] + tags: [ install ] - when: (hostvars.localhost.pause_result is defined and (hostvars.localhost.pause_result.user_input == "1" or hostvars.localhost.pause_result.user_input == "3" or hostvars.localhost.pause_result.user_input == "6")) or hostvars.localhost.pause_result is not defined - tags: [ install, reconf ] - # ****************** MOUNT ********************** - - name: === Mounting === + - name: === Start === block: - - name: Start openvpn on the remote server + - name: Start localhost wireguard interface become: true become_method: sudo - command: - cmd: "/usr/sbin/openvpn --config /etc/openvpn/server.conf --daemon" - chdir: /etc/openvpn - #command: systemctl restart openvpn - #@server.service) - - - name: Deploy tunnels on the local host + shell: "ip link set {{ localhost_wg_interface }} up" + delegate_to: localhost + + - name: Bring tun0 interface up become: true become_method: sudo - command: "/usr/sbin/openvpn --config ../openvpn_files/client-{{ ovpnNumber }}.ovpn {{ default_args }} {{ extra_args}}" - # WARNING : If you modify the beginning of this line (between openvpn and .ovpn), remember to modify the openvpn pkill - # line for local host up above - delegate_to: localhost - + shell: ip link set tun0 up + - name: Start FireQOS become: true become_method: sudo command: "fireqos start" - - when: (hostvars.localhost.pause_result is defined and (hostvars.localhost.pause_result.user_input == "1" or hostvars.localhost.pause_result.user_input == "3" or hostvars.localhost.pause_result.user_input == "5" or hostvars.localhost.pause_result.user_input == "6")) or hostvars.localhost.pause_result is not defined - tags: [ install, start, reconf] - + + when: hostvars.localhost.pause_result is not defined or hostvars.localhost.pause_result.user_input in ["1", "2", "5"] + tags: [ install, start ] + diff --git a/vpn_register.py b/vpn_register.py new file mode 100755 index 0000000..b8f636b --- /dev/null +++ b/vpn_register.py @@ -0,0 +1,45 @@ +#!/bin/python3 +import subprocess +etc_hosts_filename = "/etc/hosts" +etc_ansible_hosts_filename = "/etc/ansible/hosts" +etc_firehol_vpn_list_filename = "/etc/firehol/vpn_list" +vpn_list = [] +while True : + a=input("Entrez l'adresse d'un vpn (touche A pour finir) \n") + if a=="A": + break + vpn_list.append(a) + +file = open(etc_hosts_filename, "r") +file_content = file.readlines() +file_right_content = [] +for line in file_content: + if "vpn" not in line : + file_right_content.append(line) + +file.close() + +file = open(etc_hosts_filename, "w") +for line in file_right_content : + file.write(line) +i = 1 +for ip_vpn in vpn_list : + file.write(ip_vpn + " vpn" + str(i) + "\n") + i+=1 +file.close() + +file = open(etc_ansible_hosts_filename, "w") +file.write("[vpn] \n\n") +for i in range (len(vpn_list)) : + file.write("vpn" + str(i+1) + " id=" + str(i+1) + "\n") +file.close() + +file = open(etc_firehol_vpn_list_filename, "w") +for i in range(len(vpn_list)) : + file.write("vpn" + str(i+1) + "=" + vpn_list[i] +"\n") +file.close() + +for i in range(len(vpn_list)) : + s = "ssh-keygen -R vpn" + str(i+1) + subprocess.run(args = s, shell = True) +