Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .yamllint
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ rules:
level: warning
comments:
min-spaces-from-content: 1
comments-indentation: false
octal-values:
forbid-implicit-octal: true
forbid-explicit-octal: true
braces:
max-spaces-inside: 1
truthy:
Expand Down
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ See our [release announcement](https://blog.trailofbits.com/2016/12/12/meet-algo

## Features

* Supports only IKEv2 with strong crypto (AES-GCM, SHA2, and P-256) for iOS, macOS, and Linux
* Supports only IKEv2 with strong crypto (AES-GCM, SHA2, and P-256) for iOS, MacOS, and Linux
* Supports [WireGuard](https://www.wireguard.com/) for all of the above, in addition to Android and Windows 11
* Generates .conf files and QR codes for iOS, macOS, Android, and Windows WireGuard clients
* Generates Apple profiles to auto-configure iOS and macOS devices for IPsec - no client software required
* Includes a helper script to add and remove users
* Includes helper scripts to add, remove, and manage users
* 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
* Privacy-focused with minimal logging, automatic log rotation, and configurable privacy enhancements
* Based on Ubuntu 22.04 LTS with automatic security updates
* Installs to DigitalOcean, Amazon Lightsail, Amazon EC2, Vultr, Microsoft Azure, Google Compute Engine, Scaleway, OpenStack, CloudStack, Hetzner Cloud, Linode, or [your own Ubuntu server (for advanced users)](docs/deploy-to-ubuntu.md)

## Anti-features
Expand Down Expand Up @@ -175,6 +176,33 @@ To add or remove users, first edit the `users` list in your `config.cfg` file. A

After the process completes, new configuration files will be generated in the `configs` directory for any new users. The Algo VPN server will be updated to contain only the users listed in the `config.cfg` file. Removed users will no longer be able to connect, and new users will have fresh certificates and configuration files ready for use.

## Privacy and Logging

Algo takes a pragmatic approach to privacy. By default, we minimize logging while maintaining enough information for security and troubleshooting.

What IS logged by default:
* System security events (failed SSH attempts, firewall blocks, system updates)
* Kernel messages and boot diagnostics (with reduced verbosity)
* WireGuard client state (visible via `sudo wg` - shows last endpoint and handshake time)
* Basic service status (service starts/stops/errors)
* All logs automatically rotate and delete after 7 days

Privacy is controlled by two main settings in `config.cfg`:
* `strongswan_log_level: -1` - Controls StrongSwan connection logging (-1 = disabled, 2 = debug)
* `privacy_enhancements_enabled: true` - Master switch for log rotation, history clearing, log filtering, and cleanup

To enable full debugging when troubleshooting, set both `strongswan_log_level: 2` and `privacy_enhancements_enabled: false`. This will capture detailed connection logs and disable all privacy features. Remember to revert these changes after debugging.

After deployment, verify your privacy settings:
```bash
ssh -F configs/<server_ip>/ssh_config <hostname>
sudo /usr/local/bin/privacy-monitor.sh
```

Perfect privacy is impossible with any VPN solution. Your cloud provider sees and logs network traffic metadata regardless of your server configuration. And of course, your ISP knows you're connecting to a VPN server, even if they can't see what you're doing through it.
For the highest level of privacy, treat your Algo servers as disposable. Spin up a new instance when you need it, use it for your specific purpose, then destroy it completely. The ephemeral nature of cloud infrastructure can be a privacy feature if you use it intentionally.
## Additional Documentation
* [FAQ](docs/faq.md)
* [Troubleshooting](docs/troubleshooting.md)
Expand Down
130 changes: 42 additions & 88 deletions config.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -12,106 +12,61 @@ users:

### Review these options BEFORE you run Algo, as they are very difficult/impossible to change after the server is deployed.

# Performance optimizations (reduces deployment time)
# Skip reboots unless kernel was updated (saves 0-5 minutes)
performance_skip_optional_reboots: false
# Use parallel key generation for certificates (saves 1-2 minutes)
performance_parallel_crypto: false
# Batch install all packages in one operation (saves 30-60 seconds)
performance_parallel_packages: false
# Pre-install universal packages via cloud-init (saves 30-90 seconds)
performance_preinstall_packages: false
# Configure VPN services in parallel (saves 1-2 minutes)
performance_parallel_services: false

# Change default SSH port for the cloud roles only
# It doesn't apply if you deploy to your existing Ubuntu Server
# SSH port for cloud deployments (doesn't apply to existing Ubuntu servers)
ssh_port: 4160

# Deploy StrongSwan to enable IPsec support
# VPN protocols to deploy
ipsec_enabled: true

# Deploy WireGuard
# WireGuard will listen on 51820/UDP. You might need to change to another port
# if your network blocks this one. Be aware that 53/UDP (DNS) is blocked on some
# mobile data networks.
wireguard_enabled: true
wireguard_port: 51820
wireguard_port: 51820 # Change if blocked by your network (avoid 53/UDP)

# This feature allows you to configure the Algo server to send outbound traffic
# through a different external IP address than the one you are establishing the VPN connection with.
# More info https://trailofbits.github.io/algo/cloud-alternative-ingress-ip.html
# Available for the following cloud providers:
# - DigitalOcean
# Use different IP for outbound traffic (DigitalOcean only)
alternative_ingress_ip: false

# Reduce the MTU of the VPN tunnel
# Some cloud and internet providers use a smaller MTU (Maximum Transmission
# Unit) than the normal value of 1500 and if you don't reduce the MTU of your
# VPN tunnel some network connections will hang. Algo will attempt to set this
# automatically based on your server, but if connections hang you might need to
# adjust this yourself.
# See: https://github.com/trailofbits/algo/blob/master/docs/troubleshooting.md#various-websites-appear-to-be-offline-through-the-vpn
# Reduce MTU if connections hang (0 = auto-detect)
# See: docs/troubleshooting.md#various-websites-appear-to-be-offline-through-the-vpn
reduce_mtu: 0

# Algo will use the following lists to block ads. You can add new block lists
# after deployment by modifying the line starting "BLOCKLIST_URLS=" at:
# /usr/local/sbin/adblock.sh
# If you load very large blocklists, you may also have to modify resource limits:
# /etc/systemd/system/dnsmasq.service.d/100-CustomLimitations.conf
# Ad blocking lists (modify /usr/local/sbin/adblock.sh after deployment to add more)
adblock_lists:
- "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
- "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"

# Enable DNS encryption.
# If 'false', 'dns_servers' should be specified below.
# DNS encryption can not be disabled if DNS adblocking is enabled
# DNS encryption (required if using ad blocking)
dns_encryption: true

# Block traffic between connected clients. Change this to false to enable
# connected clients to reach each other, as well as other computers on the
# same LAN as your Algo server (i.e. the "road warrior" setup). In this
# case, you may also want to enable SMB/CIFS and NETBIOS traffic below.
# Client isolation (set false for "road warrior" setup where clients can reach each other)
BetweenClients_DROP: true
block_smb: true # Block SMB/CIFS traffic
block_netbios: true # Block NETBIOS traffic

# Block SMB/CIFS traffic
block_smb: true

# Block NETBIOS traffic
block_netbios: true

# Your Algo server will automatically install security updates. Some updates
# require a reboot to take effect but your Algo server will not reboot itself
# automatically unless you change 'enabled' below from 'false' to 'true', in
# which case a reboot will take place if necessary at the time specified (as
# HH:MM) in the time zone of your Algo server. The default time zone is UTC.
# Automatic reboot for security updates (time in server's timezone, default UTC)
unattended_reboot:
enabled: false
time: 06:00

### Privacy Settings ###
# StrongSwan connection logging (-1 = disabled, 2 = debug)
strongswan_log_level: -1

# Master switch for privacy enhancements (log rotation, history clearing, etc.)
# Set to false for debugging. For advanced privacy options, see roles/privacy/defaults/main.yml
privacy_enhancements_enabled: true

### Advanced users only below this line ###

# DNS servers which will be used if 'dns_encryption' is 'true'. Multiple
# providers may be specified, but avoid mixing providers that filter results
# (like Cisco) with those that don't (like Cloudflare) or you could get
# inconsistent results. The list of available public providers can be found
# here:
# https://github.com/DNSCrypt/dnscrypt-resolvers/blob/master/v2/public-resolvers.md
# DNSCrypt providers (see https://github.com/DNSCrypt/dnscrypt-resolvers/blob/master/v2/public-resolvers.md)
dnscrypt_servers:
ipv4:
- cloudflare
# - google
# - <YourCustomServer> # E.g., if using NextDNS, this will be something like NextDNS-abc123.
# You must also fill in custom_server_stamps below. You may specify
# multiple custom servers.
# - YourCustomServer # For NextDNS etc., add stamp below
ipv6:
- cloudflare-ipv6

custom_server_stamps:
# YourCustomServer: 'sdns://...'

# DNS servers which will be used if 'dns_encryption' is 'false'.
# Fallback resolvers for systemd-resolved
# The default is to use Cloudflare.
# DNS servers when encryption is disabled
dns_servers:
ipv4:
- 1.1.1.1
Expand All @@ -120,37 +75,36 @@ dns_servers:
- 2606:4700:4700::1111
- 2606:4700:4700::1001

# Store the PKI in a ram disk. Enabled only if store_pki (retain the PKI) is set to false
# Supports on MacOS and Linux only (including Windows Subsystem for Linux)
# Store PKI in RAM disk when not retaining (MacOS/Linux only)
pki_in_tmpfs: true

# Set this to 'true' when running './algo update-users' if you want ALL users to get new certs, not just new users.
# Regenerate ALL user certs on update-users (not just new users)
keys_clean_all: false

# StrongSwan log level
# https://wiki.strongswan.org/projects/strongswan/wiki/LoggerConfiguration
strongswan_log_level: 2

# rightsourceip for ipsec
# ipv4
### VPN Network Configuration ###
strongswan_network: 10.48.0.0/16
# ipv6
strongswan_network_ipv6: '2001:db8:4160::/48'

# If you're behind NAT or a firewall and you want to receive incoming connections long after network traffic has gone silent.
# This option will keep the "connection" open in the eyes of NAT.
# See: https://www.wireguard.com/quickstart/#nat-and-firewall-traversal-persistence
wireguard_PersistentKeepalive: 0

# WireGuard network configuration
wireguard_network_ipv4: 10.49.0.0/16
wireguard_network_ipv6: 2001:db8:a160::/48

# Keep NAT connections alive (0 = disabled)
wireguard_PersistentKeepalive: 0

### Experimental Performance Options ###
# These are experimental and may cause issues. Enable at your own risk.
# performance_skip_optional_reboots: false # Skip non-kernel reboots
# performance_parallel_crypto: false # Parallel key generation
# performance_parallel_packages: false # Batch package installation
# performance_preinstall_packages: false # Pre-install via cloud-init
# performance_parallel_services: false # Configure VPN services in parallel

# Randomly generated IP address for the local dns resolver
local_service_ip: "{{ '172.16.0.1' | ansible.utils.ipmath(1048573 | random(seed=algo_server_name + ansible_fqdn)) }}"
local_service_ipv6: "{{ 'fd00::1' | ansible.utils.ipmath(1048573 | random(seed=algo_server_name + ansible_fqdn)) }}"

# Hide sensitive data
# Hide sensitive data in Ansible output during deployment (passwords, keys, etc.)
# This is NOT related to privacy/logging on the VPN server itself
algo_no_log: true

congrats:
Expand Down Expand Up @@ -218,11 +172,11 @@ cloud_providers:
image: Ubuntu 22.04 Jammy Jellyfish
arch: x86_64
hetzner:
server_type: cpx11
server_type: cpx11
image: ubuntu-22.04
openstack:
flavor_ram: ">=512"
image: Ubuntu-22.04
image: Ubuntu-22.04
cloudstack:
size: Micro
image: Linux Ubuntu 22.04 LTS 64-bit
Expand Down
5 changes: 5 additions & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* [I deployed an Algo server. Can you update it with new features?](#i-deployed-an-algo-server-can-you-update-it-with-new-features)
* [Where did the name "Algo" come from?](#where-did-the-name-algo-come-from)
* [Can DNS filtering be disabled?](#can-dns-filtering-be-disabled)
* [Does Algo support zero logging?](#does-algo-support-zero-logging)
* [Wasn't IPSEC backdoored by the US government?](#wasnt-ipsec-backdoored-by-the-us-government)
* [What inbound ports are used?](#what-inbound-ports-are-used)
* [How do I monitor user activity?](#how-do-i-monitor-user-activity)
Expand Down Expand Up @@ -59,6 +60,10 @@ Algo is short for "Al Gore", the **V**ice **P**resident of **N**etworks everywhe

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=<random_ip>` 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.

## Does Algo support zero logging?

Yes, Algo includes privacy enhancements that minimize logging by default. StrongSwan connection logging is disabled, DNSCrypt syslog is turned off, and logs are automatically rotated after 7 days. However, some system-level logging remains for security and troubleshooting purposes. For detailed privacy configuration and limitations, see the [Privacy and Logging](#privacy-and-logging) section in the README.

## Wasn't IPSEC backdoored by the US government?

No.
Expand Down
9 changes: 8 additions & 1 deletion roles/client/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,14 @@
dest: "{{ item.dest }}"
line: "{{ item.line }}"
create: true
mode: "{{ item.mode }}"
with_items:
- dest: "{{ configs_prefix }}/ipsec.conf"
line: include ipsec.{{ IP_subject_alt_name }}.conf
mode: '0644'
- dest: "{{ configs_prefix }}/ipsec.secrets"
line: include ipsec.{{ IP_subject_alt_name }}.secrets
mode: '0600'
notify:
- restart strongswan

Expand All @@ -59,18 +62,22 @@
dest: "{{ configs_prefix }}/strongswan.d/relax-ca-constraints.conf"
owner: root
group: root
mode: 0644
mode: '0644'

- name: Setup the certificates and keys
template:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: "{{ item.mode }}"
with_items:
- src: configs/{{ IP_subject_alt_name }}/ipsec/.pki/certs/{{ vpn_user }}.crt
dest: "{{ configs_prefix }}/ipsec.d/certs/{{ vpn_user }}.crt"
mode: '0644'
- src: configs/{{ IP_subject_alt_name }}/ipsec/.pki/cacert.pem
dest: "{{ configs_prefix }}/ipsec.d/cacerts/{{ IP_subject_alt_name }}.pem"
mode: '0644'
- src: configs/{{ IP_subject_alt_name }}/ipsec/.pki/private/{{ vpn_user }}.key
dest: "{{ configs_prefix }}/ipsec.d/private/{{ vpn_user }}.key"
mode: '0600'
notify:
- restart strongswan
1 change: 1 addition & 0 deletions roles/cloud-azure/tasks/prompts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
tenant: "{{ azure_tenant | default(lookup('env', 'AZURE_TENANT'), true) }}"
client_id: "{{ azure_client_id | default(lookup('env', 'AZURE_CLIENT_ID'), true) }}"
subscription_id: "{{ azure_subscription_id | default(lookup('env', 'AZURE_SUBSCRIPTION_ID'), true) }}"
no_log: true

- block:
- name: Set the default region
set_fact:
default_region: >-

Check failure on line 12 in roles/cloud-azure/tasks/prompts.yml

View workflow job for this annotation

GitHub Actions / Linting

jinja[spacing]

Jinja2 spacing could be improved: {% for r in azure_regions %} {%- if r['name'] == "eastus" %}{{ loop.index }}{% endif %} {%- endfor %} -> {% for r in azure_regions %}{%- if r['name'] == "eastus" %}{{ loop.index }}{% endif %}{%- endfor %}
{% for r in azure_regions %}
{%- if r['name'] == "eastus" %}{{ loop.index }}{% endif %}
{%- endfor %}
Expand Down
1 change: 1 addition & 0 deletions roles/cloud-cloudstack/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

- block:
- set_fact:
algo_region: >-

Check failure on line 10 in roles/cloud-cloudstack/tasks/main.yml

View workflow job for this annotation

GitHub Actions / Linting

jinja[spacing]

Jinja2 spacing could be improved: {% if region is defined %}{{ region }} {%- elif _algo_region.user_input is defined and _algo_region.user_input | length > 0 %}{{ cs_zones[_algo_region.user_input | int -1 ]['name'] }} {%- else %}{{ cs_zones[default_zone | int - 1]['name'] }}{% endif %} -> {% if region is defined %}{{ region }}{%- elif _algo_region.user_input is defined and _algo_region.user_input | length > 0 %}{{ cs_zones[_algo_region.user_input | int - 1]['name'] }}{%- else %}{{ cs_zones[default_zone | int - 1]['name'] }}{% endif %}
{% if region is defined %}{{ region }}
{%- elif _algo_region.user_input is defined and _algo_region.user_input | length > 0 %}{{ cs_zones[_algo_region.user_input | int -1 ]['name'] }}
{%- else %}{{ cs_zones[default_zone | int - 1]['name'] }}{% endif %}
Expand Down Expand Up @@ -57,3 +57,4 @@
CLOUDSTACK_KEY: "{{ algo_cs_key }}"
CLOUDSTACK_SECRET: "{{ algo_cs_token }}"
CLOUDSTACK_ENDPOINT: "{{ algo_cs_url }}"
no_log: true
4 changes: 4 additions & 0 deletions roles/cloud-cloudstack/tasks/prompts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
when:
- cs_key is undefined
- lookup('env', 'CLOUDSTACK_KEY')|length <= 0
no_log: true

- pause:
prompt: |
Expand All @@ -17,6 +18,7 @@
when:
- cs_secret is undefined
- lookup('env', 'CLOUDSTACK_SECRET')|length <= 0
no_log: true

- pause:
prompt: |
Expand All @@ -34,6 +36,7 @@
{{ cs_url | default(_cs_url.user_input|default(None)) |
default(lookup('env', 'CLOUDSTACK_ENDPOINT'), true) |
default('https://api.exoscale.com/compute', true) }}
no_log: true

- name: Get zones on cloud
cs_zone_info:
Expand All @@ -42,6 +45,7 @@
CLOUDSTACK_KEY: "{{ algo_cs_key }}"
CLOUDSTACK_SECRET: "{{ algo_cs_token }}"
CLOUDSTACK_ENDPOINT: "{{ algo_cs_url }}"
no_log: true

- name: Extract zones from output
set_fact:
Expand Down
3 changes: 3 additions & 0 deletions roles/cloud-digitalocean/tasks/prompts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
when:
- do_token is undefined
- lookup('env', 'DO_API_TOKEN')|length <= 0
no_log: true

- name: Set the token as a fact
set_fact:
algo_do_token: "{{ do_token | default(_do_token.user_input | default(None)) | default(lookup('env', 'DO_API_TOKEN'), true) }}"
no_log: true

- name: Get regions
uri:
Expand All @@ -21,6 +23,7 @@
Content-Type: application/json
Authorization: Bearer {{ algo_do_token }}
register: _do_regions
no_log: true

- name: Set facts about the regions
set_fact:
Expand Down
1 change: 1 addition & 0 deletions roles/cloud-ec2/tasks/cloudformation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
tags:
Environment: Algo
register: stack
no_log: true
1 change: 1 addition & 0 deletions roles/cloud-ec2/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
architecture: "{{ cloud_providers.ec2.image.arch }}"
name: ubuntu/images/hvm-ssd/{{ cloud_providers.ec2.image.name }}-*64-server-*
register: ami_search
no_log: true

- name: Set the ami id as a fact
set_fact:
Expand Down
Loading
Loading