From b526f738817dea239f43d84f225d9ef12e868e53 Mon Sep 17 00:00:00 2001 From: TC1977 <37350377+TC1977@users.noreply.github.com> Date: Mon, 29 Apr 2019 04:40:20 -0400 Subject: [PATCH 01/25] Update troubleshooting.md - regions not available (#1414) Changes the "region not available" question to reflect Algo behavior since #976. Also addresses #1413. Adds a couple of quote marks to the Ubuntu error question, which disappeared for some reason. --- docs/troubleshooting.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 5bd0f84a..4901f496 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -18,7 +18,7 @@ First of all, check [this](https://github.com/trailofbits/algo#features) and ens * [Windows: The value of parameter linuxConfiguration.ssh.publicKeys.keyData is invalid](#windows-the-value-of-parameter-linuxconfigurationsshpublickeyskeydata-is-invalid) * [Docker: Failed to connect to the host via ssh](#docker-failed-to-connect-to-the-host-via-ssh) * [Wireguard: Unable to find 'configs/...' in expected paths](#wireguard-unable-to-find-configs-in-expected-paths) - * [Ubuntu Error: "unable to write 'random state" when generating CA password](#ubuntu-error-unable-to-write-random-state-when-generating-ca-password") + * [Ubuntu Error: "unable to write 'random state'" when generating CA password](#ubuntu-error-unable-to-write-random-state-when-generating-ca-password) * [Connection Problems](#connection-problems) * [I'm blocked or get CAPTCHAs when I access certain websites](#im-blocked-or-get-captchas-when-i-access-certain-websites) * [I want to change the list of trusted Wifi networks on my Apple device](#i-want-to-change-the-list-of-trusted-wifi-networks-on-my-apple-device) @@ -153,7 +153,9 @@ You need to reset the permissions on your `.ssh` directory. Run `chmod 700 /home ### The region you want is not available -You want to install Algo to a specific region in a cloud provider, but that region is not available in the list given by the installer. In that case, you should [file an issue](https://github.com/trailofbits/algo/issues/new). Cloud providers add new regions on a regular basis and we don't always keep up. File an issue and give us information about what region is missing and we'll add it. +Algo downloads the regions from the supported cloud providers (other than Microsoft Azure) listed in the first menu using APIs. If the region you want isn't available, the cloud provider has probably taken it offline for some reason. You should investigate further with your cloud provider. + +If there's a specific region you want to install to in Microsoft Azure that isn't available, you should [file an issue](https://github.com/trailofbits/algo/issues/new), give us information about what region is missing, and we'll add it. ### AWS: SSH permission denied with an ECDSA key @@ -269,7 +271,7 @@ sudo rm -rf /etc/wireguard/*.lock ``` Then immediately re-run `./algo`. -### Ubuntu Error: "unable to write 'random state" when generating CA password +### Ubuntu Error: "unable to write 'random state'" when generating CA password When running Algo, you received an error like this: From d6a1fb91bddfec25a690041c244ef3191fe9860f Mon Sep 17 00:00:00 2001 From: Jack Ivanov <17044561+jackivanov@users.noreply.github.com> Date: Wed, 1 May 2019 11:51:06 +0200 Subject: [PATCH 02/25] WIP: Facts definition fix (#1415) Facts definition fix --- input.yml | 18 +++++++++--------- playbooks/cloud-pre.yml | 4 +++- roles/local/tasks/prompts.yml | 28 ++++++++++++++-------------- users.yml | 8 ++++---- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/input.yml b/input.yml index d7d6aecd..fa4984b2 100644 --- a/input.yml +++ b/input.yml @@ -26,7 +26,7 @@ tasks: - block: - - name: Region prompt + - name: Cloud prompt pause: prompt: | What provider would you like to use? @@ -122,11 +122,11 @@ {{ _server | regex_replace('(?!\.)(\W|_)', '-') }} algo_ondemand_cellular: >- {% if ondemand_cellular is defined %}{{ ondemand_cellular | bool }} - {%- elif _ondemand_cellular.user_input %}{{ booleans_map[_ondemand_cellular.user_input] | default(defaults['ondemand_cellular']) }} + {%- elif _ondemand_cellular.user_input is defined %}{{ booleans_map[_ondemand_cellular.user_input] | default(defaults['ondemand_cellular']) }} {%- else %}false{% endif %} algo_ondemand_wifi: >- {% if ondemand_wifi is defined %}{{ ondemand_wifi | bool }} - {%- elif _ondemand_wifi.user_input %}{{ booleans_map[_ondemand_wifi.user_input] | default(defaults['ondemand_wifi']) }} + {%- elif _ondemand_wifi.user_input is defined %}{{ booleans_map[_ondemand_wifi.user_input] | default(defaults['ondemand_wifi']) }} {%- else %}false{% endif %} algo_ondemand_wifi_exclude: >- {% if ondemand_wifi_exclude is defined %}{{ ondemand_wifi_exclude | b64encode }} @@ -135,19 +135,19 @@ {%- else %}{{ '_null' | b64encode }}{% endif %} algo_local_dns: >- {% if local_dns is defined %}{{ local_dns | bool }} - {%- elif _local_dns.user_input %}{{ booleans_map[_local_dns.user_input] | default(defaults['local_dns']) }} + {%- elif _local_dns.user_input is defined %}{{ booleans_map[_local_dns.user_input] | default(defaults['local_dns']) }} {%- else %}false{% endif %} algo_ssh_tunneling: >- {% if ssh_tunneling is defined %}{{ ssh_tunneling | bool }} - {%- elif _ssh_tunneling.user_input %}{{ booleans_map[_ssh_tunneling.user_input] | default(defaults['ssh_tunneling']) }} + {%- elif _ssh_tunneling.user_input is defined %}{{ booleans_map[_ssh_tunneling.user_input] | default(defaults['ssh_tunneling']) }} {%- else %}false{% endif %} algo_windows: >- {% if windows is defined %}{{ windows | bool }} - {%- elif _windows.user_input %}{{ booleans_map[_windows.user_input] | default(defaults['windows']) }} + {%- elif _windows.user_input is defined %}{{ booleans_map[_windows.user_input] | default(defaults['windows']) }} {%- else %}false{% endif %} algo_store_cakey: >- - {% if store_cakey is defined %}{{ store_cakey | bool }} - {%- elif _store_cakey.user_input %}{{ booleans_map[_store_cakey.user_input] | default(defaults['store_cakey']) }} - {%- else %}false{% endif %} + {% if ipsec_enabled %}{%- if store_cakey is defined %}{{ store_cakey | bool }} + {%- elif _store_cakey.user_input is defined %}{{ booleans_map[_store_cakey.user_input] | default(defaults['store_cakey']) }} + {%- else %}false{% endif %}{% endif %} rescue: - include_tasks: playbooks/rescue.yml diff --git a/playbooks/cloud-pre.yml b/playbooks/cloud-pre.yml index f25dafa0..710702cd 100644 --- a/playbooks/cloud-pre.yml +++ b/playbooks/cloud-pre.yml @@ -4,12 +4,14 @@ shell: > ./algo-showenv.sh \ 'algo_provider "{{ algo_provider }}"' \ + {% if ipsec_enabled %} 'algo_ondemand_cellular "{{ algo_ondemand_cellular }}"' \ 'algo_ondemand_wifi "{{ algo_ondemand_wifi }}"' \ 'algo_ondemand_wifi_exclude "{{ algo_ondemand_wifi_exclude }}"' \ + 'algo_windows "{{ algo_windows }}"' \ + {% endif %} 'algo_local_dns "{{ algo_local_dns }}"' \ 'algo_ssh_tunneling "{{ algo_ssh_tunneling }}"' \ - 'algo_windows "{{ algo_windows }}"' \ 'wireguard_enabled "{{ wireguard_enabled }}"' \ 'dns_encryption "{{ dns_encryption }}"' \ > /dev/tty diff --git a/roles/local/tasks/prompts.yml b/roles/local/tasks/prompts.yml index 9df53f4f..fa085ecd 100644 --- a/roles/local/tasks/prompts.yml +++ b/roles/local/tasks/prompts.yml @@ -13,21 +13,21 @@ {%- elif _algo_server.user_input %}{{ _algo_server.user_input }} {%- else %}localhost{% endif %} -- pause: - prompt: | - What user should we use to login on the server? (note: passwordless login required, or ignore if you're deploying to localhost) - [root] - register: _algo_ssh_user - when: - - ssh_user is undefined - - cloud_instance_ip != "localhost" +- block: + - pause: + prompt: | + What user should we use to login on the server? (note: passwordless login required, or ignore if you're deploying to localhost) + [root] + register: _algo_ssh_user + when: ssh_user is undefined -- name: Set the facts - set_fact: - ansible_ssh_user: >- - {% if ssh_user is defined %}{{ ssh_user }} - {%- elif _algo_ssh_user.user_input %}{{ _algo_ssh_user.user_input }} - {%- else %}root{% endif %} + - name: Set the facts + set_fact: + ansible_ssh_user: >- + {% if ssh_user is defined %}{{ ssh_user }} + {%- elif _algo_ssh_user.user_input %}{{ _algo_ssh_user.user_input }} + {%- else %}root{% endif %} + when: cloud_instance_ip != "localhost" - pause: prompt: | diff --git a/users.yml b/users.yml index e33f04ec..9d2b21e3 100644 --- a/users.yml +++ b/users.yml @@ -40,6 +40,10 @@ {%- elif _ca_password.user_input %}{{ _ca_password.user_input }} {%- else %}omit{% endif %} + - name: Local pre-tasks + import_tasks: playbooks/cloud-pre.yml + become: false + - name: Add the server to the vpn-host group add_host: name: "{{ algo_server }}" @@ -61,10 +65,6 @@ tasks: - block: - - name: Local pre-tasks - import_tasks: playbooks/cloud-pre.yml - become: false - - import_role: name: common From 6b33d09d9fc81ec2e2b1fdb4e47df676f400b2e2 Mon Sep 17 00:00:00 2001 From: Jack Ivanov <17044561+jackivanov@users.noreply.github.com> Date: Fri, 3 May 2019 09:55:45 +0200 Subject: [PATCH 03/25] Scaleway modules (#1410) * Scaleway modules * Update docs --- docs/deploy-from-ansible.md | 1 - library/scaleway_compute.py | 619 +++++++++++++++++++++ roles/cloud-scaleway/tasks/image_facts.yml | 10 - roles/cloud-scaleway/tasks/main.yml | 155 ++---- roles/cloud-scaleway/tasks/prompts.yml | 13 +- 5 files changed, 657 insertions(+), 141 deletions(-) create mode 100644 library/scaleway_compute.py delete mode 100644 roles/cloud-scaleway/tasks/image_facts.yml diff --git a/docs/deploy-from-ansible.md b/docs/deploy-from-ansible.md index 9816f0bd..f062a04f 100644 --- a/docs/deploy-from-ansible.md +++ b/docs/deploy-from-ansible.md @@ -230,7 +230,6 @@ Possible options can be gathered via cli `aws lightsail get-regions` Required variables: - [scaleway_token](https://www.scaleway.com/docs/generate-an-api-token/) -- [scaleway_org](https://cloud.scaleway.com/#/billing) - region Possible regions: diff --git a/library/scaleway_compute.py b/library/scaleway_compute.py new file mode 100644 index 00000000..8aa6ce48 --- /dev/null +++ b/library/scaleway_compute.py @@ -0,0 +1,619 @@ +#!/usr/bin/python +# +# Scaleway Compute management module +# +# Copyright (C) 2018 Online SAS. +# https://www.scaleway.com +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + +DOCUMENTATION = ''' +--- +module: scaleway_compute +short_description: Scaleway compute management module +version_added: "2.6" +author: Remy Leone (@sieben) +description: + - "This module manages compute instances on Scaleway." +extends_documentation_fragment: scaleway + +options: + + enable_ipv6: + description: + - Enable public IPv6 connectivity on the instance + default: false + type: bool + + boot_type: + description: + - Boot method + default: bootscript + choices: + - bootscript + - local + + image: + description: + - Image identifier used to start the instance with + required: true + + name: + description: + - Name of the instance + + organization: + description: + - Organization identifier + required: true + + state: + description: + - Indicate desired state of the instance. + default: present + choices: + - present + - absent + - running + - restarted + - stopped + + tags: + description: + - List of tags to apply to the instance (5 max) + required: false + default: [] + + region: + description: + - Scaleway compute zone + required: true + choices: + - ams1 + - EMEA-NL-EVS + - par1 + - EMEA-FR-PAR1 + + commercial_type: + description: + - Commercial name of the compute node + required: true + choices: + - ARM64-2GB + - ARM64-4GB + - ARM64-8GB + - ARM64-16GB + - ARM64-32GB + - ARM64-64GB + - ARM64-128GB + - C1 + - C2S + - C2M + - C2L + - START1-XS + - START1-S + - START1-M + - START1-L + - X64-15GB + - X64-30GB + - X64-60GB + - X64-120GB + + wait: + description: + - Wait for the instance to reach its desired state before returning. + type: bool + default: 'no' + + wait_timeout: + description: + - Time to wait for the server to reach the expected state + required: false + default: 300 + + wait_sleep_time: + description: + - Time to wait before every attempt to check the state of the server + required: false + default: 3 +''' + +EXAMPLES = ''' +- name: Create a server + scaleway_compute: + name: foobar + state: present + image: 89ee4018-f8c3-4dc4-a6b5-bca14f985ebe + organization: 951df375-e094-4d26-97c1-ba548eeb9c42 + region: ams1 + commercial_type: VC1S + tags: + - test + - www + +- name: Destroy it right after + scaleway_compute: + name: foobar + state: absent + image: 89ee4018-f8c3-4dc4-a6b5-bca14f985ebe + organization: 951df375-e094-4d26-97c1-ba548eeb9c42 + region: ams1 + commercial_type: VC1S +''' + +RETURN = ''' +''' + +import datetime +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six.moves.urllib.parse import quote as urlquote +from ansible.module_utils.scaleway import SCALEWAY_LOCATION, scaleway_argument_spec, Scaleway + +SCALEWAY_COMMERCIAL_TYPES = [ + + # Virtual ARM64 compute instance + 'ARM64-2GB', + 'ARM64-4GB', + 'ARM64-8GB', + 'ARM64-16GB', + 'ARM64-32GB', + 'ARM64-64GB', + 'ARM64-128GB', + + # Baremetal + 'C1', # ARM64 (4 cores) - 2GB + 'C2S', # X86-64 (4 cores) - 8GB + 'C2M', # X86-64 (8 cores) - 16GB + 'C2L', # x86-64 (8 cores) - 32 GB + + # Virtual X86-64 compute instance + 'START1-XS', # Starter X86-64 (1 core) - 1GB - 25 GB NVMe + 'START1-S', # Starter X86-64 (2 cores) - 2GB - 50 GB NVMe + 'START1-M', # Starter X86-64 (4 cores) - 4GB - 100 GB NVMe + 'START1-L', # Starter X86-64 (8 cores) - 8GB - 200 GB NVMe + 'X64-15GB', + 'X64-30GB', + 'X64-60GB', + 'X64-120GB', +] + +SCALEWAY_SERVER_STATES = ( + 'stopped', + 'stopping', + 'starting', + 'running', + 'locked' +) + +SCALEWAY_TRANSITIONS_STATES = ( + "stopping", + "starting", + "pending" +) + + +def fetch_state(compute_api, server): + compute_api.module.debug("fetch_state of server: %s" % server["id"]) + response = compute_api.get(path="servers/%s" % server["id"]) + + if response.status_code == 404: + return "absent" + + if not response.ok: + msg = 'Error during state fetching: (%s) %s' % (response.status_code, response.json) + compute_api.module.fail_json(msg=msg) + + try: + compute_api.module.debug("Server %s in state: %s" % (server["id"], response.json["server"]["state"])) + return response.json["server"]["state"] + except KeyError: + compute_api.module.fail_json(msg="Could not fetch state in %s" % response.json) + + +def wait_to_complete_state_transition(compute_api, server): + wait = compute_api.module.params["wait"] + if not wait: + return + wait_timeout = compute_api.module.params["wait_timeout"] + wait_sleep_time = compute_api.module.params["wait_sleep_time"] + + start = datetime.datetime.utcnow() + end = start + datetime.timedelta(seconds=wait_timeout) + while datetime.datetime.utcnow() < end: + compute_api.module.debug("We are going to wait for the server to finish its transition") + if fetch_state(compute_api, server) not in SCALEWAY_TRANSITIONS_STATES: + compute_api.module.debug("It seems that the server is not in transition anymore.") + compute_api.module.debug("Server in state: %s" % fetch_state(compute_api, server)) + break + time.sleep(wait_sleep_time) + else: + compute_api.module.fail_json(msg="Server takes too long to finish its transition") + + +def create_server(compute_api, server): + compute_api.module.debug("Starting a create_server") + target_server = None + response = compute_api.post(path="servers", + data={"enable_ipv6": server["enable_ipv6"], + "boot_type": server["boot_type"], + "tags": server["tags"], + "commercial_type": server["commercial_type"], + "image": server["image"], + "name": server["name"], + "organization": server["organization"]}) + + if not response.ok: + msg = 'Error during server creation: (%s) %s' % (response.status_code, response.json) + compute_api.module.fail_json(msg=msg) + + try: + target_server = response.json["server"] + except KeyError: + compute_api.module.fail_json(msg="Error in getting the server information from: %s" % response.json) + + wait_to_complete_state_transition(compute_api=compute_api, server=target_server) + + return target_server + + +def restart_server(compute_api, server): + return perform_action(compute_api=compute_api, server=server, action="reboot") + + +def stop_server(compute_api, server): + return perform_action(compute_api=compute_api, server=server, action="poweroff") + + +def start_server(compute_api, server): + return perform_action(compute_api=compute_api, server=server, action="poweron") + + +def perform_action(compute_api, server, action): + response = compute_api.post(path="servers/%s/action" % server["id"], + data={"action": action}) + if not response.ok: + msg = 'Error during server %s: (%s) %s' % (action, response.status_code, response.json) + compute_api.module.fail_json(msg=msg) + + wait_to_complete_state_transition(compute_api=compute_api, server=server) + + return response + + +def remove_server(compute_api, server): + compute_api.module.debug("Starting remove server strategy") + response = compute_api.delete(path="servers/%s" % server["id"]) + if not response.ok: + msg = 'Error during server deletion: (%s) %s' % (response.status_code, response.json) + compute_api.module.fail_json(msg=msg) + + wait_to_complete_state_transition(compute_api=compute_api, server=server) + + return response + + +def present_strategy(compute_api, wished_server): + compute_api.module.debug("Starting present strategy") + changed = False + query_results = find(compute_api=compute_api, wished_server=wished_server, per_page=1) + + if not query_results: + changed = True + if compute_api.module.check_mode: + return changed, {"status": "A server would be created."} + + target_server = create_server(compute_api=compute_api, server=wished_server) + else: + target_server = query_results[0] + + if server_attributes_should_be_changed(compute_api=compute_api, target_server=target_server, + wished_server=wished_server): + changed = True + + if compute_api.module.check_mode: + return changed, {"status": "Server %s attributes would be changed." % target_server["id"]} + + server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server) + + return changed, target_server + + +def absent_strategy(compute_api, wished_server): + compute_api.module.debug("Starting absent strategy") + changed = False + target_server = None + query_results = find(compute_api=compute_api, wished_server=wished_server, per_page=1) + + if not query_results: + return changed, {"status": "Server already absent."} + else: + target_server = query_results[0] + + changed = True + + if compute_api.module.check_mode: + return changed, {"status": "Server %s would be made absent." % target_server["id"]} + + # A server MUST be stopped to be deleted. + while not fetch_state(compute_api=compute_api, server=target_server) == "stopped": + wait_to_complete_state_transition(compute_api=compute_api, server=target_server) + response = stop_server(compute_api=compute_api, server=target_server) + + if not response.ok: + err_msg = 'Error while stopping a server before removing it [{0}: {1}]'.format(response.status_code, + response.json) + compute_api.module.fail_json(msg=err_msg) + + wait_to_complete_state_transition(compute_api=compute_api, server=target_server) + + response = remove_server(compute_api=compute_api, server=target_server) + + if not response.ok: + err_msg = 'Error while removing server [{0}: {1}]'.format(response.status_code, response.json) + compute_api.module.fail_json(msg=err_msg) + + return changed, {"status": "Server %s deleted" % target_server["id"]} + + +def running_strategy(compute_api, wished_server): + compute_api.module.debug("Starting running strategy") + changed = False + query_results = find(compute_api=compute_api, wished_server=wished_server, per_page=1) + + if not query_results: + changed = True + if compute_api.module.check_mode: + return changed, {"status": "A server would be created before being run."} + + target_server = create_server(compute_api=compute_api, server=wished_server) + else: + target_server = query_results[0] + + if server_attributes_should_be_changed(compute_api=compute_api, target_server=target_server, + wished_server=wished_server): + changed = True + + if compute_api.module.check_mode: + return changed, {"status": "Server %s attributes would be changed before running it." % target_server["id"]} + + server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server) + + current_state = fetch_state(compute_api=compute_api, server=target_server) + if current_state not in ("running", "starting"): + compute_api.module.debug("running_strategy: Server in state: %s" % current_state) + changed = True + + if compute_api.module.check_mode: + return changed, {"status": "Server %s attributes would be changed." % target_server["id"]} + + response = start_server(compute_api=compute_api, server=target_server) + if not response.ok: + msg = 'Error while running server [{0}: {1}]'.format(response.status_code, response.json) + compute_api.module.fail_json(msg=msg) + + return changed, target_server + + +def stop_strategy(compute_api, wished_server): + compute_api.module.debug("Starting stop strategy") + query_results = find(compute_api=compute_api, wished_server=wished_server, per_page=1) + + changed = False + + if not query_results: + + if compute_api.module.check_mode: + return changed, {"status": "A server would be created before being stopped."} + + target_server = create_server(compute_api=compute_api, server=wished_server) + changed = True + else: + target_server = query_results[0] + + compute_api.module.debug("stop_strategy: Servers are found.") + + if server_attributes_should_be_changed(compute_api=compute_api, target_server=target_server, + wished_server=wished_server): + changed = True + + if compute_api.module.check_mode: + return changed, { + "status": "Server %s attributes would be changed before stopping it." % target_server["id"]} + + server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server) + + wait_to_complete_state_transition(compute_api=compute_api, server=target_server) + + current_state = fetch_state(compute_api=compute_api, server=target_server) + if current_state not in ("stopped",): + compute_api.module.debug("stop_strategy: Server in state: %s" % current_state) + + changed = True + + if compute_api.module.check_mode: + return changed, {"status": "Server %s would be stopped." % target_server["id"]} + + response = stop_server(compute_api=compute_api, server=target_server) + compute_api.module.debug(response.json) + compute_api.module.debug(response.ok) + + if not response.ok: + msg = 'Error while stopping server [{0}: {1}]'.format(response.status_code, response.json) + compute_api.module.fail_json(msg=msg) + + return changed, target_server + + +def restart_strategy(compute_api, wished_server): + compute_api.module.debug("Starting restart strategy") + changed = False + query_results = find(compute_api=compute_api, wished_server=wished_server, per_page=1) + + if not query_results: + changed = True + if compute_api.module.check_mode: + return changed, {"status": "A server would be created before being rebooted."} + + target_server = create_server(compute_api=compute_api, server=wished_server) + else: + target_server = query_results[0] + + if server_attributes_should_be_changed(compute_api=compute_api, + target_server=target_server, + wished_server=wished_server): + changed = True + + if compute_api.module.check_mode: + return changed, { + "status": "Server %s attributes would be changed before rebooting it." % target_server["id"]} + + server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server) + + changed = True + if compute_api.module.check_mode: + return changed, {"status": "Server %s would be rebooted." % target_server["id"]} + + wait_to_complete_state_transition(compute_api=compute_api, server=target_server) + + if fetch_state(compute_api=compute_api, server=target_server) in ("running",): + response = restart_server(compute_api=compute_api, server=target_server) + wait_to_complete_state_transition(compute_api=compute_api, server=target_server) + if not response.ok: + msg = 'Error while restarting server that was running [{0}: {1}].'.format(response.status_code, + response.json) + compute_api.module.fail_json(msg=msg) + + if fetch_state(compute_api=compute_api, server=target_server) in ("stopped",): + response = restart_server(compute_api=compute_api, server=target_server) + wait_to_complete_state_transition(compute_api=compute_api, server=target_server) + if not response.ok: + msg = 'Error while restarting server that was stopped [{0}: {1}].'.format(response.status_code, + response.json) + compute_api.module.fail_json(msg=msg) + + return changed, target_server + + +state_strategy = { + "present": present_strategy, + "restarted": restart_strategy, + "stopped": stop_strategy, + "running": running_strategy, + "absent": absent_strategy +} + + +def find(compute_api, wished_server, per_page=1): + compute_api.module.debug("Getting inside find") + # Only the name attribute is accepted in the Compute query API + url = 'servers?name=%s&per_page=%d' % (urlquote(wished_server["name"]), per_page) + response = compute_api.get(url) + + if not response.ok: + msg = 'Error during server search: (%s) %s' % (response.status_code, response.json) + compute_api.module.fail_json(msg=msg) + + search_results = response.json["servers"] + + return search_results + + +PATCH_MUTABLE_SERVER_ATTRIBUTES = ( + "ipv6", + "tags", + "name", + "dynamic_ip_required", +) + + +def server_attributes_should_be_changed(compute_api, target_server, wished_server): + compute_api.module.debug("Checking if server attributes should be changed") + compute_api.module.debug("Current Server: %s" % target_server) + compute_api.module.debug("Wished Server: %s" % wished_server) + debug_dict = dict((x, (target_server[x], wished_server[x])) + for x in PATCH_MUTABLE_SERVER_ATTRIBUTES + if x in target_server and x in wished_server) + compute_api.module.debug("Debug dict %s" % debug_dict) + + try: + return any([target_server[x] != wished_server[x] + for x in PATCH_MUTABLE_SERVER_ATTRIBUTES + if x in target_server and x in wished_server]) + except AttributeError: + compute_api.module.fail_json(msg="Error while checking if attributes should be changed") + + +def server_change_attributes(compute_api, target_server, wished_server): + compute_api.module.debug("Starting patching server attributes") + patch_payload = dict((x, wished_server[x]) + for x in PATCH_MUTABLE_SERVER_ATTRIBUTES + if x in wished_server and x in target_server) + response = compute_api.patch(path="servers/%s" % target_server["id"], + data=patch_payload) + if not response.ok: + msg = 'Error during server attributes patching: (%s) %s' % (response.status_code, response.json) + compute_api.module.fail_json(msg=msg) + + wait_to_complete_state_transition(compute_api=compute_api, server=target_server) + + return response + + +def core(module): + region = module.params["region"] + wished_server = { + "state": module.params["state"], + "image": module.params["image"], + "name": module.params["name"], + "commercial_type": module.params["commercial_type"], + "enable_ipv6": module.params["enable_ipv6"], + "boot_type": module.params["boot_type"], + "tags": module.params["tags"], + "organization": module.params["organization"] + } + module.params['api_url'] = SCALEWAY_LOCATION[region]["api_endpoint"] + + compute_api = Scaleway(module=module) + + changed, summary = state_strategy[wished_server["state"]](compute_api=compute_api, wished_server=wished_server) + module.exit_json(changed=changed, msg=summary) + + +def main(): + argument_spec = scaleway_argument_spec() + argument_spec.update(dict( + image=dict(required=True), + name=dict(), + region=dict(required=True, choices=SCALEWAY_LOCATION.keys()), + commercial_type=dict(required=True, choices=SCALEWAY_COMMERCIAL_TYPES), + enable_ipv6=dict(default=False, type="bool"), + boot_type=dict(default="bootscript"), + state=dict(choices=state_strategy.keys(), default='present'), + tags=dict(type="list", default=[]), + organization=dict(required=True), + wait=dict(type="bool", default=False), + wait_timeout=dict(type="int", default=300), + wait_sleep_time=dict(type="int", default=3), + )) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + core(module) + + +if __name__ == '__main__': + main() diff --git a/roles/cloud-scaleway/tasks/image_facts.yml b/roles/cloud-scaleway/tasks/image_facts.yml deleted file mode 100644 index 41269845..00000000 --- a/roles/cloud-scaleway/tasks/image_facts.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -- name: Set image id as a fact - set_fact: - image_id: "{{ item.id }}" - no_log: true - when: - - cloud_providers.scaleway.image == item.name - - cloud_providers.scaleway.arch == item.arch - - server_disk_size == item.root_volume.size - with_items: "{{ outer_item['json']['images'] }}" diff --git a/roles/cloud-scaleway/tasks/main.yml b/roles/cloud-scaleway/tasks/main.yml index 3e9a39b8..d7aae795 100644 --- a/roles/cloud-scaleway/tasks/main.yml +++ b/roles/cloud-scaleway/tasks/main.yml @@ -1,133 +1,46 @@ - name: Include prompts import_tasks: prompts.yml -- name: Set disk size - set_fact: - server_disk_size: 50000000000 - -- name: Check server size - set_fact: - server_disk_size: 25000000000 - when: cloud_providers.scaleway.size == "START1-XS" - -- name: Check if server exists - uri: - url: "https://cp-{{ algo_region }}.scaleway.com/servers" - method: GET - headers: - Content-Type: 'application/json' - X-Auth-Token: "{{ algo_scaleway_token }}" - status_code: 200 - register: scaleway_servers - -- name: Set server id as a fact - set_fact: - server_id: "{{ item.id }}" - no_log: true - when: algo_server_name == item.name - with_items: "{{ scaleway_servers.json.servers }}" - -- name: Create a server if it doesn't exist - block: - - name: Get the organization id - uri: - url: https://account.cloud.online.net/organizations - method: GET - headers: - Content-Type: 'application/json' - X-Auth-Token: "{{ algo_scaleway_token }}" - status_code: 200 - register: scaleway_organizations - - - name: Set organization id as a fact - set_fact: - organization_id: "{{ item.id }}" - no_log: true - when: algo_scaleway_org == item.name - with_items: "{{ scaleway_organizations.json.organizations }}" - - - name: Get total count of images - uri: - url: "https://cp-{{ algo_region }}.scaleway.com/images" - method: GET - headers: - Content-Type: 'application/json' - X-Auth-Token: "{{ algo_scaleway_token }}" - status_code: 200 - register: scaleway_pages +- block: + - name: Gather Scaleway organizations facts + scaleway_organization_facts: - name: Get images - uri: - url: "https://cp-{{ algo_region }}.scaleway.com/images?per_page=100&page={{ item }}" - method: GET - headers: - Content-Type: 'application/json' - X-Auth-Token: "{{ algo_scaleway_token }}" - status_code: 200 - register: scaleway_images - with_sequence: start=1 end={{ ((scaleway_pages.x_total_count|int / 100)| round )|int }} + scaleway_image_facts: + region: "{{ algo_region }}" - - name: Set image id as a fact - include_tasks: image_facts.yml - with_items: "{{ scaleway_images['results'] }}" - loop_control: - loop_var: outer_item + - name: Set cloud specific facts + set_fact: + organization_id: "{{ scaleway_organization_facts[0]['id'] }}" + images: >- + [{% for i in scaleway_image_facts -%} + {% if i.name == cloud_providers.scaleway.image and + i.arch == cloud_providers.scaleway.arch -%} + '{{ i.id }}'{% if not loop.last %},{% endif %} + {%- endif -%} + {%- endfor -%}] - name: Create a server - uri: - url: "https://cp-{{ algo_region }}.scaleway.com/servers/" - method: POST - headers: - Content-Type: 'application/json' - X-Auth-Token: "{{ algo_scaleway_token }}" - body: - organization: "{{ organization_id }}" - name: "{{ algo_server_name }}" - image: "{{ image_id }}" - commercial_type: "{{ cloud_providers.scaleway.size }}" - enable_ipv6: true - boot_type: local - tags: - - Environment:Algo - - AUTHORIZED_KEY={{ lookup('file', SSH_keys.public)|regex_replace(' ', '_') }} - status_code: 201 - body_format: json + scaleway_compute: + name: "{{ algo_server_name }}" + enable_ipv6: true + boot_type: local + state: running + image: "{{ images[0] }}" + organization: "{{ organization_id }}" + region: "{{ algo_region }}" + commercial_type: "{{ cloud_providers.scaleway.size }}" + wait: true + tags: + - Environment:Algo + - AUTHORIZED_KEY={{ lookup('file', SSH_keys.public)|regex_replace(' ', '_') }} register: algo_instance - - - name: Set server id as a fact - set_fact: - server_id: "{{ algo_instance.json.server.id }}" - when: server_id is not defined - -- name: Power on the server - uri: - url: https://cp-{{ algo_region }}.scaleway.com/servers/{{ server_id }}/action - method: POST - headers: - Content-Type: application/json - X-Auth-Token: "{{ algo_scaleway_token }}" - body: - action: poweron - status_code: 202 - body_format: json - ignore_errors: true - no_log: true - -- name: Wait for the server to become running - uri: - url: "https://cp-{{ algo_region }}.scaleway.com/servers/{{ server_id }}" - method: GET - headers: - Content-Type: 'application/json' - X-Auth-Token: "{{ algo_scaleway_token }}" - status_code: 200 - until: - - algo_instance.json.server.state is defined - - algo_instance.json.server.state == "running" - retries: 20 - delay: 30 - register: algo_instance + until: algo_instance.msg.public_ip + retries: 3 + delay: 3 + environment: + SCW_TOKEN: "{{ algo_scaleway_token }}" - set_fact: - cloud_instance_ip: "{{ algo_instance['json']['server']['public_ip']['address'] }}" + cloud_instance_ip: "{{ algo_instance.msg.public_ip.address }}" ansible_ssh_user: root diff --git a/roles/cloud-scaleway/tasks/prompts.yml b/roles/cloud-scaleway/tasks/prompts.yml index b481880d..be4743fb 100644 --- a/roles/cloud-scaleway/tasks/prompts.yml +++ b/roles/cloud-scaleway/tasks/prompts.yml @@ -4,13 +4,9 @@ Enter your auth token (https://www.scaleway.com/docs/generate-an-api-token/) echo: false register: _scaleway_token - when: scaleway_token is undefined - -- pause: - prompt: | - Enter your organization name (https://cloud.scaleway.com/#/billing) - register: _scaleway_org - when: scaleway_org is undefined + when: + - scaleway_token is undefined + - lookup('env','SCW_TOKEN')|length <= 0 - pause: prompt: | @@ -26,8 +22,7 @@ - name: Set scaleway facts set_fact: - algo_scaleway_token: "{{ scaleway_token | default(_scaleway_token.user_input) }}" - algo_scaleway_org: "{{ scaleway_org | default(_scaleway_org.user_input|default(omit)) }}" + algo_scaleway_token: "{{ scaleway_token | default(_scaleway_token.user_input) | default(lookup('env','SCW_TOKEN'), true) }}" algo_region: >- {% if region is defined %}{{ region }} {%- elif _algo_region.user_input %}{{ scaleway_regions[_algo_region.user_input | int -1 ]['alias'] }} From 826a2c5036b1f7d407c4c4faba5827a8cc8c6fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20L=C3=A9one?= Date: Sun, 12 May 2019 11:21:55 +0200 Subject: [PATCH 04/25] Add documentation about Scaleway credentials (#1419) --- README.md | 1 + docs/cloud-scaleway.md | 9 +++++++++ roles/cloud-scaleway/tasks/prompts.yml | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 docs/cloud-scaleway.md diff --git a/README.md b/README.md index ef6bfbfc..327bdf1f 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,7 @@ After this process completes, the Algo VPN server will contain only the users li - Configure [Azure](docs/cloud-azure.md) - Configure [DigitalOcean](docs/cloud-do.md) - Configure [Google Cloud Platform](docs/cloud-gce.md) + - Configure [Scaleway](docs/cloud-scaleway.md) - Configure [Vultr](docs/cloud-vultr.md) * Advanced Deployment - Deploy to your own [FreeBSD](docs/deploy-to-freebsd.md) server diff --git a/docs/cloud-scaleway.md b/docs/cloud-scaleway.md new file mode 100644 index 00000000..7e6a02ae --- /dev/null +++ b/docs/cloud-scaleway.md @@ -0,0 +1,9 @@ +### Configuration file + +Algo requires an API key from your Scaleway account to create a server. +The API key is generated by going to your Scaleway credentials at [https://console.scaleway.com/account/credentials](https://console.scaleway.com/account/credentials), and then selecting "Generate new token" on the right side of the box labeled "API Tokens". + +Enter this token when Algo prompts you for the `auth token`. +This information will be pass as the `algo_scaleway_token` variable when asked for in the Algo prompt. + +Your organization ID is also on this page: https://console.scaleway.com/account/credentials diff --git a/roles/cloud-scaleway/tasks/prompts.yml b/roles/cloud-scaleway/tasks/prompts.yml index be4743fb..7e371d21 100644 --- a/roles/cloud-scaleway/tasks/prompts.yml +++ b/roles/cloud-scaleway/tasks/prompts.yml @@ -1,7 +1,7 @@ --- - pause: prompt: | - Enter your auth token (https://www.scaleway.com/docs/generate-an-api-token/) + Enter your auth token (https://trailofbits.github.io/algo/cloud-scaleway.html) echo: false register: _scaleway_token when: From bcf2008b8d7c1cd34c5871356e0af2197bf88e19 Mon Sep 17 00:00:00 2001 From: TC1977 <37350377+TC1977@users.noreply.github.com> Date: Mon, 13 May 2019 03:33:22 -0400 Subject: [PATCH 05/25] Update deploy-from-script-or-cloud-init-to-localhost.md (#1433) I was going to add this onto the existing PR for docs update, but it turned out to be a little more involved and require some testing of actual deployment. --- ...-from-script-or-cloud-init-to-localhost.md | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/docs/deploy-from-script-or-cloud-init-to-localhost.md b/docs/deploy-from-script-or-cloud-init-to-localhost.md index 6070562c..2e4308ab 100644 --- a/docs/deploy-from-script-or-cloud-init-to-localhost.md +++ b/docs/deploy-from-script-or-cloud-init-to-localhost.md @@ -1,33 +1,33 @@ # Deploy from script or cloud-init -You can use `install.sh` to prepare the environment and deploy AlgoVPN on the local Ubuntu server in one shot using cloud-init or run the script directly on the server. The script doesn't configure any parameters in your cloud, so it's on your own to configure related [firewall rules](/docs/firewalls.md), a floating ip address and other resources you may need. +You can use `install.sh` to prepare the environment and deploy AlgoVPN on the local Ubuntu server in one shot using cloud-init, or run the script directly on the server after it's been created. The script doesn't configure any parameters in your cloud, so it's on your own to configure related [firewall rules](/docs/firewalls.md), a floating ip address and other resources you may need. The output of the install script (including the p12 and CA passwords) and user config files will be installed into the `/opt/algo` directory. ## Cloud init deployment -You can copy-paste the snippet below to the user data (cloud-init or startup script) field when creating a new server. For now it is only possible for [DigitalOcean](https://www.digitalocean.com/docs/droplets/resources/metadata/), Amazon [EC2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html) and [Lightsail](https://lightsail.aws.amazon.com/ls/docs/en/articles/lightsail-how-to-configure-server-additional-data-shell-script), [Google Cloud](https://cloud.google.com/compute/docs/startupscript) and [Azure](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/using-cloud-init). +You can copy-paste the snippet below to the user data (cloud-init or startup script) field when creating a new server. For now it is only possible for [DigitalOcean](https://www.digitalocean.com/docs/droplets/resources/metadata/), Amazon [EC2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html) and [Lightsail](https://lightsail.aws.amazon.com/ls/docs/en/articles/lightsail-how-to-configure-server-additional-data-shell-script), [Google Cloud](https://cloud.google.com/compute/docs/startupscript), [Azure](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/using-cloud-init) and [Vultr](https://my.vultr.com/startup/), although Vultr doesn't [officially support cloud-init](https://www.vultr.com/docs/getting-started-with-cloud-init). ``` #!/bin/bash curl -s https://raw.githubusercontent.com/trailofbits/algo/master/install.sh | sudo -E bash -x ``` -The command will prepare the environment and install AlgoVPN with default parameters. If you want to modify the behaviour you may define additional variables. +The command will prepare the environment and install AlgoVPN with the default parameters below. If you want to modify the behavior you may define additional variables. ## Variables -`METHOD` - which method of the deployment to use. Possible values are local and cloud. Default: cloud. The cloud method is intended to use in cloud-init deployments only. If you are not using cloud-init to deploy the server you have to use the local method -`ONDEMAND_CELLULAR` - "Connect On Demand" when connected to cellular networks. Bollean. Default: false -`ONDEMAND_WIFI` - "Connect On Demand" when connected to Wi-Fi. Default: false -`ONDEMAND_WIFI_EXCLUDE` - List the names of any trusted Wi-Fi networks where macOS/iOS IPsec clients should not use "Connect On Demand". Comma-separated list. -`WINDOWS` - To support Windows 10 or Linux Desktop clients. Default: false -`STORE_CAKEY` - To retain the CA key. (required to add users in the future, but less secure). Default: false. -`LOCAL_DNS` - To install an ad blocking DNS resolver. Default: false. -`SSH_TUNNELING` - Enable SSH tunneling for each user. Default: false -`ENDPOINT` - The public IP address or domain name of your server: (IMPORTANT! This is used to verify the certificate). It will be gathered automatically for DigitalOcean, AWS, GCE or Azure if the `METHOD` is cloud. Otherwise you need to define this variable according to your public IP address. -`USERS` - list of VPN users. Comma-separated list. +`METHOD` - which method of the deployment to use. Possible values are local and cloud. Default: cloud. The cloud method is intended to use in cloud-init deployments only. If you are not using cloud-init to deploy the server you have to use the local method. +`ONDEMAND_CELLULAR` - "Connect On Demand" when connected to cellular networks. Boolean. Default: false. +`ONDEMAND_WIFI` - "Connect On Demand" when connected to Wi-Fi. Default: false. +`ONDEMAND_WIFI_EXCLUDE` - List the names of any trusted Wi-Fi networks where macOS/iOS IPsec clients should not use "Connect On Demand". Comma-separated list. +`WINDOWS` - To support Windows 10 or Linux Desktop clients. Default: false. +`STORE_CAKEY` - To retain the CA key. (required to add users in the future, but less secure). Default: false. +`LOCAL_DNS` - To install an ad blocking DNS resolver. Default: false. +`SSH_TUNNELING` - Enable SSH tunneling for each user. Default: false. +`ENDPOINT` - The public IP address or domain name of your server: (IMPORTANT! This is used to verify the certificate). It will be gathered automatically for DigitalOcean, AWS, GCE, Azure or Vultr if the `METHOD` is cloud. Otherwise you need to define this variable according to your public IP address. +`USERS` - list of VPN users. Comma-separated list. Default: user1. `REPO_SLUG` - Owner and repository that used to get the installation scripts from. Default: trailofbits/algo. -`REPO_BRANCH` - Branch for `REPO_SLUG`. Default: master. -`EXTRA_VARS` - Additional extra variables. -`ANSIBLE_EXTRA_ARGS` - Any available ansible parameters. ie: `--skip-tags apparmor`. +`REPO_BRANCH` - Branch for `REPO_SLUG`. Default: master. +`EXTRA_VARS` - Additional extra variables. +`ANSIBLE_EXTRA_ARGS` - Any available ansible parameters. ie: `--skip-tags apparmor`. ## Examples @@ -46,6 +46,7 @@ curl -s https://raw.githubusercontent.com/trailofbits/algo/master/install.sh | s ``` export METHOD=local export ONDEMAND_CELLULAR=true +export ENDPOINT=[your server's IP here] curl -s https://raw.githubusercontent.com/trailofbits/algo/master/install.sh | sudo -E bash -x ``` @@ -54,5 +55,5 @@ curl -s https://raw.githubusercontent.com/trailofbits/algo/master/install.sh | s The arguments order as per [variables](#variables) above ``` -curl -s https://raw.githubusercontent.com/trailofbits/algo/master/install.sh | sudo -E bash -x -s local true false _null true true true true myvpnserver.com +curl -s https://raw.githubusercontent.com/trailofbits/algo/master/install.sh | sudo -E bash -x -s local true false _null true true true true myvpnserver.com phone,laptop,desktop ``` From 515494e90eb73d3ceb47d72a6a7669c865517445 Mon Sep 17 00:00:00 2001 From: Jack Ivanov <17044561+jackivanov@users.noreply.github.com> Date: Wed, 15 May 2019 19:33:07 +0200 Subject: [PATCH 06/25] Update config.cfg --- config.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.cfg b/config.cfg index a652749f..1e12c8d0 100644 --- a/config.cfg +++ b/config.cfg @@ -142,13 +142,13 @@ cloud_providers: digitalocean: size: s-1vcpu-1gb image: "ubuntu-18-04-x64" + ec2: # Change the encrypted flag to "true" to enable AWS volume encryption, for encryption of data at rest. # Warning: the Algo script will take approximately 6 minutes longer to complete. # Also note that the documented AWS minimum permissions aren't sufficient. # You will have to edit the AWS user policy documented at # https://github.com/trailofbits/algo/blob/master/docs/cloud-amazon-ec2.md to also allow "ec2:CopyImage". # See https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage-edit.html - ec2: encrypted: false size: t2.micro image: From 3ce92f9feecbd053924163fd73735595ee0ee5b6 Mon Sep 17 00:00:00 2001 From: Jack Ivanov <17044561+jackivanov@users.noreply.github.com> Date: Thu, 16 May 2019 07:17:00 +0200 Subject: [PATCH 07/25] Update deploy-from-ansible.md Closes #1434 --- docs/deploy-from-ansible.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/deploy-from-ansible.md b/docs/deploy-from-ansible.md index f062a04f..8d2180d7 100644 --- a/docs/deploy-from-ansible.md +++ b/docs/deploy-from-ansible.md @@ -114,7 +114,8 @@ Additional variables: "ec2:DescribeImages", "ec2:DescribeKeyPairs", "ec2:DescribeRegions", - "ec2:ImportKeyPair" + "ec2:ImportKeyPair", + "ec2:CopyImage" ], "Resource": [ "*" From de88211fb95fd9b73b366d864e8d5944c0e3bf88 Mon Sep 17 00:00:00 2001 From: Jack Ivanov <17044561+jackivanov@users.noreply.github.com> Date: Thu, 16 May 2019 13:28:59 +0200 Subject: [PATCH 08/25] Update config.cfg Closes #1435 --- config.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.cfg b/config.cfg index 1e12c8d0..c5393ac3 100644 --- a/config.cfg +++ b/config.cfg @@ -8,7 +8,7 @@ users: - laptop - desktop -# NOTE: You must "escape" any usernames with leading 0's, like "000dan" +# NOTE: You must "escape" any usernames with leading 0's or containing only numbers, like "000dan" or "123" ### Advanced users only below this line ### From 638a355196edcd4b13095aaef2f7d470a6e4c069 Mon Sep 17 00:00:00 2001 From: TC1977 <37350377+TC1977@users.noreply.github.com> Date: Thu, 16 May 2019 08:04:57 -0400 Subject: [PATCH 09/25] Update config.cfg (#1436) * Update config.cfg Reflects fixes in #1434 and #1435. * Update config.cfg --- config.cfg | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/config.cfg b/config.cfg index c5393ac3..2d2f8577 100644 --- a/config.cfg +++ b/config.cfg @@ -1,15 +1,14 @@ --- -# This is the list of user to generate. +# This is the list of users to generate. # Every device must have a unique username. # You can generate up to 250 users at one time. +# Usernames with leading 0's or containing only numbers should be escaped in double quotes, e.g. "000dan" or "123". users: - phone - laptop - desktop -# NOTE: You must "escape" any usernames with leading 0's or containing only numbers, like "000dan" or "123" - ### Advanced users only below this line ### # If True re-init all existing certificates. Boolean @@ -145,10 +144,6 @@ cloud_providers: ec2: # Change the encrypted flag to "true" to enable AWS volume encryption, for encryption of data at rest. # Warning: the Algo script will take approximately 6 minutes longer to complete. - # Also note that the documented AWS minimum permissions aren't sufficient. - # You will have to edit the AWS user policy documented at - # https://github.com/trailofbits/algo/blob/master/docs/cloud-amazon-ec2.md to also allow "ec2:CopyImage". - # See https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage-edit.html encrypted: false size: t2.micro image: From 38ebe4893dcee0c48a34c45d8bd2e73ccfba1511 Mon Sep 17 00:00:00 2001 From: TC1977 <37350377+TC1977@users.noreply.github.com> Date: Thu, 16 May 2019 15:01:01 -0400 Subject: [PATCH 10/25] Update docs (#1430) * Point additional docs to index.md * Update index.md Moves existing links from readme.md over to update this separate (previously out-of-date, redundant) page. * Update documented Ansible roles * Fix broken links in index.md * Complete index.md As a general rule all docs should be linked to from the index file. No? * Update SSH access instructions * Clarify SSH access instructions * Delete setup-roles.md * Update deploy-from-ansible.md Change header, insert text from setup-roles.md * Remove link to setup-roles from index.md * Fix typos * Update deploy-from-ansible.md Document other `--skip-tags` options, as well as examples for Vultr and Scaleway variables. * Update deploy-from-ansible.md Added region examples for AWS and Lightsail. Happy to add other examples if people have experience with other providers. --- README.md | 50 ++++++++++--------------------------- docs/deploy-from-ansible.md | 49 ++++++++++++++++++++---------------- docs/index.md | 16 +++++++++--- docs/setup-roles.md | 28 --------------------- 4 files changed, 53 insertions(+), 90 deletions(-) delete mode 100644 docs/setup-roles.md diff --git a/README.md b/README.md index 327bdf1f..0f094930 100644 --- a/README.md +++ b/README.md @@ -72,15 +72,15 @@ That's it! You will get the message below when the server deployment process com You can now setup clients to connect it, e.g. your iPhone or laptop. Proceed to [Configure the VPN Clients](#configure-the-vpn-clients) below. ``` - "\"#----------------------------------------------------------------------#\"", - "\"# Congratulations! #\"", - "\"# Your Algo server is running. #\"", - "\"# Config files and certificates are in the ./configs/ directory. #\"", - "\"# Go to https://whoer.net/ after connecting #\"", - "\"# and ensure that all your traffic passes through the VPN. #\"", - "\"# Local DNS resolver 172.16.0.1 #\"", - "\"# The p12 and SSH keys password is XXXXXXXX #\"", - "\"#----------------------------------------------------------------------#\"", + "# Congratulations! #" + "# Your Algo server is running. #" + "# Config files and certificates are in the ./configs/ directory. #" + "# Go to https://whoer.net/ after connecting #" + "# and ensure that all your traffic passes through the VPN. #" + "# Local DNS resolver 172.16.0.1 #" + "# The p12 and SSH keys password for new users is XXXXXXXX #" + "# The CA key password is XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #" + "# Shell access: ssh -i configs/algo.pem root@xxx.xxx.xx.xx #" ``` ## Configure the VPN Clients @@ -166,16 +166,14 @@ Use the example command below to start an SSH tunnel by replacing `user` and `ip ## SSH into Algo Server -To SSH into the Algo server for administrative purposes you can use the example command below by replacing `ip` with your own: +Your Algo server is configured for key-only SSH access for administrative purposes. Open the Terminal app, `cd` into the `algo-master` directory where you originally downloaded Algo, and then use the command listed on the success message: - `ssh root@ip -i ~/.ssh/algo.pem` + `ssh -i configs/algo.pem user@ip` -If you find yourself regularly logging into Algo then it will be useful to load your Algo ssh key automatically. Add the following snippet to the bottom of `~/.bash_profile` to add it to your shell environment permanently. +where `user` is either `root` or `ubuntu` as listed on the success message, and `ip` is the IP address of your Algo server. If you find yourself regularly logging into the server then it will be useful to load your Algo ssh key automatically. Add the following snippet to the bottom of `~/.bash_profile` to add it to your shell environment permanently. `ssh-add ~/.ssh/algo > /dev/null 2>&1` -Note the admin username is `ubuntu` instead of `root` on providers other than Digital Ocean. - ## Adding or Removing Users If you chose to save the CA certificate during the deploy process, then Algo's own scripts can easily add and remove users from the VPN server. @@ -187,29 +185,7 @@ If you chose to save the CA certificate during the deploy process, then Algo's o After this process completes, the Algo VPN server will contain only the users listed in the `config.cfg` file. ## Additional Documentation - -* Setup instructions - - Documentation for available [Ansible roles](docs/setup-roles.md) - - Deploy from [Fedora Workstation (26)](docs/deploy-from-fedora-workstation.md) - - Deploy from [RedHat/CentOS 6.x](docs/deploy-from-redhat-centos6.md) - - Deploy from [Windows](docs/deploy-from-windows.md) - - Deploy from [Ansible](docs/deploy-from-ansible.md) directly -* Client setup - - Setup [Android](docs/client-android.md) clients - - Setup [Generic/Linux](docs/client-linux.md) clients with Ansible - - Setup Ubuntu clients to use [WireGuard](docs/client-linux-wireguard.md) - - Setup Apple devices to use [IPSEC](docs/client-apple-ipsec.md) -* Cloud setup - - Configure [Amazon EC2](docs/cloud-amazon-ec2.md) - - Configure [Azure](docs/cloud-azure.md) - - Configure [DigitalOcean](docs/cloud-do.md) - - Configure [Google Cloud Platform](docs/cloud-gce.md) - - Configure [Scaleway](docs/cloud-scaleway.md) - - Configure [Vultr](docs/cloud-vultr.md) -* Advanced Deployment - - Deploy to your own [FreeBSD](docs/deploy-to-freebsd.md) server - - Deploy to your own [Ubuntu 18.04](docs/deploy-to-ubuntu.md) server - - Deploy to an [unsupported cloud provider](docs/deploy-to-unsupported-cloud.md) +* [Deployment instructions, cloud provider setup instructions, and further client setup instructions available here.](docs/index.md) * [FAQ](docs/faq.md) * [Troubleshooting](docs/troubleshooting.md) diff --git a/docs/deploy-from-ansible.md b/docs/deploy-from-ansible.md index 8d2180d7..f26de00d 100644 --- a/docs/deploy-from-ansible.md +++ b/docs/deploy-from-ansible.md @@ -1,10 +1,10 @@ -# Scripted Deployment +# Deployment from Ansible Before you begin, make sure you have installed all the dependencies necessary for your operating system as described in the [README](../README.md). You can deploy Algo non-interactively by running the Ansible playbooks directly with `ansible-playbook`. -`ansible-playbook` accepts "tags" via the `-t` or `TAGS` options. You can pass tags as a list of comma separated values. Ansible will only run plays (install roles) with the specified tags. +`ansible-playbook` accepts "tags" via the `-t` or `TAGS` options. You can pass tags as a list of comma separated values. Ansible will only run plays (install roles) with the specified tags. You can also use the `--skip-tags` option to skip certain parts of the install, such as `iptables` (overwrite iptables rules), `ipsec` (install strongSwan), `wireguard` (install Wireguard). `ansible-playbook` accepts variables via the `-e` or `--extra-vars` option. You can pass variables as space separated key=value pairs. Algo requires certain variables that are listed below. @@ -23,25 +23,25 @@ ansible-playbook main.yml -e "provider=digitalocean do_token=token" ``` -See below for more information about providers and extra variables +See below for more information about variables and roles. ### Variables - `provider` - (Required) The provider to use. See possible values below - `server_name` - (Required) Server name. Default: algo -- `ondemand_cellular` (Optional) VPN On Demand when connected to cellular networks. Default: false -- `ondemand_wifi` - (Optional. See `ondemand_wifi_exclude`) VPN On Demand when connected to WiFi networks. Default: false +- `ondemand_cellular` (Optional) VPN On Demand when connected to cellular networks with IPsec. Default: false +- `ondemand_wifi` - (Optional. See `ondemand_wifi_exclude`) VPN On Demand when connected to WiFi networks with IPsec. Default: false - `ondemand_wifi_exclude` (Required if `ondemand_wifi` set) - WiFi networks to exclude from using the VPN. Comma-separated values - `local_dns` - (Optional) Enable a DNS resolver. Default: false - `ssh_tunneling` - (Optional) Enable SSH tunneling for each user. Default: false - `windows` - (Optional) Enables compatible ciphers and key exchange to support Windows clients, less secure. Default: false - `store_cakey` - (Optional) Whether or not keep the CA key (required to add users in the future, but less secure). Default: false -If any of those unspecified ansible will ask the user to input +If any of the above variables are unspecified, ansible will ask the user to input them. ### Ansible roles -Roles can be activated by specifying an extra variable `provider` +Cloud roles can be activated by specifying an extra variable `provider`. Cloud roles: @@ -55,13 +55,25 @@ Cloud roles: Server roles: -- role: vpn +- role: strongswan + * Installs [strongSwan](https://www.strongswan.org/) + * Enables AppArmor, limits CPU and memory access, and drops user privileges + * Builds a Certificate Authority (CA) with [easy-rsa-ipsec](https://github.com/ValdikSS/easy-rsa-ipsec) and creates one client certificate per user + * Bundles the appropriate certificates into Apple mobileconfig profiles and Powershell scripts for each user - role: dns_adblocking + * Installs the [dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html) local resolver with a blacklist for advertising domains + * Constrains dnsmasq with AppArmor and cgroups CPU and memory limitations - role: dns_encryption + * Installs [dnscrypt-proxy](https://github.com/jedisct1/dnscrypt-proxy) + * Constrains dnscrypt-proxy with AppArmor and cgroups CPU and memory limitations - role: ssh_tunneling + * Adds a restricted `algo` group with no shell access and limited SSH forwarding options + * Creates one limited, local account and an SSH public key for each user - role: wireguard + * Installs a [Wireguard](https://www.wireguard.com/) server, with a startup script, and automatic checks for upgrades + * Creates wireguard.conf files for Linux clients as well as QR codes for Apple/Android clients -Note: The `vpn` role generates Apple profiles with On-Demand Wifi and Cellular if you pass the following variables: +Note: The `strongswan` role generates Apple profiles with On-Demand Wifi and Cellular if you pass the following variables: - ondemand_wifi: true - ondemand_wifi_exclude: HomeNet,OfficeWifi @@ -91,9 +103,9 @@ Possible options can be gathered calling to https://api.digitalocean.com/v2/regi Required variables: -- aws_access_key +- aws_access_key: `AKIA...` - aws_secret_key -- region +- region: e.g. `us-east-1` Possible options can be gathered via cli `aws ec2 describe-regions` @@ -180,8 +192,8 @@ Required variables: Required variables: -- [vultr_config](https://trailofbits.github.io/algo/cloud-vultr.html) -- [region](https://api.vultr.com/v1/regions/list) +- [vultr_config](https://trailofbits.github.io/algo/cloud-vultr.html): /path/to/.vultr.ini +- [region](https://api.vultr.com/v1/regions/list): e.g. `Chicago`, `'New Jersey'` ### Azure @@ -197,9 +209,9 @@ Required variables: Required variables: -- aws_access_key +- aws_access_key: `AKIA...` - aws_secret_key -- region +- region: e.g. `us-east-1` Possible options can be gathered via cli `aws lightsail get-regions` @@ -231,12 +243,7 @@ Possible options can be gathered via cli `aws lightsail get-regions` Required variables: - [scaleway_token](https://www.scaleway.com/docs/generate-an-api-token/) -- region - -Possible regions: - -- ams1 -- par1 +- region: e.g. ams1, par1 ### OpenStack diff --git a/docs/index.md b/docs/index.md index 84f07185..02214052 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,21 +1,29 @@ # Algo VPN documentation -* Setup instructions - - Documentation for available [Ansible roles](setup-roles.md) +* Deployment instructions - Deploy from [Fedora Workstation (26)](deploy-from-fedora-workstation.md) - Deploy from [RedHat/CentOS 6.x](deploy-from-redhat-centos6.md) - Deploy from [Windows](deploy-from-windows.md) - - Deploy from [Ansible](deploy-from-ansible.md) directly + - Deploy from a [Docker container](deploy-from-docker.md) + - Deploy from [Ansible](deploy-from-ansible.md) non-interactively + - Deploy onto a [cloud server at time of creation](deploy-from-script-or-cloud-init-to-localhost.md) * Client setup - Setup [Android](client-android.md) clients - Setup [Generic/Linux](client-linux.md) clients with Ansible -* Cloud setup + - Setup Ubuntu clients to use [WireGuard](client-linux-wireguard.md) + - Setup Apple devices to use [IPSEC](client-apple-ipsec.md) + - Setup Macs running macOS 10.13 or older to use [Wireguard](client-macos-wireguard.md) + - Manual Windows 10 client setup for [IPSEC](client-windows.md) +* Cloud provider setup + - Configure [Amazon EC2](cloud-amazon-ec2.md) - Configure [Azure](cloud-azure.md) - Configure [DigitalOcean](cloud-do.md) + - Configure [Google Cloud Platform](cloud-gce.md) - Configure [Vultr](cloud-vultr.md) * Advanced Deployment - Deploy to your own [FreeBSD](deploy-to-freebsd.md) server - Deploy to your own [Ubuntu 18.04](deploy-to-ubuntu.md) server - Deploy to an [unsupported cloud provider](deploy-to-unsupported-cloud.md) * [FAQ](faq.md) +* [Firewalls](firewalls.md) * [Troubleshooting](troubleshooting.md) diff --git a/docs/setup-roles.md b/docs/setup-roles.md deleted file mode 100644 index 1523d181..00000000 --- a/docs/setup-roles.md +++ /dev/null @@ -1,28 +0,0 @@ -# Ansible Roles - -## Required roles - -* **Common** - * Installs several required packages and software updates, then reboots if necessary - * Configures network interfaces, and enables packet forwarding on them -* **VPN** - * Installs [strongSwan](https://www.strongswan.org/), enables AppArmor, limits CPU and memory access, and drops user privileges - * Builds a Certificate Authority (CA) with [easy-rsa-ipsec](https://github.com/ValdikSS/easy-rsa-ipsec) and creates one client certificate per user - * Bundles the appropriate certificates into Apple mobileconfig profiles for each user - * Configures IPtables to block traffic that might pose a risk to VPN users, such as [SMB/CIFS](https://medium.com/@ValdikSS/deanonymizing-windows-users-and-capturing-microsoft-and-vpn-accounts-f7e53fe73834) - -## Optional roles - -* **Security Enhancements** - * Enables [unattended-upgrades](https://help.ubuntu.com/community/AutomaticSecurityUpdates) to ensure available patches are always applied - * Modify features like core dumps, kernel parameters, and SUID binaries to limit possible attacks - * Enhances SSH with modern ciphers and seccomp, and restricts access to old or unwanted features like X11 forwarding and SFTP -* **DNS-based Adblocking** - * Install the [dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html) local resolver with a blacklist for advertising domains - * Constrains dnsmasq with AppArmor and cgroups CPU and memory limitations -* **DNS encryption** - * Install [dnscrypt-proxy](https://github.com/jedisct1/dnscrypt-proxy) - * Constrains dingo with AppArmor and cgroups CPU and memory limitations -* **SSH Tunneling** - * Adds a restricted `algo` group with no shell access and limited SSH forwarding options - * Creates one limited, local account per user and an SSH public key for each From 5904546a483d9f2ed3daa571cb952db5ba1df62a Mon Sep 17 00:00:00 2001 From: Jack Ivanov <17044561+jackivanov@users.noreply.github.com> Date: Fri, 17 May 2019 14:49:29 +0200 Subject: [PATCH 11/25] Randomly generated IP address for the local dns resolver (#1429) * generate service IPs dynamically * update cloud-init tests * exclude ipsec and wireguard ranges from the random service ip * Update docs * @davidemyers: update wireguard docs for linux * Move to netaddr filter * AllowedIPs fix * WireGuard IPs fix --- config.cfg | 21 +++--------- docs/client-linux-wireguard.md | 32 +++---------------- docs/faq.md | 2 +- main.yml | 14 ++++++-- requirements.txt | 1 + roles/common/handlers/main.yml | 2 +- roles/common/tasks/freebsd.yml | 2 +- .../common/templates/10-algo-lo100.network.j2 | 2 +- roles/common/templates/rules.v6.j2 | 2 +- .../dns_adblocking/templates/dnsmasq.conf.j2 | 2 +- roles/wireguard/defaults/main.yml | 8 +++-- roles/wireguard/templates/server.conf.j2 | 3 +- tests/cloud-init.sh | 2 +- tests/ipsec-client.sh | 2 ++ tests/local-deploy.sh | 2 +- tests/update-users.sh | 2 +- tests/wireguard-client.sh | 2 ++ 17 files changed, 42 insertions(+), 59 deletions(-) diff --git a/config.cfg b/config.cfg index 2d2f8577..181ae022 100644 --- a/config.cfg +++ b/config.cfg @@ -39,20 +39,8 @@ wireguard_port: 51820 wireguard_PersistentKeepalive: 0 # WireGuard network configuration -_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'] }}" +wireguard_network_ipv4: 10.19.49.0/24 +wireguard_network_ipv6: fd9d:bc11:4021::/48 # Reduce the MTU of the VPN tunnel # Some cloud and internet providers use a smaller MTU (Maximum Transmission @@ -99,8 +87,9 @@ dns_servers: - 2606:4700:4700::1111 - 2606:4700:4700::1001 -# IP address for the local dns resolver -local_service_ip: 172.16.0.1 +# Randomly generated IP address for the local dns resolver +local_service_ip: "{{ '172.16.0.1' | ipmath(1048573 | random(seed=algo_server_name + ansible_fqdn)) }}" +local_service_ipv6: "{{ 'fd00::1' | ipmath(1048573 | random(seed=algo_server_name + ansible_fqdn)) }}" # Your Algo server will automatically install security updates. Some updates # require a reboot to take effect but your Algo server will not reboot itself diff --git a/docs/client-linux-wireguard.md b/docs/client-linux-wireguard.md index 52f6e85a..96455e18 100644 --- a/docs/client-linux-wireguard.md +++ b/docs/client-linux-wireguard.md @@ -1,18 +1,18 @@ -# Using Ubuntu Server as a Client with WireGuard +# Using Ubuntu as a Client with WireGuard ## Install WireGuard -To connect to your AlgoVPN using [WireGuard](https://www.wireguard.com) from Ubuntu Server, first install WireGuard: +To connect to your AlgoVPN using [WireGuard](https://www.wireguard.com) from Ubuntu, first install WireGuard: ```shell # Add the WireGuard repository: sudo add-apt-repository ppa:wireguard/wireguard -# Update the list of available packages (not necessary on Bionic or later): -sudo apt update +# Update the list of available packages (not necessary on 18.04 or later): +sudo apt update # Install the tools and kernel module: -sudo apt install wireguard +sudo apt install wireguard openresolv ``` For installation on other Linux distributions, see the [Installation](https://www.wireguard.com/install/) page on the WireGuard site. @@ -21,28 +21,6 @@ For installation on other Linux distributions, see the [Installation](https://ww The Algo-generated config files for WireGuard are named `configs//wireguard/.conf` on the system where you ran `./algo`. One file was generated for each of the users you added to `config.cfg`. Each WireGuard client you connect to your AlgoVPN must use a different config file. Choose one of these files and copy it to your Linux client. -## Configure DNS - -### Ubuntu 18.04 (Bionic) - -If your client is running Bionic (or another Linux that uses `systemd-resolved` for DNS but does not have `resolvectl` or `resolvconf` installed) you should first edit the config file. Comment out the line that begins with `DNS =` and replace it with: -``` -PostUp = systemd-resolve -i %i --set-dns=172.16.0.1 --set-domain=~. -``` -Use the IP address shown on the `DNS =` line (for most, this will be `172.16.0.1`). If the `DNS =` line contains multiple IP addresses, use multiple `--set-dns=` options. - -### Ubuntu 18.10 (Cosmic) or 19.04 (Disco) - -If your client is running Cosmic or Disco (or another Linux that uses `systemd-resolved` for DNS and has `resolvectl` but *not* `resolvconf` installed) you can either edit the config file as shown above for Bionic or run the following command once: - -``` -sudo ln -s /usr/bin/resolvectl /usr/bin/resolvconf -``` - -### Other Linux Distributions - -On other Linux distributions you might need to install the `openresolv` package. - ## Configure WireGuard Finally, install the config file on your client as `/etc/wireguard/wg0.conf` and start WireGuard: diff --git a/docs/faq.md b/docs/faq.md index 16a69b32..5e59b63f 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -50,7 +50,7 @@ Algo is short for "Al Gore", the **V**ice **P**resident of **N**etworks everywhe ## Can DNS filtering be disabled? -You can temporarily disable DNS filtering for all IPsec clients at once with the following workaround: SSH to your Algo server (using the 'shell access' command printed upon a successful deployment), edit `/etc/ipsec.conf`, and change `rightdns=172.16.0.1` to `rightdns=8.8.8.8`. Then run `sudo systemctl restart strongswan`. DNS filtering for Wireguard clients has to be disabled on each client device separately by modifying the settings in the app, or by directly modifying the `DNS` setting on the `clientname.conf` file. If all else fails, we recommend deploying a new Algo server without the adblocking feature enabled. +You can temporarily disable DNS filtering for all IPsec clients at once with the following workaround: SSH to your Algo server (using the 'shell access' command printed upon a successful deployment), edit `/etc/ipsec.conf`, and change `rightdns=` to `rightdns=8.8.8.8`. Then run `sudo systemctl restart strongswan`. DNS filtering for Wireguard clients has to be disabled on each client device separately by modifying the settings in the app, or by directly modifying the `DNS` setting on the `clientname.conf` file. If all else fails, we recommend deploying a new Algo server without the adblocking feature enabled. ## Wasn't IPSEC backdoored by the US government? diff --git a/main.yml b/main.yml index c1c14abd..45aae582 100644 --- a/main.yml +++ b/main.yml @@ -2,11 +2,19 @@ - hosts: localhost become: false tasks: - - name: Verify Ansible meets Drupal VM's version requirements. + - name: Ensure the requirements installed + debug: + msg: "{{ '' | ipaddr }}" + ignore_errors: true + no_log: true + register: ipaddr + + - name: Verify Ansible meets Algo VPN requirements. assert: - that: "ansible_version.full is version('2.7.10', '==')" + that: + - ansible_version.full is version('2.7.10', '==') + - not ipaddr.failed msg: > - Ansible version is {{ ansible_version.full }}. You must update the requirements to use this version of Algo. Try to run python -m pip install -U -r requirements.txt diff --git a/requirements.txt b/requirements.txt index 60c89a08..b79aa5fa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ ansible==2.7.10 +netaddr diff --git a/roles/common/handlers/main.yml b/roles/common/handlers/main.yml index 6b369267..ebbe91ad 100644 --- a/roles/common/handlers/main.yml +++ b/roles/common/handlers/main.yml @@ -18,7 +18,7 @@ ifconfig lo100 destroy || true && ifconfig lo100 create && ifconfig lo100 inet {{ local_service_ip }} netmask 255.255.255.255 && - ifconfig lo100 inet6 FCAA::1/64; echo $? + ifconfig lo100 inet6 {{ local_service_ipv6 }}/128; echo $? - name: restart iptables service: name=netfilter-persistent state=restarted diff --git a/roles/common/tasks/freebsd.yml b/roles/common/tasks/freebsd.yml index e0d54c16..9dbfb189 100644 --- a/roles/common/tasks/freebsd.yml +++ b/roles/common/tasks/freebsd.yml @@ -54,7 +54,7 @@ block: | cloned_interfaces="lo100" ifconfig_lo100="inet {{ local_service_ip }} netmask 255.255.255.255" - ifconfig_lo100_ipv6="inet6 FCAA::1/64" + ifconfig_lo100_ipv6="inet6 {{ local_service_ipv6 }}/128" notify: - restart loopback bsd diff --git a/roles/common/templates/10-algo-lo100.network.j2 b/roles/common/templates/10-algo-lo100.network.j2 index 87280511..ccdca7e6 100644 --- a/roles/common/templates/10-algo-lo100.network.j2 +++ b/roles/common/templates/10-algo-lo100.network.j2 @@ -4,4 +4,4 @@ Name=lo [Network] Description=lo:100 Address={{ local_service_ip }}/32 -Address=FCAA::1/64 +Address={{ local_service_ipv6 }}/128 diff --git a/roles/common/templates/rules.v6.j2 b/roles/common/templates/rules.v6.j2 index 12bed2b4..adb59f5d 100644 --- a/roles/common/templates/rules.v6.j2 +++ b/roles/common/templates/rules.v6.j2 @@ -83,7 +83,7 @@ COMMIT # particular virtual (tun,tap,...) or physical (ethernet) interface. # Accept DNS traffic to the local DNS resolver --A INPUT -d fcaa::1 -p udp --dport 53 -j ACCEPT +-A INPUT -d {{ local_service_ipv6 }}/128 -p udp --dport 53 -j ACCEPT # Drop traffic between VPN clients -A FORWARD -s {{ subnets|join(',') }} -d {{ subnets|join(',') }} -j {{ "DROP" if BetweenClients_DROP else "ACCEPT" }} diff --git a/roles/dns_adblocking/templates/dnsmasq.conf.j2 b/roles/dns_adblocking/templates/dnsmasq.conf.j2 index c52b6b9c..1857c55b 100644 --- a/roles/dns_adblocking/templates/dnsmasq.conf.j2 +++ b/roles/dns_adblocking/templates/dnsmasq.conf.j2 @@ -116,7 +116,7 @@ group=nogroup #except-interface= # Or which to listen on by address (remember to include 127.0.0.1 if # you use this.) -listen-address=127.0.0.1,FCAA::1,{{ local_service_ip }} +listen-address=127.0.0.1,{{ local_service_ipv6 }},{{ local_service_ip }} # If you want dnsmasq to provide only DNS service on an interface, # configure it as shown above, and then use the following line to # disable DHCP and TFTP on it. diff --git a/roles/wireguard/defaults/main.yml b/roles/wireguard/defaults/main.yml index 4c7f17f3..e0c82f51 100644 --- a/roles/wireguard/defaults/main.yml +++ b/roles/wireguard/defaults/main.yml @@ -10,5 +10,9 @@ wireguard_dns_servers: >- {% 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 %}" +wireguard_client_ip: >- + {{ wireguard_network_ipv4 | ipaddr(index|int+2) }} + {{ ',' + wireguard_network_ipv6 | ipaddr(index|int+2) if ipv6_support else '' }} +wireguard_server_ip: >- + {{ wireguard_network_ipv4 | ipaddr('1') }} + {{ ',' + wireguard_network_ipv6 | ipaddr('1') if ipv6_support else '' }} diff --git a/roles/wireguard/templates/server.conf.j2 b/roles/wireguard/templates/server.conf.j2 index 247c7d2f..46c280de 100644 --- a/roles/wireguard/templates/server.conf.j2 +++ b/roles/wireguard/templates/server.conf.j2 @@ -11,7 +11,6 @@ SaveConfig = false [Peer] # {{ u }} 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 %} - +AllowedIPs = {{ wireguard_network_ipv4 | ipaddr(index|int+1) | ipv4('address') }}/32{{ ',' + wireguard_network_ipv6 | ipaddr(index|int+1) | ipv6('address') + '/128' if ipv6_support else '' }} {% endif %} {% endfor %} diff --git a/tests/cloud-init.sh b/tests/cloud-init.sh index ca182cd5..e6d3209a 100755 --- a/tests/cloud-init.sh +++ b/tests/cloud-init.sh @@ -10,7 +10,7 @@ export LOCAL_DNS=true export SSH_TUNNELING=true export ENDPOINT=10.0.8.100 export USERS=desktop,user1,user2 -export EXTRA_VARS='install_headers=false tests=true apparmor_enabled=false' +export EXTRA_VARS='install_headers=false tests=true apparmor_enabled=false local_service_ip=172.16.0.1' export ANSIBLE_EXTRA_ARGS='--skip-tags apparmor' export REPO_SLUG=${TRAVIS_PULL_REQUEST_SLUG:-${TRAVIS_REPO_SLUG:-trailofbits/algo}} export REPO_BRANCH=${TRAVIS_PULL_REQUEST_BRANCH:-${TRAVIS_BRANCH:-master}} diff --git a/tests/ipsec-client.sh b/tests/ipsec-client.sh index d2c3f548..c64ca533 100755 --- a/tests/ipsec-client.sh +++ b/tests/ipsec-client.sh @@ -21,3 +21,5 @@ fping -t 900 -c3 -r3 -Dse 10.0.8.100 172.16.0.1 host google.com 172.16.0.1 echo "IPsec tests passed" + +ipsec down algovpn-10.0.8.100 diff --git a/tests/local-deploy.sh b/tests/local-deploy.sh index 99bf5c21..7699469d 100755 --- a/tests/local-deploy.sh +++ b/tests/local-deploy.sh @@ -2,7 +2,7 @@ set -ex -DEPLOY_ARGS="provider=local server=10.0.8.100 ssh_user=ubuntu endpoint=10.0.8.100 apparmor_enabled=false ondemand_cellular=true ondemand_wifi=true ondemand_wifi_exclude=test local_dns=true ssh_tunneling=true windows=true store_cakey=true install_headers=false tests=true" +DEPLOY_ARGS="provider=local server=10.0.8.100 ssh_user=ubuntu endpoint=10.0.8.100 apparmor_enabled=false ondemand_cellular=true ondemand_wifi=true ondemand_wifi_exclude=test local_dns=true ssh_tunneling=true windows=true store_cakey=true install_headers=false tests=true local_service_ip=172.16.0.1" if [ "${DEPLOY}" == "docker" ] then diff --git a/tests/update-users.sh b/tests/update-users.sh index d957787d..8c76ba1d 100755 --- a/tests/update-users.sh +++ b/tests/update-users.sh @@ -2,7 +2,7 @@ set -ex -USER_ARGS="{ 'server': '10.0.8.100', 'users': ['desktop', 'user1', 'user2'] }" +USER_ARGS="{ 'server': '10.0.8.100', 'users': ['desktop', 'user1', 'user2'], 'local_service_ip': '172.16.0.1' }" if [ "${DEPLOY}" == "docker" ] then diff --git a/tests/wireguard-client.sh b/tests/wireguard-client.sh index 7dac2a32..46b4603a 100755 --- a/tests/wireguard-client.sh +++ b/tests/wireguard-client.sh @@ -19,3 +19,5 @@ wg | grep "latest handshake" host google.com 172.16.0.1 echo "WireGuard tests passed" + +wg-quick down configs/10.0.8.100/wireguard/user1.conf From 368ebc8625b493a99b14006f09f4238d8bc5eafd Mon Sep 17 00:00:00 2001 From: Anton Strogonoff Date: Fri, 17 May 2019 22:04:14 +0800 Subject: [PATCH 12/25] fix: Use wait_for_connection to avoid failure (#1381) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With preexisting wait_for implementation, deployment to Ubuntu on Lightsail failed with a connection reset error on this task. It appears that Ansible’s wait_for_connection is the recommended way. I have successfully gotten past this task after this change, however I’d appreciate more eyes on this. --- roles/common/tasks/ubuntu.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/roles/common/tasks/ubuntu.yml b/roles/common/tasks/ubuntu.yml index 114b4180..f61dc44e 100644 --- a/roles/common/tasks/ubuntu.yml +++ b/roles/common/tasks/ubuntu.yml @@ -24,16 +24,12 @@ when: reboot_required is defined and reboot_required.stdout == 'required' ignore_errors: true - - name: Wait until SSH becomes ready... - wait_for: - port: 22 - host: "{{ inventory_hostname }}" - search_regex: OpenSSH - delay: 10 + - name: Wait until the server becomes ready... + wait_for_connection: + delay: 20 timeout: 320 when: reboot_required is defined and reboot_required.stdout == 'required' become: false - delegate_to: localhost when: algo_provider != "local" - name: Include unatteded upgrades configuration From a15d9657ce400682cbc1184a0cff9c6404f968ac Mon Sep 17 00:00:00 2001 From: TC1977 <37350377+TC1977@users.noreply.github.com> Date: Mon, 20 May 2019 05:44:56 -0400 Subject: [PATCH 13/25] Update troubleshooting.md (#1440) * Update troubleshooting.md * Fix silly typo * Add Android T-mobile fix * Fix another silly typo * Update troubleshooting.md --- docs/troubleshooting.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 4901f496..bf564a45 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -28,6 +28,7 @@ First of all, check [this](https://github.com/trailofbits/algo#features) and ens * [I can't get Network Manager to connect to the Algo server](#i-cant-get-network-manager-to-connect-to-the-algo-server) * [Various websites appear to be offline through the VPN](#various-websites-appear-to-be-offline-through-the-vpn) * [Clients appear stuck in a reconnection loop](#clients-appear-stuck-in-a-reconnection-loop) + * [Wireguard: clients can connect on Wifi but not LTE](#wireguard-clients-can-connect-on-wifi-but-not-lte) * ["Error 809" or IKE_AUTH requests that never make it to the server](#error-809-or-ike_auth-requests-that-never-make-it-to-the-server) * [Windows: Parameter is incorrect](#windows-parameter-is-incorrect) * [I have a problem not covered here](#i-have-a-problem-not-covered-here) @@ -408,6 +409,12 @@ Example command: sed -i -e 's/#*.dos_protection = yes/dos_protection = no/' /etc/strongswan.d/charon.conf && ipsec restart ``` +### WireGuard: Clients can connect on Wifi but not LTE + +Certain cloud providers (like AWS Lightsail) don't assign an IPv6 address to your server, but certain cellular carriers (e.g. T-Mobile in the United States, [EE](https://community.ee.co.uk/t5/4G-and-mobile-data/IPv4-VPN-Connectivity/td-p/757881) in the United Kingdom) operate an IPv6-only network. This somehow leads to the Wireguard app not being able to make a connection when transitioning to cell service. Go to the Wireguard app on the device when you're having problems with cell connectivity and select "Export log file" or similar option. If you see a long string of error messages like "`Failed to send data packet write udp6 [::]:49727->[2607:7700:0:2a:0:1:354:40ae]:51820: sendto: no route to host` then you might be having this problem. + +Manually disconnecting and then reconnecting should restore your connection. To solve this, you need to either "force IPv4 connection" if available on your phone, or install an IPv4 APN, which might be available from your carrier tech support. T-mobile's is available [for iOS here under "iOS IPv4/IPv6 fix"](https://www.reddit.com/r/tmobile/wiki/index), and [here is a walkthrough for Android phones](https://www.myopenrouter.com/article/vpn-connections-not-working-t-mobile-heres-how-fix). + ### "Error 809" or IKE_AUTH requests that never make it to the server On Windows, this issue may manifest with an error message that says "The network connection between your computer and the VPN server could not be established because the remote server is not responding... This is Error 809." On other operating systems, you may try to debug the issue by capturing packets with tcpdump and notice that, while IKE_SA_INIT request and responses are exchanged between the client and server, IKE_AUTH requests never make it to the server. From 72c8e9e24474929a4f5a689376820ba4e2e3cff6 Mon Sep 17 00:00:00 2001 From: shapiro125 Date: Mon, 20 May 2019 07:17:39 -0400 Subject: [PATCH 14/25] Add IPv6 support to DNS (#1425) * Add ipv6 * Add ipv6 * add ipv6 * add ipv6 * Switching out ipv6 address with local_service_ipv6 variable from #1429 * Fixing variable error --- config.cfg | 2 +- roles/dns_adblocking/templates/dnsmasq.conf.j2 | 3 +++ roles/dns_encryption/templates/dnscrypt-proxy.toml.j2 | 2 +- roles/strongswan/templates/ipsec.conf.j2 | 2 +- roles/wireguard/defaults/main.yml | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/config.cfg b/config.cfg index 181ae022..48b83fcd 100644 --- a/config.cfg +++ b/config.cfg @@ -110,7 +110,7 @@ congrats: "# Config files and certificates are in the ./configs/ directory. #" "# Go to https://whoer.net/ after connecting #" "# and ensure that all your traffic passes through the VPN. #" - "# Local DNS resolver {{ local_service_ip }} #" + "# Local DNS resolver {{ local_service_ip }}{{ ', ' + local_service_ipv6 if ipv6_support else '' }} #" p12_pass: | "# The p12 and SSH keys password for new users is {{ p12_export_password }} #" ca_key_pass: | diff --git a/roles/dns_adblocking/templates/dnsmasq.conf.j2 b/roles/dns_adblocking/templates/dnsmasq.conf.j2 index 1857c55b..7460d993 100644 --- a/roles/dns_adblocking/templates/dnsmasq.conf.j2 +++ b/roles/dns_adblocking/templates/dnsmasq.conf.j2 @@ -90,6 +90,9 @@ no-resolv # server=10.1.2.3@eth1 {% if dns_encryption %} server={{ local_service_ip }}#5353 +{% if ipv6_support -%} +server={{ local_service_ipv6 }}#5353 +{% endif %} {% else %} {% for host in dns_servers.ipv4 %} server={{ host }} diff --git a/roles/dns_encryption/templates/dnscrypt-proxy.toml.j2 b/roles/dns_encryption/templates/dnscrypt-proxy.toml.j2 index d954ff8b..a084a9d2 100644 --- a/roles/dns_encryption/templates/dnscrypt-proxy.toml.j2 +++ b/roles/dns_encryption/templates/dnscrypt-proxy.toml.j2 @@ -37,7 +37,7 @@ ## List of local addresses and ports to listen to. Can be IPv4 and/or IPv6. ## Note: When using systemd socket activation, choose an empty set (i.e. [] ). -listen_addresses = ['{{ local_service_ip }}:{{ listen_port }}'] +listen_addresses = ['{{ local_service_ip }}:{{ listen_port }}'{% if ipv6_support %}, '[{{ local_service_ipv6 }}]:{{ listen_port }}'{% endif %}] ## Maximum number of simultaneous client connections to accept diff --git a/roles/strongswan/templates/ipsec.conf.j2 b/roles/strongswan/templates/ipsec.conf.j2 index 7cd27c90..3f0a4020 100644 --- a/roles/strongswan/templates/ipsec.conf.j2 +++ b/roles/strongswan/templates/ipsec.conf.j2 @@ -31,7 +31,7 @@ conn %default rightauth=pubkey rightsourceip={{ strongswan_network }},{{ strongswan_network_ipv6 }} {% if algo_local_dns or dns_encryption %} - rightdns={{ local_service_ip }} + rightdns={{ local_service_ip }}{{ ',' + local_service_ipv6 if ipv6_support else '' }} {% else %} rightdns={% 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 %} diff --git a/roles/wireguard/defaults/main.yml b/roles/wireguard/defaults/main.yml index e0c82f51..2774d04f 100644 --- a/roles/wireguard/defaults/main.yml +++ b/roles/wireguard/defaults/main.yml @@ -6,7 +6,7 @@ wireguard_interface: wg0 keys_clean_all: false wireguard_dns_servers: >- {% if local_dns|default(false)|bool or dns_encryption|default(false)|bool %} - {{ local_service_ip }} + {{ local_service_ip }}{{ ', ' + local_service_ipv6 if ipv6_support else '' }} {% 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 %} From e3a6170ae637b82f69774373dbf19d44a7394086 Mon Sep 17 00:00:00 2001 From: Elliot Murphy Date: Mon, 20 May 2019 08:40:51 -0400 Subject: [PATCH 15/25] AWS support for existing EIP (revised) (#1292) * Support for associating to existing AWS Elastic IP Signed-off-by: Elliot Murphy * Backport ec2_eip_facts module for EIP support This means that EIP support no longer requires Ansible 2.6 The local fact module has been named ec2_elasticip_facts to avoid conflict with the ec2_eip_facts module whenever the Ansible 2.6 upgrade takes place. Signed-off-by: Elliot Murphy * Update from review feedback. Signed-off-by: Elliot Murphy * Move to the native module. Add additional condition for existing Elastic IP --- config.cfg | 7 +++++-- roles/cloud-ec2/defaults/main.yml | 1 + roles/cloud-ec2/files/stack.yaml | 17 ++++++++++++++++- roles/cloud-ec2/tasks/cloudformation.yml | 1 + roles/cloud-ec2/tasks/prompts.yml | 22 ++++++++++++++++++++++ 5 files changed, 45 insertions(+), 3 deletions(-) diff --git a/config.cfg b/config.cfg index 48b83fcd..508caa9c 100644 --- a/config.cfg +++ b/config.cfg @@ -131,9 +131,12 @@ cloud_providers: size: s-1vcpu-1gb image: "ubuntu-18-04-x64" ec2: - # Change the encrypted flag to "true" to enable AWS volume encryption, for encryption of data at rest. - # Warning: the Algo script will take approximately 6 minutes longer to complete. + # Change the encrypted flag to "true" to enable AWS volume encryption, for encryption of data at rest. + # Warning: the Algo script will take approximately 6 minutes longer to complete. encrypted: false + # Set use_existing_eip to "true" if you want to use a pre-allocated Elastic IP + # Additional prompt will be raised to determine which IP to use + use_existing_eip: true size: t2.micro image: name: "ubuntu-bionic-18.04" diff --git a/roles/cloud-ec2/defaults/main.yml b/roles/cloud-ec2/defaults/main.yml index 12b3f19d..86ae9954 100644 --- a/roles/cloud-ec2/defaults/main.yml +++ b/roles/cloud-ec2/defaults/main.yml @@ -5,3 +5,4 @@ ec2_vpc_nets: cidr_block: 172.16.0.0/16 subnet_cidr: 172.16.254.0/23 ec2_venv: "{{ playbook_dir }}/configs/.venvs/aws" +existing_eip: "" diff --git a/roles/cloud-ec2/files/stack.yaml b/roles/cloud-ec2/files/stack.yaml index 829a2cb3..5a8d2f8d 100644 --- a/roles/cloud-ec2/files/stack.yaml +++ b/roles/cloud-ec2/files/stack.yaml @@ -11,6 +11,12 @@ Parameters: Type: String WireGuardPort: Type: String + UseThisElasticIP: + Type: String + Default: '' +Conditions: + AllocateNewEIP: !Equals [!Ref UseThisElasticIP, ''] + AssociateExistingEIP: !Not [!Equals [!Ref UseThisElasticIP, '']] Resources: VPC: Type: AWS::EC2::VPC @@ -175,6 +181,7 @@ Resources: ElasticIP: Type: AWS::EC2::EIP + Condition: AllocateNewEIP Properties: Domain: vpc InstanceId: !Ref EC2Instance @@ -182,6 +189,14 @@ Resources: - EC2Instance - VPCGatewayAttachment + ElasticIPAssociation: + Type: AWS::EC2::EIPAssociation + Condition: AssociateExistingEIP + Properties: + AllocationId: !Ref UseThisElasticIP + InstanceId: !Ref EC2Instance + + Outputs: ElasticIP: - Value: !Ref ElasticIP + Value: !GetAtt [EC2Instance, PublicIp] diff --git a/roles/cloud-ec2/tasks/cloudformation.yml b/roles/cloud-ec2/tasks/cloudformation.yml index 126c5318..8aadfaa7 100644 --- a/roles/cloud-ec2/tasks/cloudformation.yml +++ b/roles/cloud-ec2/tasks/cloudformation.yml @@ -12,6 +12,7 @@ PublicSSHKeyParameter: "{{ lookup('file', SSH_keys.public) }}" ImageIdParameter: "{{ ami_image }}" WireGuardPort: "{{ wireguard_port }}" + UseThisElasticIP: "{{ existing_eip }}" tags: Environment: Algo register: stack diff --git a/roles/cloud-ec2/tasks/prompts.yml b/roles/cloud-ec2/tasks/prompts.yml index 2993f694..040af834 100644 --- a/roles/cloud-ec2/tasks/prompts.yml +++ b/roles/cloud-ec2/tasks/prompts.yml @@ -53,3 +53,25 @@ [{{ default_region }}] register: _algo_region when: region is undefined + +- block: + - name: Get existing available Elastic IPs + ec2_eip_facts: + register: raw_eip_addresses + + - set_fact: + available_eip_addresses: "{{ raw_eip_addresses.addresses | selectattr('association_id', 'undefined') | list }}" + + - pause: + prompt: >- + What Elastic IP would you like to use? + {% for eip in available_eip_addresses %} + {{ loop.index }}. {{ eip['public_ip'] }} + {% endfor %} + + Enter the number of your desired Elastic IP + register: _use_existing_eip + + - set_fact: + existing_eip: "{{ available_eip_addresses[_use_existing_eip.user_input | int -1 ]['allocation_id'] }}" + when: cloud_providers.ec2.use_existing_eip From a87b4c8a87fbad8306abce8e6473a968c5fe5118 Mon Sep 17 00:00:00 2001 From: Jack Ivanov <17044561+jackivanov@users.noreply.github.com> Date: Mon, 20 May 2019 14:45:03 +0200 Subject: [PATCH 16/25] Update config.cfg --- config.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.cfg b/config.cfg index 508caa9c..cf0318ec 100644 --- a/config.cfg +++ b/config.cfg @@ -136,7 +136,7 @@ cloud_providers: encrypted: false # Set use_existing_eip to "true" if you want to use a pre-allocated Elastic IP # Additional prompt will be raised to determine which IP to use - use_existing_eip: true + use_existing_eip: false size: t2.micro image: name: "ubuntu-bionic-18.04" From ecb4e555b48ae2cacefe58a3ce107f7cc701076b Mon Sep 17 00:00:00 2001 From: TC1977 <37350377+TC1977@users.noreply.github.com> Date: Tue, 21 May 2019 11:17:58 -0400 Subject: [PATCH 17/25] Update users: add server pick-list (#1441) * Pick server to update from menu * Command instead of shell * Move to find module Switched to the find module, and made the whole block dependent on server being undefined. * Change names * users.yml update - Add assert to check if any servers found - Set server_list as a proper list * Change 'Build string' to 'Build list' --- users.yml | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/users.yml b/users.yml index 9d2b21e3..a77676c5 100644 --- a/users.yml +++ b/users.yml @@ -7,17 +7,44 @@ tasks: - block: + - name: Get list of installed config files + find: + paths: configs/ + depth: 2 + recurse: true + hidden: true + patterns: ".config.yml" + register: _configs_list + + - name: Verify servers + assert: + that: _configs_list.matched > 0 + msg: No servers found, nothing to update. + + - name: Build list of installed servers + set_fact: + server_list: >- + [{% for i in _configs_list.files %} + '{{ i.path.split('/')[1] }}' + {{ ',' if not loop.last else '' }} + {% endfor %}] + - name: Server address prompt pause: - prompt: "Enter the IP address of your server: (or use localhost for local installation)" + prompt: | + Select the server to update user list below: + {% for r in server_list %} + {{ loop.index }}. {{ r }} + {% endfor %} register: _server - when: server is undefined + when: server is undefined + - block: - name: Set facts based on the input set_fact: algo_server: >- {% if server is defined %}{{ server }} - {%- elif _server.user_input %}{{ _server.user_input }} + {%- elif _server.user_input %}{{ server_list[_server.user_input | int -1 ] }} {%- else %}omit{% endif %} - name: Import host specific variables From 634c60962655c9f4db14135a7dc622debfe68e1b Mon Sep 17 00:00:00 2001 From: Jack Ivanov <17044561+jackivanov@users.noreply.github.com> Date: Thu, 30 May 2019 07:20:45 +0200 Subject: [PATCH 18/25] Don't set CA facts if IPsec is disabled (#1446) * Don't set CA facts if ipsec is disabled * localhost update-users fix --- config.cfg | 2 +- users.yml | 37 ++++++++++++++++++++----------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/config.cfg b/config.cfg index cf0318ec..3b6745a7 100644 --- a/config.cfg +++ b/config.cfg @@ -114,7 +114,7 @@ congrats: p12_pass: | "# The p12 and SSH keys password for new users is {{ p12_export_password }} #" ca_key_pass: | - "# The CA key password is {{ CA_password }} #" + "# The CA key password is {{ CA_password|default(omit) }} #" ssh_access: | "# Shell access: ssh -i {{ ansible_ssh_private_key_file|default(omit) }} {{ ansible_ssh_user|default(omit) }}@{{ ansible_ssh_host|default(omit) }} #" diff --git a/users.yml b/users.yml index a77676c5..540a1588 100644 --- a/users.yml +++ b/users.yml @@ -25,7 +25,8 @@ set_fact: server_list: >- [{% for i in _configs_list.files %} - '{{ i.path.split('/')[1] }}' + {% set config = lookup('file', i.path)|from_yaml %} + '{{ config.server }}' {{ ',' if not loop.last else '' }} {% endfor %}] @@ -51,21 +52,21 @@ include_vars: file: "configs/{{ algo_server }}/.config.yml" - - name: CA password prompt - pause: - prompt: Enter the password for the private CA key - echo: false - register: _ca_password - when: - - ca_password is undefined - - ipsec_enabled + - when: ipsec_enabled + block: + - name: CA password prompt + pause: + prompt: Enter the password for the private CA key + echo: false + register: _ca_password + when: ca_password is undefined - - name: Set facts based on the input - set_fact: - CA_password: >- - {% if ca_password is defined %}{{ ca_password }} - {%- elif _ca_password.user_input %}{{ _ca_password.user_input }} - {%- else %}omit{% endif %} + - name: Set facts based on the input + set_fact: + CA_password: >- + {% if ca_password is defined %}{{ ca_password }} + {%- elif _ca_password.user_input %}{{ _ca_password.user_input }} + {%- else %}omit{% endif %} - name: Local pre-tasks import_tasks: playbooks/cloud-pre.yml @@ -78,7 +79,7 @@ ansible_ssh_user: "{{ server_user|default('root') }}" ansible_connection: "{% if algo_server == 'localhost' %}local{% else %}ssh{% endif %}" ansible_python_interpreter: "/usr/bin/python3" - CA_password: "{{ CA_password }}" + CA_password: "{{ CA_password|default(omit) }}" rescue: - include_tasks: playbooks/rescue.yml @@ -111,7 +112,9 @@ - debug: msg: - "{{ congrats.common.split('\n') }}" - - " {% if p12.changed %}{{ congrats.p12_pass }}{% 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: - include_tasks: playbooks/rescue.yml From 98f89adeba5f84283c6fb51b30196d8e2bb177ba Mon Sep 17 00:00:00 2001 From: David Myers Date: Thu, 30 May 2019 08:07:22 -0400 Subject: [PATCH 19/25] Add reference to Fedora docs in README (#1456) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f094930..0b17b0b3 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ The easiest way to get an Algo server running is to let it set up a _new_ virtua python-setuptools \ python-virtualenv -y ``` - - Linux (rpm-based): See the [Pre-Install Documentation for RedHat/CentOS 6.x](docs/deploy-from-redhat-centos6.md) + - Linux (rpm-based): See the pre-installation documentation for [RedHat/CentOS 6.x](docs/deploy-from-redhat-centos6.md) or [Fedora](docs/deploy-from-fedora-workstation.md) - Windows: See the [Windows documentation](docs/deploy-from-windows.md) 4. **Install Algo's remaining dependencies.** Use the same Terminal window as the previous step and run: From c27aed708a611e62af396ea813438f92fb8d12d4 Mon Sep 17 00:00:00 2001 From: Jack Ivanov <17044561+jackivanov@users.noreply.github.com> Date: Thu, 30 May 2019 16:13:48 +0200 Subject: [PATCH 20/25] EC2 eip facts authentication fix (#1454) * EC2 eip facts authentication fix * add region to ec2_eip_facts --- roles/cloud-ec2/tasks/main.yml | 7 ------- roles/cloud-ec2/tasks/prompts.yml | 11 +++++++++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/roles/cloud-ec2/tasks/main.yml b/roles/cloud-ec2/tasks/main.yml index 44eebc9f..7d63217e 100644 --- a/roles/cloud-ec2/tasks/main.yml +++ b/roles/cloud-ec2/tasks/main.yml @@ -6,13 +6,6 @@ - name: Include prompts import_tasks: prompts.yml - - set_fact: - algo_region: >- - {% if region is defined %}{{ region }} - {%- elif _algo_region.user_input %}{{ aws_regions[_algo_region.user_input | int -1 ]['region_name'] }} - {%- else %}{{ aws_regions[default_region | int - 1]['region_name'] }}{% endif %} - stack_name: "{{ algo_server_name | replace('.', '-') }}" - - name: Locate official AMI for region ec2_ami_facts: aws_access_key: "{{ access_key }}" diff --git a/roles/cloud-ec2/tasks/prompts.yml b/roles/cloud-ec2/tasks/prompts.yml index 040af834..db805849 100644 --- a/roles/cloud-ec2/tasks/prompts.yml +++ b/roles/cloud-ec2/tasks/prompts.yml @@ -54,9 +54,20 @@ register: _algo_region when: region is undefined +- name: Set algo_region and stack_name facts + set_fact: + algo_region: >- + {% if region is defined %}{{ region }} + {%- elif _algo_region.user_input %}{{ aws_regions[_algo_region.user_input | int -1 ]['region_name'] }} + {%- else %}{{ aws_regions[default_region | int - 1]['region_name'] }}{% endif %} + stack_name: "{{ algo_server_name | replace('.', '-') }}" + - block: - name: Get existing available Elastic IPs ec2_eip_facts: + aws_access_key: "{{ access_key }}" + aws_secret_key: "{{ secret_key }}" + region: "{{ algo_region }}" register: raw_eip_addresses - set_fact: From 71c9c16ffeab3eca70df822ff299cd826b305c7f Mon Sep 17 00:00:00 2001 From: TC1977 <37350377+TC1977@users.noreply.github.com> Date: Thu, 30 May 2019 10:14:45 -0400 Subject: [PATCH 21/25] Update EC2 instructions (#1457) * Update cloud-amazon-ec2.md * Add files via upload --- docs/cloud-amazon-ec2.md | 42 +++++++++++++++-------------- docs/images/aws-ec2-new-policy.png | Bin 87193 -> 76379 bytes 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/docs/cloud-amazon-ec2.md b/docs/cloud-amazon-ec2.md index 1e81988f..1bbf30b2 100644 --- a/docs/cloud-amazon-ec2.md +++ b/docs/cloud-amazon-ec2.md @@ -89,26 +89,28 @@ Name the vpn server: After entering the server name, the script ask which region you wish to setup your new Algo instance in. Enter the number next to name of the region. ``` - What region should the server be located in? - 1. us-east-1 US East (N. Virginia) - 2. us-east-2 US East (Ohio) - 3. us-west-1 US West (N. California) - 4. us-west-2 US West (Oregon) - 5. ca-central-1 Canada (Central) - 6. eu-central-1 EU (Frankfurt) - 7. eu-west-1 EU (Ireland) - 8. eu-west-2 EU (London) - 9. eu-west-3 EU (Paris) - 10. ap-northeast-1 Asia Pacific (Tokyo) - 11. ap-northeast-2 Asia Pacific (Seoul) - 12. ap-northeast-3 Asia Pacific (Osaka-Local) - 13. ap-southeast-1 Asia Pacific (Singapore) - 14. ap-southeast-2 Asia Pacific (Sydney) - 15. ap-south-1 Asia Pacific (Mumbai) - 16. sa-east-1 South America (São Paulo) - -Enter the number of your desired region: -[1]: 10 +What region should the server be located in? +(https://docs.aws.amazon.com/general/latest/gr/rande.html#ec2_region) + 1. ap-northeast-1 + 2. ap-northeast-2 + 3. ap-south-1 + 4. ap-southeast-1 + 5. ap-southeast-2 + 6. ca-central-1 + 7. eu-central-1 + 8. eu-north-1 + 9. eu-west-1 + 10. eu-west-2 + 11. eu-west-3 + 12. sa-east-1 + 13. us-east-1 + 14. us-east-2 + 15. us-west-1 + 16. us-west-2 + +Enter the number of your desired region +[13] +: ``` You will then be asked the remainder of the standard Algo setup questions. diff --git a/docs/images/aws-ec2-new-policy.png b/docs/images/aws-ec2-new-policy.png index 691512e7f96eccaef4b4955a69aad174fa64efea..bb35b262232bca603c5eb361b652294f111cfd8f 100644 GIT binary patch literal 76379 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

amCbbO9>)ZEy}d{fcGvQhS}7 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%0D;3{7ew$+Xa)FCf%KbV1!-qnO8WI{O*3=y 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®D*sx=L}c6!qB>}otcjRuKj)N^(N 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 From a2fdc509e16f90a40ef29a559ce0fdea8c9355d3 Mon Sep 17 00:00:00 2001 From: Jack Ivanov <17044561+jackivanov@users.noreply.github.com> Date: Thu, 30 May 2019 20:57:47 +0200 Subject: [PATCH 22/25] Support for Ubuntu 19.04 (#1405) * Ubuntu 19.04 * Azure to 19.04 --- .travis.yml | 111 ++++++++++-------- README.md | 8 +- config.cfg | 10 +- docs/cloud-do.md | 6 +- docs/deploy-to-ubuntu.md | 2 +- docs/deploy-to-unsupported-cloud.md | 2 +- docs/index.md | 2 +- input.yml | 2 +- .../files/apparmor.profile.dnscrypt-proxy | 2 +- roles/dns_encryption/tasks/ubuntu.yml | 3 +- roles/strongswan/tasks/ubuntu.yml | 29 +++-- tests/pre-deploy.sh | 2 +- 12 files changed, 103 insertions(+), 76 deletions(-) diff --git a/.travis.yml b/.travis.yml index e799b05b..c730000f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,56 +51,73 @@ custom_scripts: - sudo env "PATH=$PATH" ./tests/ipsec-client.sh - sudo ./tests/ssh-tunnel.sh +stages: + - &tests-and-linters + stage: Tests + name: code checks and linters + addons: + apt: + packages: + - shellcheck + script: + - pip install ansible-lint + - shellcheck algo install.sh + - ansible-playbook main.yml --syntax-check + - ansible-lint -v *.yml + + - &deploy-local + stage: Deploy + name: local deployment from docker + addons: + apt: + sources: *default_sources + packages: *default_packages + before_install: *provisioning + before_script: + - docker build -t travis/algo . + - ./tests/local-deploy.sh + - ./tests/update-users.sh + script: *tests + + - &deploy-cloudinit + stage: Deploy + name: cloud-init deployment + addons: + apt: + sources: *default_sources + packages: *default_packages + env: DEPLOY=cloud-init + before_install: *provisioning + before_script: + - until sudo lxc exec algo -- test -f /var/log/cloud-init-output.log; do echo 'Log file not found, Sleep for 3 seconds'; sleep 3; done + - ( sudo lxc exec algo -- tail -f /var/log/cloud-init-output.log & ) + - | + until sudo lxc exec algo -- test -f /var/lib/cloud/data/result.json; do + echo 'Cloud init is not finished. Sleep for 30 seconds'; + sleep 30; + done + - sudo lxc exec algo -- test -f /opt/algo/configs/localhost/.config.yml + - sudo lxc exec algo -- tar zcf /root/algo-configs.tar -C /opt/algo/configs/ . + - sudo lxc file pull algo/root/algo-configs.tar ./ + - sudo tar -C ./configs -zxf algo-configs.tar + script: *tests + matrix: fast_finish: true include: - - stage: Tests - name: code checks and linters - addons: - apt: - packages: - - shellcheck - script: - - pip install ansible-lint - - shellcheck algo install.sh - - ansible-playbook main.yml --syntax-check - - ansible-lint -v roles/*/*/*.yml playbooks/*.yml *.yml - - - stage: Deploy - name: local deployment from docker - addons: - apt: - sources: *default_sources - packages: *default_packages - env: DEPLOY=docker - before_install: *provisioning - before_script: - - docker build -t travis/algo . - - ./tests/local-deploy.sh - - ./tests/update-users.sh - script: *tests - - - stage: Deploy - name: cloud-init deployment - addons: - apt: - sources: *default_sources - packages: *default_packages - env: DEPLOY=cloud-init - before_install: *provisioning - before_script: - - until sudo lxc exec algo -- test -f /var/log/cloud-init-output.log; do echo 'Log file not found, Sleep for 3 seconds'; sleep 3; done - - ( sudo lxc exec algo -- tail -f /var/log/cloud-init-output.log & ) - - | - until sudo lxc exec algo -- test -f /var/lib/cloud/data/result.json; do - echo 'Cloud init is not finished. Sleep for 30 seconds'; - sleep 30; - done - - sudo lxc exec algo -- test -f /opt/algo/configs/localhost/.config.yml - - sudo lxc exec algo -- tar zcf /root/algo-configs.tar -C /opt/algo/configs/ . - - sudo lxc file pull algo/root/algo-configs.tar ./ - - sudo tar -C ./configs -zxf algo-configs.tar - script: *tests + - <<: *tests-and-linters + - <<: *deploy-local + name: 'Ubuntu 18.04: local deployment from docker' + env: DEPLOY=docker UBUNTU_VERSION=18.04 + - <<: *deploy-local + name: 'Ubuntu 19.04: local deployment from docker' + env: DEPLOY=docker UBUNTU_VERSION=19.04 + - <<: *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 notifications: email: false diff --git a/README.md b/README.md index 0b17b0b3..d458055b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Algo VPN is a set of Ansible scripts that simplify the setup of a personal IPSEC * Blocks ads with a local DNS resolver (optional) * Sets up limited SSH users for tunneling traffic (optional) * Based on current versions of Ubuntu and strongSwan -* Installs to DigitalOcean, Amazon Lightsail, Amazon EC2, Vultr, Microsoft Azure, Google Compute Engine, Scaleway, OpenStack, or your own Ubuntu 18.04 LTS server +* Installs to DigitalOcean, Amazon Lightsail, Amazon EC2, Vultr, Microsoft Azure, Google Compute Engine, Scaleway, OpenStack, or your own Ubuntu server ## Anti-features @@ -93,9 +93,9 @@ WireGuard is used to provide VPN services on Apple devices. Algo generates a Wir On iOS, install the [WireGuard](https://itunes.apple.com/us/app/wireguard/id1441195209?mt=8) app from the iOS App Store. Then, use the WireGuard app to scan the QR code or AirDrop the configuration file to the device. -On macOS Mojave or later, install the [WireGuard](https://itunes.apple.com/us/app/wireguard/id1451685025?mt=12) app from the Mac App Store. WireGuard will appear in the menu bar once you run the app. Click on the WireGuard icon, choose **Import tunnel(s) from file...**, then select the appropriate WireGuard configuration file. +On macOS Mojave or later, install the [WireGuard](https://itunes.apple.com/us/app/wireguard/id1451685025?mt=12) app from the Mac App Store. WireGuard will appear in the menu bar once you run the app. Click on the WireGuard icon, choose **Import tunnel(s) from file...**, then select the appropriate WireGuard configuration file. -On either iOS or macOS, you can enable "Connect on Demand" and/or exclude certain trusted Wi-Fi networks (such as your home or work) by editing the tunnel configuration in the WireGuard app. (Algo can't do this automatically for you.) +On either iOS or macOS, you can enable "Connect on Demand" and/or exclude certain trusted Wi-Fi networks (such as your home or work) by editing the tunnel configuration in the WireGuard app. (Algo can't do this automatically for you.) Installing WireGuard is a little more complicated on older version of macOS. See [Using macOS as a Client with WireGuard](docs/client-macos-wireguard.md). @@ -122,7 +122,7 @@ Network Manager does not support AES-GCM. In order to support Linux Desktop clie Install strongSwan, then copy the included ipsec_user.conf, ipsec_user.secrets, user.crt (user certificate), and user.key (private key) files to your client device. These will require customization based on your exact use case. These files were originally generated with a point-to-point OpenWRT-based VPN in mind. -#### Ubuntu Server 18.04 example +#### Ubuntu Server example 1. `sudo apt-get install strongswan libstrongswan-standard-plugins`: install strongSwan 2. `/etc/ipsec.d/certs`: copy `.crt` from `algo-master/configs//ipsec/manual/.crt` diff --git a/config.cfg b/config.cfg index 3b6745a7..c4698ac6 100644 --- a/config.cfg +++ b/config.cfg @@ -126,10 +126,10 @@ SSH_keys: cloud_providers: azure: size: Basic_A0 - image: 18.04-LTS + image: 19.04 digitalocean: size: s-1vcpu-1gb - image: "ubuntu-18-04-x64" + image: "ubuntu-19-04-x64" ec2: # Change the encrypted flag to "true" to enable AWS volume encryption, for encryption of data at rest. # Warning: the Algo script will take approximately 6 minutes longer to complete. @@ -139,11 +139,11 @@ cloud_providers: use_existing_eip: false size: t2.micro image: - name: "ubuntu-bionic-18.04" + name: "ubuntu-disco-19.04" owner: "099720109477" gce: size: f1-micro - image: ubuntu-1804 + image: ubuntu-1904 external_static_ip: false lightsail: size: nano_1_0 @@ -156,7 +156,7 @@ cloud_providers: flavor_ram: ">=512" image: Ubuntu-18.04 vultr: - os: Ubuntu 18.04 x64 + os: Ubuntu 19.04 x64 size: 1024 MB RAM,25 GB SSD,1.00 TB BW local: diff --git a/docs/cloud-do.md b/docs/cloud-do.md index c4230a99..3c6a0a5b 100644 --- a/docs/cloud-do.md +++ b/docs/cloud-do.md @@ -34,8 +34,8 @@ What provider would you like to use? 6. Google Compute Engine 7. Scaleway 8. OpenStack (DreamCompute optimised) - 9. Install to existing Ubuntu 18.04 server (Advanced) - + 9. Install to existing Ubuntu server (Advanced) + Enter the number of your desired provider : 1 @@ -68,7 +68,7 @@ What region should the server be located in? 7. sfo2 San Francisco 2 8. sgp1 Singapore 1 9. tor1 Toronto 1 - + Enter the number of your desired region [6] : diff --git a/docs/deploy-to-ubuntu.md b/docs/deploy-to-ubuntu.md index 29a54e64..794cf5f7 100644 --- a/docs/deploy-to-ubuntu.md +++ b/docs/deploy-to-ubuntu.md @@ -4,7 +4,7 @@ You can use Algo to configure a local server as an AlgoVPN rather than create an Install the Algo scripts on your server and follow the normal installation instructions, then choose: ``` -Install to existing Ubuntu 18.04 server (Advanced) +Install to existing Ubuntu 18.04 or 19.04 server (Advanced) ``` Make sure your server is running the operating system specified. diff --git a/docs/deploy-to-unsupported-cloud.md b/docs/deploy-to-unsupported-cloud.md index 7fd176f7..e6d03e80 100644 --- a/docs/deploy-to-unsupported-cloud.md +++ b/docs/deploy-to-unsupported-cloud.md @@ -2,7 +2,7 @@ Algo officially supports DigitalOcean, Amazon Web Services, Microsoft Azure, and Google Cloud Engine. 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), and +1. the base operating system image that Algo uses (Ubuntu 18.04, 19.04), 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/docs/index.md b/docs/index.md index 02214052..118a6552 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,7 +22,7 @@ - Configure [Vultr](cloud-vultr.md) * Advanced Deployment - Deploy to your own [FreeBSD](deploy-to-freebsd.md) server - - Deploy to your own [Ubuntu 18.04](deploy-to-ubuntu.md) server + - Deploy to your own [Ubuntu](deploy-to-ubuntu.md) server - Deploy to an [unsupported cloud provider](deploy-to-unsupported-cloud.md) * [FAQ](faq.md) * [Firewalls](firewalls.md) diff --git a/input.yml b/input.yml index fa4984b2..659977a3 100644 --- a/input.yml +++ b/input.yml @@ -20,7 +20,7 @@ - { name: Google Compute Engine, alias: gce } - { name: Scaleway, alias: scaleway} - { name: OpenStack (DreamCompute optimised), alias: openstack } - - { name: Install to existing Ubuntu 18.04 server (Advanced), alias: local } + - { name: Install to existing Ubuntu 18.04 or 19.04 server (Advanced), alias: local } vars_files: - config.cfg diff --git a/roles/dns_encryption/files/apparmor.profile.dnscrypt-proxy b/roles/dns_encryption/files/apparmor.profile.dnscrypt-proxy index c2258688..51de03f6 100644 --- a/roles/dns_encryption/files/apparmor.profile.dnscrypt-proxy +++ b/roles/dns_encryption/files/apparmor.profile.dnscrypt-proxy @@ -1,6 +1,6 @@ #include -/usr/bin/dnscrypt-proxy flags=(attach_disconnected) { +/usr/{s,}bin/dnscrypt-proxy flags=(attach_disconnected) { #include #include #include diff --git a/roles/dns_encryption/tasks/ubuntu.yml b/roles/dns_encryption/tasks/ubuntu.yml index 76f0e159..198da88d 100644 --- a/roles/dns_encryption/tasks/ubuntu.yml +++ b/roles/dns_encryption/tasks/ubuntu.yml @@ -2,8 +2,9 @@ - name: Add the repository apt_repository: state: present - codename: bionic + codename: "{{ ansible_distribution_release }}" repo: ppa:shevchuk/dnscrypt-proxy + when: ansible_distribution_version is version_compare('19.04', '<') register: result until: result is succeeded retries: 10 diff --git a/roles/strongswan/tasks/ubuntu.yml b/roles/strongswan/tasks/ubuntu.yml index afaffa38..f85293e3 100644 --- a/roles/strongswan/tasks/ubuntu.yml +++ b/roles/strongswan/tasks/ubuntu.yml @@ -10,17 +10,26 @@ update_cache: yes install_recommends: yes -- name: Ubuntu | Enforcing ipsec with apparmor - command: aa-enforce "{{ item }}" +- block: + # https://bugs.launchpad.net/ubuntu/+source/strongswan/+bug/1826238 + - name: Ubuntu | Charon profile for apparmor configured + copy: + dest: /etc/apparmor.d/local/usr.lib.ipsec.charon + content: ' capability setpcap,' + owner: root + group: root + mode: 0644 + notify: restart strongswan + + - name: Ubuntu | Enforcing ipsec with apparmor + command: aa-enforce "{{ item }}" + changed_when: false + with_items: + - /usr/lib/ipsec/charon + - /usr/lib/ipsec/lookip + - /usr/lib/ipsec/stroke + tags: apparmor when: apparmor_enabled|default(false)|bool - changed_when: false - with_items: - - /usr/lib/ipsec/charon - - /usr/lib/ipsec/lookip - - /usr/lib/ipsec/stroke - notify: - - restart apparmor - tags: ['apparmor'] - name: Ubuntu | Enable services service: name={{ item }} enabled=yes diff --git a/tests/pre-deploy.sh b/tests/pre-deploy.sh index 764eb673..e56922d2 100755 --- a/tests/pre-deploy.sh +++ b/tests/pre-deploy.sh @@ -19,7 +19,7 @@ systemctl restart lxd-bridge.service lxd-containers.service lxd.service lxc profile set default raw.lxc lxc.aa_profile=unconfined lxc profile set default security.privileged true lxc profile show default -lxc launch ubuntu:18.04 algo +lxc launch ubuntu:${UBUNTU_VERSION} algo ip addr From d03eaed7a6ead0d20f56ea79f5bd750e88aad004 Mon Sep 17 00:00:00 2001 From: Jack Ivanov <17044561+jackivanov@users.noreply.github.com> Date: Thu, 30 May 2019 21:41:31 +0200 Subject: [PATCH 23/25] Update CHANGELOG.md --- CHANGELOG.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 843d8ad4..573af56d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,28 @@ -## 1.1 (Unreleased) +## 1.1 [(Unreleased)](https://github.com/trailofbits/algo/tree/HEAD) ### Added +- Support for Ubuntu 19.04 [\#1405](https://github.com/trailofbits/algo/pull/1405) ([jackivanov](https://github.com/jackivanov)) +- AWS support for existing EIP [\#1292](https://github.com/trailofbits/algo/pull/1292) ([statik](https://github.com/statik)) +- Script to support cloud-init and local easy deploy [\#1366](https://github.com/trailofbits/algo/pull/1366) ([jackivanov](https://github.com/jackivanov)) +- Automatically create cloud firewall rules for installs onto Vultr [\#1400](https://github.com/trailofbits/algo/pull/1400) ([TC1977](https://github.com/TC1977)) +- Randomly generated IP address for the local dns resolver [\#1429](https://github.com/trailofbits/algo/pull/1429) ([jackivanov](https://github.com/jackivanov)) +- Update users: add server pick-list [\#1441](https://github.com/trailofbits/algo/pull/1441) ([TC1977](https://github.com/TC1977)) +- Additional testing [\#213](https://github.com/trailofbits/algo/issues/213) +- Add IPv6 support to DNS [\#1425](https://github.com/trailofbits/algo/pull/1425) ([shapiro125](https://github.com/shapiro125)) +- Additional p12 with the CA cert included [\#1403](https://github.com/trailofbits/algo/pull/1403) ([jackivanov](https://github.com/jackivanov)) ### Fixed +- Fixes error in 10-algo-lo100.network [\#1369](https://github.com/trailofbits/algo/pull/1369) ([adamluk](https://github.com/adamluk)) +- Error message is missing for some roles [\#1364](https://github.com/trailofbits/algo/issues/1364) +- DNS leak in Linux/Wireguard when LAN gateway/DNS is 172.16.0.1 [\#1422](https://github.com/trailofbits/algo/issues/1422) +- Installation error after \#1397 [\#1409](https://github.com/trailofbits/algo/issues/1409) + +### Changed +- Refactoring, Linting and additional tests [\#1397](https://github.com/trailofbits/algo/pull/1397) ([jackivanov](https://github.com/jackivanov)) +- Scaleway modules [\#1410](https://github.com/trailofbits/algo/pull/1410) ([jackivanov](https://github.com/jackivanov)) +- Use VULTR_API_CONFIG variable if set [\#1374](https://github.com/trailofbits/algo/pull/1374) ([davidemyers](https://github.com/davidemyers)) +- Simplify Apple Profile Configuration Template [\#1033](https://github.com/trailofbits/algo/pull/1033) ([faf0](https://github.com/faf0)) +- Include roles as separate tasks [\#1365](https://github.com/trailofbits/algo/pull/1365) ([jackivanov](https://github.com/jackivanov)) ## 1.0 (Mar 19, 2019) From 2d04f65284aec089117f80dc7c1e01e2bf299e48 Mon Sep 17 00:00:00 2001 From: Jack Ivanov <17044561+jackivanov@users.noreply.github.com> Date: Thu, 30 May 2019 21:43:12 +0200 Subject: [PATCH 24/25] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 573af56d..e55a965b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ - Simplify Apple Profile Configuration Template [\#1033](https://github.com/trailofbits/algo/pull/1033) ([faf0](https://github.com/faf0)) - Include roles as separate tasks [\#1365](https://github.com/trailofbits/algo/pull/1365) ([jackivanov](https://github.com/jackivanov)) -## 1.0 (Mar 19, 2019) +## 1.0 [(Mar 19, 2019)](https://github.com/trailofbits/algo/tree/v1.0) ### Added - Tagged releases and changelog [\#724](https://github.com/trailofbits/algo/issues/724) From 498cf463911712f69699e73b90c2d61ed7950e44 Mon Sep 17 00:00:00 2001 From: Jack Ivanov <17044561+jackivanov@users.noreply.github.com> Date: Mon, 3 Jun 2019 01:01:08 +0200 Subject: [PATCH 25/25] Block link-local networks. Block traffic from SSH tunnels to VPN clients (#1458) --- roles/common/templates/rules.v4.j2 | 7 +++++++ roles/common/templates/rules.v6.j2 | 2 ++ roles/ssh_tunneling/tasks/main.yml | 7 +++++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/roles/common/templates/rules.v4.j2 b/roles/common/templates/rules.v4.j2 index d71f51fb..05789b7f 100644 --- a/roles/common/templates/rules.v4.j2 +++ b/roles/common/templates/rules.v4.j2 @@ -77,6 +77,13 @@ COMMIT # Drop traffic between VPN clients -A FORWARD -s {{ subnets|join(',') }} -d {{ subnets|join(',') }} -j {{ "DROP" if BetweenClients_DROP else "ACCEPT" }} +# Drop traffic to VPN clients from SSH tunnels +-A OUTPUT -d {{ subnets|join(',') }} -m owner --gid-owner 15000 -j {{ "DROP" if BetweenClients_DROP else "ACCEPT" }} + +# Drop traffic to the link-local network +-A FORWARD -s {{ subnets|join(',') }} -d 169.254.0.0/16 -j DROP +# Drop traffic to the link-local network from SSH tunnels +-A OUTPUT -d 169.254.0.0/16 -m owner --gid-owner 15000 -j DROP # Forward any packet that's part of an established connection -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT diff --git a/roles/common/templates/rules.v6.j2 b/roles/common/templates/rules.v6.j2 index adb59f5d..4c42f14e 100644 --- a/roles/common/templates/rules.v6.j2 +++ b/roles/common/templates/rules.v6.j2 @@ -87,6 +87,8 @@ COMMIT # Drop traffic between VPN clients -A FORWARD -s {{ subnets|join(',') }} -d {{ subnets|join(',') }} -j {{ "DROP" if BetweenClients_DROP else "ACCEPT" }} +# Drop traffic to VPN clients from SSH tunnels +-A OUTPUT -d {{ subnets|join(',') }} -m owner --gid-owner 15000 -j {{ "DROP" if BetweenClients_DROP else "ACCEPT" }} -A FORWARD -j ICMPV6-CHECK -A FORWARD -p tcp --dport 445 -j DROP diff --git a/roles/ssh_tunneling/tasks/main.yml b/roles/ssh_tunneling/tasks/main.yml index 437fa47f..2226bbe7 100644 --- a/roles/ssh_tunneling/tasks/main.yml +++ b/roles/ssh_tunneling/tasks/main.yml @@ -14,7 +14,10 @@ - restart ssh - name: Ensure that the algo group exist - group: name=algo state=present + group: + name: algo + state: present + gid: 15000 - name: Ensure that the jail directory exist file: @@ -28,7 +31,7 @@ - name: Ensure that the SSH users exist user: name: "{{ item }}" - groups: algo + group: algo home: '/var/jail/{{ item }}' createhome: yes generate_ssh_key: false