diff --git a/README.md b/README.md index be4ce6b..2308c31 100644 --- a/README.md +++ b/README.md @@ -123,9 +123,9 @@ Install strongSwan, then copy the included ipsec_user.conf, ipsec_user.secrets, #### Ubuntu Server 18.04 example 1. `sudo apt-get install strongswan libstrongswan-standard-plugins`: install strongSwan -2. `/etc/ipsec.d/certs`: copy `.crt` from `algo-master/configs//pki/certs/.crt` -3. `/etc/ipsec.d/private`: copy `.key` from `algo-master/configs//pki/private/.key` -4. `/etc/ipsec.d/cacerts`: copy `cacert.pem` from `algo-master/configs//pki/cacert.pem` +2. `/etc/ipsec.d/certs`: copy `.crt` from `algo-master/configs//ipsec/manual/.crt` +3. `/etc/ipsec.d/private`: copy `.key` from `algo-master/configs//ipsec/manual/.key` +4. `/etc/ipsec.d/cacerts`: copy `cacert.pem` from `algo-master/configs//ipsec/manual/cacert.pem` 5. `/etc/ipsec.secrets`: add your `user.key` to the list, e.g. ` : ECDSA .key` 6. `/etc/ipsec.conf`: add the connection from `ipsec_user.conf` and ensure `leftcert` matches the `.crt` filename 7. `sudo ipsec restart`: pick up config changes @@ -160,7 +160,7 @@ If you turned on the optional SSH tunneling role, then local user accounts will Use the example command below to start an SSH tunnel by replacing `user` and `ip` with your own. Once the tunnel is setup, you can configure a browser or other application to use 127.0.0.1:1080 as a SOCKS proxy to route traffic through the Algo server. - `ssh -D 127.0.0.1:1080 -f -q -C -N user@ip -i configs/ip_user.ssh.pem` + `ssh -D 127.0.0.1:1080 -f -q -C -N user@ip -i configs//ssh-tunnel/.pem` ## SSH into Algo Server diff --git a/config.cfg b/config.cfg index 15aa586..bfcaac6 100644 --- a/config.cfg +++ b/config.cfg @@ -18,8 +18,14 @@ keys_clean_all: False # Clean up cloud python environments clean_environment: false -vpn_network: 10.19.48.0/24 -vpn_network_ipv6: 'fd9d:bc11:4020::/48' +# Deploy StrongSwan to enable IPsec support +ipsec_enabled: true + +# StrongSwan log level +# https://wiki.strongswan.org/projects/strongswan/wiki/LoggerConfiguration +strongswan_log_level: 2 + +# Deploy WireGuard wireguard_enabled: true wireguard_port: 51820 # If you're behind NAT or a firewall and you want to receive incoming connections long after network traffic has gone silent. @@ -36,10 +42,6 @@ wireguard_PersistentKeepalive: 0 # See: https://github.com/trailofbits/algo/blob/master/docs/troubleshooting.md#various-websites-appear-to-be-offline-through-the-vpn reduce_mtu: 0 -# StrongSwan log level -# https://wiki.strongswan.org/projects/strongswan/wiki/LoggerConfiguration -strongswan_log_level: 2 - # Algo will use the following lists to block ads. You can add new block lists # after deployment by modifying the line starting "BLOCKLIST_URLS=" at: # /usr/local/sbin/adblock.sh @@ -90,10 +92,6 @@ unattended_reboot: enabled: false time: 06:00 -pkcs12_PayloadCertificateUUID: "{{ 900000 | random | to_uuid | upper }}" -VPN_PayloadIdentifier: "{{ 800000 | random | to_uuid | upper }}" -CA_PayloadIdentifier: "{{ 700000 | random | to_uuid | upper }}" - # Block traffic between connected clients BetweenClients_DROP: true diff --git a/input.yml b/input.yml index 34ae4f6..5cc6017 100644 --- a/input.yml +++ b/input.yml @@ -48,30 +48,45 @@ when: - server_name is undefined - algo_provider != "local" + - block: + - pause: + prompt: | + Do you want macOS/iOS IPsec clients to enable "Connect On Demand" when connected to cellular networks? + [y/N] + register: _ondemand_cellular + when: ondemand_cellular is undefined - - pause: - prompt: | - Do you want macOS/iOS IPsec clients to enable "Connect On Demand" when connected to cellular networks? - [y/N] - register: _ondemand_cellular - when: ondemand_cellular is undefined + - pause: + prompt: | + Do you want macOS/iOS IPsec clients to enable "Connect On Demand" when connected to Wi-Fi? + [y/N] + register: _ondemand_wifi + when: ondemand_wifi is undefined - - pause: - prompt: | - Do you want macOS/iOS IPsec clients to enable "Connect On Demand" when connected to Wi-Fi? - [y/N] - register: _ondemand_wifi - when: ondemand_wifi is undefined + - pause: + prompt: | + List the names of any trusted Wi-Fi networks where macOS/iOS IPsec clients should not use "Connect On Demand" + (e.g., your home network. Comma-separated value, e.g., HomeNet,OfficeWifi,AlgoWiFi) + register: _ondemand_wifi_exclude + when: + - ondemand_wifi_exclude is undefined + - (ondemand_wifi|default(false)|bool) or + (booleans_map[_ondemand_wifi.user_input|default(omit)]|default(false)) - - pause: - prompt: | - List the names of any trusted Wi-Fi networks where macOS/iOS IPsec clients should not use "Connect On Demand" - (e.g., your home network. Comma-separated value, e.g., HomeNet,OfficeWifi,AlgoWiFi) - register: _ondemand_wifi_exclude - when: - - ondemand_wifi_exclude is undefined - - (ondemand_wifi|default(false)|bool) or - (booleans_map[_ondemand_wifi.user_input|default(omit)]|default(false)) + - pause: + prompt: | + Do you want the VPN to support Windows 10 or Linux Desktop clients? (enables compatible ciphers and key exchange, less secure) + [y/N] + register: _windows + when: windows is undefined + + - pause: + prompt: | + Do you want to retain the CA key? (required to add users in the future, but less secure) + [y/N] + register: _store_cakey + when: store_cakey is undefined + when: ipsec_enabled - pause: prompt: | @@ -87,20 +102,6 @@ register: _ssh_tunneling when: ssh_tunneling is undefined - - pause: - prompt: | - Do you want the VPN to support Windows 10 or Linux Desktop clients? (enables compatible ciphers and key exchange, less secure) - [y/N] - register: _windows - when: windows is undefined - - - pause: - prompt: | - Do you want to retain the CA key? (required to add users in the future, but less secure) - [y/N] - register: _store_cakey - when: store_cakey is undefined - - name: Set facts based on the input set_fact: algo_server_name: >- diff --git a/playbooks/cloud-post.yml b/playbooks/cloud-post.yml index 1561153..0ada114 100644 --- a/playbooks/cloud-post.yml +++ b/playbooks/cloud-post.yml @@ -9,7 +9,7 @@ groups: vpn-host ansible_connection: "{% if cloud_instance_ip == 'localhost' %}local{% else %}ssh{% endif %}" ansible_ssh_user: "{{ ansible_ssh_user }}" - ansible_python_interpreter: "/usr/bin/python2.7" + ansible_python_interpreter: "/usr/bin/python3" algo_provider: "{{ algo_provider }}" algo_server_name: "{{ algo_server_name }}" algo_ondemand_cellular: "{{ algo_ondemand_cellular }}" diff --git a/playbooks/win_script_rebuild.yml b/playbooks/win_script_rebuild.yml deleted file mode 100644 index 12bdfe9..0000000 --- a/playbooks/win_script_rebuild.yml +++ /dev/null @@ -1,67 +0,0 @@ ---- - -# This playbook is designed to help when modifying the Windows script template -# in roles/vpn/templates/client_windows.ps1.j2 -# It rebuilds the client_USER.ps1 scripts for each user defined in config.cfg, -# without redeploying users or opening an SSH connection to the Algo server at -# all. -# -# This playbook is _not_ part of a normal Algo deployment. -# It is only intended to speed up development of the client_USER.ps1 Windows -# Algo install scripts. -# -# REQUIREMENTS -# - Algo must have been deployed once -# - Windows users must have been enabled at deployment time -# - All users defined in config.cfg must not have changed -# - Only one Algo deployment exists in the configs/ directory -# - There must be exactly one subfolder in the configs/ directory: -# the folder named after the IP of the algo server - -- hosts: localhost - gather_facts: False - tags: always - vars_files: - - ../config.cfg - - tasks: - - - name: Get config subdir - shell: find ../configs/* -maxdepth 0 -type d | sed 's/.*\///' - register: config_subdir_result - - fail: - msg: - - "Found wrong number of config subdirs... stdout:" - - "{{ config_subdir_result.split('\n') }}" - when: config_subdir_result.stdout.split('\n') | length != 1 - - set_fact: - IP_subject_alt_name: "{{ config_subdir_result.stdout }}" - - debug: - var: IP_subject_alt_name - - - name: Register p12 PayloadContent - shell: cat private/{{ item }}.p12 | base64 - register: PayloadContent - args: - chdir: "../configs/{{ IP_subject_alt_name }}/pki/" - with_items: "{{ users }}" - - - name: Set facts for mobileconfigs - set_fact: - proxy_enabled: false - PayloadContentCA: "{{ lookup('file' , '../configs/{{ IP_subject_alt_name }}/pki/cacert.pem')|b64encode }}" - - - name: Build the windows client powershell script - template: - src: ../roles/vpn/templates/client_windows.ps1.j2 - dest: ../configs/{{ IP_subject_alt_name }}/windows_{{ item.0 }}.ps1 - mode: 0600 - with_together: - - "{{ users }}" - - "{{ PayloadContent.results }}" - - - name: List windows client powershell scripts - debug: - msg: "configs/{{ IP_subject_alt_name }}/windows_{{ item }}.ps1" - with_items: - - "{{ users }}" diff --git a/roles/client/tasks/main.yml b/roles/client/tasks/main.yml index 60fafed..e3b1634 100644 --- a/roles/client/tasks/main.yml +++ b/roles/client/tasks/main.yml @@ -50,11 +50,11 @@ src: "{{ item.src }}" dest: "{{ item.dest }}" with_items: - - src: "configs/{{ IP_subject_alt_name }}/pki/certs/{{ vpn_user }}.crt" + - src: "configs/{{ IP_subject_alt_name }}/ipsec/.pki/certs/{{ vpn_user }}.crt" dest: "{{ configs_prefix }}/ipsec.d/certs/{{ vpn_user }}.crt" - - src: "configs/{{ IP_subject_alt_name }}/pki/cacert.pem" + - src: "configs/{{ IP_subject_alt_name }}/ipsec/.pki/cacert.pem" dest: "{{ configs_prefix }}/ipsec.d/cacerts/{{ IP_subject_alt_name }}.pem" - - src: "configs/{{ IP_subject_alt_name }}/pki/private/{{ vpn_user }}.key" + - src: "configs/{{ IP_subject_alt_name }}/ipsec/.pki/private/{{ vpn_user }}.key" dest: "{{ configs_prefix }}/ipsec.d/private/{{ vpn_user }}.key" notify: - restart strongswan diff --git a/roles/common/handlers/main.yml b/roles/common/handlers/main.yml index 1415245..06f5080 100644 --- a/roles/common/handlers/main.yml +++ b/roles/common/handlers/main.yml @@ -19,3 +19,9 @@ ifconfig lo100 create && ifconfig lo100 inet {{ local_service_ip }} netmask 255.255.255.255 && ifconfig lo100 inet6 FCAA::1/64; echo $? + +- name: save iptables + shell: service netfilter-persistent save + +- name: restart iptables + service: name=netfilter-persistent state=restarted diff --git a/roles/common/tasks/freebsd.yml b/roles/common/tasks/freebsd.yml index 70ebe8f..8136263 100644 --- a/roles/common/tasks/freebsd.yml +++ b/roles/common/tasks/freebsd.yml @@ -1,4 +1,16 @@ --- +- name: FreeBSD | Install prerequisites + package: + name: + - python3 + - sudo + vars: + ansible_python_interpreter: /usr/local/bin/python2.7 + +- name: Set python3 as the interpreter to use + set_fact: + ansible_python_interpreter: /usr/local/bin/python3 + - name: Gather facts setup: @@ -15,7 +27,6 @@ strongswan_additional_plugins: - kernel-pfroute - kernel-pfkey - ansible_python_interpreter: /usr/local/bin/python2.7 tools: - git - subversion diff --git a/roles/vpn/tasks/iptables.yml b/roles/common/tasks/iptables.yml similarity index 100% rename from roles/vpn/tasks/iptables.yml rename to roles/common/tasks/iptables.yml diff --git a/roles/common/tasks/ubuntu.yml b/roles/common/tasks/ubuntu.yml index 37d469e..a37a8c0 100644 --- a/roles/common/tasks/ubuntu.yml +++ b/roles/common/tasks/ubuntu.yml @@ -1,24 +1,4 @@ --- -- block: - - name: Ubuntu | Install prerequisites - apt: - name: "{{ item }}" - update_cache: true - with_items: - - python2.7 - - sudo - - - name: Ubuntu | Configure defaults - alternatives: - name: python - link: /usr/bin/python - path: /usr/bin/python2.7 - priority: 1 - tags: - - update-alternatives - vars: - ansible_python_interpreter: /usr/bin/python3 - - name: Gather facts setup: @@ -115,15 +95,20 @@ value: 1 - name: Install tools - package: name="{{ item }}" state=present + apt: + name: "{{ item }}" + state: present + update_cache: true with_items: - "{{ tools|default([]) }}" - name: Install headers apt: - name: "{{ item }}" + name: + - linux-headers-generic + - "linux-headers-{{ ansible_kernel }}" state: present when: install_headers - with_items: - - linux-headers-generic - - "linux-headers-{{ ansible_kernel }}" + +- include_tasks: iptables.yml + tags: iptables diff --git a/roles/vpn/templates/rules.v4.j2 b/roles/common/templates/rules.v4.j2 similarity index 69% rename from roles/vpn/templates/rules.v4.j2 rename to roles/common/templates/rules.v4.j2 index 1b487e6..d71f51f 100644 --- a/roles/vpn/templates/rules.v4.j2 +++ b/roles/common/templates/rules.v4.j2 @@ -1,3 +1,6 @@ +{% set subnets = ([strongswan_network] if ipsec_enabled else []) + ([wireguard_network_ipv4] if wireguard_enabled else []) %} +{% set ports = (['500', '4500'] if ipsec_enabled else []) + ([wireguard_port] if wireguard_enabled else []) %} + #### The mangle table # This table allows us to modify packet headers # Packets enter this table first @@ -10,8 +13,8 @@ :OUTPUT ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] -{% if reduce_mtu|int > 0 %} --A FORWARD -s {{ vpn_network }} -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss {{ 1360 - reduce_mtu|int }} +{% if reduce_mtu|int > 0 and ipsec_enabled %} +-A FORWARD -s {{ strongswan_network }} -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss {{ 1360 - reduce_mtu|int }} {% endif %} COMMIT @@ -27,7 +30,7 @@ COMMIT :POSTROUTING ACCEPT [0:0] # Allow traffic from the VPN network to the outside world, and replies --A POSTROUTING -s {{ vpn_network }}{% if wireguard_enabled %},{{ wireguard_vpn_network }}{% endif %} -m policy --pol none --dir out -j MASQUERADE +-A POSTROUTING -s {{ subnets|join(',') }} -m policy --pol none --dir out -j MASQUERADE COMMIT @@ -54,12 +57,15 @@ COMMIT -A INPUT -p ah -j ACCEPT # rate limit ICMP traffic per source -A INPUT -p icmp --icmp-type echo-request -m hashlimit --hashlimit-upto 5/s --hashlimit-mode srcip --hashlimit-srcmask 32 --hashlimit-name icmp-echo-drop -j ACCEPT -# Accept IPSEC traffic to ports 500 (IPSEC) and 4500 (MOBIKE aka IKE + NAT traversal) --A INPUT -p udp -m multiport --dports 500,4500{% if wireguard_enabled %},{{ wireguard_port}}{% endif %} -j ACCEPT +# Accept IPSEC/WireGuard traffic to ports {{ subnets|join(',') }} +-A INPUT -p udp -m multiport --dports {{ ports|join(',') }} -j ACCEPT # Allow new traffic to port 22 (SSH) -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT -# Allow any traffic from the VPN + +{% if ipsec_enabled %} +# Allow any traffic from the IPsec VPN -A INPUT -p ipencap -m policy --dir in --pol ipsec --proto esp -j ACCEPT +{% endif %} # TODO: # The IP of the resolver should be bound to a DUMMY interface. @@ -70,10 +76,7 @@ COMMIT -A INPUT -d {{ local_service_ip }} -p udp --dport 53 -j ACCEPT # Drop traffic between VPN clients -{% if BetweenClients_DROP %} -{% set BetweenClientsPolicy = "DROP" %} -{% endif %} --A FORWARD -s {{ vpn_network }}{% if wireguard_enabled %},{{ wireguard_vpn_network }}{% endif %} -d {{ vpn_network }}{% if wireguard_enabled %},{{ wireguard_vpn_network }}{% endif %} -j {{ BetweenClientsPolicy | default("ACCEPT") }} +-A FORWARD -s {{ subnets|join(',') }} -d {{ subnets|join(',') }} -j {{ "DROP" if BetweenClients_DROP else "ACCEPT" }} # Forward any packet that's part of an established connection -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT @@ -83,12 +86,14 @@ COMMIT -A FORWARD -p udp -m multiport --ports 137,138 -j DROP -A FORWARD -p tcp -m multiport --ports 137,139 -j DROP +{% if ipsec_enabled %} # Forward any IPSEC traffic from the VPN network --A FORWARD -m conntrack --ctstate NEW -s {{ vpn_network }} -m policy --pol ipsec --dir in -j ACCEPT +-A FORWARD -m conntrack --ctstate NEW -s {{ strongswan_network }} -m policy --pol ipsec --dir in -j ACCEPT +{% endif %} -# Forward any traffic from the WireGuard VPN network {% if wireguard_enabled %} --A FORWARD -m conntrack --ctstate NEW -s {{ wireguard_vpn_network }} -m policy --pol none --dir in -j ACCEPT +# Forward any traffic from the WireGuard VPN network +-A FORWARD -m conntrack --ctstate NEW -s {{ wireguard_network_ipv4 }} -m policy --pol none --dir in -j ACCEPT {% endif %} COMMIT diff --git a/roles/vpn/templates/rules.v6.j2 b/roles/common/templates/rules.v6.j2 similarity index 77% rename from roles/vpn/templates/rules.v6.j2 rename to roles/common/templates/rules.v6.j2 index 6095e21..12bed2b 100644 --- a/roles/vpn/templates/rules.v6.j2 +++ b/roles/common/templates/rules.v6.j2 @@ -1,3 +1,6 @@ +{% set subnets = ([strongswan_network_ipv6] if ipsec_enabled else []) + ([wireguard_network_ipv6] if wireguard_enabled else []) %} +{% set ports = (['500', '4500'] if ipsec_enabled else []) + ([wireguard_port] if wireguard_enabled else []) %} + #### The mangle table # This table allows us to modify packet headers # Packets enter this table first @@ -10,8 +13,8 @@ :OUTPUT ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] -{% if reduce_mtu|int > 0 %} --A FORWARD -s {{ vpn_network_ipv6 }} -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss {{ 1340 - reduce_mtu|int }} +{% if reduce_mtu|int > 0 and ipsec_enabled %} +-A FORWARD -s {{ strongswan_network_ipv6 }} -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss {{ 1340 - reduce_mtu|int }} {% endif %} COMMIT @@ -26,7 +29,7 @@ COMMIT :POSTROUTING ACCEPT [0:0] # Allow traffic from the VPN network to the outside world, and replies --A POSTROUTING -s {{ vpn_network_ipv6 }}{% if wireguard_enabled %},{{ wireguard_vpn_network_ipv6 }}{% endif %} -m policy --pol none --dir out -j MASQUERADE +-A POSTROUTING -s {{ subnets|join(',') }} -m policy --pol none --dir out -j MASQUERADE COMMIT @@ -60,8 +63,8 @@ COMMIT -A INPUT -m ah -j ACCEPT # rate limit ICMP traffic per source -A INPUT -p icmpv6 --icmpv6-type echo-request -m hashlimit --hashlimit-upto 5/s --hashlimit-mode srcip --hashlimit-srcmask 32 --hashlimit-name icmp-echo-drop -j ACCEPT -# Accept IPSEC traffic to ports 500 (IPSEC) and 4500 (MOBIKE aka IKE + NAT traversal) --A INPUT -p udp -m multiport --dports 500,4500{% if wireguard_enabled %},{{ wireguard_port}}{% endif %} -j ACCEPT +# Accept IPSEC/WireGuard traffic to ports {{ subnets|join(',') }} +-A INPUT -p udp -m multiport --dports {{ ports|join(',') }} -j ACCEPT # Allow new traffic to port 22 (SSH) -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT @@ -83,19 +86,18 @@ COMMIT -A INPUT -d fcaa::1 -p udp --dport 53 -j ACCEPT # Drop traffic between VPN clients -{% if BetweenClients_DROP %} -{% set BetweenClientsPolicy = "DROP" %} -{% endif %} --A FORWARD -s {{ vpn_network_ipv6 }}{% if wireguard_enabled %},{{ wireguard_vpn_network_ipv6 }}{% endif %} -d {{ vpn_network_ipv6 }}{% if wireguard_enabled %},{{ wireguard_vpn_network_ipv6 }}{% endif %} -j {{ BetweenClientsPolicy | default("ACCEPT") }} +-A FORWARD -s {{ subnets|join(',') }} -d {{ subnets|join(',') }} -j {{ "DROP" if BetweenClients_DROP else "ACCEPT" }} -A FORWARD -j ICMPV6-CHECK -A FORWARD -p tcp --dport 445 -j DROP -A FORWARD -p udp -m multiport --ports 137,138 -j DROP -A FORWARD -p tcp -m multiport --ports 137,139 -j DROP -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT --A FORWARD -m conntrack --ctstate NEW -s {{ vpn_network_ipv6 }} -m policy --pol ipsec --dir in -j ACCEPT +{% if ipsec_enabled %} +-A FORWARD -m conntrack --ctstate NEW -s {{ strongswan_network_ipv6 }} -m policy --pol ipsec --dir in -j ACCEPT +{% endif %} {% if wireguard_enabled %} --A FORWARD -m conntrack --ctstate NEW -s {{ wireguard_vpn_network_ipv6 }} -m policy --pol none --dir in -j ACCEPT +-A FORWARD -m conntrack --ctstate NEW -s {{ wireguard_network_ipv6 }} -m policy --pol none --dir in -j ACCEPT {% endif %} # Use the ICMPV6-CHECK chain, described above diff --git a/roles/ssh_tunneling/defaults/main.yml b/roles/ssh_tunneling/defaults/main.yml new file mode 100644 index 0000000..3ed9b59 --- /dev/null +++ b/roles/ssh_tunneling/defaults/main.yml @@ -0,0 +1,2 @@ +--- +ssh_tunnels_config_path: "configs/{{ IP_subject_alt_name }}/ssh-tunnel/" diff --git a/roles/ssh_tunneling/tasks/main.yml b/roles/ssh_tunneling/tasks/main.yml index 259464b..c52840f 100644 --- a/roles/ssh_tunneling/tasks/main.yml +++ b/roles/ssh_tunneling/tasks/main.yml @@ -25,65 +25,93 @@ owner: root group: "{{ root_group|default('root') }}" - - name: Ensure that the SSH users exist - user: - name: "{{ item }}" - groups: algo - home: '/var/jail/{{ item }}' - createhome: yes - generate_ssh_key: false - shell: /bin/false - state: present - append: yes - with_items: "{{ users }}" - tags: update-users + - block: + - name: Ensure that the SSH users exist + user: + name: "{{ item }}" + groups: algo + home: '/var/jail/{{ item }}' + createhome: yes + generate_ssh_key: false + shell: /bin/false + state: present + append: yes + with_items: "{{ users }}" - - name: The authorized keys file created - authorized_key: - user: "{{ item }}" - key: "{{ lookup('file', 'configs/' + IP_subject_alt_name + '/pki/public/' + item + '.pub') }}" - state: present - manage_dir: true - exclusive: true - with_items: "{{ users }}" - tags: update-users + - block: + - name: Clean up the ssh-tunnel directory + file: + dest: "{{ ssh_tunnels_config_path }}" + state: absent + when: keys_clean_all|bool == True - - name: Generate SSH fingerprints - shell: ssh-keyscan {{ IP_subject_alt_name }} 2>/dev/null - register: ssh_fingerprints + - name: Ensure the config directories exist + file: + dest: "{{ ssh_tunnels_config_path }}" + state: directory + recurse: yes + mode: '0700' - - name: Fetch the known_hosts file - local_action: - module: template - src: known_hosts.j2 - dest: configs/{{ IP_subject_alt_name }}/known_hosts - become: no + - name: Check if the private keys exist + stat: + path: "{{ ssh_tunnels_config_path }}/{{ item }}.pem" + register: privatekey + with_items: "{{ users }}" - - name: Build the client ssh config - local_action: - module: template - src: ssh_config.j2 - dest: configs/{{ IP_subject_alt_name }}/{{ item }}.ssh_config - mode: 0600 - become: false - tags: update-users - with_items: "{{ users }}" + - name: Build ssh private keys + openssl_privatekey: + path: "{{ ssh_tunnels_config_path }}/{{ item.item }}.pem" + passphrase: "{{ p12_export_password }}" + cipher: aes256 + force: false + no_log: true + when: not item.stat.exists + with_items: "{{ privatekey.results }}" + register: openssl_privatekey - - name: Get active users - getent: - database: group - key: algo - split: ':' - tags: update-users + - name: Build ssh public keys + openssl_publickey: + path: "{{ ssh_tunnels_config_path }}/{{ item.item.item }}.pub" + privatekey_path: "{{ ssh_tunnels_config_path }}/{{ item.item.item }}.pem" + privatekey_passphrase: "{{ p12_export_password }}" + format: OpenSSH + force: true + no_log: true + when: item.changed + with_items: "{{ openssl_privatekey.results }}" + + - name: Build the client ssh config + template: + src: ssh_config.j2 + dest: "{{ ssh_tunnels_config_path }}/{{ item }}.ssh_config" + mode: 0700 + with_items: "{{ users }}" + delegate_to: localhost + become: false + + - name: The authorized keys file created + authorized_key: + user: "{{ item }}" + key: "{{ lookup('file', ssh_tunnels_config_path + '/' + item + '.pub') }}" + state: present + manage_dir: true + exclusive: true + with_items: "{{ users }}" + + - name: Get active users + getent: + database: group + key: algo + split: ':' - - name: Delete non-existing users - user: - name: "{{ item }}" - state: absent - remove: yes - force: yes - when: item not in users - with_items: "{{ getent_group['algo'][2].split(',') }}" + - name: Delete non-existing users + user: + name: "{{ item }}" + state: absent + remove: yes + force: yes + when: item not in users + with_items: "{{ getent_group['algo'][2].split(',') }}" tags: update-users rescue: - debug: var=fail_hint diff --git a/roles/ssh_tunneling/templates/known_hosts.j2 b/roles/ssh_tunneling/templates/known_hosts.j2 deleted file mode 100644 index 98d33c4..0000000 --- a/roles/ssh_tunneling/templates/known_hosts.j2 +++ /dev/null @@ -1,3 +0,0 @@ -{% for item in ssh_fingerprints.stdout_lines %} -{{ item }} -{% endfor %} diff --git a/roles/vpn/defaults/main.yml b/roles/strongswan/defaults/main.yml similarity index 51% rename from roles/vpn/defaults/main.yml rename to roles/strongswan/defaults/main.yml index c9b81ce..b896933 100644 --- a/roles/vpn/defaults/main.yml +++ b/roles/strongswan/defaults/main.yml @@ -1,31 +1,11 @@ --- +ipsec_config_path: "configs/{{ IP_subject_alt_name }}/ipsec/" +ipsec_pki_path: "{{ ipsec_config_path }}/.pki/" +strongswan_network: 10.19.48.0/24 +strongswan_network_ipv6: 'fd9d:bc11:4020::/48' strongswan_shell: /usr/sbin/nologin strongswan_home: /var/lib/strongswan BetweenClients_DROP: true -wireguard_config_path: "configs/{{ IP_subject_alt_name }}/wireguard/" -wireguard_interface: wg0 -wireguard_network_ipv4: - subnet: 10.19.49.0 - prefix: 24 - gateway: 10.19.49.1 - clients_range: 10.19.49 - clients_start: 2 -wireguard_network_ipv6: - subnet: 'fd9d:bc11:4021::' - prefix: 48 - gateway: 'fd9d:bc11:4021::1' - clients_range: 'fd9d:bc11:4021::' - clients_start: 2 -wireguard_vpn_network: "{{ wireguard_network_ipv4['subnet'] }}/{{ wireguard_network_ipv4['prefix'] }}" -wireguard_vpn_network_ipv6: "{{ wireguard_network_ipv6['subnet'] }}/{{ wireguard_network_ipv6['prefix'] }}" -keys_clean_all: false -wireguard_dns_servers: >- - {% if local_dns|default(false)|bool or dns_encryption|default(false)|bool == true %} - {{ local_service_ip }} - {% else %} - {% for host in dns_servers.ipv4 %}{{ host }}{% if not loop.last %},{% endif %}{% endfor %}{% if ipv6_support %},{% for host in dns_servers.ipv6 %}{{ host }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %} - {% endif %} - algo_ondemand_cellular: false algo_ondemand_wifi: false algo_ondemand_wifi_exclude: '_null' @@ -65,3 +45,7 @@ ciphers: compat: ike: aes256gcm16-prfsha512-ecp384,aes256-sha2_512-prfsha512-ecp384,aes256-sha2_384-prfsha384-ecp384! esp: aes256gcm16-ecp384,aes256-sha2_512-prfsha512-ecp384! + +pkcs12_PayloadCertificateUUID: "{{ 900000 | random | to_uuid | upper }}" +VPN_PayloadIdentifier: "{{ 800000 | random | to_uuid | upper }}" +CA_PayloadIdentifier: "{{ 700000 | random | to_uuid | upper }}" diff --git a/roles/vpn/handlers/main.yml b/roles/strongswan/handlers/main.yml similarity index 63% rename from roles/vpn/handlers/main.yml rename to roles/strongswan/handlers/main.yml index 8ce3163..6e7968f 100644 --- a/roles/vpn/handlers/main.yml +++ b/roles/strongswan/handlers/main.yml @@ -7,11 +7,5 @@ - name: restart apparmor service: name=apparmor state=restarted -- name: save iptables - shell: service netfilter-persistent save - -- name: restart iptables - service: name=netfilter-persistent state=restarted - - name: rereadcrls shell: ipsec rereadcrls; ipsec purgecrls diff --git a/roles/vpn/meta/main.yml b/roles/strongswan/meta/main.yml similarity index 100% rename from roles/vpn/meta/main.yml rename to roles/strongswan/meta/main.yml diff --git a/roles/vpn/tasks/client_configs.yml b/roles/strongswan/tasks/client_configs.yml similarity index 65% rename from roles/vpn/tasks/client_configs.yml rename to roles/strongswan/tasks/client_configs.yml index 827bef7..de4ff0f 100644 --- a/roles/vpn/tasks/client_configs.yml +++ b/roles/strongswan/tasks/client_configs.yml @@ -1,20 +1,19 @@ --- - - name: Register p12 PayloadContent shell: cat private/{{ item }}.p12 | base64 register: PayloadContent args: - chdir: "configs/{{ IP_subject_alt_name }}/pki/" + chdir: "{{ ipsec_pki_path }}" with_items: "{{ users }}" - name: Set facts for mobileconfigs set_fact: - PayloadContentCA: "{{ lookup('file' , 'configs/{{ IP_subject_alt_name }}/pki/cacert.pem')|b64encode }}" + PayloadContentCA: "{{ lookup('file' , '{{ ipsec_pki_path }}/cacert.pem')|b64encode }}" - name: Build the mobileconfigs template: src: mobileconfig.j2 - dest: configs/{{ IP_subject_alt_name }}/{{ item.0 }}.mobileconfig + dest: "{{ ipsec_config_path }}/apple/{{ item.0 }}.mobileconfig" mode: 0600 with_together: - "{{ users }}" @@ -24,7 +23,7 @@ - name: Build the client ipsec config file template: src: client_ipsec.conf.j2 - dest: configs/{{ IP_subject_alt_name }}/ipsec_{{ item }}.conf + dest: "{{ ipsec_config_path }}/manual/{{ item }}.conf" mode: 0600 with_items: - "{{ users }}" @@ -32,7 +31,7 @@ - name: Build the client ipsec secret file template: src: client_ipsec.secrets.j2 - dest: configs/{{ IP_subject_alt_name }}/ipsec_{{ item }}.secrets + dest: "{{ ipsec_config_path }}/manual/{{ item }}.secrets" mode: 0600 with_items: - "{{ users }}" @@ -40,7 +39,7 @@ - name: Build the windows client powershell script template: src: client_windows.ps1.j2 - dest: configs/{{ IP_subject_alt_name }}/windows_{{ item.0 }}.ps1 + dest: "{{ ipsec_config_path }}/windows/{{ item.0 }}.ps1" mode: 0600 when: algo_windows with_together: @@ -49,8 +48,6 @@ - name: Restrict permissions for the local private directories file: - path: "{{ item }}" + path: "{{ ipsec_config_path }}" state: directory mode: 0700 - with_items: - - configs/{{ IP_subject_alt_name }} diff --git a/roles/strongswan/tasks/distribute_keys.yml b/roles/strongswan/tasks/distribute_keys.yml new file mode 100644 index 0000000..02df30b --- /dev/null +++ b/roles/strongswan/tasks/distribute_keys.yml @@ -0,0 +1,27 @@ +--- + +- name: Copy the keys to the strongswan directory + copy: + src: "{{ ipsec_pki_path }}/{{ item.src }}" + dest: "{{ config_prefix|default('/') }}etc/ipsec.d/{{ item.dest }}" + owner: "{{ item.owner }}" + group: "{{ item.group }}" + mode: "{{ item.mode }}" + with_items: + - src: "cacert.pem" + dest: "cacerts/ca.crt" + owner: strongswan + group: "{{ root_group|default('root') }}" + mode: "0600" + - src: "certs/{{ IP_subject_alt_name }}.crt" + dest: "certs/{{ IP_subject_alt_name }}.crt" + owner: strongswan + group: "{{ root_group|default('root') }}" + mode: "0600" + - src: "private/{{ IP_subject_alt_name }}.key" + dest: "private/{{ IP_subject_alt_name }}.key" + owner: strongswan + group: "{{ root_group|default('root') }}" + mode: "0600" + notify: + - restart strongswan diff --git a/roles/vpn/tasks/ipsec_configuration.yml b/roles/strongswan/tasks/ipsec_configuration.yml similarity index 87% rename from roles/vpn/tasks/ipsec_configuration.yml rename to roles/strongswan/tasks/ipsec_configuration.yml index cc7c21e..ce5b3e5 100644 --- a/roles/vpn/tasks/ipsec_configuration.yml +++ b/roles/strongswan/tasks/ipsec_configuration.yml @@ -3,23 +3,23 @@ - name: Setup the config files from our templates template: src: "{{ item.src }}" - dest: "{{ item.dest }}" + dest: "{{ config_prefix|default('/') }}etc/{{ item.dest }}" owner: "{{ item.owner }}" group: "{{ item.group }}" mode: "{{ item.mode }}" with_items: - src: strongswan.conf.j2 - dest: "{{ config_prefix|default('/') }}etc/strongswan.conf" + dest: "strongswan.conf" owner: root group: "{{ root_group|default('root') }}" mode: "0644" - src: ipsec.conf.j2 - dest: "{{ config_prefix|default('/') }}etc/ipsec.conf" + dest: "ipsec.conf" owner: root group: "{{ root_group|default('root') }}" mode: "0644" - src: ipsec.secrets.j2 - dest: "{{ config_prefix|default('/') }}etc/ipsec.secrets" + dest: "ipsec.secrets" owner: strongswan group: "{{ root_group|default('root') }}" mode: "0600" diff --git a/roles/vpn/tasks/main.yml b/roles/strongswan/tasks/main.yml similarity index 84% rename from roles/vpn/tasks/main.yml rename to roles/strongswan/tasks/main.yml index bfe929c..6b9699e 100644 --- a/roles/vpn/tasks/main.yml +++ b/roles/strongswan/tasks/main.yml @@ -1,11 +1,5 @@ --- - block: - - name: Include WireGuard role - include_role: - name: wireguard - tags: wireguard - when: wireguard_enabled and ansible_distribution == 'Ubuntu' - - include_tasks: ubuntu.yml when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu' diff --git a/roles/vpn/tasks/openssl.yml b/roles/strongswan/tasks/openssl.yml similarity index 79% rename from roles/vpn/tasks/openssl.yml rename to roles/strongswan/tasks/openssl.yml index 3a286be..694bb83 100644 --- a/roles/vpn/tasks/openssl.yml +++ b/roles/strongswan/tasks/openssl.yml @@ -7,13 +7,13 @@ - name: Ensure the pki directory does not exist file: - dest: configs/{{ IP_subject_alt_name }}/pki + dest: "{{ ipsec_pki_path }}" state: absent when: keys_clean_all|bool == True - name: Ensure the pki directories exist file: - dest: "configs/{{ IP_subject_alt_name }}/pki/{{ item }}" + dest: "{{ ipsec_pki_path }}/{{ item }}" state: directory recurse: yes mode: '0700' @@ -26,9 +26,20 @@ - public - reqs + - name: Ensure the config directories exist + file: + dest: "{{ ipsec_config_path }}/{{ item }}" + state: directory + recurse: yes + mode: '0700' + with_items: + - apple + - windows + - manual + - name: Ensure the files exist file: - dest: "configs/{{ IP_subject_alt_name }}/pki/{{ item }}" + dest: "{{ ipsec_pki_path }}/{{ item }}" state: touch with_items: - ".rnd" @@ -40,7 +51,7 @@ - name: Generate the openssl server configs template: src: openssl.cnf.j2 - dest: "configs/{{ IP_subject_alt_name }}/pki/openssl.cnf" + dest: "{{ ipsec_pki_path }}/openssl.cnf" - name: Build the CA pair shell: > @@ -55,20 +66,19 @@ -passout pass:"{{ CA_password }}" && touch {{ IP_subject_alt_name }}_ca_generated args: - chdir: "configs/{{ IP_subject_alt_name }}/pki/" + chdir: "{{ ipsec_pki_path }}" creates: "{{ IP_subject_alt_name }}_ca_generated" executable: bash - name: Copy the CA certificate copy: - src: "configs/{{ IP_subject_alt_name }}/pki/cacert.pem" - dest: "configs/{{ IP_subject_alt_name }}/cacert.pem" - mode: 0600 + src: "{{ ipsec_pki_path }}/cacert.pem" + dest: "{{ ipsec_config_path }}/manual/cacert.pem" - name: Generate the serial number shell: echo 01 > serial && touch serial_generated args: - chdir: "configs/{{ IP_subject_alt_name }}/pki/" + chdir: "{{ ipsec_pki_path }}" creates: serial_generated - name: Build the server pair @@ -90,7 +100,7 @@ -subj "/CN={{ IP_subject_alt_name }}" && touch certs/{{ IP_subject_alt_name }}_crt_generated args: - chdir: "configs/{{ IP_subject_alt_name }}/pki/" + chdir: "{{ ipsec_pki_path }}" creates: certs/{{ IP_subject_alt_name }}_crt_generated executable: bash @@ -113,23 +123,15 @@ -subj "/CN={{ item }}" && touch certs/{{ item }}_crt_generated args: - chdir: "configs/{{ IP_subject_alt_name }}/pki/" + chdir: "{{ ipsec_pki_path }}" creates: certs/{{ item }}_crt_generated executable: bash with_items: "{{ users }}" - - name: Create links for the private keys - file: - src: "pki/private/{{ item }}.key" - dest: "configs/{{ IP_subject_alt_name }}/{{ item }}.ssh.pem" - state: link - force: true - with_items: "{{ users }}" - - name: Build openssh public keys openssl_publickey: - path: "configs/{{ IP_subject_alt_name }}/pki/public/{{ item }}.pub" - privatekey_path: "configs/{{ IP_subject_alt_name }}/pki/private/{{ item }}.key" + path: "{{ ipsec_pki_path }}/public/{{ item }}.pub" + privatekey_path: "{{ ipsec_pki_path }}/private/{{ item }}.key" format: OpenSSH with_items: "{{ users }}" @@ -144,16 +146,15 @@ -out private/{{ item }}.p12 -passout pass:"{{ p12_export_password }}" args: - chdir: "configs/{{ IP_subject_alt_name }}/pki/" + chdir: "{{ ipsec_pki_path }}" executable: bash with_items: "{{ users }}" register: p12 - name: Copy the p12 certificates copy: - src: "configs/{{ IP_subject_alt_name }}/pki/private/{{ item }}.p12" - dest: "configs/{{ IP_subject_alt_name }}/{{ item }}.p12" - mode: 0600 + src: "{{ ipsec_pki_path }}/private/{{ item }}.p12" + dest: "{{ ipsec_config_path }}/manual/{{ item }}.p12" with_items: - "{{ users }}" @@ -164,7 +165,7 @@ awk '{print $5}' | sed 's/\/CN=//g' args: - chdir: "configs/{{ IP_subject_alt_name }}/pki/" + chdir: "{{ ipsec_pki_path}}" register: valid_certs - name: Revoke non-existing users @@ -176,7 +177,7 @@ -out crl/{{ item }}.crt register: gencrl args: - chdir: configs/{{ IP_subject_alt_name }}/pki/ + chdir: "{{ ipsec_pki_path }}" creates: crl/{{ item }}.crt executable: bash when: item not in users @@ -192,7 +193,7 @@ - gencrl is defined - gencrl.changed args: - chdir: configs/{{ IP_subject_alt_name }}/pki/ + chdir: "{{ ipsec_pki_path }}" executable: bash delegate_to: localhost become: no @@ -201,7 +202,7 @@ - name: Copy the CRL to the vpn server copy: - src: configs/{{ IP_subject_alt_name }}/pki/crl/algo.root.pem + src: "{{ ipsec_pki_path }}/crl/algo.root.pem" dest: "{{ config_prefix|default('/') }}etc/ipsec.d/crls/algo.root.pem" when: - gencrl is defined diff --git a/roles/vpn/tasks/ubuntu.yml b/roles/strongswan/tasks/ubuntu.yml similarity index 95% rename from roles/vpn/tasks/ubuntu.yml rename to roles/strongswan/tasks/ubuntu.yml index 6f58544..41b5835 100644 --- a/roles/vpn/tasks/ubuntu.yml +++ b/roles/strongswan/tasks/ubuntu.yml @@ -43,6 +43,3 @@ notify: - daemon-reload - restart strongswan - -- include_tasks: iptables.yml - tags: iptables diff --git a/roles/vpn/templates/100-CustomLimitations.conf.j2 b/roles/strongswan/templates/100-CustomLimitations.conf.j2 similarity index 100% rename from roles/vpn/templates/100-CustomLimitations.conf.j2 rename to roles/strongswan/templates/100-CustomLimitations.conf.j2 diff --git a/roles/vpn/templates/client_ipsec.conf.j2 b/roles/strongswan/templates/client_ipsec.conf.j2 similarity index 100% rename from roles/vpn/templates/client_ipsec.conf.j2 rename to roles/strongswan/templates/client_ipsec.conf.j2 diff --git a/roles/vpn/templates/client_ipsec.secrets.j2 b/roles/strongswan/templates/client_ipsec.secrets.j2 similarity index 100% rename from roles/vpn/templates/client_ipsec.secrets.j2 rename to roles/strongswan/templates/client_ipsec.secrets.j2 diff --git a/roles/vpn/templates/client_windows.ps1.j2 b/roles/strongswan/templates/client_windows.ps1.j2 similarity index 100% rename from roles/vpn/templates/client_windows.ps1.j2 rename to roles/strongswan/templates/client_windows.ps1.j2 diff --git a/roles/vpn/templates/ipsec.conf.j2 b/roles/strongswan/templates/ipsec.conf.j2 similarity index 94% rename from roles/vpn/templates/ipsec.conf.j2 rename to roles/strongswan/templates/ipsec.conf.j2 index 086e18a..68fa346 100644 --- a/roles/vpn/templates/ipsec.conf.j2 +++ b/roles/strongswan/templates/ipsec.conf.j2 @@ -27,7 +27,7 @@ conn %default right=%any rightauth=pubkey - rightsourceip={{ vpn_network }},{{ vpn_network_ipv6 }} + rightsourceip={{ strongswan_network }},{{ strongswan_network_ipv6 }} {% if algo_local_dns or dns_encryption %} rightdns={{ local_service_ip }} {% else %} diff --git a/roles/vpn/templates/ipsec.secrets.j2 b/roles/strongswan/templates/ipsec.secrets.j2 similarity index 100% rename from roles/vpn/templates/ipsec.secrets.j2 rename to roles/strongswan/templates/ipsec.secrets.j2 diff --git a/roles/vpn/templates/mobileconfig.j2 b/roles/strongswan/templates/mobileconfig.j2 similarity index 100% rename from roles/vpn/templates/mobileconfig.j2 rename to roles/strongswan/templates/mobileconfig.j2 diff --git a/roles/vpn/templates/openssl.cnf.j2 b/roles/strongswan/templates/openssl.cnf.j2 similarity index 100% rename from roles/vpn/templates/openssl.cnf.j2 rename to roles/strongswan/templates/openssl.cnf.j2 diff --git a/roles/vpn/templates/strongswan.conf.j2 b/roles/strongswan/templates/strongswan.conf.j2 similarity index 100% rename from roles/vpn/templates/strongswan.conf.j2 rename to roles/strongswan/templates/strongswan.conf.j2 diff --git a/roles/vpn/tasks/distribute_keys.yml b/roles/vpn/tasks/distribute_keys.yml deleted file mode 100644 index d50ecfa..0000000 --- a/roles/vpn/tasks/distribute_keys.yml +++ /dev/null @@ -1,27 +0,0 @@ ---- - -- name: Copy the keys to the strongswan directory - copy: - src: "{{ item.src }}" - dest: "{{ item.dest }}" - owner: "{{ item.owner }}" - group: "{{ item.group }}" - mode: "{{ item.mode }}" - with_items: - - src: "configs/{{ IP_subject_alt_name }}/pki/cacert.pem" - dest: "{{ config_prefix|default('/') }}etc/ipsec.d/cacerts/ca.crt" - owner: strongswan - group: "{{ root_group|default('root') }}" - mode: "0600" - - src: "configs/{{ IP_subject_alt_name }}/pki/certs/{{ IP_subject_alt_name }}.crt" - dest: "{{ config_prefix|default('/') }}etc/ipsec.d/certs/{{ IP_subject_alt_name }}.crt" - owner: strongswan - group: "{{ root_group|default('root') }}" - mode: "0600" - - src: "configs/{{ IP_subject_alt_name }}/pki/private/{{ IP_subject_alt_name }}.key" - dest: "{{ config_prefix|default('/') }}etc/ipsec.d/private/{{ IP_subject_alt_name }}.key" - owner: strongswan - group: "{{ root_group|default('root') }}" - mode: "0600" - notify: - - restart strongswan diff --git a/roles/wireguard/defaults/main.yml b/roles/wireguard/defaults/main.yml index 7961c6a..d008336 100644 --- a/roles/wireguard/defaults/main.yml +++ b/roles/wireguard/defaults/main.yml @@ -1,4 +1,28 @@ --- wireguard_PersistentKeepalive: 0 -wireguard_client_ip: "{{ wireguard_network_ipv4['clients_range'] }}.{{ wireguard_network_ipv4['clients_start'] + index|int + 1 }}/{{ wireguard_network_ipv4['prefix'] }}{% if ipv6_support %},{{ wireguard_network_ipv6['clients_range'] }}{{ wireguard_network_ipv6['clients_start'] + index|int + 1 }}/{{ wireguard_network_ipv6['prefix'] }}{% endif %}" -wireguard_server_ip: "{{ wireguard_network_ipv4['gateway'] }}/{{ wireguard_network_ipv4['prefix'] }}{% if ipv6_support %},{{ wireguard_network_ipv6['gateway'] }}/{{ wireguard_network_ipv6['prefix'] }}{% endif %}" +wireguard_config_path: "configs/{{ IP_subject_alt_name }}/wireguard/" +wireguard_pki_path: "{{ wireguard_config_path }}/.pki/" +wireguard_interface: wg0 +_wireguard_network_ipv4: + subnet: 10.19.49.0 + prefix: 24 + gateway: 10.19.49.1 + clients_range: 10.19.49 + clients_start: 2 +_wireguard_network_ipv6: + subnet: 'fd9d:bc11:4021::' + prefix: 48 + gateway: 'fd9d:bc11:4021::1' + clients_range: 'fd9d:bc11:4021::' + clients_start: 2 +wireguard_network_ipv4: "{{ _wireguard_network_ipv4['subnet'] }}/{{ _wireguard_network_ipv4['prefix'] }}" +wireguard_network_ipv6: "{{ _wireguard_network_ipv6['subnet'] }}/{{ _wireguard_network_ipv6['prefix'] }}" +keys_clean_all: false +wireguard_dns_servers: >- + {% if local_dns|default(false)|bool or dns_encryption|default(false)|bool == true %} + {{ local_service_ip }} + {% else %} + {% for host in dns_servers.ipv4 %}{{ host }}{% if not loop.last %},{% endif %}{% endfor %}{% if ipv6_support %},{% for host in dns_servers.ipv6 %}{{ host }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %} + {% endif %} +wireguard_client_ip: "{{ _wireguard_network_ipv4['clients_range'] }}.{{ _wireguard_network_ipv4['clients_start'] + index|int + 1 }}/{{ _wireguard_network_ipv4['prefix'] }}{% if ipv6_support %},{{ _wireguard_network_ipv6['clients_range'] }}{{ _wireguard_network_ipv6['clients_start'] + index|int + 1 }}/{{ _wireguard_network_ipv6['prefix'] }}{% endif %}" +wireguard_server_ip: "{{ _wireguard_network_ipv4['gateway'] }}/{{ _wireguard_network_ipv4['prefix'] }}{% if ipv6_support %},{{ _wireguard_network_ipv6['gateway'] }}/{{ _wireguard_network_ipv6['prefix'] }}{% endif %}" diff --git a/roles/wireguard/tasks/keys.yml b/roles/wireguard/tasks/keys.yml index 3343408..e13a015 100644 --- a/roles/wireguard/tasks/keys.yml +++ b/roles/wireguard/tasks/keys.yml @@ -20,7 +20,7 @@ - block: - name: Save private keys copy: - dest: "{{ wireguard_config_path }}/private/{{ item['item'] }}" + dest: "{{ wireguard_pki_path }}/private/{{ item['item'] }}" content: "{{ item['stdout'] }}" mode: "0600" no_log: true @@ -39,7 +39,7 @@ when: wg_genkey.changed - name: Generate public keys - shell: echo "{{ lookup('file', wireguard_config_path + '/private/' + item) }}" | wg pubkey + shell: echo "{{ lookup('file', wireguard_pki_path + '/private/' + item) }}" | wg pubkey register: wg_pubkey changed_when: false args: @@ -50,7 +50,7 @@ - name: Save public keys copy: - dest: "{{ wireguard_config_path }}/public/{{ item['item'] }}" + dest: "{{ wireguard_pki_path }}/public/{{ item['item'] }}" content: "{{ item['stdout'] }}" mode: "0600" no_log: true diff --git a/roles/wireguard/tasks/main.yml b/roles/wireguard/tasks/main.yml index fa184fd..235eaa4 100644 --- a/roles/wireguard/tasks/main.yml +++ b/roles/wireguard/tasks/main.yml @@ -1,7 +1,7 @@ --- - name: Ensure the required directories exist file: - dest: "{{ wireguard_config_path }}/{{ item }}" + dest: "{{ wireguard_pki_path }}/{{ item }}" state: directory recurse: true with_items: @@ -28,7 +28,7 @@ - block: - name: WireGuard user list updated lineinfile: - dest: "{{ wireguard_config_path }}/index.txt" + dest: "{{ wireguard_pki_path }}/index.txt" create: true mode: "0600" insertafter: EOF @@ -37,7 +37,7 @@ with_items: "{{ users }}" - set_fact: - wireguard_users: "{{ (lookup('file', wireguard_config_path + 'index.txt')).split('\n') }}" + wireguard_users: "{{ (lookup('file', wireguard_pki_path + 'index.txt')).split('\n') }}" - name: WireGuard users config generated template: diff --git a/roles/wireguard/templates/client.conf.j2 b/roles/wireguard/templates/client.conf.j2 index b601abb..4c2fc62 100644 --- a/roles/wireguard/templates/client.conf.j2 +++ b/roles/wireguard/templates/client.conf.j2 @@ -1,12 +1,12 @@ [Interface] -PrivateKey = {{ lookup('file', wireguard_config_path + '/private/' + item.1) }} +PrivateKey = {{ lookup('file', wireguard_pki_path + '/private/' + item.1) }} Address = {{ wireguard_client_ip }} DNS = {{ wireguard_dns_servers }} {% if reduce_mtu|int > 0 %}MTU = {{ 1420 - reduce_mtu|int }} {% endif %} [Peer] -PublicKey = {{ lookup('file', wireguard_config_path + '/public/' + IP_subject_alt_name) }} +PublicKey = {{ lookup('file', wireguard_pki_path + '/public/' + IP_subject_alt_name) }} AllowedIPs = 0.0.0.0/0, ::/0 Endpoint = {{ IP_subject_alt_name }}:{{ wireguard_port }} {{ 'PersistentKeepalive = ' + wireguard_PersistentKeepalive|string if wireguard_PersistentKeepalive > 0 else '' }} diff --git a/roles/wireguard/templates/server.conf.j2 b/roles/wireguard/templates/server.conf.j2 index eb77f13..247c7d2 100644 --- a/roles/wireguard/templates/server.conf.j2 +++ b/roles/wireguard/templates/server.conf.j2 @@ -1,7 +1,7 @@ [Interface] Address = {{ wireguard_server_ip }} ListenPort = {{ wireguard_port }} -PrivateKey = {{ lookup('file', wireguard_config_path + '/private/' + IP_subject_alt_name) }} +PrivateKey = {{ lookup('file', wireguard_pki_path + '/private/' + IP_subject_alt_name) }} SaveConfig = false {% for u in wireguard_users %} @@ -10,8 +10,8 @@ SaveConfig = false [Peer] # {{ u }} -PublicKey = {{ lookup('file', wireguard_config_path + '/public/' + u) }} -AllowedIPs = {{ wireguard_network_ipv4['clients_range'] }}.{{ wireguard_network_ipv4['clients_start'] + index }}/32{% if ipv6_support %},{{ wireguard_network_ipv6['clients_range'] }}{{ wireguard_network_ipv6['clients_start'] + index }}/128{% endif %} +PublicKey = {{ lookup('file', wireguard_pki_path + '/public/' + u) }} +AllowedIPs = {{ _wireguard_network_ipv4['clients_range'] }}.{{ _wireguard_network_ipv4['clients_start'] + index }}/32{% if ipv6_support %},{{ _wireguard_network_ipv6['clients_range'] }}{{ _wireguard_network_ipv6['clients_start'] + index }}/128{% endif %} {% endif %} {% endfor %} diff --git a/server.yml b/server.yml index b6e8340..1ab3834 100644 --- a/server.yml +++ b/server.yml @@ -19,8 +19,9 @@ - role: wireguard when: wireguard_enabled tags: wireguard - - role: vpn - tags: vpn + - role: strongswan + when: ipsec_enabled + tags: ipsec - role: ssh_tunneling when: algo_ssh_tunneling tags: ssh_tunneling @@ -30,15 +31,17 @@ - name: Delete the CA key local_action: module: file - path: "configs/{{ IP_subject_alt_name }}/pki/private/cakey.pem" + path: "{{ ipsec_pki_path }}/private/cakey.pem" state: absent become: false - when: not algo_store_cakey + when: + - ipsec_enabled + - not algo_store_cakey - name: Dump the configuration local_action: module: copy - dest: "configs/{{ IP_subject_alt_name }}/config.yml" + dest: "configs/{{ IP_subject_alt_name }}/.config.yml" content: | server: {{ 'localhost' if inventory_hostname == 'localhost' else inventory_hostname }} server_user: {{ ansible_ssh_user }} @@ -55,6 +58,8 @@ algo_windows: {{ algo_windows }} algo_store_cakey: {{ algo_store_cakey }} IP_subject_alt_name: {{ IP_subject_alt_name }} + ipsec_enabled: {{ ipsec_enabled }} + wireguard_enabled: {{ wireguard_enabled }} {% if tests|default(false)|bool %}ca_password: {{ CA_password }}{% endif %} become: false @@ -69,9 +74,9 @@ - debug: msg: - "{{ congrats.common.split('\n') }}" - - " {{ congrats.p12_pass }}" - - " {% if algo_store_cakey %}{{ congrats.ca_key_pass }}{% endif %}" - - " {% if algo_provider != 'local' %}{{ congrats.ssh_access }}{% endif %}" + - " {{ congrats.p12_pass if algo_ssh_tunneling or ipsec_enabled else '' }}" + - " {{ congrats.ca_key_pass if algo_store_cakey and ipsec_enabled else '' }}" + - " {{ congrats.ssh_access if algo_provider != 'local' else ''}}" tags: always rescue: - debug: var=fail_hint diff --git a/tests/update-users.sh b/tests/update-users.sh index 2387103..c083994 100755 --- a/tests/update-users.sh +++ b/tests/update-users.sh @@ -11,7 +11,11 @@ else ansible-playbook users.yml -e "${USER_ARGS}" -t update-users fi -if sudo openssl crl -inform pem -noout -text -in configs/$LXC_IP/pki/crl/phone.crt | grep CRL +# +# IPsec +# + +if sudo openssl crl -inform pem -noout -text -in configs/$LXC_IP/ipsec/.pki/crl/phone.crt | grep CRL then echo "The CRL check passed" else @@ -19,10 +23,34 @@ if sudo openssl crl -inform pem -noout -text -in configs/$LXC_IP/pki/crl/phone.c exit 1 fi -if sudo openssl x509 -inform pem -noout -text -in configs/$LXC_IP/pki/certs/user1.crt | grep CN=user1 +if sudo openssl x509 -inform pem -noout -text -in configs/$LXC_IP/ipsec/.pki/certs/user1.crt | grep CN=user1 then echo "The new user exists" else echo "The new user does not exist" exit 1 fi + +# +# WireGuard +# + +if sudo test -f configs/$LXC_IP/wireguard/user1.conf + then + echo "WireGuard: The new user exists" + else + echo "WireGuard: The new user does not exist" + exit 1 +fi + +# +# SSH tunneling +# + +if sudo test -f configs/$LXC_IP/ssh-tunnel/user1.ssh_config + then + echo "SSH Tunneling: The new user exists" + else + echo "SSH Tunneling: The new user does not exist" + exit 1 +fi diff --git a/users.yml b/users.yml index 6442263..3f74294 100644 --- a/users.yml +++ b/users.yml @@ -21,13 +21,15 @@ - name: Import host specific variables include_vars: - file: "configs/{{ algo_server }}/config.yml" + file: "configs/{{ algo_server }}/.config.yml" - pause: prompt: Enter the password for the private CA key echo: false register: _ca_password - when: ca_password is undefined + when: + - ca_password is undefined + - ipsec_enabled - name: Set facts based on the input set_fact: @@ -42,7 +44,7 @@ groups: vpn-host ansible_ssh_user: "{{ server_user|default('root') }}" ansible_connection: "{% if algo_server == 'localhost' %}local{% else %}ssh{% endif %}" - ansible_python_interpreter: "/usr/bin/python2.7" + ansible_python_interpreter: "/usr/bin/python3" CA_password: "{{ CA_password }}" rescue: - debug: var=fail_hint @@ -56,7 +58,7 @@ become: true vars_files: - config.cfg - - "configs/{{ inventory_hostname }}/config.yml" + - "configs/{{ inventory_hostname }}/.config.yml" pre_tasks: - block: @@ -74,8 +76,9 @@ - role: wireguard tags: [ 'vpn', 'wireguard' ] when: wireguard_enabled - - role: vpn - tags: vpn + - role: strongswan + when: ipsec_enabled + tags: ipsec - role: ssh_tunneling when: algo_ssh_tunneling