diff --git a/IPv6-status.md b/IPv6-status.md new file mode 100644 index 000000000..79eba32bc --- /dev/null +++ b/IPv6-status.md @@ -0,0 +1,7 @@ +# IPv6 Status + +IPv6 support has been tested on the following providers with successful results: + + - DigitalOcean + - Linode + - Vultr using localhost install diff --git a/global_vars/default-site.yml b/global_vars/default-site.yml index d119dbce4..9de6de670 100644 --- a/global_vars/default-site.yml +++ b/global_vars/default-site.yml @@ -12,6 +12,8 @@ streisand_ssh_private_key: "~/.ssh/id_rsa" vpn_clients: 10 streisand_ad_blocking_enabled: no +streisand_ipv6_enabled: no + streisand_openconnect_enabled: yes streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: yes diff --git a/global_vars/integration/test-site.yml b/global_vars/integration/test-site.yml index c8b53b35f..c5367c59f 100644 --- a/global_vars/integration/test-site.yml +++ b/global_vars/integration/test-site.yml @@ -10,6 +10,8 @@ streisand_admin_email_var: "" # Take a few extra steps during server provisioning to make the client tests work streisand_client_test: true +streisand_ipv6_enabled: no + # Only services with corresponding tests are enabled. streisand_ad_blocking_enabled: yes streisand_shadowsocks_enabled: yes diff --git a/global_vars/noninteractive/amazon-site.yml b/global_vars/noninteractive/amazon-site.yml index 645bc3562..5364b646e 100644 --- a/global_vars/noninteractive/amazon-site.yml +++ b/global_vars/noninteractive/amazon-site.yml @@ -15,6 +15,8 @@ streisand_ssh_private_key: "~/.ssh/id_rsa" vpn_clients: 10 streisand_ad_blocking_enabled: no +streisand_ipv6_enabled: yes + streisand_openconnect_enabled: yes streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: yes diff --git a/global_vars/noninteractive/azure-site.yml b/global_vars/noninteractive/azure-site.yml index 359d1e324..7f5bed138 100644 --- a/global_vars/noninteractive/azure-site.yml +++ b/global_vars/noninteractive/azure-site.yml @@ -14,7 +14,8 @@ streisand_ssh_private_key: "~/.ssh/id_rsa" vpn_clients: 10 -streisand_ad_blocking_enabled: no +streisand_ipv6_enabled: no + streisand_openconnect_enabled: yes streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: yes diff --git a/global_vars/noninteractive/digitalocean-site.yml b/global_vars/noninteractive/digitalocean-site.yml index 45068ba1c..37ed361a6 100644 --- a/global_vars/noninteractive/digitalocean-site.yml +++ b/global_vars/noninteractive/digitalocean-site.yml @@ -19,6 +19,8 @@ streisand_ssh_private_key: "~/.ssh/id_rsa" vpn_clients: 10 streisand_ad_blocking_enabled: no +streisand_ipv6_enabled: yes + streisand_openconnect_enabled: yes streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: yes diff --git a/global_vars/noninteractive/google-site.yml b/global_vars/noninteractive/google-site.yml index ff6c12044..ae766acd8 100644 --- a/global_vars/noninteractive/google-site.yml +++ b/global_vars/noninteractive/google-site.yml @@ -13,7 +13,8 @@ streisand_ssh_private_key: "~/.ssh/id_rsa" vpn_clients: 10 -streisand_ad_blocking_enabled: no +streisand_ipv6_enabled: no + streisand_openconnect_enabled: yes streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: yes diff --git a/global_vars/noninteractive/linode-site.yml b/global_vars/noninteractive/linode-site.yml index 4a2d44078..7c97b3905 100644 --- a/global_vars/noninteractive/linode-site.yml +++ b/global_vars/noninteractive/linode-site.yml @@ -13,6 +13,8 @@ streisand_ssh_private_key: "~/.ssh/id_rsa" vpn_clients: 10 streisand_ad_blocking_enabled: no +streisand_ipv6_enabled: yes + streisand_openconnect_enabled: yes streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: yes diff --git a/global_vars/noninteractive/local-site.yml b/global_vars/noninteractive/local-site.yml index 4d5c4f966..00c5de2ea 100644 --- a/global_vars/noninteractive/local-site.yml +++ b/global_vars/noninteractive/local-site.yml @@ -14,6 +14,8 @@ streisand_ssh_private_key: "~/.ssh/id_rsa" vpn_clients: 10 streisand_ad_blocking_enabled: no +streisand_ipv6_enabled: yes + streisand_openconnect_enabled: yes streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: yes diff --git a/global_vars/noninteractive/rackspace-site.yml b/global_vars/noninteractive/rackspace-site.yml index ae84f9471..99fb3acaf 100644 --- a/global_vars/noninteractive/rackspace-site.yml +++ b/global_vars/noninteractive/rackspace-site.yml @@ -13,6 +13,8 @@ streisand_ssh_private_key: "~/.ssh/id_rsa" vpn_clients: 10 streisand_ad_blocking_enabled: no +streisand_ipv6_enabled: yes + streisand_openconnect_enabled: yes streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: yes diff --git a/global_vars/vars.yml b/global_vars/vars.yml new file mode 100644 index 000000000..7ac22bc82 --- /dev/null +++ b/global_vars/vars.yml @@ -0,0 +1,12 @@ +--- +upstream_dns_servers: + - 8.8.8.8 + - 8.8.4.4 + +upstream_dns_servers_v6: + - "2001:4860:4860::8888" + - "2001:4860:4860::8844" + +streisand_client_test: no + +streisand_site_vars: "{{ lookup('env','HOME') }}/.streisand/site.yml" diff --git a/playbooks/customize.yml b/playbooks/customize.yml index 87d03c0fc..d1c7188b4 100644 --- a/playbooks/customize.yml +++ b/playbooks/customize.yml @@ -16,6 +16,10 @@ prompt: "Enable DNS-based ad-blocking? Press enter for default " default: "no" private: no + - name: streisand_ipv6_enabled + prompt: "Enable IPv6, if available? Press enter for default (no)" + default: "no" + private: no - name: streisand_openconnect_enabled prompt: "Enable OpenConnect? Press enter for default " default: "yes" @@ -74,6 +78,9 @@ path: "{{ streisand_site_vars }}" regexp: "^streisand_ad_blocking_enabled: (?:yes|no)$" line: "streisand_ad_blocking_enabled: {{ streisand_ad_blocking_enabled }}" + - lineinfile: + regexp: "^streisand_ipv6_enabled: (?:yes|no)$" + line: "streisand_ipv6_enabled: {{ streisand_ipv6_enabled }}" - lineinfile: path: "{{ streisand_site_vars }}" regexp: "^streisand_openconnect_enabled: (?:yes|no)$" diff --git a/playbooks/roles/certificates/templates/openssl.cnf.j2 b/playbooks/roles/certificates/templates/openssl.cnf.j2 index e81bea633..1a35c65b0 100644 --- a/playbooks/roles/certificates/templates/openssl.cnf.j2 +++ b/playbooks/roles/certificates/templates/openssl.cnf.j2 @@ -52,6 +52,10 @@ subjectAltName = @alt_names {% for item in tls_sans %} IP.{{ loop.index }} = {{ item }} {% endfor %} +{% if streisand_ipv6_address is defined %} +IP.1 = {{ streisand_ipv6_address }} +{% endif %} + [ req_distinguished_name ] countryName = Country Name (2 letter code) diff --git a/playbooks/roles/common/tasks/set-default-variables.yml b/playbooks/roles/common/tasks/set-default-variables.yml index 67b11af2e..a0fc6382a 100644 --- a/playbooks/roles/common/tasks/set-default-variables.yml +++ b/playbooks/roles/common/tasks/set-default-variables.yml @@ -39,3 +39,16 @@ - import_tasks: detect-public-ip.yml when: (hostvars['127.0.0.1']['streisand_genesis_role'] is defined and ((hostvars['127.0.0.1']['streisand_genesis_role'] == "localhost") or (hostvars['127.0.0.1']['streisand_genesis_role'] == "existing-server"))) + +- name: If streisand_ipv6_address is undefined and IPv6 is enabled, change it to Ansible's default IPv6 address + set_fact: + streisand_ipv6_address: "{{ ansible_default_ipv6.address }}" + when: + - streisand_ipv6_address is not defined + - streisand_ipv6_enabled + - ansible_default_ipv6.address is defined + +- name: If there's an IPv6 address, generate a gateway URL using it + set_fact: + streisand_gateway_url_ipv6: "https://[{{ streisand_ipv6_address }}]" + when: streisand_ipv6_address is defined diff --git a/playbooks/roles/dnsmasq/templates/dnsmasq.conf.j2 b/playbooks/roles/dnsmasq/templates/dnsmasq.conf.j2 index d28901e6a..2bc08057e 100644 --- a/playbooks/roles/dnsmasq/templates/dnsmasq.conf.j2 +++ b/playbooks/roles/dnsmasq/templates/dnsmasq.conf.j2 @@ -5,7 +5,7 @@ # dnsmasq will not automatically listen on the loopback interface. To achieve # this, its IP address, 127.0.0.1, must be explicitly given as # a --listen-address option. -listen-address=127.0.0.1 +listen-address=::1,127.0.0.1 # Never forward plain names (without a dot or domain part) domain-needed @@ -21,3 +21,8 @@ no-resolv {% for item in upstream_dns_servers %} server={{ item }} {% endfor %} +{% if streisand_ipv6_address is defined %} +{% for item in upstream_dns_servers_v6 %} +server={{ item }} +{% endfor %} +{% endif %} diff --git a/playbooks/roles/ip-forwarding/tasks/main.yml b/playbooks/roles/ip-forwarding/tasks/main.yml index f491ef521..59670d753 100644 --- a/playbooks/roles/ip-forwarding/tasks/main.yml +++ b/playbooks/roles/ip-forwarding/tasks/main.yml @@ -5,9 +5,15 @@ value: 1 when: ansible_virtualization_type != 'lxc' -- name: "Add IPv4 traffic forwarding persistence service to init" - copy: - src: streisand-ipforward.sh +- name: "Enable IPv6 traffic forwarding" + sysctl: + name: net.ipv6.conf.all.forwarding + value: 1 + when: (ansible_virtualization_type != 'lxc') and (streisand_ipv6_enabled) + +- name: "Add IPv4/IPv6 traffic forwarding persistence service to init" + template: + src: streisand-ipforward.sh.j2 dest: /etc/init.d/streisand-ipforward mode: 0755 diff --git a/playbooks/roles/ip-forwarding/files/streisand-ipforward.sh b/playbooks/roles/ip-forwarding/templates/streisand-ipforward.sh.j2 similarity index 79% rename from playbooks/roles/ip-forwarding/files/streisand-ipforward.sh rename to playbooks/roles/ip-forwarding/templates/streisand-ipforward.sh.j2 index 71e7fba02..fd12ab1be 100644 --- a/playbooks/roles/ip-forwarding/files/streisand-ipforward.sh +++ b/playbooks/roles/ip-forwarding/templates/streisand-ipforward.sh.j2 @@ -9,7 +9,10 @@ ### END INIT INFO echo 1 > /proc/sys/net/ipv4/ip_forward - echo 0 | tee /proc/sys/net/ipv4/conf/*/*_redirects +{% if streisand_ipv6_address is defined %} +echo 1 > /proc/sys/net/ipv6/conf/all/forwarding +{% endif %} + exit 0 diff --git a/playbooks/roles/openconnect/tasks/firewall.yml b/playbooks/roles/openconnect/tasks/firewall.yml index f388cdfd0..c1bccfef6 100644 --- a/playbooks/roles/openconnect/tasks/firewall.yml +++ b/playbooks/roles/openconnect/tasks/firewall.yml @@ -4,7 +4,11 @@ to_port: "53" proto: "udp" rule: "allow" - from_ip: "192.168.1.0/24" + from_ip: "{{ item.addr }}" + with_items: + - { addr: "{{ ocserv_ipv4_network }}" } + - { addr: "{{ ocserv_ipv6_network }}", create: "{{ streisand_ipv6_enabled }}" } + when: item.create | default(True) | bool - name: Ensure UFW allows OpenConnect (ocserv) ufw: diff --git a/playbooks/roles/openconnect/templates/config.j2 b/playbooks/roles/openconnect/templates/config.j2 index 8858e347c..ef39206b6 100644 --- a/playbooks/roles/openconnect/templates/config.j2 +++ b/playbooks/roles/openconnect/templates/config.j2 @@ -45,8 +45,18 @@ use-occtl = true pid-file = {{ ocserv_pid_file }} device = vpns default-domain = example.com -ipv4-network = 192.168.1.0 -ipv4-netmask = 255.255.255.0 +ipv4-network = {{ ocserv_ipv4_network }} + +{% if streisand_ipv6_address is defined %} +ipv6-network = {{ ocserv_ipv6_network }} +{% for item in upstream_dns_servers_v6 %} +dns = {{ item }} +{% endfor %} +ipv6-subnet-prefix = 64 +{% endif %} + +route = default + ping-leases = false cisco-client-compat = true max-clients = {{ vpn_clients + 1 }} diff --git a/playbooks/roles/openconnect/templates/ocserv-iptables.service.j2 b/playbooks/roles/openconnect/templates/ocserv-iptables.service.j2 index 93b3dbb54..ba0b9d21e 100644 --- a/playbooks/roles/openconnect/templates/ocserv-iptables.service.j2 +++ b/playbooks/roles/openconnect/templates/ocserv-iptables.service.j2 @@ -8,5 +8,9 @@ Type=oneshot RemainAfterExit=true ExecStart=/sbin/{{ ocserv_firewall_rule }} +{% if streisand_ipv6_enabled %} +ExecStart=/sbin/{{ ocserv_firewall_rule_v6 }} +{% endif %} + [Install] -WantedBy=multi-user.target \ No newline at end of file +WantedBy=multi-user.target diff --git a/playbooks/roles/openconnect/templates/ocserv-ipv6tables.service.j2 b/playbooks/roles/openconnect/templates/ocserv-ipv6tables.service.j2 new file mode 100644 index 000000000..6b8646139 --- /dev/null +++ b/playbooks/roles/openconnect/templates/ocserv-ipv6tables.service.j2 @@ -0,0 +1,12 @@ +[Unit] +Description=Set the firewall rules required for ocserv +After=network.target +Before=ocserv.service + +[Service] +Type=oneshot +RemainAfterExit=true +ExecStart=/sbin/{{ ocserv_firewall_rule_v6 }} + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/playbooks/roles/openconnect/vars/main.yml b/playbooks/roles/openconnect/vars/main.yml index 4f1456a92..a0108c8ef 100644 --- a/playbooks/roles/openconnect/vars/main.yml +++ b/playbooks/roles/openconnect/vars/main.yml @@ -3,6 +3,10 @@ ocserv_path: "/etc/ocserv" ocserv_ca: "{{ ocserv_path }}/ca" ocserv_config_file: "{{ ocserv_path }}/ocserv.conf" ocserv_firewall_rule: "iptables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -j MASQUERADE" +ocserv_firewall_rule_v6: "ip6tables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -j MASQUERADE" + +ocserv_ipv4_network: "192.168.1.0/24" +ocserv_ipv6_network: "fda9:4efe:7e3b:03ea::/48" ocserv_days_valid: "1825" ocserv_pid_file: "/var/run/ocserv.pid" diff --git a/playbooks/roles/openvpn/tasks/firewall.yml b/playbooks/roles/openvpn/tasks/firewall.yml index e2d516629..a0e13f16f 100644 --- a/playbooks/roles/openvpn/tasks/firewall.yml +++ b/playbooks/roles/openvpn/tasks/firewall.yml @@ -3,19 +3,32 @@ command: "{{ item }}" with_items: "{{ openvpn_firewall_rules }}" -- name: Ensure UFW allows DNS requests from OpenVPN clients +- name: Allow OpenVPN over IPv6 through the firewall + command: "{{ item }}" + with_items: "{{ openvpn_ipv6_firewall_rules }}" + when: streisand_ipv6_address is defined + +- name: Ensure UFW allows DNS requests from OpenVPN TCP clients ufw: to_port: "53" proto: "udp" rule: "allow" - from_ip: "10.8.0.0/24" + from_ip: "{{ item.addr }}" + with_items: + - { addr: "{{ openvpn_server_tcp_ipv4_address }}" } + - { addr: "{{ openvpn_server_tcp_ipv6_address }}", create: "{{ streisand_ipv6_enabled }}" } + when: item.create | default(True) | bool - name: Ensure UFW allows DNS requests from OpenVPN UDP clients ufw: to_port: "53" proto: "udp" rule: "allow" - from_ip: "10.9.0.0/24" + from_ip: "{{ item.addr }}" + with_items: + - { addr: "{{ openvpn_server_udp_ipv4_address }}" } + - { addr: "{{ openvpn_server_udp_ipv6_address }}", create: "{{ streisand_ipv6_enabled }}" } + when: item.create | default(True) | bool - name: Ensure UFW allows OpenVPN ufw: diff --git a/playbooks/roles/openvpn/tasks/main.yml b/playbooks/roles/openvpn/tasks/main.yml index 18d03f64c..37cca7a2f 100644 --- a/playbooks/roles/openvpn/tasks/main.yml +++ b/playbooks/roles/openvpn/tasks/main.yml @@ -2,10 +2,14 @@ # Add the apt key and install OpenVPN - import_tasks: install.yml -- name: "Configure DNSMasq to listen on {{ dnsmasq_openvpn_tcp_ip }}:53 and {{ dnsmasq_openvpn_udp_ip }}:53" +- name: "Configure DNSMasq to listen on TCP and UDP ports 53" template: src: openvpn_dnsmasq.conf.j2 dest: /etc/dnsmasq.d/openvpn.conf + with_items: + - { src: "openvpn_dnsmasq.conf.j2", dst: "/etc/dnsmasq.d/openvpn.conf" } + - { src: "openvpn_dnsmasqv6.conf.j2", dst: "/etc/dnsmasq.d/openvpnv6.conf", create: "{{ streisand_ipv6_enabled }}" } + when: item.create | default(True) | bool notify: Restart dnsmasq - include_role: diff --git a/playbooks/roles/openvpn/templates/client-common.j2 b/playbooks/roles/openvpn/templates/client-common.j2 index cffd8cbf3..db9cc6470 100644 --- a/playbooks/roles/openvpn/templates/client-common.j2 +++ b/playbooks/roles/openvpn/templates/client-common.j2 @@ -1,4 +1,4 @@ -dev tun +dev tun-ipv6 cipher {{ openvpn_cipher }} auth {{ openvpn_auth_digest }} resolv-retry infinite @@ -10,7 +10,8 @@ verify-x509-name {{ openvpn_server_common_name.stdout }} name tls-version-min 1.2 compress verb 3 -route {{ streisand_ipv4_address }} 255.255.255.255 net_gateway + +#route {{ streisand_ipv4_address }} 255.255.255.255 net_gateway {{ openvpn_ca_contents.stdout }} diff --git a/playbooks/roles/openvpn/templates/client-direct-udp.ovpn.j2 b/playbooks/roles/openvpn/templates/client-direct-udp.ovpn.j2 index 7b588a498..9cb4bb081 100644 --- a/playbooks/roles/openvpn/templates/client-direct-udp.ovpn.j2 +++ b/playbooks/roles/openvpn/templates/client-direct-udp.ovpn.j2 @@ -1,4 +1,6 @@ client -remote {{ openvpn_server }} {{ openvpn_port_udp }} -proto udp +{% if streisand_ipv6_address is defined %} +remote {{ streisand_ipv6_address }} {{ openvpn_port_udp }} udp6 +{% endif %} +remote {{ openvpn_server }} {{ openvpn_port_udp }} udp {% include "client-common.j2" %} diff --git a/playbooks/roles/openvpn/templates/client-direct.ovpn.j2 b/playbooks/roles/openvpn/templates/client-direct.ovpn.j2 index 7355b2387..68b23658a 100644 --- a/playbooks/roles/openvpn/templates/client-direct.ovpn.j2 +++ b/playbooks/roles/openvpn/templates/client-direct.ovpn.j2 @@ -1,4 +1,6 @@ client -remote {{ openvpn_server }} {{ openvpn_port }} -proto tcp +{% if streisand_ipv6_address is defined %} +remote {{ streisand_ipv6_address }} {{ openvpn_port}} tcp6 +{% endif %} +remote {{ openvpn_server }} {{ openvpn_port }} tcp {% include "client-common.j2" %} diff --git a/playbooks/roles/openvpn/templates/client-sslh.ovpn.j2 b/playbooks/roles/openvpn/templates/client-sslh.ovpn.j2 index 880e351d2..5a891bffb 100644 --- a/playbooks/roles/openvpn/templates/client-sslh.ovpn.j2 +++ b/playbooks/roles/openvpn/templates/client-sslh.ovpn.j2 @@ -1,4 +1,6 @@ client -remote {{ openvpn_server }} {{ openvpn_port_sslh }} -proto tcp +{% if streisand_ipv6_address is defined %} +remote {{ streisand_ipv6_address }} {{ openvpn_port_sslh }} tcp6 +{% endif %} +remote {{ openvpn_server }} {{ openvpn_port_sslh }} tcp {% include "client-common.j2" %} diff --git a/playbooks/roles/openvpn/templates/etc_openvpn_server.conf.j2 b/playbooks/roles/openvpn/templates/etc_openvpn_server.conf.j2 index 1af2b8702..d56c281ff 100644 --- a/playbooks/roles/openvpn/templates/etc_openvpn_server.conf.j2 +++ b/playbooks/roles/openvpn/templates/etc_openvpn_server.conf.j2 @@ -1,5 +1,11 @@ server 10.8.0.0 255.255.255.0 push "dhcp-option DNS {{ dnsmasq_openvpn_tcp_ip }}" -proto tcp +proto tcp6 + +{% if streisand_ipv6_address is defined %} +server-ipv6 2001:db8:0:124::/64 +push "dhcp-option DNS6 {{ dnsmasq_openvpn_tcp_ipv6 }}" +{% endif %} + port {{ openvpn_port }} {% include "etc_openvpn_server_common.j2" %} diff --git a/playbooks/roles/openvpn/templates/etc_openvpn_server_common.j2 b/playbooks/roles/openvpn/templates/etc_openvpn_server_common.j2 index bc3485ff3..8d5804289 100644 --- a/playbooks/roles/openvpn/templates/etc_openvpn_server_common.j2 +++ b/playbooks/roles/openvpn/templates/etc_openvpn_server_common.j2 @@ -6,6 +6,10 @@ dh none ifconfig-pool-persist ipp.txt push "redirect-gateway def1" +{% if streisand_ipv6_address is defined %} +push "route-ipv6 ::/0" +{% endif %} + # Fix for the Windows 10 DNS leak described here: # https://community.openvpn.net/openvpn/ticket/605 push block-outside-dns diff --git a/playbooks/roles/openvpn/templates/etc_openvpn_server_udp.conf.j2 b/playbooks/roles/openvpn/templates/etc_openvpn_server_udp.conf.j2 index 528ff2cd5..a4d6e4607 100644 --- a/playbooks/roles/openvpn/templates/etc_openvpn_server_udp.conf.j2 +++ b/playbooks/roles/openvpn/templates/etc_openvpn_server_udp.conf.j2 @@ -1,5 +1,11 @@ server 10.9.0.0 255.255.255.0 push "dhcp-option DNS {{ dnsmasq_openvpn_udp_ip }}" -proto udp + +{% if streisand_ipv6_address is defined %} +server-ipv6 2001:db8:0:123::/64 +push "dhcp-option DNS6 {{ dnsmasq_openvpn_udp_ipv6 }}" +{% endif %} + +proto udp6 port {{ openvpn_port_udp }} {% include "etc_openvpn_server_common.j2" %} diff --git a/playbooks/roles/openvpn/templates/openvpn-iptables.service.j2 b/playbooks/roles/openvpn/templates/openvpn-iptables.service.j2 index 28fe64e46..55c23dbd1 100644 --- a/playbooks/roles/openvpn/templates/openvpn-iptables.service.j2 +++ b/playbooks/roles/openvpn/templates/openvpn-iptables.service.j2 @@ -10,5 +10,11 @@ RemainAfterExit=true ExecStart=/sbin/{{ rule }} {% endfor %} +{% if streisand_ipv6_enabled %} +{% for rule in openvpn_ipv6_firewall_rules %} +ExecStart=/sbin/{{ rule }} +{% endfor %} +{% endif %} + [Install] WantedBy=multi-user.target diff --git a/playbooks/roles/openvpn/templates/openvpn_dnsmasqv6.conf.j2 b/playbooks/roles/openvpn/templates/openvpn_dnsmasqv6.conf.j2 new file mode 100644 index 000000000..431a476a4 --- /dev/null +++ b/playbooks/roles/openvpn/templates/openvpn_dnsmasqv6.conf.j2 @@ -0,0 +1,2 @@ +# Listen on the OpenVPN TCP and UDP addresses +listen-address={{ dnsmasq_openvpn_tcp_ipv6 }},{{ dnsmasq_openvpn_udp_ipv6 }} diff --git a/playbooks/roles/openvpn/vars/main.yml b/playbooks/roles/openvpn/vars/main.yml index 5ab469384..2d0d7a879 100644 --- a/playbooks/roles/openvpn/vars/main.yml +++ b/playbooks/roles/openvpn/vars/main.yml @@ -16,12 +16,28 @@ openvpn_combined_profile_filename: "{{ openvpn_server }}-combined.ovpn" dnsmasq_openvpn_tcp_ip: "10.8.0.1" dnsmasq_openvpn_udp_ip: "10.9.0.1" +openvpn_server_tcp_ipv4_address: "10.9.0.0/24" +openvpn_server_udp_ipv4_address: "10.8.0.0/24" + +dnsmasq_openvpn_tcp_ipv6: "2001:db8:0:124::1001" +dnsmasq_openvpn_udp_ipv6: "2001:db8:0:123::1001" + +openvpn_server_tcp_ipv6_address: "2001:db8:0:124::/64" +openvpn_server_udp_ipv6_address: "2001:db8:0:123::/64" + openvpn_firewall_rules: - "iptables --wait {{ streisand_iptables_wait }} -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT" - - "iptables --wait {{ streisand_iptables_wait }} -A FORWARD -s 10.8.0.0/24 -j ACCEPT" - - "iptables --wait {{ streisand_iptables_wait }} -A FORWARD -s 10.9.0.0/24 -j ACCEPT" - - "iptables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -s 10.8.0.0/24 -o {{ ansible_default_ipv4.interface }} -j MASQUERADE" - - "iptables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -s 10.9.0.0/24 -o {{ ansible_default_ipv4.interface }} -j MASQUERADE" + - "iptables --wait {{ streisand_iptables_wait }} -A FORWARD -s {{ openvpn_server_udp_ipv4_address }} -j ACCEPT" + - "iptables --wait {{ streisand_iptables_wait }} -A FORWARD -s {{ openvpn_server_tcp_ipv4_address }} -j ACCEPT" + - "iptables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -s {{ openvpn_server_udp_ipv4_address }} -o {{ ansible_default_ipv4.interface }} -j MASQUERADE" + - "iptables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -s {{ openvpn_server_tcp_ipv4_address }} -o {{ ansible_default_ipv4.interface }} -j MASQUERADE" + +openvpn_ipv6_firewall_rules: + - "ip6tables --wait {{ streisand_iptables_wait }} -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT" + - "ip6tables --wait {{ streisand_iptables_wait }} -A FORWARD -s {{ openvpn_server_tcp_ipv6_address }} -j ACCEPT" + - "ip6tables --wait {{ streisand_iptables_wait }} -A FORWARD -s {{ openvpn_server_udp_ipv6_address }} -j ACCEPT" + - "ip6tables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -s {{ openvpn_server_tcp_ipv6_address }} -o {{ ansible_default_ipv6.interface }} -j MASQUERADE" + - "ip6tables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -s {{ openvpn_server_udp_ipv6_address }} -o {{ ansible_default_ipv6.interface }} -j MASQUERADE" openvpn_gateway_location: "{{ streisand_gateway_location }}/openvpn" diff --git a/playbooks/roles/shadowsocks/templates/config.json.j2 b/playbooks/roles/shadowsocks/templates/config.json.j2 index d315a3c5e..463ce2372 100644 --- a/playbooks/roles/shadowsocks/templates/config.json.j2 +++ b/playbooks/roles/shadowsocks/templates/config.json.j2 @@ -1,5 +1,10 @@ { - "server":"{{ ansible_default_ipv4.address }}", + "server": +{% if streisand_ipv6_address is defined %} + ["{{ ansible_default_ipv4.address }}", "{{ ansible_default_ipv6.address }}"], +{% else %} + "{{ ansible_default_ipv4.address }}", +{% endif %} "server_port":{{ shadowsocks_server_port }}, "local_port":{{ shadowsocks_local_port }}, "password":"{{ shadowsocks_password.stdout }}", diff --git a/playbooks/roles/sslh/templates/sslh.cfg.j2 b/playbooks/roles/sslh/templates/sslh.cfg.j2 index 31623c59d..a0080111b 100644 --- a/playbooks/roles/sslh/templates/sslh.cfg.j2 +++ b/playbooks/roles/sslh/templates/sslh.cfg.j2 @@ -6,6 +6,9 @@ pidfile: "{{ sslh_pid_file }}"; listen: ( +{% if streisand_ipv6_address is defined %} + { host: "{{ ansible_default_ipv6.address }}"; port: "443"; }, +{% endif %} { host: "{{ ansible_default_ipv4.address }}"; port: "443"; } ); diff --git a/playbooks/roles/streisand-gateway/templates/instructions-fr.md.j2 b/playbooks/roles/streisand-gateway/templates/instructions-fr.md.j2 index 67a944b1a..cebc9cb61 100644 --- a/playbooks/roles/streisand-gateway/templates/instructions-fr.md.j2 +++ b/playbooks/roles/streisand-gateway/templates/instructions-fr.md.j2 @@ -190,6 +190,10 @@ Connexion à votre passerelle Streisand [{{ streisand_domain }}](https://{{ streisand_domain }}/index-fr.html) {% else %} [{{ streisand_gateway_url }}]({{ streisand_gateway_url }}/index-fr.html) +{% if streisand_gateway_url_ipv6 is defined %} + +IPv6: [{{ streisand_gateway_url_ipv6 }}]({{ streisand_gateway_url_ipv6 }}) +{% endif %} {% endif %} username: `{{ streisand_gateway_username }}` diff --git a/playbooks/roles/streisand-gateway/templates/instructions.md.j2 b/playbooks/roles/streisand-gateway/templates/instructions.md.j2 index 14b42b4a5..093d846cd 100644 --- a/playbooks/roles/streisand-gateway/templates/instructions.md.j2 +++ b/playbooks/roles/streisand-gateway/templates/instructions.md.j2 @@ -180,6 +180,10 @@ Connecting to your Streisand Gateway [{{ streisand_domain }}](https://{{ streisand_domain }}) {% else %} [{{ streisand_gateway_url }}]({{ streisand_gateway_url }}) +{% if streisand_gateway_url_ipv6 is defined %} + +IPv6: [{{ streisand_gateway_url_ipv6 }}]({{ streisand_gateway_url_ipv6 }}) +{% endif %} {% endif %} username: `{{ streisand_gateway_username }}` diff --git a/playbooks/roles/streisand-gateway/templates/openssl-local.cnf.j2 b/playbooks/roles/streisand-gateway/templates/openssl-local.cnf.j2 index 3dd4eaa80..41dc08324 100644 --- a/playbooks/roles/streisand-gateway/templates/openssl-local.cnf.j2 +++ b/playbooks/roles/streisand-gateway/templates/openssl-local.cnf.j2 @@ -44,6 +44,10 @@ emailAddress = optional [ alt_names ] IP.0 = {{ streisand_ipv4_address }} DNS.0 = {{ streisand_ipv4_address }} +{% if streisand_ipv6_address is defined %} +IP.1 = {{ streisand_ipv6_address }} +DNS.1 = {{ streisand_ipv6_address }} +{% endif %} [ req_distinguished_name ] countryName = Country Name (2 letter code) diff --git a/playbooks/roles/stunnel/templates/stunnel-local.conf.j2 b/playbooks/roles/stunnel/templates/stunnel-local.conf.j2 index 9f4aa68da..9fe584beb 100644 --- a/playbooks/roles/stunnel/templates/stunnel-local.conf.j2 +++ b/playbooks/roles/stunnel/templates/stunnel-local.conf.j2 @@ -2,4 +2,7 @@ client = yes [stunnel] accept = 127.0.0.1:{{ stunnel_local_port }} +{% if streisand_ipv6_address is defined %} +connect = {{ streisand_ipv6_address }}:{{ stunnel_remote_port }} +{% endif %} connect = {{ streisand_ipv4_address }}:{{ stunnel_remote_port }} diff --git a/playbooks/roles/wireguard/tasks/firewall.yml b/playbooks/roles/wireguard/tasks/firewall.yml index 43c90986a..4dfbf9f07 100644 --- a/playbooks/roles/wireguard/tasks/firewall.yml +++ b/playbooks/roles/wireguard/tasks/firewall.yml @@ -1,28 +1,43 @@ --- -- name: Ensure UFW allows DNS requests from WireGuard clients +- name: "Ensure UFW allows DNS requests from WireGuard clients" ufw: to_port: "53" proto: "udp" rule: "allow" from_ip: "10.192.122.0/24" -- name: Ensure UFW allows WireGuard +- name: "Ensure UFW allows DNS requests from WireGuard IPv6 clients" + ufw: + to_port: "53" + proto: "udp" + rule: "allow" + from_ip: "fde9:7496:c3d7:a47f::/64" + when: streisand_ipv6_address is defined + +- name: "Ensure UFW allows WireGuard" ufw: to_port: "{{ wireguard_port }}" proto: "udp" rule: "allow" -- name: Allow WireGuard through the firewall +- name: "Allow WireGuard through the firewall" command: "{{ item }}" with_items: "{{ wireguard_firewall_rules }}" -- name: "Add WireGuard firewall persistence service to init" +- name: "Allow WireGuard over IPv6 through the firewall" + command: "{{ item }}" + with_items: "{{ wireguard_firewallv6_rules }}" + when: streisand_ipv6_address is defined + +- name: "Add WireGuard firewall persistence service" template: - src: streisand-wireguard-service.sh.j2 - dest: /etc/init.d/streisand-wireguard - mode: 0755 + src: wireguard-iptables.service.j2 + dest: /etc/systemd/system/wireguard-iptables.service + mode: 0644 -- name: "Enable the streisand-wireguard init service" - service: - name: streisand-wireguard +- name: "Enable the wireguard-iptables service" + systemd: + daemon_reload: yes + name: wireguard-iptables.service enabled: yes + state: started diff --git a/playbooks/roles/wireguard/tasks/main.yml b/playbooks/roles/wireguard/tasks/main.yml index c44a207d7..69b358fa1 100644 --- a/playbooks/roles/wireguard/tasks/main.yml +++ b/playbooks/roles/wireguard/tasks/main.yml @@ -122,6 +122,12 @@ src: wireguard_dnsmasq.conf.j2 dest: /etc/dnsmasq.d/wireguard.conf +- name: "Configure DNSMasq to listen on {{ dnsmasq_wireguard_ipv6 }}:53" + template: + src: wireguard_dnsmasqv6.conf.j2 + dest: /etc/dnsmasq.d/wireguardv6.conf + when: streisand_ipv6_enabled + # NOTE(@cpu): We don't use a `notify` to "Restart dnsmasq" here because it seems # that in some conditions Ansible mistakenly believes the dnsmasq restart can be # skipped. We also don't use "reloaded" instead of "restarted" here because diff --git a/playbooks/roles/wireguard/templates/client.conf.j2 b/playbooks/roles/wireguard/templates/client.conf.j2 index 40a7f51ce..40ed59872 100644 --- a/playbooks/roles/wireguard/templates/client.conf.j2 +++ b/playbooks/roles/wireguard/templates/client.conf.j2 @@ -1,6 +1,8 @@ # "{{ item[0].stdout }}" - Streisand WireGuard Client Profile [Interface] -Address = 10.192.122.{{ (item[0].item|int) + 1 }}/32 +Address = 10.192.122.{{ (item[0].item|int) + 1 }}/32{% if streisand_ipv6_address is defined %},fde9:7496:c3d7:a47f::{{ 1001+(item[0].item|int) }}/128 {% endif %} + + # The use of DNS below effectively expands to: # PostUp = echo nameserver {{ dnsmasq_wireguard_ip }} | resolvconf -a tun.%i -m 0 -x # PostDown = resolvconf -d tun.%i @@ -8,7 +10,9 @@ Address = 10.192.122.{{ (item[0].item|int) + 1 }}/32 # and use a variant of the PostUp/PostDown lines above. # The IP address of the DNS server that is available via the encrypted # WireGuard interface is {{ dnsmasq_wireguard_ip }}. -DNS = {{ dnsmasq_wireguard_ip }} +DNS = {{ dnsmasq_wireguard_ip }}{% if streisand_ipv6_address is defined %},{{ dnsmasq_wireguard_ipv6 }} {% endif %} + + PrivateKey = {{ item[1].stdout }} [Peer] diff --git a/playbooks/roles/wireguard/templates/server.conf.j2 b/playbooks/roles/wireguard/templates/server.conf.j2 index dc544e56c..2d0c107d2 100644 --- a/playbooks/roles/wireguard/templates/server.conf.j2 +++ b/playbooks/roles/wireguard/templates/server.conf.j2 @@ -1,5 +1,6 @@ [Interface] -Address = 10.192.122.1/24 +Address = 10.192.122.1/24{% if streisand_ipv6_address is defined %},fde9:7496:c3d7:a47f::1001/64 +{% endif %} SaveConfig = true ListenPort = {{ wireguard_port }} PrivateKey = {{ wireguard_server_private_key }} @@ -8,6 +9,7 @@ PrivateKey = {{ wireguard_server_private_key }} # "{{ client.stdout }}" Client Peer [Peer] PublicKey = {{ wireguard_client_pubkeys.results[(client.item|int)-1].stdout }} -AllowedIPs = 10.192.122.{{ (client.item|int)+1 }}/32 +AllowedIPs = 10.192.122.{{ (client.item|int)+1 }}/32{% if streisand_ipv6_address is defined %},fde9:7496:c3d7:a47f::{{ 1000+(client.item|int)+1 }}/128 +{% endif %} {% endfor %} diff --git a/playbooks/roles/wireguard/templates/streisand-wireguard-service.sh.j2 b/playbooks/roles/wireguard/templates/streisand-wireguard-service.sh.j2 deleted file mode 100644 index 91f76fa8f..000000000 --- a/playbooks/roles/wireguard/templates/streisand-wireguard-service.sh.j2 +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -### BEGIN INIT INFO -# Provides: streisand-wireguard -# Required-Start: $network $remote_fs $local_fs -# Required-Stop: $network $remote_fs $local_fs -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Persist WireGuard firewall rules for Streisand -### END INIT INFO - -{% for rule in wireguard_firewall_rules %} -{{ rule }} -{% endfor %} - -exit 0 diff --git a/playbooks/roles/wireguard/templates/wireguard-iptables.service.j2 b/playbooks/roles/wireguard/templates/wireguard-iptables.service.j2 new file mode 100644 index 000000000..8f2557e81 --- /dev/null +++ b/playbooks/roles/wireguard/templates/wireguard-iptables.service.j2 @@ -0,0 +1,19 @@ +[Unit] +Description=Firewall rules required for WireGuard +After=network.target + +[Service] +Type=oneshot +RemainAfterExit=true +{% for rule in wireguard_firewall_rules %} +ExecStart=/sbin/{{ rule }} +{% endfor %} + +{% if streisand_ipv6_address is defined %} +{% for rule in wireguard_firewallv6_rules %} +ExecStart=/sbin/{{ rule }} +{% endfor %} +{% endif %} + +[Install] +WantedBy=multi-user.target diff --git a/playbooks/roles/wireguard/templates/wireguard_dnsmasqv6.conf.j2 b/playbooks/roles/wireguard/templates/wireguard_dnsmasqv6.conf.j2 new file mode 100644 index 000000000..0535e3910 --- /dev/null +++ b/playbooks/roles/wireguard/templates/wireguard_dnsmasqv6.conf.j2 @@ -0,0 +1,2 @@ +# Listen on the WireGuard IPv6 address +listen-address={{ dnsmasq_wireguard_ipv6 }} diff --git a/playbooks/roles/wireguard/vars/main.yml b/playbooks/roles/wireguard/vars/main.yml index 906d7cca0..7dd5bee41 100644 --- a/playbooks/roles/wireguard/vars/main.yml +++ b/playbooks/roles/wireguard/vars/main.yml @@ -6,9 +6,14 @@ wireguard_server_private_key_file: "{{ wireguard_path }}/server.key" wireguard_server_public_key_file: "{{ wireguard_path }}/server.pub" dnsmasq_wireguard_ip: "10.192.122.1" +dnsmasq_wireguard_ipv6: "fde9:7496:c3d7:a47f::1001" wireguard_firewall_rules: - "iptables --wait {{ streisand_iptables_wait }} -A FORWARD -s 10.192.122.0/24 -j ACCEPT" - "iptables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -s 10.192.122.0/24 -o {{ ansible_default_ipv4.interface }} -j MASQUERADE" +wireguard_firewallv6_rules: + - "ip6tables --wait {{ streisand_iptables_wait }} -A FORWARD -s fde9:7496:c3d7:a47f::/64 -j ACCEPT" + - "ip6tables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -s fde9:7496:c3d7:a47f::/64 -o {{ ansible_default_ipv6.interface }} -j MASQUERADE" + wireguard_gateway_location: "{{ streisand_gateway_location }}/wireguard" diff --git a/tests/site_vars/openconnect.yml b/tests/site_vars/openconnect.yml index 6fb72a75d..721b52872 100644 --- a/tests/site_vars/openconnect.yml +++ b/tests/site_vars/openconnect.yml @@ -3,6 +3,7 @@ vpn_clients: 5 streisand_ad_blocking_enabled: no streisand_cloudflared_enabled: no +streisand_ipv6_enabled: yes streisand_openconnect_enabled: yes streisand_openvpn_enabled: no streisand_shadowsocks_enabled: no diff --git a/tests/site_vars/openvpn.yml b/tests/site_vars/openvpn.yml index ae3cfbd21..6199611d8 100644 --- a/tests/site_vars/openvpn.yml +++ b/tests/site_vars/openvpn.yml @@ -3,6 +3,7 @@ vpn_clients: 5 streisand_ad_blocking_enabled: no streisand_cloudflared_enabled: no +streisand_ipv6_enabled: yes streisand_openconnect_enabled: no streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: no diff --git a/tests/site_vars/random.yml b/tests/site_vars/random.yml index 2e66461a0..65abe9d0b 100644 --- a/tests/site_vars/random.yml +++ b/tests/site_vars/random.yml @@ -4,6 +4,7 @@ vpn_clients: 1 # Streisand CI's task randomizes these "_enabled" vars at build-time streisand_ad_blocking_enabled: no streisand_cloudflared_enabled: no +streisand_ipv6_enabled: no streisand_openconnect_enabled: no streisand_openvpn_enabled: no streisand_shadowsocks_enabled: no diff --git a/tests/site_vars/shadowsocks.yml b/tests/site_vars/shadowsocks.yml index b90607ee6..f0cc76ad9 100644 --- a/tests/site_vars/shadowsocks.yml +++ b/tests/site_vars/shadowsocks.yml @@ -3,6 +3,7 @@ vpn_clients: 5 streisand_ad_blocking_enabled: no streisand_cloudflared_enabled: no +streisand_ipv6_enabled: yes streisand_openconnect_enabled: no streisand_openvpn_enabled: no streisand_shadowsocks_enabled: yes diff --git a/tests/site_vars/ssh.yml b/tests/site_vars/ssh.yml index a50453364..dcc47fbdb 100644 --- a/tests/site_vars/ssh.yml +++ b/tests/site_vars/ssh.yml @@ -3,6 +3,7 @@ vpn_clients: 5 streisand_ad_blocking_enabled: no streisand_cloudflared_enabled: no +streisand_ipv6_enabled: yes streisand_openconnect_enabled: no streisand_openvpn_enabled: no streisand_shadowsocks_enabled: no