diff --git a/.travis.yml b/.travis.yml index a3d9aba..462fb6e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -112,12 +112,18 @@ matrix: - <<: *deploy-local name: 'Ubuntu 19.04: local deployment from docker' env: DEPLOY=docker UBUNTU_VERSION=19.04 + - <<: *deploy-local + name: 'Ubuntu 19.10: local deployment from docker' + env: DEPLOY=docker UBUNTU_VERSION=19.10 - <<: *deploy-cloudinit name: 'Ubuntu 18.04: cloud-init deployment' env: DEPLOY=cloud-init UBUNTU_VERSION=18.04 - <<: *deploy-cloudinit name: 'Ubuntu 19.04: cloud-init deployment' env: DEPLOY=cloud-init UBUNTU_VERSION=19.04 + - <<: *deploy-cloudinit + name: 'Ubuntu 19.10: cloud-init deployment' + env: DEPLOY=cloud-init UBUNTU_VERSION=19.10 notifications: email: false diff --git a/config.cfg b/config.cfg index e0f1431..c5fbc4d 100644 --- a/config.cfg +++ b/config.cfg @@ -15,6 +15,9 @@ users: ipsec_enabled: true # Deploy WireGuard +# WireGuard will listen on 51820/UDP. You might need to change to another port +# if your network blocks this one. Be aware that 53/UDP (DNS) is blocked on some +# mobile data networks. wireguard_enabled: true wireguard_port: 51820 @@ -136,6 +139,7 @@ congrats: SSH_keys: comment: algo@ssh private: configs/algo.pem + private_tmp: /tmp/algo-ssh.pem public: configs/algo.pem.pub cloud_providers: diff --git a/docs/deploy-to-ubuntu.md b/docs/deploy-to-ubuntu.md index 60bfe6c..0ccf50a 100644 --- a/docs/deploy-to-ubuntu.md +++ b/docs/deploy-to-ubuntu.md @@ -4,7 +4,7 @@ You can use Algo to configure a pre-existing server as an AlgoVPN rather than us Install the Algo scripts following the normal installation instructions, then choose: ``` -Install to existing Ubuntu 18.04 or 19.04 server (Advanced) +Install to existing Ubuntu 18.04, 19.04, or 19.10 server (Advanced) ``` Make sure your target server is running an unmodified copy of the operating system version specified. The target can be the same system where you've installed the Algo scripts, or a remote system that you are able to access as root via SSH without needing to enter the SSH key passphrase (such as when using `ssh-agent`). diff --git a/docs/deploy-to-unsupported-cloud.md b/docs/deploy-to-unsupported-cloud.md index 24c2b81..48ba3d9 100644 --- a/docs/deploy-to-unsupported-cloud.md +++ b/docs/deploy-to-unsupported-cloud.md @@ -2,7 +2,7 @@ Algo officially supports the [cloud providers listed here](https://github.com/trailofbits/algo/blob/master/README.md#deploy-the-algo-server). If you want to deploy Algo on another virtual hosting provider, that provider must support: -1. the base operating system image that Algo uses (Ubuntu 18.04, 19.04), and +1. the base operating system image that Algo uses (Ubuntu 18.04, 19.04, or 19.10), and 2. a minimum of certain kernel modules required for the strongSwan IPsec server. Please see the [Required Kernel Modules](https://wiki.strongswan.org/projects/strongswan/wiki/KernelModules) documentation from strongSwan for a list of the specific required modules and a script to check for them. As a first step, we recommend running their shell script to determine initial compatibility with your new hosting provider. diff --git a/input.yml b/input.yml index 0a56680..781d2c6 100644 --- a/input.yml +++ b/input.yml @@ -21,7 +21,7 @@ - { name: Scaleway, alias: scaleway} - { name: OpenStack (DreamCompute optimised), alias: openstack } - { name: CloudStack (Exoscale optimised), alias: cloudstack } - - { name: Install to existing Ubuntu 18.04 or 19.04 server (Advanced), alias: local } + - { name: Install to existing Ubuntu 18.04, 19.04, or 19.10 server (Advanced), alias: local } vars_files: - config.cfg diff --git a/main.yml b/main.yml index d317430..6f0e9ca 100644 --- a/main.yml +++ b/main.yml @@ -2,6 +2,18 @@ - hosts: localhost become: false tasks: + - name: Playbook dir stat + stat: + path: "{{ playbook_dir }}" + register: _playbook_dir + + - name: Ensure Ansible is not being run in a world writable directory + assert: + that: _playbook_dir.stat.mode|int <= 0775 + msg: > + Ansible is being run in a world writable directory ({{ playbook_dir }}), ignoring it as an ansible.cfg source. + For more information see https://docs.ansible.com/ansible/devel/reference_appendices/config.html#cfg-in-world-writable-dir + - name: Ensure the requirements installed debug: msg: "{{ '' | ipaddr }}" diff --git a/playbooks/cloud-post.yml b/playbooks/cloud-post.yml index ad81291..78eb607 100644 --- a/playbooks/cloud-post.yml +++ b/playbooks/cloud-post.yml @@ -23,7 +23,7 @@ - name: Additional variables for the server add_host: name: "{% if cloud_instance_ip == 'localhost' %}localhost{% else %}{{ cloud_instance_ip }}{% endif %}" - ansible_ssh_private_key_file: "{{ SSH_keys.private }}" + ansible_ssh_private_key_file: "{{ SSH_keys.private_tmp }}" when: algo_provider != 'local' - name: Wait until SSH becomes ready... diff --git a/playbooks/cloud-pre.yml b/playbooks/cloud-pre.yml index fb0dee0..44259bd 100644 --- a/playbooks/cloud-pre.yml +++ b/playbooks/cloud-pre.yml @@ -29,17 +29,26 @@ delegate_to: localhost become: false -- name: Generate the SSH private key - openssl_privatekey: - path: "{{ SSH_keys.private }}" - size: 2048 - mode: "0600" - type: RSA - when: algo_provider != "local" +- block: + - name: Generate the SSH private key + openssl_privatekey: + path: "{{ SSH_keys.private }}" + size: 2048 + mode: "0600" + type: RSA -- name: Generate the SSH public key - openssl_publickey: - path: "{{ SSH_keys.public }}" - privatekey_path: "{{ SSH_keys.private }}" - format: OpenSSH + - name: Generate the SSH public key + openssl_publickey: + path: "{{ SSH_keys.public }}" + privatekey_path: "{{ SSH_keys.private }}" + format: OpenSSH + + - name: Copy the private SSH key to /tmp + copy: + src: "{{ SSH_keys.private }}" + dest: "{{ SSH_keys.private_tmp }}" + force: true + mode: '0600' + delegate_to: localhost + become: false when: algo_provider != "local" diff --git a/roles/cloud-cloudstack/tasks/main.yml b/roles/cloud-cloudstack/tasks/main.yml index e65ad42..a881c83 100644 --- a/roles/cloud-cloudstack/tasks/main.yml +++ b/roles/cloud-cloudstack/tasks/main.yml @@ -1,70 +1,63 @@ --- +- name: Build python virtual environment + import_tasks: venv.yml + +- name: Include prompts + import_tasks: prompts.yml + - block: - - name: Build python virtual environment - import_tasks: venv.yml + - set_fact: + algo_region: >- + {% if region is defined %}{{ region }} + {%- elif _algo_region.user_input is defined and _algo_region.user_input | length > 0 %}{{ cs_zones[_algo_region.user_input | int -1 ]['name'] }} + {%- else %}{{ cs_zones[default_zone | int - 1]['name'] }}{% endif %} - - name: Include prompts - import_tasks: prompts.yml + - name: Security group created + cs_securitygroup: + name: "{{ algo_server_name }}-security_group" + description: AlgoVPN security group + register: cs_security_group - - block: - - set_fact: - algo_region: >- - {% if region is defined %}{{ region }} - {%- elif _algo_region.user_input is defined and _algo_region.user_input | length > 0 %}{{ cs_zones[_algo_region.user_input | int -1 ]['name'] }} - {%- else %}{{ cs_zones[default_zone | int - 1]['name'] }}{% endif %} + - name: Security rules created + cs_securitygroup_rule: + security_group: "{{ cs_security_group.name }}" + protocol: "{{ item.proto }}" + start_port: "{{ item.start_port }}" + end_port: "{{ item.end_port }}" + cidr: "{{ item.range }}" + with_items: + - { proto: tcp, start_port: 22, end_port: 22, range: 0.0.0.0/0 } + - { proto: udp, start_port: 4500, end_port: 4500, range: 0.0.0.0/0 } + - { proto: udp, start_port: 500, end_port: 500, range: 0.0.0.0/0 } + - { proto: udp, start_port: "{{ wireguard_port }}", end_port: "{{ wireguard_port }}", range: 0.0.0.0/0 } - - name: Security group created - cs_securitygroup: - name: "{{ algo_server_name }}-security_group" - description: AlgoVPN security group - register: cs_security_group + - name: Keypair created + cs_sshkeypair: + name: "{{ SSH_keys.comment|regex_replace('@', '_') }}" + public_key: "{{ lookup('file', '{{ SSH_keys.public }}') }}" + register: cs_keypair - - name: Security rules created - cs_securitygroup_rule: - security_group: "{{ cs_security_group.name }}" - protocol: "{{ item.proto }}" - start_port: "{{ item.start_port }}" - end_port: "{{ item.end_port }}" - cidr: "{{ item.range }}" - with_items: - - { proto: tcp, start_port: 22, end_port: 22, range: 0.0.0.0/0 } - - { proto: udp, start_port: 4500, end_port: 4500, range: 0.0.0.0/0 } - - { proto: udp, start_port: 500, end_port: 500, range: 0.0.0.0/0 } - - { proto: udp, start_port: "{{ wireguard_port }}", end_port: "{{ wireguard_port }}", range: 0.0.0.0/0 } + - name: Set facts + set_fact: + image_id: "{{ cloud_providers.cloudstack.image }}" + size: "{{ cloud_providers.cloudstack.size }}" + disk: "{{ cloud_providers.cloudstack.disk }}" + keypair_name: "{{ cs_keypair.name }}" - - name: Keypair created - cs_sshkeypair: - name: "{{ SSH_keys.comment|regex_replace('@', '_') }}" - public_key: "{{ lookup('file', '{{ SSH_keys.public }}') }}" - register: cs_keypair + - name: Server created + cs_instance: + name: "{{ algo_server_name }}" + root_disk_size: "{{ disk }}" + template: "{{ image_id }}" + ssh_key: "{{ keypair_name }}" + security_groups: "{{ cs_security_group.name }}" + zone: "{{ algo_region }}" + service_offering: "{{ size }}" + register: cs_server - - name: Set facts - set_fact: - image_id: "{{ cloud_providers.cloudstack.image }}" - size: "{{ cloud_providers.cloudstack.size }}" - disk: "{{ cloud_providers.cloudstack.disk }}" - keypair_name: "{{ cs_keypair.name }}" - - - name: Server created - cs_instance: - name: "{{ algo_server_name }}" - root_disk_size: "{{ disk }}" - template: "{{ image_id }}" - ssh_key: "{{ keypair_name }}" - security_groups: "{{ cs_security_group.name }}" - zone: "{{ algo_region }}" - service_offering: "{{ size }}" - register: cs_server - - - set_fact: - cloud_instance_ip: "{{ cs_server.default_ip }}" - ansible_ssh_user: ubuntu - environment: - CLOUDSTACK_CONFIG: "{{ algo_cs_config }}" - CLOUDSTACK_REGION: "{{ algo_cs_region }}" - - rescue: - - debug: var=fail_hint - tags: always - - fail: - tags: always + - set_fact: + cloud_instance_ip: "{{ cs_server.default_ip }}" + ansible_ssh_user: ubuntu + environment: + CLOUDSTACK_CONFIG: "{{ algo_cs_config }}" + CLOUDSTACK_REGION: "{{ algo_cs_region }}" diff --git a/roles/cloud-cloudstack/tasks/prompts.yml b/roles/cloud-cloudstack/tasks/prompts.yml index 62812be..dc80dcf 100644 --- a/roles/cloud-cloudstack/tasks/prompts.yml +++ b/roles/cloud-cloudstack/tasks/prompts.yml @@ -51,5 +51,4 @@ [{{ default_zone }}] register: _algo_region when: region is undefined - environment: - PYTHONPATH: "{{ cloudstack_venv }}/lib/python2.7/site-packages/" + diff --git a/roles/common/templates/rules.v4.j2 b/roles/common/templates/rules.v4.j2 index 4d8f60b..9708435 100644 --- a/roles/common/templates/rules.v4.j2 +++ b/roles/common/templates/rules.v4.j2 @@ -1,5 +1,5 @@ {% 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 []) %} +{% set ports = (['500', '4500'] if ipsec_enabled else []) + ([wireguard_port] if wireguard_enabled else []) + ([wireguard_port_actual] if wireguard_enabled and wireguard_port|int == wireguard_port_avoid|int else []) %} #### The mangle table # This table allows us to modify packet headers @@ -29,6 +29,11 @@ COMMIT :PREROUTING ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] +{% if wireguard_enabled and wireguard_port|int == wireguard_port_avoid|int %} +# Handle the special case of allowing access to WireGuard over an already used +# port like 53 +-A PREROUTING --in-interface {{ ansible_default_ipv4['interface'] }} -p udp --dport {{ wireguard_port_avoid }} -j REDIRECT --to-port {{ wireguard_port_actual }} +{% endif %} # Allow traffic from the VPN network to the outside world, and replies -A POSTROUTING -s {{ subnets|join(',') }} -m policy --pol none --dir out -j MASQUERADE diff --git a/roles/common/templates/rules.v6.j2 b/roles/common/templates/rules.v6.j2 index d586a42..5969a95 100644 --- a/roles/common/templates/rules.v6.j2 +++ b/roles/common/templates/rules.v6.j2 @@ -1,5 +1,5 @@ {% 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 []) %} +{% set ports = (['500', '4500'] if ipsec_enabled else []) + ([wireguard_port] if wireguard_enabled else []) + ([wireguard_port_actual] if wireguard_enabled and wireguard_port|int == wireguard_port_avoid|int else []) %} #### The mangle table # This table allows us to modify packet headers @@ -28,6 +28,11 @@ COMMIT :PREROUTING ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] +{% if wireguard_enabled and wireguard_port|int == wireguard_port_avoid|int %} +# Handle the special case of allowing access to WireGuard over an already used +# port like 53 +-A PREROUTING --in-interface {{ ansible_default_ipv6['interface'] }} -p udp --dport {{ wireguard_port_avoid }} -j REDIRECT --to-port {{ wireguard_port_actual }} +{% endif %} # Allow traffic from the VPN network to the outside world, and replies -A POSTROUTING -s {{ subnets|join(',') }} -m policy --pol none --dir out -j MASQUERADE diff --git a/roles/wireguard/defaults/main.yml b/roles/wireguard/defaults/main.yml index 190ab61..030511f 100644 --- a/roles/wireguard/defaults/main.yml +++ b/roles/wireguard/defaults/main.yml @@ -3,6 +3,8 @@ wireguard_PersistentKeepalive: 0 wireguard_config_path: "configs/{{ IP_subject_alt_name }}/wireguard/" wireguard_pki_path: "{{ wireguard_config_path }}/.pki/" wireguard_interface: wg0 +wireguard_port_avoid: 53 +wireguard_port_actual: 51820 keys_clean_all: false wireguard_dns_servers: >- {% if algo_dns_adblocking|default(false)|bool or dns_encryption|default(false)|bool %} diff --git a/roles/wireguard/templates/server.conf.j2 b/roles/wireguard/templates/server.conf.j2 index b7a8580..0104f5f 100644 --- a/roles/wireguard/templates/server.conf.j2 +++ b/roles/wireguard/templates/server.conf.j2 @@ -1,6 +1,6 @@ [Interface] Address = {{ wireguard_server_ip }} -ListenPort = {{ wireguard_port }} +ListenPort = {{ wireguard_port_actual if wireguard_port|int == wireguard_port_avoid|int else wireguard_port }} PrivateKey = {{ lookup('file', wireguard_pki_path + '/private/' + IP_subject_alt_name) }} SaveConfig = false diff --git a/server.yml b/server.yml index 0eb7866..b46b650 100644 --- a/server.yml +++ b/server.yml @@ -41,7 +41,7 @@ server: {{ 'localhost' if inventory_hostname == 'localhost' else inventory_hostname }} server_user: {{ ansible_ssh_user }} {% if algo_provider != "local" %} - ansible_ssh_private_key_file: {{ ansible_ssh_private_key_file|default(SSH_keys.private) }} + ansible_ssh_private_key_file: {{ SSH_keys.private }} {% endif %} algo_provider: {{ algo_provider }} algo_server_name: {{ algo_server_name }}