Repository: StreisandEffect/streisand Branch: master Commit: af5eb7dac157 Files: 377 Total size: 1005.7 KB Directory structure: gitextract_p3pz680y/ ├── .github/ │ ├── ISSUE_TEMPLATE.md │ └── workflows/ │ └── streisand.yml ├── .gitignore ├── Advanced installation.md ├── CONTRIBUTING.md ├── Features.md ├── Installation.md ├── LICENSE ├── README-chs.md ├── README-fr.md ├── README-ru.md ├── README.md ├── Services.md ├── Vagrantfile ├── Vagrantfile.remotetest ├── ansible.cfg ├── deploy/ │ ├── streisand-existing-cloud-server.sh │ ├── streisand-local.sh │ └── streisand-new-cloud-server.sh ├── documentation/ │ ├── AWS-fr.md │ ├── AWS.md │ ├── AZURE-fr.md │ ├── AZURE.md │ ├── SOURCES.md │ ├── certificates.md │ ├── localization_howto.md │ ├── modular_roles.md │ └── testing.md ├── global_vars/ │ ├── default-site.yml │ ├── globals.yml │ ├── integration/ │ │ └── test-site.yml │ └── noninteractive/ │ ├── amazon-site.yml │ ├── azure-site.yml │ ├── digitalocean-site.yml │ ├── google-site.yml │ ├── linode-site.yml │ ├── local-site.yml │ └── rackspace-site.yml ├── inventories/ │ ├── inventory │ └── inventory-local-provision ├── library/ │ └── digital_ocean_droplet.py ├── playbooks/ │ ├── amazon.yml │ ├── azure.yml │ ├── cloud-status.yml │ ├── customize.yml │ ├── digitalocean.yml │ ├── ec2-metadata-instance.yml │ ├── existing-server.yml │ ├── google.yml │ ├── group_vars/ │ │ └── all │ ├── lets-encrypt.yml │ ├── linode.yml │ ├── localhost.yml │ ├── provider-detect.yml │ ├── python.yml │ ├── rackspace.yml │ ├── roles/ │ │ ├── ad-blocking/ │ │ │ ├── files/ │ │ │ │ ├── download-blocklists │ │ │ │ ├── download-blocklists.service │ │ │ │ ├── download-blocklists.timer │ │ │ │ ├── transform-domain-list │ │ │ │ └── transform-host-list │ │ │ └── tasks/ │ │ │ └── main.yml │ │ ├── azure-security-group/ │ │ │ ├── meta/ │ │ │ │ └── main.yml │ │ │ ├── tasks/ │ │ │ │ └── main.yml │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── certificates/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ ├── tasks/ │ │ │ │ ├── ca-server.yml │ │ │ │ ├── client.yml │ │ │ │ ├── main.yml │ │ │ │ └── pkcs.yml │ │ │ ├── templates/ │ │ │ │ ├── allowed_vpn_certs.j2 │ │ │ │ └── openssl.cnf.j2 │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── cloudflared/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ ├── files/ │ │ │ │ └── cloudflared.service │ │ │ ├── handlers/ │ │ │ │ └── main.yml │ │ │ ├── meta/ │ │ │ │ └── main.yml │ │ │ ├── tasks/ │ │ │ │ ├── install_binary.yml │ │ │ │ ├── install_package.yml │ │ │ │ └── main.yml │ │ │ ├── templates/ │ │ │ │ └── cloudflared.j2 │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── common/ │ │ │ ├── files/ │ │ │ │ ├── english.txt │ │ │ │ ├── footer.html │ │ │ │ └── header.html │ │ │ ├── tasks/ │ │ │ │ ├── detect-public-ip.yml │ │ │ │ ├── main.yml │ │ │ │ └── set-default-variables.yml │ │ │ ├── templates/ │ │ │ │ ├── 20auto-upgrades.j2 │ │ │ │ ├── 50unattended-upgrades.j2 │ │ │ │ └── test-client-inventory.j2 │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── diagnostics/ │ │ │ ├── tasks/ │ │ │ │ └── main.yml │ │ │ └── templates/ │ │ │ └── streisand-diagnostics.md.j2 │ │ ├── dnsmasq/ │ │ │ ├── handlers/ │ │ │ │ └── main.yml │ │ │ ├── tasks/ │ │ │ │ └── main.yml │ │ │ ├── templates/ │ │ │ │ ├── dnsmasq.conf.j2 │ │ │ │ └── dnsmasq.service.j2 │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── download-and-verify/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ └── tasks/ │ │ │ └── main.yml │ │ ├── ec2-security-group/ │ │ │ ├── tasks/ │ │ │ │ └── main.yml │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── gce-network/ │ │ │ ├── tasks/ │ │ │ │ └── main.yml │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── genesis-amazon/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ ├── files/ │ │ │ │ └── aws-metadata-instance.service │ │ │ ├── meta/ │ │ │ │ └── main.yml │ │ │ └── tasks/ │ │ │ └── main.yml │ │ ├── genesis-azure/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ ├── meta/ │ │ │ │ └── main.yml │ │ │ └── tasks/ │ │ │ └── main.yml │ │ ├── genesis-digitalocean/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ └── tasks/ │ │ │ └── main.yml │ │ ├── genesis-google/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ ├── meta/ │ │ │ │ └── main.yml │ │ │ └── tasks/ │ │ │ └── main.yml │ │ ├── genesis-linode/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ └── tasks/ │ │ │ └── main.yml │ │ ├── genesis-rackspace/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ └── tasks/ │ │ │ └── main.yml │ │ ├── gpg/ │ │ │ ├── files/ │ │ │ │ ├── 2D8330C2.daniel@binaryparadox.net.asc │ │ │ │ ├── 2F2B01E7.security@openvpn.net.asc │ │ │ │ ├── 4AE8DA82.putty@projects.tartarus.org.asc │ │ │ │ ├── 7F343FA7.nmav@redhat.com.asc │ │ │ │ ├── 93298290.torbrowser@torproject.org.asc │ │ │ │ ├── 96865171.nmav@gnutls.org.asc │ │ │ │ ├── A697A56F.corban@raunco.co.asc │ │ │ │ ├── AF16234E.alimakki@gmail.com.asc │ │ │ │ ├── CDF6583E.josh@joshlund.com.asc │ │ │ │ ├── DD3AAAA3.Michal.Trojnara@stunnel.org.asc │ │ │ │ └── F67DA905.nop@nop.com.asc │ │ │ ├── tasks/ │ │ │ │ └── main.yml │ │ │ ├── templates/ │ │ │ │ ├── dirmngr.conf.j2 │ │ │ │ └── streisand-gpg-refresh.j2 │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── i18n-docs/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ ├── tasks/ │ │ │ │ └── main.yml │ │ │ └── templates/ │ │ │ └── languages.md.j2 │ │ ├── ip-forwarding/ │ │ │ ├── files/ │ │ │ │ └── streisand-ipforward.sh │ │ │ └── tasks/ │ │ │ └── main.yml │ │ ├── lets-encrypt/ │ │ │ ├── files/ │ │ │ │ └── 01-reload-nginx.sh │ │ │ ├── tasks/ │ │ │ │ ├── firewall.yml │ │ │ │ ├── install.yml │ │ │ │ └── main.yml │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── nginx/ │ │ │ ├── files/ │ │ │ │ ├── nginx.conf │ │ │ │ └── nginx_signing.key │ │ │ ├── tasks/ │ │ │ │ └── main.yml │ │ │ ├── templates/ │ │ │ │ └── nginx.service.j2 │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── openconnect/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ ├── files/ │ │ │ │ ├── ocserv-pam │ │ │ │ └── openconnect.conf │ │ │ ├── handlers/ │ │ │ │ └── main.yml │ │ │ ├── meta/ │ │ │ │ └── main.yml │ │ │ ├── tasks/ │ │ │ │ ├── docs.yml │ │ │ │ ├── firewall.yml │ │ │ │ ├── install.yml │ │ │ │ ├── main.yml │ │ │ │ └── mirror.yml │ │ │ ├── templates/ │ │ │ │ ├── client.mobileconfig.j2 │ │ │ │ ├── config.j2 │ │ │ │ ├── instructions-fr.md.j2 │ │ │ │ ├── instructions.md.j2 │ │ │ │ ├── mirror-fr.md.j2 │ │ │ │ ├── mirror.md.j2 │ │ │ │ ├── ocserv-iptables.service.j2 │ │ │ │ └── ocserv.service.j2 │ │ │ └── vars/ │ │ │ ├── main.yml │ │ │ └── mirror.yml │ │ ├── openvpn/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ ├── files/ │ │ │ │ └── openvpn_signing.key │ │ │ ├── handlers/ │ │ │ │ └── main.yml │ │ │ ├── meta/ │ │ │ │ └── main.yml │ │ │ ├── tasks/ │ │ │ │ ├── docs.yml │ │ │ │ ├── firewall.yml │ │ │ │ ├── install.yml │ │ │ │ ├── main.yml │ │ │ │ └── mirror.yml │ │ │ ├── templates/ │ │ │ │ ├── client-combined.ovpn.j2 │ │ │ │ ├── client-common.j2 │ │ │ │ ├── client-direct-udp.ovpn.j2 │ │ │ │ ├── client-direct.ovpn.j2 │ │ │ │ ├── client-sslh.ovpn.j2 │ │ │ │ ├── client-stunnel.ovpn.j2 │ │ │ │ ├── etc_openvpn_server.conf.j2 │ │ │ │ ├── etc_openvpn_server_common.j2 │ │ │ │ ├── etc_openvpn_server_udp.conf.j2 │ │ │ │ ├── instructions-fr.md.j2 │ │ │ │ ├── instructions.md.j2 │ │ │ │ ├── mirror-fr.md.j2 │ │ │ │ ├── mirror.md.j2 │ │ │ │ ├── openvpn-iptables.service.j2 │ │ │ │ ├── openvpn.service.j2 │ │ │ │ ├── openvpn_dnsmasq.conf.j2 │ │ │ │ ├── stunnel-instructions-fr.md.j2 │ │ │ │ └── stunnel-instructions.md.j2 │ │ │ └── vars/ │ │ │ ├── main.yml │ │ │ └── mirror.yml │ │ ├── service-net/ │ │ │ ├── files/ │ │ │ │ ├── 10-service0.netdev │ │ │ │ ├── 10-service0.network │ │ │ │ └── service-net.conf │ │ │ ├── meta/ │ │ │ │ └── main.yml │ │ │ └── tasks/ │ │ │ └── main.yml │ │ ├── shadowsocks/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ ├── handlers/ │ │ │ │ └── main.yml │ │ │ ├── meta/ │ │ │ │ └── main.yml │ │ │ ├── tasks/ │ │ │ │ ├── docs.yml │ │ │ │ ├── firewall.yml │ │ │ │ ├── main.yml │ │ │ │ ├── mirror.yml │ │ │ │ ├── simple-obfs.yml │ │ │ │ └── v2ray.yml │ │ │ ├── templates/ │ │ │ │ ├── config.json.j2 │ │ │ │ ├── instructions-fr.md.j2 │ │ │ │ ├── instructions.md.j2 │ │ │ │ ├── mirror-fr.md.j2 │ │ │ │ ├── mirror.md.j2 │ │ │ │ ├── shadowsocks-libev.default.j2 │ │ │ │ └── shadowsocks-libev.service.j2 │ │ │ └── vars/ │ │ │ ├── main.yml │ │ │ └── mirror.yml │ │ ├── ssh/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ ├── files/ │ │ │ │ └── sshd_config │ │ │ ├── handlers/ │ │ │ │ └── main.yml │ │ │ ├── tasks/ │ │ │ │ └── main.yml │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── ssh-forward/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ ├── tasks/ │ │ │ │ ├── docs.yml │ │ │ │ ├── main.yml │ │ │ │ └── mirror.yml │ │ │ ├── templates/ │ │ │ │ ├── instructions-fr.md.j2 │ │ │ │ ├── instructions.md.j2 │ │ │ │ ├── mirror-fr.md.j2 │ │ │ │ └── mirror.md.j2 │ │ │ └── vars/ │ │ │ ├── main.yml │ │ │ └── mirror.yml │ │ ├── sslh/ │ │ │ ├── handlers/ │ │ │ │ └── main.yml │ │ │ ├── tasks/ │ │ │ │ └── main.yml │ │ │ ├── templates/ │ │ │ │ ├── sslh.cfg.j2 │ │ │ │ ├── sslh.default.j2 │ │ │ │ └── sslh.service.j2 │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── streisand-gateway/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ ├── handlers/ │ │ │ │ └── main.yml │ │ │ ├── meta/ │ │ │ │ └── main.yml │ │ │ ├── tasks/ │ │ │ │ ├── docs.yml │ │ │ │ ├── fetch-and-cleanup.yml │ │ │ │ ├── main.yml │ │ │ │ └── openssl.yml │ │ │ ├── templates/ │ │ │ │ ├── firewall-fr.md.j2 │ │ │ │ ├── firewall.md.j2 │ │ │ │ ├── index-fr.md.j2 │ │ │ │ ├── index.md.j2 │ │ │ │ ├── instructions-fr.md.j2 │ │ │ │ ├── instructions.md.j2 │ │ │ │ ├── openssl-local.cnf.j2 │ │ │ │ └── vhost.j2 │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── streisand-mirror/ │ │ │ ├── tasks/ │ │ │ │ └── main.yml │ │ │ ├── templates/ │ │ │ │ ├── mirror-index-fr.md.j2 │ │ │ │ └── mirror-index.md.j2 │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── stunnel/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ ├── handlers/ │ │ │ │ └── main.yml │ │ │ ├── tasks/ │ │ │ │ ├── firewall.yml │ │ │ │ ├── main.yml │ │ │ │ └── mirror.yml │ │ │ ├── templates/ │ │ │ │ ├── mirror-fr.md.j2 │ │ │ │ ├── mirror.md.j2 │ │ │ │ ├── stunnel-local.conf.j2 │ │ │ │ ├── stunnel-remote.conf.j2 │ │ │ │ └── stunnel.service.j2 │ │ │ └── vars/ │ │ │ ├── main.yml │ │ │ └── mirror.yml │ │ ├── sysctl/ │ │ │ ├── tasks/ │ │ │ │ └── main.yml │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── test-client/ │ │ │ ├── files/ │ │ │ │ ├── openvpn_signing.key │ │ │ │ └── shadowsocks-qr-decode.py │ │ │ ├── tasks/ │ │ │ │ ├── main.yml │ │ │ │ ├── openconnect-profiletest.yml │ │ │ │ ├── openconnect.yml │ │ │ │ ├── openvpn-profileget.yml │ │ │ │ ├── openvpn-profiletest.yml │ │ │ │ ├── openvpn-test.yml │ │ │ │ ├── openvpn.yml │ │ │ │ ├── shadowsocks.yml │ │ │ │ ├── ssh-forward.yml │ │ │ │ ├── stunnel.yml │ │ │ │ ├── tor.yml │ │ │ │ ├── wireguard-profiletest.yml │ │ │ │ └── wireguard.yml │ │ │ ├── templates/ │ │ │ │ ├── obfs4.relay.client.torrc.j2 │ │ │ │ ├── openvpn-profile-addons.j2 │ │ │ │ ├── ssh-config.j2 │ │ │ │ ├── streisand-gateway-test.sh.j2 │ │ │ │ └── streisand-shadowsocks-forward-test.sh.j2 │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── tinyproxy/ │ │ │ ├── handlers/ │ │ │ │ └── main.yml │ │ │ ├── tasks/ │ │ │ │ └── main.yml │ │ │ ├── templates/ │ │ │ │ ├── tinyproxy.conf.j2 │ │ │ │ ├── tinyproxy.service.j2 │ │ │ │ └── tinyproxytmp.conf.j2 │ │ │ └── vars/ │ │ │ └── main.yml │ │ ├── tor-bridge/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ ├── files/ │ │ │ │ └── apparmor-local-override │ │ │ ├── handlers/ │ │ │ │ └── main.yml │ │ │ ├── meta/ │ │ │ │ └── main.yml │ │ │ ├── tasks/ │ │ │ │ ├── docs.yml │ │ │ │ ├── firewall.yml │ │ │ │ ├── main.yml │ │ │ │ ├── mirror-common.yml │ │ │ │ └── mirror.yml │ │ │ ├── templates/ │ │ │ │ ├── hidden-service-vhost.j2 │ │ │ │ ├── instructions-fr.md.j2 │ │ │ │ ├── instructions.md.j2 │ │ │ │ ├── mirror-fr.md.j2 │ │ │ │ ├── mirror.md.j2 │ │ │ │ └── torrc.j2 │ │ │ └── vars/ │ │ │ ├── main.yml │ │ │ ├── mirror-common.yml │ │ │ ├── mirror-download.yml │ │ │ └── mirror.yml │ │ ├── ufw/ │ │ │ └── tasks/ │ │ │ └── main.yml │ │ ├── validation/ │ │ │ ├── defaults/ │ │ │ │ └── main.yml │ │ │ └── tasks/ │ │ │ ├── main.yml │ │ │ └── ssh.yml │ │ └── wireguard/ │ │ ├── defaults/ │ │ │ └── main.yml │ │ ├── meta/ │ │ │ └── main.yml │ │ ├── tasks/ │ │ │ ├── docs.yml │ │ │ ├── firewall.yml │ │ │ ├── install.yml │ │ │ └── main.yml │ │ ├── templates/ │ │ │ ├── client-openwrt.txt.j2 │ │ │ ├── client.conf.j2 │ │ │ ├── instructions-fr.md.j2 │ │ │ ├── instructions.md.j2 │ │ │ ├── server.conf.j2 │ │ │ ├── streisand-wireguard-service.sh.j2 │ │ │ └── wireguard_dnsmasq.conf.j2 │ │ └── vars/ │ │ └── main.yml │ ├── ssh-setup.yml │ ├── streisand.yml │ ├── test-client.yml │ ├── vagrant.yml │ └── validate.yml ├── requirements.txt ├── streisand ├── tests/ │ ├── README.md │ ├── ansible.cfg │ ├── development-setup.yml │ ├── group_vars/ │ │ └── all/ │ │ └── all │ ├── inventory │ ├── randomize_sitevars.sh │ ├── remote_test.sh │ ├── run.yml │ ├── shellcheck.sh │ ├── site_vars/ │ │ ├── cloudflared.yml │ │ ├── openconnect.yml │ │ ├── openvpn.yml │ │ ├── random.yml │ │ ├── shadowsocks.yml │ │ └── ssh.yml │ ├── syntax-check.yml │ ├── tests.sh │ ├── vars_ci.yml │ ├── yamlcheck.sh │ └── yamllint-config.yml └── util/ ├── ansible_check.sh ├── dependencies.txt ├── print-aws-regions.py ├── source_check_and_default_site_vars.sh ├── source_validate_and_deploy.sh ├── ubuntu-dependencies.sh ├── venv-dependencies.sh └── version_at_least.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ ### Expected behavior: ### Actual Behavior: ### Steps to Reproduce: 1. [ contents of `streisand-diagnostics.md` here ] ### Additional Details: #### *Log output from Ansible or other relevant services (link to Gist for longer output):* ##### *Target Cloud Provider:* ##### *Operating System of target host:* ##### *Operating System of client:* ##### *Version of Ansible, using `ansible --version` :* ##### *Output from `git rev-parse HEAD` in your Streisand directory :* ================================================ FILE: .github/workflows/streisand.yml ================================================ --- name: Streisand on: [push, pull_request] jobs: deps: name: Dependencies runs-on: ubuntu-16.04 env: ANSIBLE_CONFIG: tests/ansible.cfg ANSIBLE_NOCOWS: true #ANSIBLE_VERBOSITY: 5 DEBIAN_FRONTEND: noninteractive strategy: #max-parallel: 5 fail-fast: false matrix: # 3.5 for older distro's like xenial, and 3.8 to test the latest python python-version: [3.5, 3.8] # Test ansible 2.8 and "latest" ansible-version: [">=2.8,<2.9", ">=2.9"] env_vars: - {RUN: "shellcheck yamlcheck syntax ci", SITE: "global_vars/default-site.yml"} - {RUN: "ci", SITE: "tests/site_vars/openconnect.yml"} - {RUN: "ci", SITE: "tests/site_vars/openvpn.yml"} - {RUN: "ci", SITE: "tests/site_vars/shadowsocks.yml"} - {RUN: "ci", SITE: "tests/site_vars/ssh.yml"} - {RUN: "ci", SITE: "random"} steps: # Removing dotnetdev.list, and microsoft-prod.list for now, as they seem to constantly have issues # with multiple github issues being opened in response. Removing shellcheck so we can install # a newer version from snap. - name: Remove apt lists and packages we dont need run: | sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list sudo rm -rf /var/lib/apt/lists/* sudo apt-get clean sudo apt-get update -qq sudo apt-get purge -yqq shellcheck sudo dpkg -l linux-{image,headers}-* | awk '/^ii/{print $2}' | egrep '[0-9]+\.[0-9]+\.[0-9]+' | grep -v $(uname -r | cut -d- -f-2) | xargs sudo apt-get -yqq purge sudo apt-get --purge autoremove -yqq - name: Update Yarn Debian key run: | curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - - name: Install additonal deps run: | sudo apt-get install ca-certificates sudo snap install --channel=edge shellcheck - uses: actions/checkout@v1 with: fetch-depth: 1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: fetch-depth: 1 python-version: ${{ matrix.python-version }} - name: Install additional pip dependencies run: | python -m pip install --upgrade pip pip install "ansible${{ matrix.ansible-version }}" pip install urllib3 yamllint ansible --version - name: Run tests env: ${{ matrix.env_vars }} run: ./tests/tests.sh ================================================ FILE: .gitignore ================================================ generated-docs *.retry *.pyc *~ *.swp *.tmp *.temp .DS_Store .vagrant/ ubuntu-xenial-16.04-cloudimg-console.log # Ignore changes to the existing server inventory to allow users to modify it inventories/inventory-existing # Ignore the Streisand diagnostics file we generate each run streisand-diagnostics.md # We recommend people use a Python virtualenv, and document this as # the location. venv/ ================================================ FILE: Advanced installation.md ================================================ # Advanced installation ### Running Streisand to Provision Localhost ### If you can't run Streisand in the normal manner (running from your client home machine/laptop to configure a remote server) Streisand supports a local provisioning mode for **machines running Ubuntu 16.04**. After following the basic instructions, choose "Localhost (Advanced)" from the menu after running `./streisand`. **Note:** Running Streisand against localhost can be a destructive action! You will be potentially overwriting configuration files and must be certain that you are affecting the correct machine. **Note:** The machine must be running Ubuntu 16.04. **Note:** If you have an external firewall available, see the `generated-docs` directory for a `-firewall-information.html` document with information on which ports need to be opened. ### Running Streisand on Other Providers ### You can also run Streisand on a new Ubuntu 16.04 server. Dedicated hardware? Great! Esoteric cloud provider? Awesome! To do so, simply choose "Existing Server (Advanced)" from the menu after running `./streisand` and provide the IP address of the existing server when prompted. The server must be accessible using the `$HOME/.ssh/id_rsa` SSH Key, and **root** is used as the connecting user by default. If your provider requires you to SSH with a different user than root (e.g. `ubuntu`) specify the `ANSIBLE_SSH_USER` environmental variable (e.g. `ANSIBLE_SSH_USER=ubuntu`) when you run `./streisand`. **Note:** Running Streisand against an existing server can be a destructive action! You will be potentially overwriting configuration files and must be certain that you are affecting the correct machine. **Note:** The machine must be running Ubuntu 16.04. **Note:** If you have an external firewall available, see the `generated-docs` directory for a `-firewall-information.html` document with information on which ports need to be opened. ### Noninteractive Deployment ### Alternative scripts and configuration file examples are provided for noninteractive deployment, in which all of the required information is passed on the command line or in a configuration file. Example configuration files are found under `global_vars/noninteractive`. Copy and edit the desired parameters, such as providing API tokens and other choices, and then run the appropriate script. To deploy a new Streisand server: deploy/streisand-new-cloud-server.sh \ --provider digitalocean \ --site-config global_vars/noninteractive/digitalocean-site.yml To run the Streisand provisioning on the local machine: deploy/streisand-local.sh \ --site-config global_vars/noninteractive/local-site.yml To run the Streisand provisioning against an existing server: deploy/streisand-existing-cloud-server.sh \ --ip-address 10.10.10.10 \ --ssh-user root \ --site-config global_vars/noninteractive/digitalocean-site.yml ================================================ FILE: CONTRIBUTING.md ================================================ ## Contributing Issues and Bug Reports Thanks for your interest in helping improve Streisand! This documentation page has some helpful tips and things to keep in mind to make your contributions to Streisand as great as they can be. ### Keep it Positive/Empathetic Encountering bugs is a frustrating experience! Missing features speak to unaddressed needs. We understand that and want to help. Please also remember that Streisand is 100% a volunteer effort. Nobody is paid to work on Streisand, and nobody does it as their full time job. Where possible try to keep discussion tone positive and remember that the folks working on Streisand fit it into their spare time between other real life commitments (family, vacation, jobs, hobbies, relaxation!). ### Which Repository? If your issue is a question or you need help with setting up / configuring Streisand, then please ask on the [streisand-discussions](https://github.com/StreisandEffect/streisand-discussions/) Github repository. Similarly, if you are requesting a new feature we prefer it be done on the [streisand-discussions](https://github.com/StreisandEffect/streisand-discussions) Github repository. ### Before Submitting an Issue Before submitting new issues please do a search in [open issues](https://github.com/StreisandEffect/streisand/issues) to see if the issue or refinement you have in mind has already been suggested. For features/questions search [the streisand-discussions open issues](https://github.com/StreisandEffect/streisand-discussions/issues). If you find your issue already exists, feel free to add constructive comments such as new information, additional insights on how to reproduce, etc. If you're only interested in sharing that the feature is important to you, or that the bug affects you then add a [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments). Using reactions in place of a "+1" or "me too!" comments helps keeps the conversation clear and concise. 👍 - upvote 👎 - downvote If you cannot find an existing issue that describes your bug or feature, submit a new issue using the guidelines below. ## Writing Good Bug Reports and Feature Requests Describe a single problem or feature request per issue. Do not include multiple bugs or feature requests in the same issue. The more information you can provide, the more likely someone will be successful reproducing the issue and finding a fix. #Take advantage of Github Markdown styling to make your issue easier to read. For example, Wrap single line code statements or logs in single backticks: \` code here \`. Wrap multi-line in triple backticks: \``` multi-line code here \```. #### Streisand Diagnostics File Streisand automatically creates a markdown file in the project directory called `streisand-diagnostics.md`. Every time you run Streisand this file is populated with some basic diagnostic information about your system & Streisand options that are enabled. Sharing the contents of this file in new issues is a great way to answer a lot of required questions without having to do any work besides copy & pasting! #### What Other Information to Include in Your Issue Please try your best to answer all of the questions in [the Streisand Issue template](https://github.com/StreisandEffect/streisand/blob/master/.github/ISSUE_TEMPLATE.md). Answering these questions help us skip a lot of "Github telephone tag" that takes up time that could be used fixing the bug. When describing your issue focus on: * Reproducible steps (1... 2... 3...) and what you expected versus what you actually saw. * Log output from Ansible or other relevant services, ideally linking to a Gist for longer log output. * Information about your system (OS, version, etc) * Information about the target Streisand server (cloud provider, etc) Don't feel bad if we can't reproduce the issue and ask for more information. It's impossible to know what questions will be most relevant for every potential bug! #### Why Was My Issue Closed??? Issues are generally closed for one of three reasons: 1. There wasn't enough information or nobody has been replying to questions asked. 2. The issue was created on the wrong repository and should have been created as a [streisand-discussions](https://github.com/StreisandEffect/streisand-discussions) issue or vice-versa. 3. After discussion and evaluation it was determined the problem was fixed or wasn't appropriate for Streisand We try very hard to keep the issue list clean. Don't feel bad if your issue was closed, it can always be reopened if there are disagreements to discuss or the situation has changed. ## Contributing Fixes If you are interested in fixing issues and contributing directly to the code base please feel free to submit a PR. We're excited to help! If there is an existing issue for the problem you are interested in fixing please leave a quick comment indicating that you'd like to start working on a solution. That way we can avoid duplicating work. Similarly, talking about how you plan to implement a fix or a new feature ahead of starting gives folks a chance to give early design input which will save time later during code review! #### CI Testing Streisand uses [Travis CI](https://travis-ci.org/jlund/streisand) for continuous integration testing. Make sure to read [testing.md](https://github.com/StreisandEffect/streisand/blob/master/documentation/testing.md) where we describe the tests that are run, how to work around breakages for features unsupported by LXC, and tips for developing on localhost with Vagrant. All PRs will have to pass CI to be merged. Feel free to submit a PR with broken CI if you need help getting the tests to pass. #### Code Reviews All PRs need at least one approved code review from a maintainer in order to be merged. Maintainers may not review their own pull requests. Streisand is a volunteer effort. Please allow at least 7 days to pass before following up on missing reviews. If more than 7 days have elapsed and you are still waiting on a review or a reply from a maintainer please leave a friendly "bump" message and "@ mention" their username. #### Modularity We've recently begun working on modularizing Streisand so that installations can be customized to include/not include certain VPN services. Additions to Streisand should endeavour to fit into this model. We describe the philosophy and design patterns used in this approach in [modular_roles.md](https://github.com/StreisandEffect/streisand/blob/master/documentation/modular_roles.md) ================================================ FILE: Features.md ================================================ # Features * A single command sets up a brand new Ubuntu 16.04 server running a [wide variety of anti-censorship software](#services-provided) that can completely mask and encrypt all of your Internet traffic. * Streisand natively supports the creation of new servers at [Amazon EC2](https://aws.amazon.com/ec2/), [Azure](https://azure.microsoft.com), [DigitalOcean](https://www.digitalocean.com/), [Google Compute Engine](https://cloud.google.com/compute/), [Linode](https://www.linode.com/), and [Rackspace](https://www.rackspace.com/)—with more providers coming soon! It also runs on any Ubuntu 16.04 server regardless of provider, and **hundreds** of instances can be configured simultaneously using this method. * The process is completely automated and only takes about ten minutes, which is pretty awesome when you consider that it would require the average system administrator several days of frustration to set up even a small subset of what Streisand offers in its out-of-the-box configuration. * Once your Streisand server is running, you can give the custom connection instructions to friends, family members, and fellow activists. The connection instructions contain an embedded copy of the server's unique SSL certificate, so you only have to send them a single file. * Each server is entirely self-contained and comes with absolutely everything that users need to get started, including cryptographically verified mirrors of all common clients. This renders any attempted censorship of default download locations completely ineffective. * But wait, there's more... More Features ------------- * Nginx powers a password-protected and encrypted Gateway that serves as the starting point for new users. The Gateway is accessible over SSL, or as a Tor [hidden service](https://www.torproject.org/docs/hidden-services.html.en). * Beautiful, custom, step-by-step client configuration instructions are generated for each new server that Streisand creates. Users can quickly access these instructions through any web browser. The instructions are responsive and look fantastic on mobile phones. * The integrity of mirrored software is ensured using SHA-256 checksums, or by verifying GPG signatures if the project provides them. This protects users from downloading corrupted files. * All ancillary files, such as OpenVPN configuration profiles, are also available via the Gateway. * Current Tor users can take advantage of the additional services Streisand sets up in order to transfer large files or to handle other traffic (e.g. BitTorrent) that isn't appropriate for the Tor network. * A unique password, SSL certificate, and SSL private key are generated for each Streisand Gateway. The Gateway instructions and certificate are transferred via SSH at the conclusion of Streisand's execution. * Distinct services and multiple daemons provide an enormous amount of flexibility. If one connection method gets blocked there are numerous options available, most of which are resistant to Deep Packet Inspection. * All of the connection methods (including direct OpenVPN connections) are effective against the type of blocking Turkey has been experimenting with. * OpenConnect/AnyConnect, OpenSSH, OpenVPN (wrapped in stunnel), Shadowsocks, Tor (with obfsproxy and the obfs4 pluggable transport), and WireGuard are all currently effective against China's Great Firewall. * Every task has been thoroughly documented and given a detailed description. Streisand is simultaneously the most complete HOWTO in existence for the setup of all of the software it installs, and also the antidote for ever having to do any of this by hand again. * All software runs on ports that have been deliberately chosen to make simplistic port blocking unrealistic without causing massive collateral damage. OpenVPN, for example, does not run on its default port of 1194, but instead uses port 636, the standard port for LDAP/SSL connections that are beloved by companies worldwide. ================================================ FILE: Installation.md ================================================ # Installation Please read all installation instructions **carefully** before proceeding. If you're an expert, and installing on a cloud provider Streisand doesn't support, make sure to read [the advanced installation instructions](Advanced%20installation.md). ## Important: definitions ## Streisand is based on [Ansible](https://www.ansible.com/), an automation tool that is typically used to provision and configure files and packages on remote servers. Streisand automatically sets up **another server** with the VPN packages and configuration. We call the machine that sets up the Streisand server the *builder*. Think of the builder as "a place to stand." * If you don't have a suitable builder machine, you could set up another cloud server to use as your builder. That means you'd have two cloud servers at the end — the builder, and your fresh new Streisand *server*. When you're done with the builder, make sure you download the *builder's* `streisand` directory — very important to keep the contents of that directory! — you could delete the *builder* cloud server.) * Although it's not recommended, sometimes you can use a fresh server as both the builder and the server. See the [the advanced installation instructions](Advanced%20installation.md). ## Prerequisites ## The Streisand builder requires a Linux, macOS, or BSD system. * Using native Windows as a builder is not supported, but Ubuntu on the [Windows Subsystem For Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/faq) should work. ([Ubuntu install link on Microsoft Store](https://www.microsoft.com/en-us/p/ubuntu-1804-lts/9n9tngvndl3q)) Complete all of these tasks on your local machine. All of the commands should be run inside a command-line session. ### SSH key Make sure an SSH public key is present in `~/.ssh/id_rsa.pub`. * SSH keys are a more secure alternative to passwords that allow you to prove your identity to a server or service built on public key cryptography. The public key is something that you can give to others, whereas the private key should be kept secret (like a password). To check if you already have an SSH public key, enter the following command at a command prompt: ``` ls ~/.ssh ``` If you see an `id_rsa.pub` file, then you have an SSH public key. If you do not have an SSH key pair, you can generate one by using this command and following the defaults: ``` ssh-keygen ``` If you'd like to use an SSH key with a different name or from a non-standard location, please enter *yes* when asked if you'd like to customize your instance during installation. * **Please note**: You will need these keys to access your Streisand instance over SSH. Please keep `~/.ssh/id_rsa` and `~/.ssh/id_rsa.pub` for the lifetime of the Streisand server. ## Bootstrap ## Install the bootstrap packages: Git, and Python 3.5 or later. Some environments need additional packages. Here's how to set up these packages: * On Debian and Ubuntu: ``` sudo apt-get install git python3 python3-venv ``` * On Fedora 30, some additional packages are needed later: ``` dnf install git python3 gcc python3-devel python3-crypto \ python3-pycurl libcurl-devel ``` * On CentOS 7, `python36` is available from the EPEL repository; some additional packages are needed later: ``` sudo yum -y update && sudo yum install -y epel-release sudo yum -y update && sudo yum install -y \ git gcc python36-devel python36-crypto python36-pycurl \ libcurl-devel ``` * On macOS, `git` is part of the Developer Tools, and it will be installed the first time you run it. Python 3.5 or later is required. Either use [Homebrew](https://brew.sh/) and run `brew install python3`, or see [the Python.org Mac download site](https://www.python.org/downloads/mac-osx/); the package you want to download is the "macOS 64-bit installer". ## Execution ## 1. Clone the Streisand repository and enter the directory. git clone https://github.com/StreisandEffect/streisand.git && cd streisand 1. Run the installer for Ansible and its dependencies. The installer will detect missing packages, and print the commands needed to install them. (Ignore the Python 2.7 `DEPRECATION` warning; ignore the warning from python-novaclient that pbr 5.1.3 is incompatible.) ./util/venv-dependencies.sh ./venv 1. Activate the Ansible packages that were installed. source ./venv/bin/activate 1. Execute the Streisand script. ./streisand 1. Follow the prompts to choose your provider, the physical region for the server, and its name. You will also be asked to enter API information. 1. Once login information and API keys are entered, Streisand will begin spinning up a new remote server. 1. Wait for the setup to complete (this usually takes around ten minutes) and look for the corresponding files in the `generated-docs` folder in the Streisand repository directory. The HTML file will explain how to connect to the Gateway over SSL, or via the Tor hidden service. All instructions, files, mirrored clients, and keys for the new server can then be found on the Gateway. You are all done! ## Keep the results! You should keep a copy of the `generated-docs` directory for the life of the server. Remember to save your `~/.ssh/id_rsa` and `~/.ssh/id_rsa.pub` SSH keys too. You'll need them in case you want to troubleshoot or perform maintenance on your server later. ================================================ FILE: LICENSE ================================================ Copyright 2014-2017 Joshua Lund OpenVPN role courtesy of Sovereign (https://github.com/al3x/sovereign) Copyright 2013-2014 Luke Cyca, Ben Ford, Greg Karékinian, Joshua Lund, and Alex Payne. L2TP/IPsec configuration files courtesy of Lin Song (https://github.com/hwdsl2/setup-ipsec-vpn). Copyright 2014-2017 Lin Song, and based on the work of Thomas Sarlandie (Copyright 2012). Modifications to the L2TP/IPsec configuration files are licensed under CC Attribution-ShareAlike 3.0 Unported (http://creativecommons.org/licenses/by-sa/3.0/). Cloudflared DNS-over-HTTPS role courtesy of Steven Foerster (https://github.com/sfoerster/ansible-cloudflared). Copyright 2019 Steven Foerster, and based on the work of Ben Dews (Copyright 2018). This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README-chs.md ================================================

Automate the effect

- - - [English](README.md), [Français](README-fr.md), [简体中文](README-chs.md), [Русский](README-ru.md) | [Mirror](https://gitlab.com/alimakki/streisand) - - - [![Build Status](https://github.com/StreisandEffect/streisand/workflows/Streisand/badge.svg)](https://github.com/StreisandEffect/streisand/actions) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/espadrine.svg?style=social&label=Follow%20%40StreisandVPN)](https://twitter.com/StreisandVPN) Streisand ========= **欲盖弥彰的[Streisand效应](https://zh.wikipedia.org/wiki/%E5%8F%B2%E7%BF%A0%E7%8F%8A%E6%95%88%E5%BA%94).** 互联网对我们并不公平。ISP、通信、政治家,他们串通一气封锁那些我们感兴趣和关注的网站和信息。或许是时候*打破枷锁*,来场正面较量了。 Streisand介绍 --------------------- * 只需要一个简单的脚本,就能在全新的 Ubuntu 16.04 服务器上运行[多个不同的科学上网工具](#提供的服务),它们能够让你匿名并且加密所有的网络流量。 * Streisand 原生支持多个 VPS 供应商,其中包括[亚马逊EC2](https://aws.amazon.com/ec2/),[微软云服务](https://azure.microsoft.com),[DigitalOcean](https://www.digitalocean.com/),[Google云计算](https://cloud.google.com/compute/),[Linode](https://www.linode.com/)和[Rackspace](https://www.rackspace.com/);随着软件的开发还将支持更多云和VPS——只要运行的是 Ubuntu 16.04 ,不论提供商是谁还是有**成百个**实例都能用这个方法部署。 * 整个部署过程顺利的话大概在10分钟左右搞定。试想一个没有系统管理能力的人可能要花数天来完成其中一项工作,而我们用 Streisand 让你获得获得开箱既得的畅快体验。 * 一旦部署完成,你可以将使用指南发送给你的朋友,家人和你觉得对你重要的人**(译者注:原文是社会活动家)**。在这个指南中包含唯一的一个 SSL 证书,这也意味着你发送给他们的只是一个简单的文件而已。 * 部署好网关中包含了用户需要的一切内容,例如设置向导,所支持操作系统需要的客户端。即使无法下载到官方客户端的朋友都可以在网关中的镜像里下载到需要的最新版本客户端。 * 这才是开始,来来来,看看后面更精彩... 更多特性 ------------- * 新用户登录时,使用 Nginx 提供密码保护和网关加密。加密网关通过 SSL 证书,或者通过 [Tor](https://www.torproject.org/docs/hidden-services.html.en)隐藏服务进行加密。 * 网关将自动生成客户端配置说明,架设在轻量级的 http 服务器 Nginx 上。而您可以用电脑或是手机的浏览器轻松阅读,只需按部就班就能轻松配置客户端了。 * 所有科学上网需要的客户端软件都进行了 SHA-256 检查,并且通过 GPG 进行了签名认证。确保那些无法通过官方渠道下载客户端的用户同样能够从镜像放心下载。 * 所有客户端需要的额外文件,比如:OpenVPN 的配置文件,都可以通过网关下载到。 * 目前 Tor 用户可以借助 Streisand 所提供的优秀特性传输大文件或者控制 Tor 服务其他流量(比如BT,传统的 Tor 并不适合进行BT这样的数据传输)。 * 网关会自动生成一个唯一的密码、一个 SSL 证书和一个 SSL 私钥。在Streisand部署后,网关说明和证书都通过 SSH 进行传输。 * 不同的服务和多个守护进程带来了巨大的灵活性。如果其中一个连接方式遭到封锁,还有其他方式可以尝试使用。它们大多数都能够避免深度包检测。 * OpenConnect/AnyConnect, OpenSSH(没有测试), OpenVPN (stunnel加持的), Shadowsocks, and Tor (obfs4进行混淆传输)都可以在中国使用 * 每一个科学上网工具都提供了文档和详细的描述。 Streisand 或许是迄今为止最为全面的指南,帮助你安装和配置客户端。在必要的时候也能够再次通过手动完成任何相关操作。 * 为了避免科学上网工具被大面积破坏,端口在设计上也是有讲究的。比方说 OpenVPN ,并没有运行在默认的1194端口,而是636端口,这个是很多跨国公司使用的标准 LDAP/SSL 连接端口。 提供的服务 ----------------- * [OpenSSH](https://www.openssh.com/) * 支持 Windows 和 Android 的 SSH 隧道, 并且需要使用 PuTTY 将默认的密钥对导出成 .ppk 的格式; * [Tinyproxy](https://banu.com/tinyproxy/) 默认安装并绑定到主机,它作为一个 http(s) 代理提供给那些原生不支持 SOCKS 代理的软件通过 SSH 隧道访问网络,比如说 Android 上的鸟嘀咕。 * 针对 [sshuttle](https://github.com/sshuttle/sshuttle) 的一个无特权转发用户和产生的 SSH 密钥对,同样也兼容 SOCKS; * [OpenConnect](https://ocserv.gitlab.io/www/index.html) / [Cisco AnyConnect](https://www.cisco.com/c/en/us/products/security/anyconnect-secure-mobility-client/index.html) * oepnConnect (ocserv) 是一个非常强劲、轻巧的 VPN 服务器,并且完全兼容思科的 AnyConnect 客户端; * 其中包涵了很多顶级的标准[协议](https://ocserv.gitlab.io/www/technical.html),比如:HTTP, TLS 和 DTLS, 当然还有很多被跨国公司广泛使用的且流行的技术; * 这就意味着 OpenConnect 非常易用且高速,而且经得住审查的考验,几乎从未被封锁。 * [OpenVPN](https://openvpn.net/index.php/open-source.html) * 从自带的 .ovpn 配置文件生成一个简单的客户端配置文件; * 同时支持 TCP 和 UDP 连接; * 客户端的 DNS 解析由 [Dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html) 负责,避免 DNS 泄露; * 启用 TLS 认证,有助于防止主动探测攻击。错误的 HMAC 流量并不会被轻易丢弃。 * [Shadowsocks](https://shadowsocks.org/en/index.html) * 安装的是高性能的 libev 版本,这个版本能够处理数以千计的并发连接; * Android 和 iOS 只需要扫描一个二维码就能完成自动配置。DNS 可以设置为 8.8.8.8,或者将配置一一复制粘贴到客户端上; * 采用 ChaCha20 和 Poly1305 对 [AEAD](https://shadowsocks.org/en/spec/AEAD-Ciphers.html) 进行加密,增强了安全性并提升了穿透性; * 使用 [simple-obfs](https://github.com/shadowsocks/simple-obfs) 插件提供流量混淆以便于从审查的网络中脱逃(尤其是QOS节流中)。 * [sslh](https://www.rutschle.net/tech/sslh/README.html) * sslh 是一个协议解复用器(这个我不了解,如果有更好的翻译请request),在一个高度限制的网络环境下(只能访问 http 端口的网络为例),它作为一种备选方案,仍然可以通过 OpenSSH 和 OpenVPN 进行连接,因为通过 sslh 让二者共享了 443 端口。 * [Stunnel](https://www.stunnel.org/index.html) * 监听并且将 OpenVPN 的流量进行封装,让 OpenVPN 的流量伪装成标注的 SSL 流量,从而可以让 OpenVPN 客户端成功通过隧道进行连接,躲避深度包检测。 * 通过隧道连接的 OpenVPN 的配置文件和直接连接 OpenVPN 的配置文件是一起生成的,详细的说明也一同生成。 * stunnel 证书和密钥是 PKCS #12 格式,SSL 隧道程序能够很好的兼容,尤其 [OpenVPN Android](https://play.google.com/store/apps/details?id=de.blinkt.openvpn) 版本也能够通过 [SSLDroid](https://play.google.com/store/apps/details?id=hu.blint.ssldroid) 传输。在中国的移动设备上使用 OpenVPN 成为可能*(据译者所知,大陆 OpenVPN 以前完全阵亡)*。 * [Tor](https://www.torproject.org/) * 桥接的名字是随即生成的; * [Obfsproxy](https://www.torproject.org/projects/obfsproxy.html.en) 默认安装并且配置支持 obfs4 传输; * Android 手机使用 [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) 扫描二维码即可获取桥接信息并完成自动配置。 * [UFW](https://wiki.ubuntu.com/UncomplicatedFirewall) * 防火墙根据不同的服务完全配置好,任何未经授权的流量都将被阻止。 * [系统无人值守安全更新](https://help.ubuntu.com/community/AutomaticSecurityUpdates) * Streisand 所在的服务器自动配置成无人值守更新,自动更新级别为*安全更新*。 * [WireGuard](https://www.wireguard.com/) * Linux 用户可以使用下一代更加简化,基于内核的黑科技 VPN ,速度快,而且使用了很多之前 VPN 所没有加密类型。 安装 ------------ 在你搞事情之前,认真阅读 ### 重要说明 ### Streisand 基于 [Ansible](https://www.ansible.com/) ,它可以在远程服务器完成自动配置、打包等工作,Streisand 是将远程服务器自动配置成为多个 VPN 服务及科学上网的工具。 Streisand 运行在**你自己的计算机上时(或者你电脑的虚拟机上时)**,它将把网关部署到你 VPS 提供商的**另一个服务器**上(通过你自己的API自动生成)。另外,如果 Streisand 运行在 VPS 上,它将会把网关部署到**另一个 VPS 上**,所以说原先你运行 Streisand 的那个 VPS 就多余了,记得部署完成并获得文档后把它删掉,而部署出来的那个 VPS 你是无法使用 SSH 连接进去的,除非你有公钥(当然这是不可能的,因为整个配置过程都没有提供公钥给你下载或者你想办法把它搞出来)。 在某些情况下,你可能需要运行 Streisand/Ansible 在 VPS 上并将其自身配置为 Streisand 服务器,这种模式适合当你无法在你自己的计算机上运行和安装 Streisand/Ansible 时,或本地与 VPS 之间的 SSH 连接不稳定时。 ### 准备工作 ### 在本地计算机完成以下所有步骤(也可以在 VPS 上运行)。 * Streisand 运行在 BSD,Linux,或者 macOS 上,Windows 上是无法运行的。所有的指令都需要在终端下运行。 * 需要 Python 2.7 ,一般在 macOS 、Linux 及 BSD 系统上都是标配,如果你使用的发行版标配是 Python 3,你需要安装 Python 2.7 来运行 Streisand。 * 确定你的 SSH key 储存在 ~/.ssh/id\_rsa.pub 。 * 如果不曾有过 SSH key,你需要用下面的命令生成一个: ssh-keygen * 安装 Git 。 * 基于 Debian 和 Ubuntu 的 Linux 发行版 sudo apt install git * 在 Fedora sudo dnf install git * 在 macOS 上 (需要通过 [Homebrew](https://brew.sh/) 进行安装) brew install git * 利用 Python 安装 [pip](https://pip.pypa.io/en/latest/) 包管理 * 基于 Debian 和 Ubuntu 的 Linux 发行版 sudo apt install python-paramiko python-pip python-pycurl python-dev build-essential * 在 Fedora sudo dnf install python-pip * 在 macOS 上 sudo easy_install pip sudo pip install pycurl * 安装 [Ansible](https://www.ansible.com/) 。 * 在 macOS 上 brew install ansible * 在 Linux 和 其他 BSD 上 sudo pip install ansible markupsafe * 以下使用 pip 安装的 Python 库根据你所使用的 VPS 供应商不同而不同。如果你准备将目前使用的 VPS 变成网关,可以跳过此步。 * 亚马逊 EC2 sudo pip install boto boto3 * 微软云服务 sudo pip install ansible[azure] * Google sudo pip install "apache-libcloud>=1.17.0" * Linode sudo pip install linode-api4 * Rackspace 云 sudo pip install pyrax * **特别需要注意的是如果你使用 Python** 是通过 Homebrew 安装的,还需要运行以下命令来确定找到必要的库文件 mkdir -p ~/Library/Python/2.7/lib/python/site-packages echo '/usr/local/lib/python2.7/site-packages' > ~/Library/Python/2.7/lib/python/site-packages/homebrew.pth ### 执行 ### 1. 从 Streisand 抓取源码 git clone https://github.com/StreisandEffect/streisand.git && cd streisand 2. 执行 Streisand 脚本。 ./streisand 3. 根据实际情况从弹出的问题中填写或选择选项,比如服务器的物理位置,它的名字。还有最重要的是 API 信息(可以从问题提示中找到如何提供 API 信息)。 4. 一旦登录信息和 API key 正确无误完成,Streisand 就开始安装到另一个 VPS 上了(或者把你目前的 VPS 变成网关)。 5. 整个配置完成大概需要10分钟左右的样子,完成后,在 Streisand 目录下会生成一个 'generated-docs' 文件夹,里面储存了4个 html 文件,其中包含了网关的 SSL 证书和如何连接的详细说明。当你使用这些方法连接上网关以后,网关里详细描述了设置客户端的方法、需要额外下载的文件,客户端镜像,密钥,只要耐心配置客户端就一切搞定了。 **译者注:到这里官方英文配置就告一段落了。译者在自己配置的过程中还遇到一些小麻烦,需要各位朋友注意。** * 从本地用 Streisand 安装到网关的模式,如果能用这种模式最好,就不要选择其他的模式了,因为这样他生成的 generated-docs 就在本地,你用浏览器打开就能直接下载到证书文件,不折腾。 * 在 VPS 上用 Streisand 安装到新的 VPS 模式还有后面介绍的将正在运行的 VPS 转变为网关这种模式,你会发现你很难在 VPS 上阅读 generated-docs 文件夹中的4个 html 文档,这个时候有几种方法可选: * 使用 sftp 下载文件; * 在目前的 VPS 上安装一个 apache2 ,然后 cp -r generated-doc /var/www/html/ ,然后从浏览器输入 VPS 的地址直接浏览并下载密钥(严格地说,这不安全,因为不是 https 连接,如果截获了数据便可以知道如何登录你科学上网的那个网关了。**如果是使用转化那个模式,就不要用这种方法了**)。 * 在 VPS 上使用 scp 将 generated-docs 目录全部推送到你本地暴露在公网下的 Linux, Unix 或者 macOS 里,或者另一个 VPS 里也可以。命令大概是 scp -r generated-docs user@×××.×××.×××.×××:/home/user/ ### 将正在使用中的 VPS 变成 Streisand 服务器 (高级使用) ### 如果你本地使用的计算机无法运行 Streisand ,你可以将正在运行的 VPS 转变为网关。只需要在 VPS 上运行 ./streisand 并在菜单中选择 "Localhost (Advanced)" 就可以了。 **但是需要注意的是**这个操作是无法挽回的,它将把你正在使用的 VPS 完全转变为网关后,之前如果你在上面搭建博客或者用于测试某些软件,那完成这个操作后,它们将不复存在。 ### 在其他的 VPS 供应商上运行 (高级使用)### 你同样可以将 Streisand 运行在其他 VPS 供应商(提供更好的硬件也没问题,奇葩的 VPS 供应商也行)的 16.04 Ubuntu 上,只需要你在运行 ./streisand 的时候选择菜单中的 "Existing Server (Advanced)" 就可以。你需要提供这个 VPS 的 IP 地址。 这个 VPS 必须使用 `$HOME/.ssh/id_rsa` 来储存 SSH key,并且可以使用 **root** 作为默认用户登录 VPS,如果提供商没有给你 root 用户作为默认用户登录,而是别的用户名,比如:`ubuntu` ,那么在运行 `./streisand` 之前需要额外配置 `ANSIBLE_SSH_USER` 环境变量,比如修改为:`ANSIBLE_SSH_USER=ubuntu` 。 ### 非交互式部署 (高级使用)### 如果你想做非交互式部署, 你可以在 `global_vars/noninteractive`找到配置文件和脚本文件。你需要在配置文件或命令行录入必要信息。 将 Streisand 在 VPS 供应商上运行: deploy/streisand-new-cloud-server.sh \ --provider digitalocean \ --site-config global_vars/noninteractive/digitalocean-site.yml 将 Streisand 在正在使用中的服务器上运行: deploy/streisand-local.sh \ --site-config global_vars/noninteractive/local-site.yml 将 Streisand 在已现有的服务器上运行 : deploy/streisand-existing-cloud-server.sh \ --ip-address 10.10.10.10 \ --ssh-user root \ --site-config global_vars/noninteractive/digitalocean-site.yml 未来特性 ----------------- * 更简便的设置 如果你对 Streisand 有任何期待和想法,或者你找到 BUG ,请联系我们并且发 [Issue Tracker](https://github.com/StreisandEffect/streisand/issues) 。 核心的贡献者们 ---------------- * Jay Carlson (@nopdotcom) * Nick Clarke (@nickolasclarke) * Joshua Lund (@jlund) * Ali Makki (@alimakki) * Daniel McCarney (@cpu) * Corban Raun (@CorbanR) 相关知识 ---------------- [Jason A. Donenfeld](https://www.zx2c4.com/) 凭借他的智慧和果敢重新设计一个现代的 VPN,就像我们看到的 [WireGuard](https://www.wireguard.com/) 。他非常耐心和认真的给予帮助并提供了优质的反馈,在此,向他表示我由衷的感谢。 对 [Trevor Smith](https://github.com/trevorsmith) 在项目上的付出,简直无法形容,非常感谢他,正是他提出了网关的提案,提供了数不胜数反馈,在公布前灵机一动,创建了 html 模板,让现在的**一切看上去那么屌**。 非常感谢 [Paul Wouters](https://nohats.ca/) 的 [The Libreswan Project](https://libreswan.org/) ,正是他的耐心调试和设置,才让 L2TP/IPsec 工作的那么好。 另外,[Joshua Lund](https://github.com/jlund)开始这个项目工作的时候,他差不多把 [Starcadian's](https://www.starcadian.com/) 的 'Sunset Blood' 听了300遍(译者:这张专辑节奏感不错)。 ================================================ FILE: README-fr.md ================================================

Automate the effect

- - - [English](README.md), [Français](README-fr.md), [简体中文](README-chs.md), [Русский](README-ru.md) | [Miroir](https://gitlab.com/alimakki/streisand) - - - [![Build Status](https://github.com/StreisandEffect/streisand/workflows/Streisand/badge.svg)](https://github.com/StreisandEffect/streisand/actions) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/espadrine.svg?style=social&label=Follow%20%40StreisandVPN)](https://twitter.com/StreisandVPN) Streisand ========= **Silence la censure. Automatiser [l'effet](https://fr.wikipedia.org/wiki/Effet_Streisand).** L'Internet peut être un peu injuste. Il est trop facile pour les fournisseurs de services Internet, les télécoms, les politiciens et les entreprises de bloquer l'accès aux sites et aux informations qui vous intéressent. Mais briser ces restrictions est *difficile*. Ou est-ce? Présentation de Streisand ------------------------- * Une seule commande configure un tout nouveau serveur Ubuntu 16.04 exécutant une [grande variété de logiciels anti-censure](#services-provided) qui peuvent masquer et chiffrer totalement votre trafic Internet. * Streisand supporte nativement la création de nouveaux serveurs chez [Amazon EC2](https://aws.amazon.com/ec2/), [Azure](https://azure.microsoft.com/fr-fr/), [DigitalOcean](https://www.digitalocean.com/), [Google Compute Engine](Https://cloud.google.com/compute/), [Linode](https://www.linode.com/) et [Rackspace](https://www.rackspace.com/)— et plus de fournisseurs à venir! Il fonctionne également sur n'importe quel serveur Ubuntu 16.04 quel que soit le fournisseur, et des **centaines** d'instances peuvent être configurés simultanément en utilisant cette méthode. * Le processus est entièrement automatisé et ne prend que quelques dizaines de minutes, ce qui est assez remarquable si vous considérez qu'il faudrait un administrateur système au moins plusieurs jours de contrainte pour mettre en place un petit sous-ensemble de ce que Streisand offre dans sa configuration. * Une fois que votre serveur Streisand est en cours d'exécution, vous pouvez donner les instructions de connexion personnalisée à vos amis, membres de la famille et activistes. Les instructions de connexion contiennent une copie intégrée du certificat SSL unique du serveur, il vous suffit de leur envoyer un seul fichier. * Chaque serveur est entièrement autonome et comprend tout ce dont les utilisateurs ont besoin pour démarrer, y compris les miroirs cryptographiquement vérifiés de tous les clients communs. Cela rend toute tentative de censure des emplacements de téléchargement par défaut complètement inefficace. * Mais attendez, il y a plus.. Plus de fonctionnalités ----------------------- * Nginx alimente la passerelle protégée par mot de passe et chiffrée qui sert de point de départ pour les nouveaux utilisateurs. La passerelle est accessible via SSL, ou comme [service caché](https://www.torproject.org/docs/hidden-services.html.en) Tor. * Instructions belles, personnalisées, et étape par étape, les configurations du client sont générées pour chaque nouveau serveur que Streisand crée. Les utilisateurs peuvent accéder rapidement à ces instructions via n'importe quel navigateur Web. Les instructions sont réactives et fantastiques sur les téléphones mobiles. * L'intégrité du logiciel mis en miroir est assurée en utilisant les sommes de contrôle SHA-256 ou en vérifiant les signatures GPG si le projet les fournit. Cela protège les utilisateurs contre le téléchargement de fichiers corrompus. * Tous les fichiers auxiliaires, tels que les profils de configuration OpenVPN, sont également disponibles via la passerelle. * Les utilisateurs actuels de Tor peuvent profiter des services supplémentaires que Streisand met en place pour transférer des fichiers volumineux ou pour traiter d'autres types de trafic (par exemple BitTorrent) qui ne sont pas appropriés pour le réseau Tor. * Un mot de passe unique, un certificat SSL et une clé privée SSL sont générés pour chaque passerelle Streisand. Les instructions de la passerelle et le certificat sont transférés via SSH à la fin de l'exécution de Streisand. * Des services distincts et des daemons multiples offrent une énorme flexibilité. Si une méthode de connexion est bloquée, il existe de nombreuses options disponibles, dont la plupart sont résistantes à l'inspection des paquets en profondeur. * Toutes les méthodes de connexion (y compris les connexions directes OpenVPN) sont efficaces contre le type de blocage avec lequel la Turquie a expérimenté. * OpenConnect/AnyConnect, OpenSSH, OpenVPN (enveloppé dans stunnel), Shadowsocks, Tor (avec obfsproxy et obfs4 transports enfichables), et WireGuard sont tous actuellement efficace contre le grand pare-feu de la Chine. * Chaque tâche a été bien documentée et a donné une description détaillée. Streisand est simultanément le HOWTO le plus complet en existance pour l'installation de tous les logiciels qu'il installe, et aussi l'antidote pour avoir à faire jamais tout cela à la main de nouveau. * Tous les logiciels fonctionnent sur des ports qui ont été délibérément choisis pour rendre le blocage de ports simpliste irréaliste sans causer de dommages collatéraux massifs. OpenVPN, par exemple, ne fonctionne pas sur le port défaut de 1194, mais utilise le port standard 636 pour les connexions LDAP/SSL qui sont aimés par des entreprises du monde entier. Services fournis ---------------- * [OpenSSH](https://www.openssh.com/) * Les tunnels Windows et Android SSH sont également pris en charge et une copie des clés sont exportée dans le format .ppk que PuTTY requiert. * [Tinyproxy](https://banu.com/tinyproxy/) est installé et lié à localhost. Il peut être accédé sur un tunnel SSH par des programmes qui ne prennent pas en charge nativement SOCKS et qui nécessitent un proxy HTTP, comme Twitter pour Android. * Un utilisateur de transfert non privilégié et une paire paire de clés asymétriques SSH sont générés pour les fonctionnalités [sshuttle](https://github.com/sshuttle/sshuttle) et SOCKS. * [OpenConnect](https://ocserv.gitlab.io/www/index.html)/[Cisco AnyConnect](https://www.cisco.com/c/en/us/products/security/anyconnect-secure-mobility-client/index.html) * OpenConnect (ocserv) est un serveur VPN extrêmement performant et léger qui offre également une compatibilité totale avec les clients officiels Cisco AnyConnect. * Le [protocole](https://ocserv.gitlab.io/www/technical.html) est bâti sur des standards comme HTTP, TLS et DTLS, et c'est l'une des technologies VPN les plus populaires et largement utilisées parmi des grands entreprises multi-nationales. * Cela signifie qu'en plus de sa facilité d'utilisation et de sa rapidité, OpenConnect est également très résistant à la censure et presque jamais bloqué. * [OpenVPN](https://openvpn.net/index.php/open-source.html) * Des profils autonome "unifiés" .ovpn sont générés pour une configuration de client facile en utilisant un seul fichier. * Les connexions TCP et UDP sont prises en charge. * La résolution DNS du client est gérée via [Dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html) pour empêcher les fuites DNS. * L'authentification TLS est activée, ce qui permet de vous protéger contre les attaques actives. Le trafic qui n'a pas de HMAC approprié est simplement abandonné. * [Shadowsocks](https://shadowsocks.org/en/index.html) * La [variante libev](https://github.com/shadowsocks/shadowsocks-libev) haute performance est installée. Cette version est capable de gérer des milliers de connexions simultanées. * Un code QR est généré qui peut être utilisé pour configurer automatiquement les clients Android et iOS en prenant simplement une photo. Vous pouvez étiqueter "8.8.8.8" sur ce mur de béton, ou vous pouvez coller les instructions de Shadowsocks et quelques codes QR à la place! * [AEAD](https://shadowsocks.org/fr/spec/AEAD-Ciphers.html) est activé avec ChaCha20 et Poly1305 pour un contournement plus efficace du GFW. * Le plugin [simple-obfs](https://github.com/shadowsocks/simple-obfs) est installé afin de fournir une techique d'évasion du votre trafic sur des réseaux hostiles (en particulier ceux qui appliquent la limitation de la qualité de service (QOS)). * [sslh](https://www.rutschle.net/tech/sslh/README.html) * Sslh est un démultiplexeur de protocole qui permet à Nginx, OpenSSH et OpenVPN de partager le port 443. Cela fournit une autre option de connexion et signifie que vous pouvez toujours acheminer le trafic via OpenSSH et OpenVPN même si vous êtes sur un réseau restrictif qui bloque tout accès à des ports non HTTP. * [Stunnel](https://www.stunnel.org/index.html) * Écoute et enveloppe les connexions OpenVPN. Cela les fait ressembler au trafic SSL standard et permet aux clients OpenVPN d'établir avec succès des tunnels même en présence d'une inspection approfondie des paquets. * Des profils unifiés pour les connexions OpenVPN enveloppées de stunnel sont générés à côté des profils de connexion directe. Des instructions détaillées sont également générées. * Le certificat stunnel et la clé sont exportés au format PKCS # 12 afin qu'ils soient compatibles avec d'autres applications de tunneling SSL. Notamment, cela permet à [OpenVPN pour Android](https://play.google.com/store/apps/details?id=de.blinkt.openvpn&hl=fr) de tunneliser son trafic via [SSLDroid](https://play.google .com / store / apps / details? Id = hu.blint.ssldroid). OpenVPN en Chine sur un appareil mobile? Oui! * [Tor](https://www.torproject.org/) * Un [pont relais](https://www.torproject.org/docs/bridges) est mis en place avec un surnom aléatoire. * [Obfsproxy](https://www.torproject.org/projects/obfsproxy.html.en) est installé et configuré avec le support pour le transport enfichable obfs4. * Un code BridgeQR est généré et peut être utilisé pour configurer automatiquement [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android&hl=fr) pour Android. * [UFW](https://wiki.ubuntu.com/UncomplicatedFirewall) * Les règles de pare-feu sont configurées pour chaque service et tout trafic qui est envoyé vers un port non autorisé sera bloqué. * [unattended-upgrades](https://help.ubuntu.com/community/AutomaticSecurityUpdates) * Votre serveur Streisand est configuré pour installer automatiquement de nouvelles mises à jour de sécurité. * [WireGuard](https://www.wireguard.com/) * Les utilisateurs Linux peuvent profiter d'un VPN de nouvelle génération qui est simple mais à la même fois moderne, basé dans le noyau, et utilise des principes cryptographiques modernes que toutes les autres solutions VPN ne fournissent pas. Installation ------------ Veuillez lire *attentivement* toutes les instructions d'installation avant de poursuivre. ### Clarification importante ### Streisand est basé sur [Ansible](https://www.ansible.com/), un outil d'automatisation qui est généralement utilisé pour fournir et configurer des fichiers et des paquets sur des serveurs distants. Cela signifie que lorsque vous exécutez Streisand, il configure automatiquement **un autre serveur distant** avec les paquets VPN et ses configurations. Streisand va déployer **un autre serveur** sur votre fournisseur d'hébergement choisi lorsque vous exécutez **sur votre ordinateur local** (par exemple, votre ordinateur portable). Habituellement, vous **n'utilisez pas Streisand sur le serveur distant** car, par défaut, cela entraînerait le déploiement d'un autre serveur à partir de votre serveur et rendrait le premier serveur redondant (Ouf!). Dans certains cas, les utilisateurs avancés peuvent opter pour le mode de provisionnement local pour que le système fonctionne avec Streisand/Ansible se configure comme un serveur Streisand. Il s'agit d'un mode de configuration mieux réservé quand ce n'est pas possible d'installer Ansible sur votre ordinateur local ou lorsque votre connexion à un fournisseur de cloud est peu fiable pour les connexions SSH requis par Ansible. ### Prérequis ### Effectuez toutes ces tâches sur votre machine locale. * Streisand nécessite un système BSD, Linux ou macOS. À partir de maintenant, Windows n'est pas soutenu. Toutes les commandes suivantes doivent être exécutées à l'intérieur d'une session Terminal. * Python 2.7 est nécessaire. Cela est standard sur macOS, et est la valeur par défaut sur presque toutes les distributions Linux et BSD. Si votre distribution emploie Python 3 à la place, vous devrez installer la version 2.7 pour que Streisand fonctionne correctement. * Assurez-vous qu'une clé publique SSH est présente dans ~/.ssh/id\_rsa.pub. * Les clés SSH constituent une alternative plus sécurisé aux mots de passe qui vous permettent de prouver votre identité à un serveur ou à un service basé sur la cryptographie à clé publique. Fondamentalement, la clé publique est quelque chose que vous pouvez partager aux autres, alors que la clé privée doit être gardée secrète (comme un mot de passe). * Pour vérifier si vous avez déjà une clé publique SSH, entrez la commande suivante à l'invite de commande. ls ~/.ssh * Si vous voyez un fichier id_rsa.pub, vous avez une clé publique SSH. * Si vous n'avez pas de paire de clés SSH, vous pouvez en générer une en utilisant cette commande et en suivant les valeurs par défaut: ssh-keygen * Si vous souhaitez utiliser une clé SSH avec un nom différent ou dans un emplacement non standard, veuillez entrer 'oui' lorsqu'on vous demande si vous souhaitez personnaliser votre instance lors de l'installation. * **Notez**: Vous aurez besoin de ces clés pour accéder à votre instance Streisand via SSH. Conservez-les pour la durée de vie de votre serveur Streisand. * Installez Git. * Sur Debian et Ubuntu sudo apt-get install git * Sur Fedora 27, certains progiciels sont nécessaires plus tard sudo dnf install git python2-pip gcc python2-devel python2-crypto python2-pycurl libcurl-devel * Sur CentOS 7, `pip` est disponible dans le dépôt EPEL; certains progiciels supplémentaires sont nécessaires plus tard. sudo yum -y update && sudo yum install -y epel-release sudo yum -y update && sudo yum install -y git gcc python-devel python-crypto python-pycurl python-pip libcurl-devel * Sur macOS, `git` fait partie des outils de développement et sera installé la première fois que vous l'exécuterez. S'il n'y a pas déjà une commande `pip` installée, installez-la avec: sudo python2.7 -m ensurepip ### Exécution ### 1. Clonez le répertoire Streisand et entrez dans le répertoire. git clone https://github.com/StreisandEffect/streisand.git && cd streisand 2. Exécutez le programme d'installation pour Ansible et ses dépendances. ./util/venv-dependencies.sh ./venv * Le programme d'installation détectera les progiciels manquants et imprimera les commandes nécessaires pour les installer. 3. Activez les progciels Ansible installés. source ./venv/bin/activate 4. Exécutez le script Streisand. ./streisand 5. Suivez les instructions pour choisir votre fournisseur, la région physique du serveur, et son nom. Vous serez également invité à entrer les informations de l'API. 6. Une fois les informations de connexion et les clés d'API saisies, Streisand commencera à faire tourner un nouveau serveur distant. 5. Attendez que l'installation soit terminée (cela prend habituellement environ dix minutes) et recherchez les fichiers correspondants dans le dossier 'generated-docs' dans le répertoire du dépôt Streisand. Le fichier HTML expliquera comment se connecter à la passerelle via SSL ou via le service caché Tor. Toutes les instructions, les fichiers, les clients en miroir et les clés du nouveau serveur se trouvent alors sur la passerelle. Vous avez fini! ### Installation de Streisand sur localhost (Avancé) ### Si vous ne pouvez pas exécuter Streisand de la manière normale (à partir de votre ordinateur client/ordinateur portable pour configurer un serveur distant), Streisand prend en charge un mode de provisionnement local. Choisissez simplement "Localhost (Advanced)" dans le menu après avoir exécuté `./streisand`. **Note:** L'installation de Streisand sur localhost peut être une action destructive! Vous pourriez potentiellement écraser des fichiers de configuration; vous devez être certain que vous affectez la machine correcte. ### Exécution de Streisand sur d'autres fournisseurs (Avancé) ### Vous pouvez également exécuter Streisand sur un nouveau serveur Ubuntu 16.04. Serveur dédié? Génial! Fournisseur de cloud ésotérique? Fantastique! Pour ce faire, choisissez simplement `Existing server (Advanced)` dans le menu après avoir exécuté `./streisand` et fournissez l'adresse IP du serveur existant lorsque vous y êtes invité. Le serveur doit être accessible en utilisant la clé SSH `$HOME/.ssh/id_rsa`, avec **root** comme utilisateur de connexion par défaut. Si votre fournisseur vous demande un utilisateur SSH au lieu de `root` (par exemple, `ubuntu`), spécifiez la variable environnementale `ANSIBLE_SSH_USER` (par exemple `ANSIBLE_SSH_USER=ubuntu`) lorsque vous exécutez `./streisand`. **Note:** L'installation de Streisand sur localhost peut être une action destructive! Vous pourriez potentiellement écraser des fichiers de configuration; vous devez être certain que vous affectez la machine correcte. ### Déploiement non interactif (Avancé) ### Des scripts alternatifs et des exemples de fichiers de configuration sont fournis pour les déploiements non interactifs, dans lequel toutes les informations requises sont transmises sur la ligne de commande ou dans un fichier de configuration. Des exemples de fichiers de configuration se trouvent sous `global_vars/noninteractive`. Copiez et modifiez les paramètres souhaités, tels que la fourniture de jetons d'API et d'autres choix, puis exécutez le script. Pour déployer un nouveau serveur Streisand: deploy/streisand-new-cloud-server.sh \ --provider digitalocean \ --site-config global_vars/noninteractive/digitalocean-site.yml Pour exécuter l'approvisionnement Streisand sur la machine locale: deploy/streisand-local.sh \ --site-config global_vars/noninteractive/local-site.yml Pour exécuter l'approvisionnement Streisand contre un serveur existant: deploy/streisand-existing-cloud-server.sh \ --ip-address 10.10.10.10 \ --ssh-user root \ --site-config global_vars/noninteractive/digitalocean-site.yml Nouvelles fonctionnalités à venir --------------------------------- * Configuration simplifiée. S'il ya quelque chose que vous pensez que Streisand devrait faire, ou si vous trouviez un bug dans sa documentation ou son exécution, s'il vous plaît déposer un rapport sur le [Issue Tracker](https://github.com/StreisandEffect/streisand/issues). Contributeurs principaux ------------------------ * Jay Carlson (@nopdotcom) * Nick Clarke (@nickolasclarke) * Joshua Lund (@jlund) * Ali Makki (@alimakki) * Daniel McCarney (@cpu) * Corban Raun (@CorbanR) Remerciements ------------- [Jason A. Donenfeld](https://www.zx2c4.com/) mérite beaucoup de crédit pour être assez courageux à réimaginer ce qu'est un VPN moderne devrait ressembler et pour mettre au monde quelque chose aussi épatant que [WireGuard](https://www.wireguard.com/). Il a nos sincères remerciements pour toute son aide, patience et ses commentaires. [Corban Raun](https://github.com/CorbanR) à eu la gentillesse de me prêter un ordinateur portable Windows qui m'a permi de tester et d'affiner les instructions pour cette plate-forme, aussi bien qu'il était un grand partisan du projet dès le début. Nous sommes reconnaissants à [Trevor Smith](https://github.com/trevorsmith) pour ses contributions massives au projet. Il a suggéré l'approche passerelle, fourni des tonnes de commentaires inestimable, a fait *tout* pour apparaître mieux, et développé le modèle HTML qui a servi d'inspiration pour faire passer les choses au niveau suivant avant la diffusion publique de Streisand. J'ai également apprécié l'utilisation fréquente de son iPhone tout en testant des clients divers. Un grand merci à [Paul Wouters](https://nohats.ca/) de [The Libreswan Projet](https://libreswan.org/) à son aide généreuse pour le débogage des configurations d'L2TP/IPsec. L'album 'Sunset Blood' de [Starcadian](https://starcadian.com/) a été répété environ 300 fois au cours des premiers mois de travail sur le projet au début de 2014. ================================================ FILE: README-ru.md ================================================

Automate the effect

- - - [English](README.md), [Français](README-fr.md), [简体中文](README-chs.md), [Русский](README-ru.md) | [Зеркало](https://gitlab.com/alimakki/streisand) - - - [![Build Status](https://github.com/StreisandEffect/streisand/workflows/Streisand/badge.svg)](https://github.com/StreisandEffect/streisand/actions) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/espadrine.svg?style=social&label=Follow%20%40StreisandVPN)](https://twitter.com/StreisandVPN) Стрейзанд ========= **Заставьте цензуру замолчать. Автоматизируйте [эффект Стрейзанд](https://ru.wikipedia.org/wiki/Эффект_Стрейзанд).** Интернет может быть немножечко несправедливым. Провайдерам, телекоммуникационным гигантам, политикам и корпорациям слишком просто блокировать доступ к сайтам и информации, которая важна для вас. Но преодолеть эти ограничения сложно. Или нет? Представляем Стрейзанд --------------------- * Одна-единственная команда настраивает с нуля сервер под операционной системой Ubuntu 16.04 с большим набором [ПО для противодействия цензуре](#services-provided), который может полностью скрыть и зашифровать весь ваш трафик. * Стрейзанд поддерживает создание новых серверов в [Amazon EC2](https://aws.amazon.com/ec2/), [Azure](https://azure.microsoft.com), [DigitalOcean](https://www.digitalocean.com/), [Google Compute Engine](https://cloud.google.com/compute/), [Linode](https://www.linode.com/), и [Rackspace](https://www.rackspace.com/). В скором времени ожидается поддержка также и других облачных хостеров. Стрейзанд также можно запускать на любом сервере с операционной системой Ubuntu 16.04 вне зависимости от хостера, и **сотни** серверов могут быть одновременно сконфигурированы с применением этого метода. * Процесс полностью автоматизирован и занимает примерно десять минут, что довольно круто, учитывая что среднему системному администратору требуется несколько дней возни, для того, чтобы настроить малую часть того, что Стрейзанд предлагает "из коробки". * После того, как ваш сервер Стрейзанд запущен, вы можете раздать инструкции по подключению друзьям, членам семьи и соратникам. Инструкции по подключению содержат в себе копию SSL-сертификата, уникального для каждого сервера, так что вам нужно послать им всего один файл. * Каждый сервер полностью самодостаточен и содержит абсолютно всё, что нужно для того, чтобы начать использовать Стрейзанд, включая криптографически верифицированные копии основного клиентского ПО. Это позволяет обойти попытки подвергнуть цензуре соответствующее ПО. * Но это еще не всё... Дополнительные особенности ------------- * Nginx поддерживает защищённый паролем и зашифрованный Портал, который служит отправной точкой для новых пользователей. Портал доступен с ипользованием SSL или через [скрытые сервисы](https://www.torproject.org/docs/hidden-services.html.en) Tor. * Стрейзанд генерирует замечательные, персонализированные пошаговые инструкции по подключению для пользователей. Пользователи могут легко получить доступ к этим инструкциям через любой веб браузер. Инструкции также отлично выглядят на мобильных телефонах. * Неизменность копий программного обеспечения подтверждена с помощью контрольных сумм SHA-256 или с использованием криптографических подписей GPG, если конкретный проект их предоставляет. Это предотвращает загрузку испорченных файлов. * Все дополнительные файлы, такие как конфигурационные профили OpenVPN также доступны через Портал. * Пользователи Tor могут также пользоваться дополнительными сервисами, устанавливаемыми Стрейзанд, для передачи больших файлов или для использования видов трафика (например BitTorrent), для которых Tor изначально не предназначен. * Для каждого шлюза Стрейзанд создается уникальный пароль, SSL-сертификат и приватный ключ SSL. Инструкции и сертификат передаются через SSH в конце выполнения Стрейзанд. * Отдельные сервисы и множество демонов предоставляют впечатляющую гибкость. Если один из методов будет заблокирован, множество других остается доступными и большая часть из них устойчива к Deep Packet Inspection (DPI). * Все методы подключения (включая прямые соединения OpenVPN) эффективны против методов блокировки, с которыми экспериментирует Турция. * OpenConnect/AnyConnect, OpenSSH, OpenVPN (завернутый в stunnel), Shadowsocks, Tor (с obfsproxy и подключаемым транспортом obfs4 ) и WireGuard эффективны против Великого Китайского Файрволла. * Каждая задача тщательно документирована и снабжена детальным описанием. Стрейзанд одновременно является самым полным HOWTO об установке всего ПО, которое он включается и является страховкой от установки всего этого руками. * Всё ПО сознательно использует порты так, чтобы сделать простое блокирование портов невозможным без нанесения блокирующей стороной значительного сопутствующего ущерба. К примеру, OpenVPN использует не порт по умолчанию 1194, а 636, стандартный порт для LDAP/SSL соединений, часто используемый компаниями во всем мире. Предоставляемые сервисы ----------------- * [OpenSSH](https://www.openssh.com/) * Создается непривилегированный пользователь и пара ключей для [sshuttle](https://github.com/sshuttle/sshuttle) и SOCKS. * Поддерживаются также SSH-туннели Windows и Android, создается копия пары ключей в .ppk формате для PuTTY * Установлен [Tinyproxy](https://banu.com/tinyproxy/) и подключен к localhost. Программы, которые не поддерживают SOCKS и требуют наличия HTTP proxy, такие как Twitter для Android, могу подключиться к нему через SSH-туннель. * [OpenConnect](https://ocserv.gitlab.io/www/index.html) / [Cisco AnyConnect](https://www.cisco.com/c/en/us/products/security/anyconnect-secure-mobility-client/index.html) * OpenConnect (ocserv) - высокопроизводительный и легковесный VPN-сервер полностью совместимый с официальными клиентами Cisco AnyConnect. * Его [протокол](https://ocserv.gitlab.io/www/technical.html) построен на классических стандартах, таких как HTTP, TLS и DTLS и является одним из самых популярных и широко используемых мультинациональными корпорациями VPN технологий. * Это означает, что кроме своей простоты и скорости, OpenConnect также устойчив к цензуре и практически никогда не блокируется. * [OpenVPN](https://openvpn.net/index.php/open-source.html) * Для каждого клиента создаются унифицированные .ovpn профили для простой настройки клиента с использованием только одного файла. * Поддерживаются соединения TCP и UDP. * Определение адресов для клиента исползует [Dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html) для предотвращения утечек DNS-запросов. * Включена TLS Authentication для защиты от зондирующих атак. Трафик не имеющий корректного HMAC будет попросту проигнорирован. * [Shadowsocks](https://shadowsocks.org/en/index.html) * Установлен высокопроизводительный [вариант libev](https://github.com/shadowsocks/shadowsocks-libev). Эта версия обрабатывает тысячи одновременных соединений. * Создается QR код который можно использовать для автоматической настройки Android и iOS клиентов. Вы можете написать '8.8.8.8' на бетонной стене, или вы можете наклеить инструкции для Shadowsocks и QR коды на ту же стену. * Включена поддержка [AEAD](https://shadowsocks.org/en/spec/AEAD-Ciphers.html) с ChaCha20 и Poly1305 для усиленной безопасности и уклонения от GFW. * [sslh](https://www.rutschle.net/tech/sslh/README.html) * Sslh - демультиплексор протоколов, позволяющий Nginx, OpenSSH и OpenVPN совместно использовать порт 443. Это предоставляет альтернативный метод подключения и означает, что вы можете перенаправлять трафик через OpenSSH и OpenVPN даже если вы находитесь в сети с очень строгими правилами, которая блокирует все соединения не с HTTP. * [Stunnel](https://www.stunnel.org/index.html) * Слушает и упаковывает соединения OpenVPN. Это заставляет их выглядеть как стандартный SSL трафик и позволяет OpenVPN клиентам устанавливать туннели даже в случае использования Deep Packet Inspection. * Создаются как профили для прямых соединений, так и унифицированные профили для соединений OpenVPN через stunnel. И подробные инструкции тоже. * Сертификат stunnel и ключ экспортируются в формате PKCS #12 для совместимости с другими приложениями для туннелирования SSL. В частности , это позволяет [OpenVPN for Android](https://play.google.com/store/apps/details?id=de.blinkt.openvpn) туннелировать свой трафик через [SSLDroid](https://play.google.com/store/apps/details?id=hu.blint.ssldroid). OpenVPN в Китае на мобильном устройстве? Да! * [Tor](https://www.torproject.org/) * Настроен [bridge relay](https://www.torproject.org/docs/bridges) со случайно сгенерированным именем. * Установлен [Obfsproxy](https://www.torproject.org/projects/obfsproxy.html.en) и настроен с поддержкой подключаемого транспорта obfs4. * Сгенерирован код BridgeQR для автоматического конфигурирования [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) для Android. * [UFW](https://wiki.ubuntu.com/UncomplicatedFirewall) * Для каждого сервиса настроены правила файрвола, так что любой трафик отправленный на запрещенный порт будет заблокирован. * [unattended-upgrades](https://help.ubuntu.com/community/AutomaticSecurityUpdates) * Ваш Стрейзанд сервер настроен для автоматической установки обновлений, связанных с безопасностью. * [WireGuard](https://www.wireguard.io/) * Пользователи Linux могут насладиться простым и прекрасным VPN, который также является сказочно быстрым и использует современные криптографические принципы, отсутствующие в других высокоскоростных VPN решениях Установка ------------ Пожалуйста, **внимательно** прочитайте инструкции по установке перед тем, как продолжать. ### Важное разъяснение ### Стрейзанд основан на [Ansible](https://www.ansible.com/), инструменте автоматизации, который обычно используется для установки и настройки файлов и пакетов на удалённых серверах. Стрейзанд автоматически создает **новый удалённый сервер** с пакетами и конфигурационными файлами VPN. Когда вы запустите Стрейзанд **на вашей домашней машине** (например на вашем лэптопе), он создаст и запустит **новый отдельный сервер** у хостера по вашему выбору. Обычно, вам **не надо запускать Стрейзанд на удалённом сервере** , так как по умолчанию это приведет к созданию нового сервера с вашего текущего сервера и первый сервер будет излишним. (фух!). При некоторых обстоятельствах продвинутые пользователи могут воспользоваться локальным методом настройки чтобы настроить сервер Стрейзанд на той же машине, где запущен Стрейзанд/Ansible. Этот способ конфигурации стоит использовать для ситуаций когда вы не можете установить Ansible на вашем домашнем компьютере или когда ваше соединение с облачным хостером слишком ненадежно для работы Ansible. ### Необходимые условия ### Выполните все указанные ниже шаги на вашем домашнем компьютере. * Стрейзанд требует BSD, Linux или macOS. На текущий момент Windows не поддерживается. Все указанные ниже команды должны выполняться в терминале. * Требуется наличие Python 2.7. Он присутствует в стандартной поставке macOS и практически во всех Linux и BSD дистрибутивах. Если в вашем дистрибутиве установлен Python 3, вам потребуется также установить Python 2.7 , чтобы Стрейзанд работал нормально. * Убедитесь, что открытый SSH ключ присутствует в файле ~/.ssh/id\_rsa.pub. * SSH ключи - это более защищенная альтернатива паролям, позволяющая подтвердить вашу личность на сервере или службе, построенной на криптографии с открытым ключом. Открытый ключ вы можете передавать другим, в то время как закрытый ключ должен храниться в тайне (так же как пароль). * Для того, чтобы проверить, есть ли у вас открытый SSH ключ, введите следущую комманду: ls ~/.ssh * Если вы видите файл id\_rsa.pub, значит у вас есть открытый SSH ключ. * Если у вас нет пары SSH ключей, вы можете ее сгенерировать с использованием следующей команды и выбирая значения по умолчанию: ssh-keygen * Если вы хотите использовать SSH ключи с другим именем или в другом местоположении, введите 'yes' когда установщик спросит хотите ли вы настроить сервер. * **Обратите внимание**: Эти ключи будут вам нужны для доступа к серверу Стрейзанд по SSH. Пожалуйста, сохраняйте их на протяжении всей жизни сервера. * Установите Git. * На Debian и Ubuntu sudo apt-get install git * На Fedora sudo dnf install git * На macOS (с использованием [Homebrew](https://brew.sh/)) brew install git * Установите [pip](https://pip.pypa.io/en/latest/) - систему управления пакетами для Python. * На Debian и Ubuntu (также устанавливает зависимости. необходимые для сборки Ansible и работы некоторых других модулей) sudo apt-get install python-paramiko python-pip python-pycurl python-dev build-essential * На Fedora sudo dnf install python-pip * На macOS sudo easy_install pip sudo pip install pycurl * Установите [Ansible](https://www.ansible.com/). * На macOS (с использованием [Homebrew](http://brew.sh/)) brew install ansible * На BSD или Linux (с использованием pip) sudo pip install ansible markupsafe * Установите необходимые библиотеки Python для вашего облачного хостера. Если вы настраиваете локальный или существующий сервер, вы можете пропустить этот шаг. * Amazon EC2 sudo pip install boto boto3 * Azure sudo pip install ansible[azure] * Google sudo pip install "apache-libcloud>=1.17.0" * Linode sudo pip install linode-api4 * Rackspace Cloud sudo pip install pyrax * **Важное замечание: если вы используете Python , установленный через Homebrew** то вы должны также выполнить следующие команды чтобы быть уверенным, что Python сможет найти необходимые библиотеки: mkdir -p ~/Library/Python/2.7/lib/python/site-packages echo '/usr/local/lib/python2.7/site-packages' > ~/Library/Python/2.7/lib/python/site-packages/homebrew.pth ### Выполнение ### 1. Склонируйте репозиторий Стрейзанд и перейдите в директорию git clone https://github.com/StreisandEffect/streisand.git && cd streisand 2. Запустите скрипт Стрейзанд. ./streisand 3. Следуйте инструкциям для выбора вашего хостера, региона, где будет запущен сервер и его имени. Вам также потребуется ввести информацию об API. 4. После того, как логины и API ключи предоставлены, Стрейзанд начинает создавать новый удалённый сервер. 5. Подождите, пока установка будет завершена (это обычно занимает примерно десять минут) и вы найдете файл в папке 'generated-docs' в директории репозитория Стрейзанд. HTML файл содержит инструкции как подключиться к шлюзу через SSL или через Tor. Все инструкции, файлы, копии клиентского ПО и ключ для нового сервера расположены на портале. Всё готово! ### Использование Стрейзанд для настройки локального сервера (Для продвинутых) ### Для случаев, когда вы не можете запустить Стрейзанд как обычно (с вашего домашнего компьютера или лэптопа), с тем, чтобы сконфигурировать удалённый сервер, Стрейзанд поддерживает локальный метод настройки. Просто выберите "Localhost (Advanced)" из меню, после того, как запустили `./streisand`. **Замечание:** Запуск Стрейзанд для настройки локального сервера может что-нибудь испортить. Возможно, вы перезапишете конфигурационные файлы, поэтому будьте уверены, что вы настраиваете правильный сервер. ### Использование Стрейзанд для других хостеров (Для продвинутых) ### Вы также можете запустить Стрейзанд на любом сервере Ubuntu 16.04. Выделенный сервер? Отлично! Странный облачный хостер? Замечательно! Чтобы это сделать, просто выберите "Existing Server (Advanced)" из меню после запуска `./streisand` и введите IP адрес существующего сервера , когда скрипт запросит эти данные. Этот сервер должен разрешать подключение с SSH-ключом `$HOME/.ssh/id_rsa` и по умолчанию для подключения будет использоваться пользователь **root**. Если ваш хостер требует, чтобы для подключения использовался какой-то другой пользователь (например `ubuntu`), установите переменную среды `ANSIBLE_SSH_USER` (например `ANSIBLE_SSH_USER=ubuntu` ) перед запуском `./streisand`. **Замечание:** Запуск Стрейзанд для настройки существующего сервера может что-нибудь испортить. Возможно, вы перезапишете конфигурационные файлы, поэтому будьте уверены, что вы настраиваете правильный сервер. Будущие возможности ------------------- * Более простая установка. Если у вас есть идея, что ещё может делать Стрейзанд или вы нашли баг в документации или коде, пожалуйста, оставьте сообщение в [Issue Tracker](https://github.com/StreisandEffect/streisand/issues). Ключевые разработчики --------------------- * Jay Carlson (@nopdotcom) * Nick Clarke (@nickolasclarke) * Joshua Lund (@jlund) * Ali Makki (@alimakki) * Daniel McCarney (@cpu) * Corban Raun (@CorbanR) Благодарности ------------- [Jason A. Donenfeld](https://www.zx2c4.com/) заслуживает множество благодарностей за смелое представление о том, как может выглядеть современный VPN и за то, что он создал такую отличную штуку как [WireGuard](https://www.wireguard.io/). Искреннее спасибо за его терпеливую помощь и отличную обратную связь. Мы благодарны [Trevor Smith](https://github.com/trevorsmith) за его огромный вклад в проект. Он предложил подход с использованием портала, дал множество бесценных комментариев. С его помощью *всё* стало выглядеть лучше, он создал шаблон HTML, который вдохновил меня поднять всё на новый уровень перед официальным запуском. Огромное спасибо [Paul Wouters](https://nohats.ca/) из [The Libreswan Project](https://libreswan.org/) за его великодушную помощь в отладке инсталляции L2TP/IPsec. Альбом 'Sunset Blood' группы [Starcadian's](https://www.starcadian.com/) был прослушан примерно 300 раз в цикле в течение первых месяцев работы над этим проектом в начале 2014 года. ================================================ FILE: README.md ================================================ # Streisand

Automate the effect

- - - [English](README.md), [Français](README-fr.md), [简体中文](README-chs.md), [Русский](README-ru.md) | [Mirror](https://gitlab.com/alimakki/streisand) - - - [![Build Status](https://github.com/StreisandEffect/streisand/workflows/Streisand/badge.svg)](https://github.com/StreisandEffect/streisand/actions) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/espadrine.svg?style=social&label=Follow%20%40StreisandVPN)](https://twitter.com/StreisandVPN) Streisand ========= **Silence censorship. Automate the [effect](https://en.wikipedia.org/wiki/Streisand_effect).** The Internet can be a little unfair. It's way too easy for ISPs, telecoms, politicians, and corporations to block access to the sites and information that you care about. But breaking through these restrictions is *tough*. Or is it? If you have an account with a cloud computing provider, Streisand can set up a new node with many censorship-resistant VPN services nearly automatically. You'll need a little experience with a Unix command-line. (But without Streisand, it could take days for a skilled Unix administrator to configure these services securely!) At the end, you'll have a private website with software and instructions. Here's what **[a sample Streisand server](http://streisandeffect-demo.s3-website.us-east-2.amazonaws.com/)** looks like. There's a [list of supported cloud providers](#cloud-providers); experts may be able to use Streisand to install on many other cloud providers. ## VPN services One type of tool that people use to avoid network censorship is a Virtual Private Network (VPN). There are many kinds of VPNs. Not all network censorship is alike; in some places, it changes from day to day. Streisand provides many different VPN services to try. (You don't have to install them all, though.) Some Streisand services include add-ons for further censorship and throttling resistance: * [OpenSSH](https://www.openssh.com/) * [Tinyproxy](https://banu.com/tinyproxy/) may be used as an HTTP proxy. * [OpenConnect](https://ocserv.gitlab.io/www/index.html) / [Cisco AnyConnect](https://www.cisco.com/c/en/us/products/security/anyconnect-secure-mobility-client/index.html) * This protocol is widely used by multi-national corporations, and might not be blocked. * [OpenVPN](https://openvpn.net/index.php/open-source.html) * [Stunnel](https://www.stunnel.org/index.html) add-on available. * [Shadowsocks](https://shadowsocks.org/en/index.html), * The [V2ray-plugin](https://github.com/shadowsocks/v2ray-plugin) is installed to provide robust traffic evasion on hostile networks (especially those implementing quality of service (QOS) throttling). * A private [Tor](https://www.torproject.org/) bridge relay * [Obfsproxy](https://www.torproject.org/projects/obfsproxy.html.en) with obfs4 available as an add-on. * [WireGuard](https://www.wireguard.com/), a modern high-performance protocol. See also: * A [more technical list of features](Features.md) * A [more technical list of services](Services.md) ## Cloud providers * Amazon Web Services (AWS) * Microsoft Azure * Digital Ocean * Google Compute Engine (GCE) * Linode * Rackspace #### Other providers We recommend using one of the above providers. If you are an expert and can set up a *fresh Ubuntu 16.04 server* elsewhere, there are "localhost" and "existing remote server" installation methods. For more information, see [the advanced installation instructions](Advanced%20installation.md). ## Installation You need command-line access to a Unix system. You can use Linux, BSD, or macOS; on Windows 10, the Windows Subsystem for Linux (WSL) counts as Linux. Once you're ready, see the [full installation instructions](Installation.md). ## Things we want to do better Aside from a good deal of cleanup, we could really use: * Easier setup. * Faster adoption of new censorship-avoidance tools We're looking for help with both. If there is something that you think Streisand should do, or if you find a bug in its documentation or execution, please file a report on the [Issue Tracker](https://github.com/StreisandEffect/streisand/issues). Core Contributors ---------------- * Jay Carlson (@nopdotcom) * Nick Clarke (@nickolasclarke) * Joshua Lund (@jlund) * Ali Makki (@alimakki) * Daniel McCarney (@cpu) * Corban Raun (@CorbanR) Acknowledgements ---------------- [Jason A. Donenfeld](https://www.zx2c4.com/) deserves a lot of credit for being brave enough to reimagine what a modern VPN should look like and for coming up with something as good as [WireGuard](https://www.wireguard.com/). He has our sincere thanks for all of his patient help and high-quality feedback. We are grateful to [Trevor Smith](https://github.com/trevorsmith) for his massive contributions. He suggested the Gateway approach, provided tons of invaluable feedback, made *everything* look better, and developed the HTML template that served as the inspiration to take things to the next level before Streisand's public release. Huge thanks to [Paul Wouters](https://nohats.ca/) of [The Libreswan Project](https://libreswan.org/) for his generous help troubleshooting the L2TP/IPsec setup. [Starcadian's](https://www.starcadian.com/) 'Sunset Blood' album was played on repeat approximately 300 times during the first few months of work on the project in early 2014. ================================================ FILE: Services.md ================================================ # Services Services Provided ----------------- * [OpenSSH](https://www.openssh.com/) * Windows and Android SSH tunnels are also supported, and a copy of the keypair is exported in the `.ppk` format that PuTTY requires. * [Tinyproxy](https://tinyproxy.github.io/) can be installed and bound to localhost. It can be accessed over an SSH tunnel by programs that do not natively support SOCKS and that require an HTTP proxy, such as Twitter for Android. * An unprivileged forwarding user and SSH keypair can be generated for [sshuttle](https://github.com/sshuttle/sshuttle) and SOCKS capabilities. * [OpenConnect](https://ocserv.gitlab.io/www/index.html) / [Cisco AnyConnect](https://www.cisco.com/c/en/us/products/security/anyconnect-secure-mobility-client/index.html) * OpenConnect (ocserv) is an extremely high-performance and lightweight VPN server that also features full compatibility with the official Cisco AnyConnect clients. * The [protocol](https://ocserv.gitlab.io/www/technical.html) is built on top of standards like HTTP, TLS, and DTLS, and it's one of the most popular and widely used VPN technologies among large multi-national corporations. * This means that in addition to its ease-of-use and speed, OpenConnect is also highly resistant to censorship and is almost never blocked. * [OpenVPN](https://openvpn.net/index.php/open-source.html) * When enabled, self-contained "unified" .ovpn profiles are generated for easy client configuration using only a single file. * Both TCP and UDP connections are supported. * Client DNS resolution is handled via [Dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html) to prevent DNS leaks. * TLS Authentication is enabled which helps protect against active probing attacks. Traffic that does not have the proper HMAC is simply dropped. * [Shadowsocks](https://shadowsocks.org/en/index.html) * When enabled, the high-performance [libev variant](https://github.com/shadowsocks/shadowsocks-libev) is installed. This version is capable of handling thousands of simultaneous connections. * A QR code is generated that can be used to automatically configure the Android and iOS clients by simply taking a picture. You can tag '8.8.8.8' on that concrete wall, or you can glue the Shadowsocks instructions and some QR codes to it instead! * [AEAD](https://shadowsocks.org/en/spec/AEAD-Ciphers.html) support is enabled using ChaCha20 and Poly1305 for enhanced security and improved GFW evasion. * The [v2ray-plugin](https://github.com/shadowsocks/v2ray-plugin) plugin is installed to provide robust traffic evasion on hostile networks (especially those implementing quality of service (QOS) throttling). * [sslh](https://www.rutschle.net/tech/sslh/README.html) * Sslh is a protocol demultiplexer that allows Nginx, OpenSSH, and OpenVPN to share port 443. This provides an alternative connection option and means that you can still route traffic via OpenSSH and OpenVPN even if you are on a restrictive network that blocks all access to non-HTTP ports. * [Stunnel](https://www.stunnel.org/index.html) * When enabled, listens for and wraps OpenVPN connections. This makes them look like standard SSL traffic and allows OpenVPN clients to successfully establish tunnels even in the presence of Deep Packet Inspection. * Unified profiles for stunnel-wrapped OpenVPN connections are generated alongside the direct connection profiles. Detailed instructions are also generated. * The stunnel certificate and key are exported in PKCS #12 format so they are compatible with other SSL tunneling applications. Notably, this enables [OpenVPN for Android](https://play.google.com/store/apps/details?id=de.blinkt.openvpn) to tunnel its traffic through [SSLDroid](https://play.google.com/store/apps/details?id=hu.blint.ssldroid). OpenVPN in China on a mobile device? Yes! * [Tor](https://www.torproject.org/) * If chosen by the user a [bridge relay](https://www.torproject.org/docs/bridges) is set up with a random nickname. * [Obfsproxy](https://www.torproject.org/projects/obfsproxy.html.en) is installed and configured with support for the obfs4 pluggable transport. * A BridgeQR code is generated that can be used to automatically configure [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) for Android. * [UFW](https://wiki.ubuntu.com/UncomplicatedFirewall) * Firewall rules are configured for every service, and any traffic that is sent to an unauthorized port will be blocked. * [unattended-upgrades](https://help.ubuntu.com/community/AutomaticSecurityUpdates) * Your Streisand server is configured to automatically install new security updates. * [WireGuard](https://www.wireguard.com/) * Users can take advantage of this next-gen, simple, kernel-based or user-space-based, state-of-the-art VPN that also happens to be ridiculously fast and uses modern cryptographic principles that all other highspeed VPN solutions lack. It is noticeably more performant and enery-saving, and is highly advantageous in roaming network-to-network environments, like mobile phones. A connection can be made as simply as scanning a QR code, which makes it perfect for sharing with friends and family members. It currently offers an official client for macOS, iOS, Android, Windows, a variety of Linux distibutions, OpenBSD and FreeBSD. * [Cloudflared DNS-over-HTTPS](https://developers.cloudflare.com/1.1.1.1/dns-over-https/) * Even when you are visiting a site using HTTPS, by default your DNS query is sent over an unencrypted connection (between the Streisand server and upstream DNS servers). With Streisand's DNS-over-HTTPS service provided by the cloudflared client enabled, your DNS queries are blocked from view by the cloud provider hosting your Streisand server and everyone in between them and the upstream DNS server. The DNS reply from the upstream server is also protected from both view and tampering on its way back to your Streisand server. ================================================ FILE: Vagrantfile ================================================ # See documentation/testing.md for instructions on using this Vagrantfile # Vagrant.require_version ">= 1.9.0" Vagrant.configure(2) do |config| config.vm.box = "ubuntu/xenial64" config.vm.define "streisand-host", primary: true do |streisand| streisand.vm.hostname = "streisand-host" streisand.vm.network :private_network, ip: "10.0.0.10" streisand.vm.provision "ansible" do |ansible| # NOTE: Uncomment the below line for verbose Ansible output # ansible.verbose = "v" ansible.playbook = "playbooks/vagrant.yml" ansible.host_vars = { "streisand-host" => { "streisand_ipv4_address" => "10.0.0.10" } } ansible.raw_arguments = [ "--extra-vars=@global_vars/globals.yml", "--extra-vars=@global_vars/default-site.yml", "--extra-vars=@global_vars/integration/test-site.yml" ] end end config.vm.define "streisand-client" do |client| client.vm.hostname = "streisand-client" client.vm.network :private_network, ip: "10.0.0.11" client.vm.provision "ansible" do |ansible| # NOTE: Uncomment the below line for verbose Ansible output # ansible.verbose = "v" ansible.playbook = "playbooks/test-client.yml" ansible.host_vars = { "streisand-client" => { "streisand_ip" => "10.0.0.10", } } end end end ================================================ FILE: Vagrantfile.remotetest ================================================ # See documentation/testing.md for instructions on using this Vagrantfile # # NOTE: You *MUST* replace the "REMOTE_IP_HERE" value in the `host_vars` section # with the IP address of the Streisand server you wish to test # Vagrant.require_version ">= 1.9.0" Vagrant.configure(2) do |config| config.vm.box = "ubuntu/xenial64" config.vm.define "streisand-client" do |client| client.vm.hostname = "streisand-client" client.vm.network :private_network, ip: "10.0.0.11" client.vm.provision "ansible" do |ansible| # NOTE: Uncomment the below line for verbose Ansible output #ansible.verbose = "v" ansible.playbook = "playbooks/test-client.yml" ansible.host_vars = { "streisand-client" => { "streisand_ip" => "REMOTE_IP_HERE", } } end end end ================================================ FILE: ansible.cfg ================================================ [defaults] inventory = inventories/inventory nocows = 1 # This is a convenient setting for a brand-new Streisand server that # you are connecting to for the first time. However, this line should be # commented out if you are connecting to an existing server where a # known_hosts entry has already been created. Host key checking happens # by default in Ansible. host_key_checking = False # Workaround for an Ansible issue with temporary paths: # https://github.com/ansible/ansible/issues/21562 remote_tmp = $HOME/.ansible/tmp local_tmp = $HOME/.ansible/tmp # Some providers take a long time to actually begin accepting # connections even after the OpenSSH daemon has started. timeout = 100 library=library [ssh_connection] # Enables multiplexing (lets ansible reuse opened SSH connections) ssh_args = -o ControlMaster=auto -o ControlPersist=60s pipelining = True ================================================ FILE: deploy/streisand-existing-cloud-server.sh ================================================ #!/usr/bin/env bash # # Provision an existing cloud server. # # This requires an expanded extra-vars file specific to the provider type that # sets all of the values gathered by prompts in the interactive installation. # See the contents of global_vars/noninteractive for examples that can be copied # and modified. # # Usage: # # streisand-existing-cloud-server \ # --ssh-user root \ # --ip-address 10.10.10.10 \ # --site-config path/to/digitalocean-site.yml # set -o errexit set -o nounset DIR="$( cd "$( dirname "$0" )" && pwd)" PROJECT_DIR="${DIR}/.." export DEFAULT_SITE_VARS="${PROJECT_DIR}/global_vars/default-site.yml" export GLOBAL_VARS="${PROJECT_DIR}/global_vars/globals.yml" # Include the check_ansible function from ansible_check.sh. # shellcheck source=util/source_validate_and_deploy.sh source "${PROJECT_DIR}/util/ansible_check.sh" check_ansible # -------------------------------------------------------------------------- # Reading options. # -------------------------------------------------------------------------- function usage () { cat < "${INVENTORY}" < Installation ------------ * Créer une application ( [guide visuel](https://docs.microsoft.com/fr-fr/azure/azure-resource-manager/resource-group-create-service-principal-portal#create-an-active-directory-application) )
Rechercher **Inscriptions d’applications** dans la barre de recherche et sélectionnez le premier résultat.
Sélectionnez **Inscriptions d’applications**.
Remplissez le formulaire: Name: StreisandAuth Application type: Web app / API Sign-on URL: http://StreisandAuth * Assurez que l'application peut créer des instances Azure ( [guide visuel](https://docs.microsoft.com/fr-fr/azure/azure-resource-manager/resource-group-create-service-principal-portal#assign-application-to-role) )
Rechercher **Abonnements** dans la barre de recherche et sélectionnez le premier résultat.
Choisissez l'abonnement souhaité.
Sélectionnez **Access control (IAM)** à partir du menu nouvellement ouvert.
Ajouter: Role: Contributor Select: StreisandAuth Autorisations ------------- Emplacement du fichier: ~/.azure/credentials Format du fichier ( [source](https://docs.ansible.com/ansible/guide_azure.html#storing-in-a-file) ). [default] subscription_id=xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx client_id=xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx secret=xxxxxxxxxxxxxxxxx tenant=xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Remplacez le motif **xxxxx** par les valeurs obtenues en suivant les étapes ci-dessous. * Obtenir le **subscription_id**
Rechercher pour **Subscriptions** à partir du menu nouvellement ouvert.
L'ID de l'abonnement sera dans la deuxième colonne du tableau des abonnements. * Obtenir le **client_id** ( [guide visuel](https://docs.microsoft.com/fr-fr/azure/azure-resource-manager/resource-group-create-service-principal-portal#get-application-id-and-authentication-key) )
Rechercher pour **App registrations** à partir du menu nouvellement ouvert.
Sélectionnez votre application dans la liste: **StreisandAuth**.
Le client_id est le [Guid](https://en.wikipedia.org/wiki/Universally_unique_identifier) sous **Application ID**.
* Obtenir le **secret** ( [guide visuel](https://docs.microsoft.com/fr-fr/azure/azure-resource-manager/resource-group-create-service-principal-portal#get-application-id-and-authentication-key) )
Rechercher pour **App registrations** à partir du menu nouvellement ouvert.
Sélectionnez votre application dans la liste: **StreisandAuth**.
Sélectionnez **Keys** à partir du menu nouvellement ouvert à droite.
Créer une nouvelle clé: Key description: streisand Duration: Never expires La clé secrète sera générée après la sauvegarde. * Obtenir le **tenant** ( [guide visuel](https://docs.microsoft.com/fr-fr/azure/azure-resource-manager/resource-group-create-service-principal-portal#get-tenant-id) )
Rechercher pour **Azure Active Directory** à partir du menu nouvellement ouvert.
Faites défiler vers le bas et sélectionnez **Properties** à partir du menu nouvellement ouvert à gauche.
Le locataire est le Guid sous **Directory ID**.
Problèmes possibles et dépannage -------------------------------- * You cannot register an application (Vous ne pouvez pas enregistrer une application)
Rechercher pour **Azure Active Directory** à partir du menu nouvellement ouvert.
Dans le menu gauche résultant, sélectionnez "User settings".
Assurez-vous que **App registrations** sont réglés sur **Yes**. ================================================ FILE: documentation/AZURE.md ================================================ Azure credential and setup ========================== - - - [English](AZURE.md), [Français](AZURE-fr.md) - - - Setup ----- * Create an application ( [visual guide](https://docs.microsoft.com/en-in/azure/azure-resource-manager/resource-group-create-service-principal-portal#create-an-active-directory-application) )
Search for **App registrations** in the top search bar and select the first result.
Select **New application registration**.
Fill in the application form: Name: StreisandAuth Application type: Web app / API Sign-on URL: http://StreisandAuth * Ensure that the application can create Azure instances ( [visual guide](https://docs.microsoft.com/en-in/azure/azure-resource-manager/resource-group-create-service-principal-portal#assign-application-to-role) )
Search for **Subscriptions** in the top search bar and select the first result.
Choose the desired subscription.
Select **Access control (IAM)** from the newly opened menu.
Add: Role: Contributor Select: StreisandAuth Credentials ----------- File location: ~/.azure/credentials File format ( [source](https://docs.ansible.com/ansible/guide_azure.html#storing-in-a-file) ). [default] subscription_id=xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx client_id=xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx secret=xxxxxxxxxxxxxxxxx tenant=xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Replace the **xxxxx** pattern with the values obtained by following the below steps. * Get the **subscription_id**
Search for **Subscriptions** in the top search bar and select the first result.
The subscription ID will be in the second column of the subscriptions table. * Get the **client_id** ( [visual guide](https://docs.microsoft.com/en-in/azure/azure-resource-manager/resource-group-create-service-principal-portal#get-application-id-and-authentication-key) )
Search for **App registrations** in the top search bar and select the first result.
Select your application from the list: **StreisandAuth**.
The client_id is the [Guid](https://en.wikipedia.org/wiki/Universally_unique_identifier) under **Application ID**.
* Get the **secret** ( [visual guide](https://docs.microsoft.com/en-in/azure/azure-resource-manager/resource-group-create-service-principal-portal#get-application-id-and-authentication-key) )
Search for **App registrations** in the top search bar and select the first result.
Select your application from the list: **StreisandAuth**.
Select **Certificates & secrets** from the newly opened menu on the left.
Select **New client secret** under **Client secrets**.
Create a new key: Key description: streisand Duration: Never expires The secret key will be generated after saving. * Get the **tenant** ( [visual guide](https://docs.microsoft.com/en-in/azure/azure-resource-manager/resource-group-create-service-principal-portal#get-tenant-id) )
Search for **Azure Active Directory** in the top search bar and select the first result.
Scroll down and select **Properties** from the newly opened left menu.
The tenant is the Guid under **Directory ID**.
Possible issues and troubleshooting ----------------------------------- * You cannot register an application
Search for **Azure Active Directory** in the top search bar and select the first result.
In the resulting left menu select "User settings".
Ensure that **App registrations** is set to **Yes**. ================================================ FILE: documentation/SOURCES.md ================================================ # Source of packages installed ## From APT - Ubuntu standard repositories - [Streisand Common Packages](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/common/vars/main.yml) - [`bind`](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/dnsmasq/tasks/main.yml) - [`dnsmasq`](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/dnsmasq/tasks/main.yml) - [Libreswan Compilation Dependencies](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/l2tp-ipsec/vars/main.yml) - [OpenConnect Dependencies](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/openconnect/vars/main.yml) - [Shadowsocks Dependencies](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/shadowsocks/vars/main.yml) - [`sslh`](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/sslh/tasks/main.yml) - [`stunnel4`](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/stunnel/tasks/main.yml) - [`tinyproxy`](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/tinyproxy/tasks/main.yml) - [`ufw`](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/ufw/tasks/main.yml) - from 3rd party repositories: - [`nginx` (from nginx.org)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/nginx/tasks/main.yml) - [`obbfs4proxy` (from deb.torproject.org)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/tor-bridge/tasks/main.yml) - [Wireguard Packages (from wireguard PPA)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/wireguard/tasks/install.yml) - [Acmetool (from PPA)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/lets-encrypt/tasks/install.yml) ## Source - [Libreswan](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/l2tp-ipsec/vars/main.yml) - [OpenConnect](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/openconnect/vars/main.yml) - [OpenVPN (from build.openvpn.net)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/openvpn/vars/mirror.yml) - [Shadowsocks (from github.com/shadowsocks)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/shadowsocks/tasks/main.yml) - [Shadowsocks Simple-obfs (from github.com/shadowsocks-simpleobfs)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/shadowsocks/tasks/simple-obfs.yml) # Source of all clients mirrored - Android - [Shadowsocks (from github.com/shadowsocks)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/shadowsocks/vars/mirror.yml) - Linux - [Shadowsocks (from github.com/shadowsocks)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/shadowsocks/vars/mirror.yml) - [Tor Browser (from dist.torproject.org)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/tor-bridge/vars/mirror-common.yml) - macOS - [Tunnelblick (from tunnelblick.net)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/openvpn/vars/mirror.yml) - [Shadowsocks (from github.com/shadowsocks)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/shadowsocks/vars/mirror.yml) - [Tor Browser (from dist.torproject.org)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/tor-bridge/vars/mirror-common.yml) - Windows - [OpenVPN (from build.openvpn.net)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/openvpn/vars/mirror.yml) - [Shadowsocks (from github.com/shadowsocks)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/shadowsocks/vars/mirror.yml) - [Shuttle (from github.com/sshuttle)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/streisand-mirror/vars/ssh.yml) - [Stunnel (from stunnel.org)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/stunnel/vars/mirror.yml) - [Putty (from the.earth.li)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/streisand-mirror/vars/ssh.yml) - [Tor Browser (from dist.torproject.org)](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/tor-bridge/vars/mirror-common.yml) ================================================ FILE: documentation/certificates.md ================================================ Streisand PKI ============= Streisand includes a role responsible for PKI certificate generation, separated into three heirarchical tasks: ``` CA/Server \__ CA Certificate \__ Server certificate \__ Client (1..n) \__ Client PKCS (1..n) ``` CA/Server --------- Guarded by `generate_ca_server: yes` Generate a certificate authority, and a server certificate signed by the newly minted certificate authority. Client ------ Generate client certificates using the CA from `generate_ca_server: yes`. These client certificates can be used by VPN clients to authenticate with VPN services on the server. Client (PKCS#11 format) ----------------------- Guarded by `generate_pkcs: yes` Also convert client certificates into PKCS#12 format. Infrastructure -------------- Note: Each VPN service that invokes the certificates role will generate it's own distinct public key infrastructure. The overall Streisand PKI infrastructure assumes the following structure: ``` Web Root CA: \__ gateway HTTPS cert (unless Let's Encrypt; see below) OpenConnect CA: \__ OpenConnect server cert \__ OpenConnect client 1 \__ OpenConnect client ... \__ OpenConnect client n OpenVPN CA: \__ OpenVPN server cert \__ OpenVPN client 1 \__ OpenVPN client ... \__ OpenVPN client n ISRG X1: \__ Let's Encrypt Authority X3 \__ streisand.example.com HTTPS cert ``` Usage ----- The `certificates` role can be invoked as follows (using OpenVPN as an example, with variables expanded for brevity): ``` - include_role: name: certificates vars: ca_path: "/etc/openvpn" tls_ca: "/etc/openvpn/ca" tls_client_path: "/etc/openvpn" generate_ca_server: yes generate_client: yes tls_request_subject: "/C=US/ST=California/L=Beverly Hills/O=ACME CORPORATION/OU=Anvil Department" tls_server_common_name_file: "/etc/openvpn/openvpn-common-name.txt" tls_sans: # sets the Subject Alternative Name(s) attribute(s) - "1.2.3.4" - "5.6.7.8" ``` Note the following flags: - `generate_ca_server: yes` - `generate_client: yes` - `generate_pkcs: yes` These must be made explicit, as the default value is `no`; certificates should not be generated unless they have to be. The certificates role makes certain assumptions with regards to default values, all of which can be inspected [here](https://github.com/StreisandEffect/streisand/blob/master/playbooks/roles/certificates/defaults/main.yml). ================================================ FILE: documentation/localization_howto.md ================================================ Localizing Streisand in your language ===================================== One of the most unique aspects of the Streisand project are the detailed and customized instructions that it generates for each of the VPN services it provides. The instructions are hosted on a password protected website allowing them to be easily shared among fellow users. This can help in removing ambiguity in how certain services need to be set up and can be easily updated as Streisand evolves. Scope ----- The best place to focus localization efforts is on user facing content, as opposed to developer content. For example, documentation that can help a user navigate the particularities of certain cloud providers (account setup, permissions, etc...) are good candidates for localization. On the other hand, translation of developer oriented texts, such as Ansible task names isn't needed at this time. Language codes -------------- For simplicity, it is preferable to use 2 character language codes (fr -> French, ar -> Arabic), as defined by [ISO 631-1](https://en.wikipedia.org/wiki/ISO_639). For certain locales such as Chinese vs Simplified Chinese, 3 character codes are also acceptable. Filename conventions -------------------- In order to differentiate a localized file, append the lowercase language code after the filename with a 'dash' symbol (e.x. README-fr.md for a README localized in French). Point of reference ------------------ When translating, English should be considered the point of reference to translate *from*. READMEs and Documentation ------------------------- The interaction with Streisand for an end user would be first visiting the project README, where all the necessary steps one would need to set up their computing environment are detailed, as well as a listing of all the VPN services provided by Streisand. Unlike Streisand's instructions markdown template files, READMEs and documentation are orthogonal to Streisand being able to successfully build. As a consequences, it is possible to have READMEs and documentation in more languages than Streisand builds documentation with. Defining locales ---------------- In order to define a locale for Streisand, a new entry defining your language must be added to the `streisand_languages` dict object in the `playbooks/roles/common/vars/main.yml` file, where the 2 letter code is considered the key, and a list of key-value pairs with additional data as its value. For example: ``` streisand_languages: en: file_suffix: "" language_name: "English" tor_locale: "en-US" fr: file_suffix: "-fr" # appends to the end of a file name language_name: "Français" # name of the language in the language itself tor_locale: "fr" # edge case for Tor ``` Localizing gateway instructions ------------------------------- The following roles contain user instructions that will require translation: - `playbooks/roles/openconnect/templates` 1. `instructions.md.j2` 1. `mirror.md.j2` - `playbooks/roles/openvpn/templates` 1. `instructions.md.j2` 1. `stunnel-insructions.md.j2` 1. `mirror.md.j2` - `playbooks/roles/shadowsocks/templates` 1. `instructions.md.j2` 1. `mirror.md.j2` - `playbooks/roles/ssh-forward/templates` 1. `instructions.md.j2` 1. `mirror.md.j2` - `playbooks/roles/streisand-gateway/templates` 1. `index.md.j2` 1. `instructions.md.j2` 1. `firewall.md.j2` - `playbooks/roles/streisand-mirror/templates` 1. `mirror-index.md.j2` - `playbooks/roles/stunnel/templates` 1. `mirror.md.j2` - `playbooks/roles/tor/templates` 1. `instructions.md.j2` 1. `mirror.md.j2` - `playbooks/roles/wireguard/templates` 1. `instructions.md.j2` Language selection in generated pages ------------------------------------- By default, Streisand's common header file contains language selection links (generated using the `streisand_languages` dict). For most pages the header with language selection links will be included automatically. Certain edge cases do exist where a jinja2 code block is explicitly needed with the generated html filename. These edge case files maybe all reside with the `streisand-gateway` role: - `firewall.md.j2` - `index.md.j2` - `instructions.md.j2` ``` - - - {% for key, value in streisand_languages.items() %} [{{ value.language_name }}]({{ streisand_server_name }}-*generated-html-file-name*{{ value.file_suffix }}.html)  {% endfor %} - - - ``` The HTML filename can be recovered by looking at the relevant Ansible task that generates the template. End user considerations ----------------------- While we aim to have as-close-to-the-original-source translations, linguistic differences in expressions should be taken into consideration when it makes sense. A non-technical example would be a the following expression in English: "the straw that broke the camel's back". A literal translation into French would give: "la paille qui a cassé le dos du chameau", however contextually a similar expression exists in French: "C'est la goutte d'eau qui fait déborder le vase" (it's the drop of water that made the vase overflow). Different translations, same meaning. In this case, the latter would be the 'ideal' translation, as it makes more sense to a native speaker of that language. Another aspect to consider is what language an end-user's device is set to. It is best to have a device nearby set in the same language, where you can see exactly what is displayed in particular menus and use those same exactly what is displayed. It's worth noting that not all software available in app stores or otherwise are localized in your language of choice, and many are English only. In this case, it would be sufficient to use the same English texts, and should you choose, adjacent to it between parenthesis a translation of the aforementioned text. In the case where you aren't in possession of a particular device, a best-effort approach is also acceptable. ================================================ FILE: documentation/modular_roles.md ================================================ Modular Roles =============== Streisand includes many services in an out-of-box install. This helps make censorship resistance as easy as possible. In a hostile network environment the protocols/services that are blocked can vary & change overnight. With many choices available for connecting through a Streisand instance to clear Internet there is likely always a choice that will work! For use cases less focused on censorship resistance this "kitchen sink of services" approach has two large downsides: many moving parts & a very large externally facing attack surface. The solution: Allowing users to selectively disable Streisand services they don't require, creating custom profiles that suits their needs. The challenge: Streisand was initially designed as a monolithic deploy. That is, the Ansible roles responsible for each service inter-depend on one another. To enable picking and choosing Streisand services while minimizing spaghetti complexity it's necessary to eliminate cross-role dependencies. This document aims to be a roadmap for converting roles to be stand-alone and suitable for the goal of modularizing included services. As a concrete example this document will discuss the Shadowsocks role, the first role that has been converted to this style. Rough Plan ---------- **Note**: _This is all subject to change as discussion/experience unfolds!_ 1. One role at a time, convert it to be stand alone (e.g. create its own firewall rules, do its own client mirroring, etc). 2. Add an enable flag variable for the role, default to on (e.g. Streisand by default remains ship-it-all-by-default). 3. Once all roles are converted, add infrastructure for selecting roles (helper script modifications). 4. Discuss which roles should be enabled by default to perhaps pare down the base install. 5. Update this documentation. Implementation -------------- Each service has an enable bool variable defined in `playbooks/group_vars/all` to control whether the service is going to be included or not. E.g. `streisand_shadowsocks_enabled: yes` would include Shadowsocks when provisioning a server. Every Streisand service has to handle the following responsibilities: * Installing & setting up server software/configurations. * Updating firewall rules/access controls. * Generating client connection instructions & config files. * Mirroring client software. For the purpose of connection instructions & client software each service has two directories it must create & populate: * One in `/var/www/streisand/` for connection instructions. * One in `var/www/streisand/mirror/` for client mirroring. Both directories should contain an `index.html` for the available Streisand languages. Since responsibilities between services are similar, the self-contained roles should have roughly the same structure (minimized for brevity): ``` service/ ├── meta │   └── main.yml ├── tasks │   ├── main.yml │   ├── docs.yml │   ├── firewall.yml │   └── mirror.yml └── vars ├── main.yml └── mirror.yml ``` * `meta/main.yml` is used for declaring dependencies (e.g. on `ufw` for firewall rules). Nothing special there! * `tasks/main.yml` handles the base responsibilities of installing software/configs etc. and includes the subtask files: `docs.yml`, `firewall.yml` and `mirror.yml`. * `tasks/docs.yml` handles generating connection instructions for the service under its documentation root. * `tasks/firewall.yml` handles adding `ufw`/`iptables` rules as required by the service. * `tasks/mirror.yml` handles downloading client software to the service's mirror root and generating any required client install documentation. * `vars/main.yml` are the variables required for the service's configuration/etc. * `vars/mirror.yml` are mirror-specific variables (client software versions, download URLs, expected hashes/signatures, etc) For Shadowsocks, initially the `ufw` role was responsible for adding a firewall rule for the `shadowsocks-libev` port. This created a cross-role dependency where if the `shadowsocks` role was disabled the `ufw` role would open a port unnecessarily, or reference a variable that didn't exist. To remove this cross-dependency the `shadowsocks` role was updated to declare a meta dependency on the generic firewall role (to ensure the package is installed and ready) in `meta/main.yml`. Then a `tasks/firewall.yml` subtask file was added that uses `ufw` to add the required rules. Similarly, the initial `streisand-mirror` role downloaded all of the Shadowsocks clients for the mirror using a distinct tasks `.yml` with its own variables `.yml`. This made it fairly easy to move the mirror subtasks from `streisand-mirror/tasks/shadowsocks.yml` to `shadowsocks/tasks/mirror.yml` and `streisand-mirror/vars/shadowsocks.yml` to `shadowsocks/vars/mirror.yml`. Now the `shadowsocks` role can be responsible for preparing its own client mirror. Gateway & Mirror Indices ------------------------------ While the Gateway & Mirror documentation can be largely agnostic of which services are included in a given build by outsourcing the management of subfolders of `/var/www/streisand` and `/var/www/streisand/mirror` eventually a consistent set of docs to navigate must be created based on the included services. This is done by conditionally adding links to the connection instruction & mirror indices of each subservice using the enable bool variables in the docs and mirror index files. This is the one place where the "if spaghetti" is required in order to stitch things together. As a concrete example, consider the `shadowsocks` role. Previously the `streisand-gateway` and `streisand-mirror` roles were responsible for all aspects of the Shadowsocks documentation & mirroring. Now, the `shadowsocks` role creates a `/var/www/streisand/shadowsocks/` directory and generates an `index.html` in there with connection instructions. Similarly, the client software is mirrored to `/var/www/streisand/mirror/shadowsocks/` and an `index.html` is generated in there to list the available clients & their installation instructions. The `playbooks/roles/streisand-gateway/templates/index.md.j2` index template can conditionally generate a link to the installation instructions: ``` {% if streisand_shadowsocks_enabled %} * [Shadowsocks](/shadowsocks/) {% endif %} ``` Similarly, the index can conditionally generate a link to the service client mirror: ``` {% if streisand_shadowsocks_enabled %} * [Shadowsocks](/mirror/shadowsocks/) {% endif %} ``` The same approach is taken in the mirror index template: `playbooks/roles/streisand-mirror/templates/mirror-index.md.j2`. Common Daemon Config ------------------------ Some Streisand services have to modify base daemon configurations in order to provide services like DNS/DHCP to interfaces they create. Where possible we should strive to avoid having centralized "monolith" configurations and instead use the "config.d/" approach to allow modular configuration of these shared daemons. The [original usecase](https://lists.debian.org/debian-devel/2010/04/msg00352.html) for these directories closely matches the situation Streisand is in: Once upon a time, most UNIX software was controlled by a single configuration file per software package, and all the configuration details for that package went into that file. This worked reasonably well when that file was hand-crafted by the system administrator for local needs. When distribution packaging became more and more common, it became clear that we needed better ways of forming such configuration files out of multiple fragments, often provided by multiple independent packages. Each package that needs to configure some shared service should be able to manage only its configuration without having to edit a shared configuration file used by other packages. The most common convention adopted was to permit including a directory full of configuration files, where anything dropped into that directory would become active and part of that configuration. A concrete example of this common daemon problem is the `dnsmasq` service/role and its interactions with the `wireguard` and `openvpn` roles. Prior to modularization the `dnsmasq` role made sure the daemon listened on all required intefaces by having one `/etc/dnsmasq.conf` config file generated by the `dnsmasq` role's `dnsmasq.conf.j2` template. The template had one `listen-address` configuration parameter that listed all of the interface addresses in a comma-separated form: ``` listen-address=127.0.0.1,{{ dnsmasq_openvpn_tcp_ip }},{{ dnsmasq_openvpn_udp_ip }},{{ dnsmasq_wireguard_ip }} ``` This meant that if the `openvpn` or `wireguard` roles were disabled/removed the template for the `dnsmasq` config would fail due to the missing variables. The approach used to fix this was to have the base `/etc/dnsmasq.conf` file specify only `listen-address=127.0.0.1` and update the `openvpn` and `wireguard` roles to write their own `listen-address` fragments from a template to their own config files in `/etc/dnsmasq.d/`. Now the `dnsmasq` role is decoupled from the `openvpn` and `wireguard` roles that can be added/removed at will. ================================================ FILE: documentation/testing.md ================================================ CI Testing ========================== Streisand has a `.travis.yml` file that powers [continuous integration tests](https://travis-ci.org/jlund/streisand). It works by installing required test tools (e.g. shellcheck) and an Ansible environment on a Ubuntu Trusty (14.04) machine. The `test.sh` wrapper script -------------------------------- The `test/test.sh` wrapper script is invoked to perform tests and supports an argument for specifying what actions should be taken. The following are supported arguments: * **"setup"** prepares the local environment for running a Streisand [LXC](https://linuxcontainers.org/lxc/introduction/) instance. The instance is managed via [LXD](https://linuxcontainers.org/lxd/). * **"syntax"** checks the Streisand playbooks for Ansible syntax errors. * **"run"** runs the CI tests. It assumes the local environment is already prepared from a previous "setup" run. * **"ci"** combines "setup" and "run". * **"full"** performs the same as "ci" but additionally adds verbose output to the Ansible run. This is very verbose but can be useful for diagnosing tricky broken builds. * By **default** the wrapper will run "syntax". Working around things that won't work in Travis ----------------------------------------------- Some playbooks/tasks can't be run in CI because of limitations imposed by the containerization or Travis. One example of this is installing a Tor relay. To work around this playbooks/tasks that break in CI can be gated on the `streisand_ci` variable, which is `true` only for CI runs. Where possible it's best to minimize the use of this variable for conditional execution because we want as much code to be tested as possible! Kernel Modules --------------------- By design LXC containers share the Linux kernel they use with the host machine. This means playbooks/services that require a kernel module (e.g. Libreswan) must build the kernel module on the host machine. Local Testing ========================== For local testing Streisand includes a `Vagrantfile` that creates two virtual machines: `streisand-host` and `streisand-client`. The `streisand-host` machine is provisioned with the standard Streisand playbooks and replicates a Streisand server created with a cloud provider, but running on your local computer. Note that a throwaway SSH key pair is created in the `streisand-host` virtual machine in the location specified in the Anisible variable `streisand_ssh_private_key`. This is not needed to SSH into the virtual machine using `vagrant ssh streisand-host` and is only used by the provisioning process. The `streisand-client` machine is provisioned specifically to act as a client of the `streisand-host`. It connects to the `streiand-host`'s HTTPS gateway to download client configuration files that are used by test scripts to ensure that services work "end-to-end". Using Vagrant for Local Testing ------------------------------- 1. [Install Vagrant](https://www.vagrantup.com/docs/installation/) 2. Clone the Streisand repository and enter the directory. git clone https://github.com/StreisandEffect/streisand.git && cd streisand 3. If this is your first time following these steps, create & start the `streisand-host` and `streisand-client` virtual machines with: `vagrant up` 4. To re-run the Streisand playbooks, the virtual machines can be re-provisioned with: `vagrant up --provision` Remote Testing ========================== For testing an existing Streisand server there is a `Vagrantfile.remotetest` Vagrantifle. As compared to the stock `Vagrantfile` for local testing the `Vagrantfile.remotetest` Vagrantfile does not include a `streisand-host` machine. This remotetest variant is useful to "smoke test" an existing Streisand server, or to provide end-to-end testing of a cloud provisioner. Using Vagrant for Remote Testing (Easy Way) -------------------------------------------- 1. [Install Vagrant](https://www.vagrantup.com/docs/installation/) 2. Clone the Streisand repository and enter the directory. git clone https://github.com/StreisandEffect/streisand.git && cd streisand 3. Run the `remote_test.sh` helper script and give it the remote server IP & gateway password when prompted: ./tests/remote_test.sh Using Vagrant for Remote Testing (Hard Way) -------------------------------------------- 1. [Install Vagrant](https://www.vagrantup.com/docs/installation/) 2. Clone the Streisand repository and enter the directory. git clone https://github.com/StreisandEffect/streisand.git && cd streisand 3. Edit `Vagrantfile.remotetest` and replace the `streisand_ip` host variable with the IP of the remote server. 4. Create the `generated-docs/gateway-password.txt` file with the gateway password of the Streisand server 5. If this is your first time following these steps, create & start the `streisand-client` virtual machine with: VAGRANT_VAGRANTFILE=Vagrantfile.remotetest vagrant up 4. To re-run the Streisand playbooks, the virtual machines can be re-provisioned with: VAGRANT_VAGRANTFILE=Vagrantfile.remotetest vagrant up --provision Misc Tricks ========================== * Uncomment the lines setting the `ansible.verbose` value in the Vagrantfiles to increase the verbosity of the Ansible playbook runs. * Skip the `./tests/remote_test.sh` prompts using `printf`: STREISAND_SERVER_IP=XX.XX.XX.XX; STREISAND_PASSWORD='gateway-password-goes-here'; printf "$STREISAND_SERVER_IP\n$STREISAND_PASSWORD\n" | ./tests/remote_test.sh ================================================ FILE: global_vars/default-site.yml ================================================ --- # Site specific Streisand configuration. # # This file is mutated by the playbooks/customize.yml tasks when a user chooses # to customize which Streisand services are installed. # The SSH private key that Ansible will use to connect to the Streisand node. # The associated public key will be used if required when provisioning cloud # nodes for the authorized_keys file. streisand_ssh_private_key: "~/.ssh/id_rsa" vpn_clients: 10 streisand_ad_blocking_enabled: no streisand_openconnect_enabled: yes streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: yes streisand_shadowsocks_v2ray_enabled: no streisand_ssh_forward_enabled: yes # By default sshuttle is disabled because it creates a `sshuttle` user that has # full shell privileges on the Streisand host streisand_sshuttle_enabled: no streisand_stunnel_enabled: yes streisand_tinyproxy_enabled: yes streisand_tor_enabled: no streisand_wireguard_enabled: yes streisand_cloudflared_enabled: no ================================================ FILE: global_vars/globals.yml ================================================ --- # If using regular cleartext DNS then dnsmasq will set these upstream DNS servers upstream_dns_servers: - 1.1.1.1 - 1.0.0.1 # If using DNS-over-HTTPS with cloudflared then the upstream servers and queries can be set in: # playbooks/roles/cloudflared/defaults/main.yml streisand_client_test: no streisand_site_vars: "{{ lookup('env','HOME') }}/.streisand/site.yml" ================================================ FILE: global_vars/integration/test-site.yml ================================================ --- # Test site configuration for the end-to-end integration tests. # Don't ask questions streisand_noninteractive: true confirmation: true streisand_domain_var: "" streisand_admin_email_var: "" # Take a few extra steps during server provisioning to make the client tests work streisand_client_test: true # Only services with corresponding tests are enabled. streisand_ad_blocking_enabled: yes streisand_shadowsocks_enabled: yes streisand_ssh_forward_enabled: yes streisand_openvpn_enabled: yes streisand_wireguard_enabled: yes streisand_openconnect_enabled: yes streisand_tor_enabled: no streisand_stunnel_enabled: yes streisand_tinyproxy_enabled: yes # TODO(@cpu): The services below need some manner of integration test written streisand_sshuttle_enabled: no ================================================ FILE: global_vars/noninteractive/amazon-site.yml ================================================ --- # Example site specific configuration for a noninteractive AWS deployment. # # Copy this and edit it as needed before running streisand-new-cloud-server. # streisand_noninteractive: true confirmation: true # The SSH private key that Ansible will use to connect to the Streisand node. # # This will be added to the AWS console and given the name streisand-ssh. streisand_ssh_private_key: "~/.ssh/id_rsa" vpn_clients: 10 streisand_ad_blocking_enabled: no streisand_openconnect_enabled: yes streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: yes streisand_ssh_forward_enabled: yes # By default sshuttle is disabled because it creates a `sshuttle` user that has # full shell privileges on the Streisand host streisand_sshuttle_enabled: no streisand_stunnel_enabled: yes streisand_tinyproxy_enabled: yes streisand_tor_enabled: no streisand_wireguard_enabled: yes # The AWS region number. # # See ./playbooks/amazon.yml for numbering. # # Note: aws_region_var must be a number in quotes, e.g. "3" not 3. aws_region_var: "16" # The VPC and subnet IDs to use. They can be empty strings to indicate that a # VPC will not be used. aws_vpc_id_var: "" aws_vpc_subnet_id_var: "" aws_instance_name: streisand # The AWS credentials to use. aws_access_key: "" aws_secret_key: "" # Definitions needed for Let's Encrypt HTTPS (or TLS) certificate setup. # # If these are both left as empty strings, Let's Encrypt will not be set up and # a self-signed certificate will be used instead. # # The domain to use for Let's Encrypt certificate. streisand_domain_var: "" # The admin email address for Let's Encrypt certificate registration. streisand_admin_email_var: "" ================================================ FILE: global_vars/noninteractive/azure-site.yml ================================================ --- # Example site specific configuration for a noninteractive Azure deployment. # # Copy this and edit it as needed before running streisand-new-cloud-server. # # Ensure that you have the azure credentials file set up at ~/.azure/credentials # streisand_noninteractive: true confirmation: true # The SSH private key that Ansible will use to connect to the Streisand node. streisand_ssh_private_key: "~/.ssh/id_rsa" vpn_clients: 10 streisand_ad_blocking_enabled: no streisand_openconnect_enabled: yes streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: yes streisand_ssh_forward_enabled: yes # By default sshuttle is disabled because it creates a `sshuttle` user that has # full shell privileges on the Streisand host streisand_sshuttle_enabled: no streisand_stunnel_enabled: yes streisand_tinyproxy_enabled: yes streisand_tor_enabled: no streisand_wireguard_enabled: yes # The region to deploy into. # # North America: # 1: East US (Virginia) # 2: East US 2 (Virginia) # 3: Central US (Iowa) # 4: North Central US (Illinois) # 5: South Central US (Texas) # 6: West Central US (West Central US) # 7: West US (California) # 8: West US 2 (West US 2) # 9: US Gov Virginia (Virginia) # 10: US Gov Iowa (Iowa) # 11: US DoD East (US DoD East) # 12: US DoD Central (US DoD Central) # 13: Canada East (Quebec City) # 14: Canada Central (Toronto) # # South America: # 15: Brazil South (Sao Paulo State) # # Asia: # 16: Southeast Asia (Singapore) # 17: East Asia (Hong Kong) # 18: China East (Shanghai) # 19: China North (Beijing) # 20: Japan East (Tokyo, Saitama) # 21: Japan West (Osaka) # 22: Korea Central (Seoul) # 23: Korea South (Busan) # 24: Central India (Pune) # 25: West India (Mumbai) # 26: South India (Chennai) # # Australia: # 27: Australia East (New South Wales) # 28: Australia Southeast (Victoria) # # Europe: # 29: North Europe (Ireland) # 30: West Europe (Netherlands) # 31: Germany Central (Frankfurt) # 32: Germany Northeast (Magdeburg) # 33: UK West (Cardiff) # 34: UK South (London) # # Note: azure_region_var must be a number in quotes, e.g. "1" not 1. azure_region_var: "1" azure_instance_name_var: streisand # Definitions needed for Let's Encrypt HTTPS (or TLS) certificate setup. # # If these are both left as empty strings, Let's Encrypt will not be set up and # a self-signed certificate will be used instead. # # The domain to use for Let's Encrypt certificate. streisand_domain_var: "" # The admin email address for Let's Encrypt certificate registration. streisand_admin_email_var: "" ================================================ FILE: global_vars/noninteractive/digitalocean-site.yml ================================================ --- # Example site specific configuration for a noninteractive Digital Ocean # deployment. # # Copy this and edit it as needed before running streisand-new-cloud-server. # streisand_noninteractive: true confirmation: true # The SSH private key that Ansible will use to connect to the Streisand node. # # The corresponding public key must be added to the Digital Ocean control panel # and the name given to it referenced below in the do_ssh_name variable. # The corresponding public key must be uploaded to Digital Ocean and the name # given to it referenced below in the do_ssh_name variable. streisand_ssh_private_key: "~/.ssh/id_rsa" vpn_clients: 10 streisand_ad_blocking_enabled: no streisand_openconnect_enabled: yes streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: yes streisand_ssh_forward_enabled: yes # By default sshuttle is disabled because it creates a `sshuttle` user that has # full shell privileges on the Streisand host streisand_sshuttle_enabled: no streisand_stunnel_enabled: yes streisand_tinyproxy_enabled: yes streisand_tor_enabled: no streisand_wireguard_enabled: yes # The Digital Ocean region number. # # 1. Amsterdam (Datacenter 2) # 2. Amsterdam (Datacenter 3) # 3. Bangalore # 4. Frankfurt # 5. London # 6. New York (Datacenter 1) # 7. New York (Datacenter 2) # 8. New York (Datacenter 3) # 9. San Francisco (Datacenter 1) # 10. San Francisco (Datacenter 2) # 11. Singapore # 12. Toronto # # Note: do_region must be a number in quotes, e.g. "2" not 2. do_region: "2" do_server_name: streisand # Add the Digital Ocean access token here. do_access_token_entry: "" # The name given to the key in the DigitalOcean control panel. do_ssh_name: streisand # Definitions needed for Let's Encrypt HTTPS (or TLS) certificate setup. # # If these are both left as empty strings, Let's Encrypt will not be set up and # a self-signed certificate will be used instead. # # The domain to use for Let's Encrypt certificate. streisand_domain_var: "" # The admin email address for Let's Encrypt certificate registration. streisand_admin_email_var: "" ================================================ FILE: global_vars/noninteractive/google-site.yml ================================================ --- # Example site specific configuration for a noninteractive Google Compute Engine # deployment. # # Copy this and edit it as needed before running streisand-new-cloud-server. # streisand_noninteractive: true confirmation: true # The SSH private key that Ansible will use to connect to the Streisand node. streisand_ssh_private_key: "~/.ssh/id_rsa" vpn_clients: 10 streisand_ad_blocking_enabled: no streisand_openconnect_enabled: yes streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: yes streisand_ssh_forward_enabled: yes # By default sshuttle is disabled because it creates a `sshuttle` user that has # full shell privileges on the Streisand host streisand_sshuttle_enabled: no streisand_stunnel_enabled: yes streisand_tinyproxy_enabled: yes streisand_tor_enabled: no streisand_wireguard_enabled: yes # Server location: # # 1. Central US "us-central1-a" # 2. Central US "us-central1-b" # 3. Central US "us-central1-c" # 4. Central US "us-central1-f" # 5. Eastern US "us-east4-a" # 6. Eastern US "us-east4-b" # 7. Eastern US "us-east4-c" # 8. Eastern US "us-east1-b" # 9. Eastern US "us-east1-c" # 10. Eastern US "us-east1-d" # 11. Western US "us-west1-a" # 12. Western US "us-west1-b" # 13. Western US "us-west1-c" # 14. Western Europe "europe-west1-b" # 15. Western Europe "europe-west1-c" # 16. Western Europe "europe-west1-d" # 17. Western Europe "europe-west2-a" # 18. Western Europe "europe-west2-b" # 19. Western Europe "europe-west2-c" # 20. Western Europe "europe-west3-a" # 21. Western Europe "europe-west3-b" # 22. Western Europe "europe-west3-c" # 23. Western Europe "europe-west4-a" # 24. Western Europe "europe-west4-b" # 25. Western Europe "europe-west4-c" # 26. East Asia "asia-east1-a" # 27. East Asia "asia-east1-b" # 28. East Asia "asia-east1-c" # 29. Northeast Asia "asia-northeast1-a" # 30. Northeast Asia "asia-northeast1-b" # 31. Northeast Asia "asia-northeast1-c" # 32. South Asia "asia-south1-a" # 33. South Asia "asia-south1-b" # 34. South Asia "asia-south1-c" # 35. Southeast Asia "asia-southeast1-a" # 36. Southeast Asia "asia-southeast1-b" # 37. Southeast Australia "australia-southeast1-a" # 38. Southeast Australia "australia-southeast1-b" # 39. Southeast Australia "australia-southeast1-c" # # Named zones are more robust if you use non-interactive deployments. You can still use # the numbered index, however the number can change, whereas the name will stay the same. # Keep the idx variable non-empty if you use named zones. gce_zone_idx: "do not remove this var if you have defined gce_zone" gce_zone: us-central1-c gce_server_name: streisand # The full path of your unique service account credentials file. See: # https://docs.ansible.com/ansible/guide_gce.html#credentials # https://support.google.com/cloud/answer/6158849?hl=en&ref_topic=6262490#serviceaccounts gce_json_file_location: "" # Definitions needed for Let's Encrypt HTTPS (or TLS) certificate setup. # # If these are both left as empty strings, Let's Encrypt will not be set up and # a self-signed certificate will be used instead. # # The domain to use for Let's Encrypt certificate. streisand_domain_var: "" # The admin email address for Let's Encrypt certificate registration. streisand_admin_email_var: "" ================================================ FILE: global_vars/noninteractive/linode-site.yml ================================================ --- # Example site specific configuration for a noninteractive Linode deployment. # # Copy this and edit it as needed before running streisand-new-cloud-server. # streisand_noninteractive: true confirmation: true # The SSH private key that Ansible will use to connect to the Streisand node. streisand_ssh_private_key: "~/.ssh/id_rsa" vpn_clients: 10 streisand_ad_blocking_enabled: no streisand_openconnect_enabled: yes streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: yes streisand_ssh_forward_enabled: yes # By default sshuttle is disabled because it creates a `sshuttle` user that has # full shell privileges on the Streisand host streisand_sshuttle_enabled: no streisand_stunnel_enabled: yes streisand_tinyproxy_enabled: yes streisand_tor_enabled: no streisand_wireguard_enabled: yes # Choose the server location. # 1. Atlanta # 2. Dallas # 3. Frankfurt # 4. Fremont # 5. London # 6. Newark # 7. Singapore # 8. Tokyo # 9. Tokyo 2 # # Note: linode_datacenter must be a number in quotes, e.g. "7" not 7. linode_datacenter: "7" linode_server_name: streisand # Obtain the API key from the Linode Manager console. linode_api_key: "" # Definitions needed for Let's Encrypt HTTPS (or TLS) certificate setup. # # If these are both left as empty strings, Let's Encrypt will not be set up and # a self-signed certificate will be used instead. # # The domain to use for Let's Encrypt certificate. streisand_domain_var: "" # The admin email address for Let's Encrypt certificate registration. streisand_admin_email_var: "" ================================================ FILE: global_vars/noninteractive/local-site.yml ================================================ --- # Example site specific configuration for a noninteractive local machine # deployment. # # Copy this and edit it as needed before running streisand-local. # streisand_noninteractive: true confirmation: true # Change this to the location of a key on the local system. streisand_ssh_private_key: "~/.ssh/id_rsa" vpn_clients: 10 streisand_ad_blocking_enabled: no streisand_openconnect_enabled: yes streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: yes streisand_ssh_forward_enabled: yes # By default sshuttle is disabled because it creates a `sshuttle` user that has # full shell privileges on the Streisand host streisand_sshuttle_enabled: no streisand_stunnel_enabled: yes streisand_tinyproxy_enabled: yes streisand_tor_enabled: no streisand_wireguard_enabled: yes # Definitions needed for Let's Encrypt HTTPS (or TLS) certificate setup. # # If these are both left as empty strings, Let's Encrypt will not be set up and # a self-signed certificate will be used instead. # # The domain to use for Let's Encrypt certificate. streisand_domain_var: "" # The admin email address for Let's Encrypt certificate registration. streisand_admin_email_var: "" ================================================ FILE: global_vars/noninteractive/rackspace-site.yml ================================================ --- # Example site specific configuration for a noninteractive Rackspace deployment. # # Copy this and edit it as needed before running streisand-new-cloud-server. # streisand_noninteractive: true confirmation: true # The SSH private key that Ansible will use to connect to the Streisand node. streisand_ssh_private_key: "~/.ssh/id_rsa" vpn_clients: 10 streisand_ad_blocking_enabled: no streisand_openconnect_enabled: yes streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: yes streisand_ssh_forward_enabled: yes # By default sshuttle is disabled because it creates a `sshuttle` user that has # full shell privileges on the Streisand host streisand_sshuttle_enabled: no streisand_stunnel_enabled: yes streisand_tinyproxy_enabled: yes streisand_tor_enabled: no streisand_wireguard_enabled: yes # Choose the region to deploy into. # # 1. Chicago # 2. Dallas # 3. Hong Kong # 4. Northern Virginia # 5. Sydney # # Note: rackspace_region must be a number in quotes, e.g. "1" not 1. rackspace_region: "1" rackspace_server_name: streisand # Obtain these credentials from the Rackspace Cloud Control Panel. rackspace_username: "" rackspace_api_key: "" # Definitions needed for Let's Encrypt HTTPS (or TLS) certificate setup. # # If these are both left as empty strings, Let's Encrypt will not be set up and # a self-signed certificate will be used instead. # # The domain to use for Let's Encrypt certificate. streisand_domain_var: "" # The admin email address for Let's Encrypt certificate registration. streisand_admin_email_var: "" ================================================ FILE: inventories/inventory ================================================ [localhost] localhost ansible_connection=local ansible_python_interpreter=python3 # Uncomment the following lines and update the IP address if you would # like to use Streisand to configure a server that is already running # (e.g. a server at a provider not natively supported by Streisand). # # Multiple servers can be configured simultaneously if multiple IP # addresses are defined. # # If you already have SSH fingerprints for the hosts you are configuring # (e.g. using `ssh-keyscan`) then you should also comment out the # 'host_key_checking = False' line in the ansible.cfg file. That setting # is only sensible and convenient when connecting to a brand-new host. # # [streisand-host] # 255.255.255.255 # # If the SSH user to be used to login is not "root", specify it here with # the ansible_user directive. Streisand will sudo automatically. If sudo # requires a password, use the --ask-become-pass command line option. # # [streisand-host] # 255.255.255.255 ansible_user=ubuntu ================================================ FILE: inventories/inventory-local-provision ================================================ # inventory-local-provision is a pre-built inventory file useful for doing an # advanced local install of Streisand where the server running Ansible is the # server that will be configured. # Settings for the provisioning process [localhost] # This must specify the Python interpreter when provisioning, because # "python" may be in, for example, a virtualenv without python-apt. localhost ansible_connection=local ansible_python_interpreter=/usr/bin/python # Settings for the Streisand host that will be provisioned [streisand-host] # If you need to override the name of the server (e.g. because the system # hostname is not the desired server name for documentation/etc) then add a host # var named "streisand_server_name" with the desired value. localhost ansible_connection=local streisand_noninteractive=true ================================================ FILE: library/digital_ocean_droplet.py ================================================ #!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright: Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # Origin: @jackivanov's PR#61643 for ansible: # https://github.com/ansible/ansible/blob/b8b416d30282d6aef4b3ffb7fcb01a0dd2da9f59/lib/ansible/modules/cloud/digital_ocean/digital_ocean_droplet.py # This will be removed on fix to upstream from __future__ import absolute_import, division, print_function __metaclass__ = type ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'} DOCUMENTATION = ''' --- module: digital_ocean_droplet short_description: Create and delete a DigitalOcean droplet description: - Create and delete a droplet in DigitalOcean and optionally wait for it to be active. version_added: "2.8" author: "Gurchet Rai (@gurch101)" options: state: description: - Indicate desired state of the target. default: present choices: ['present', 'absent'] id: description: - Numeric, the droplet id you want to operate on. aliases: ['droplet_id'] name: description: - String, this is the name of the droplet - must be formatted by hostname rules. unique_name: description: - require unique hostnames. By default, DigitalOcean allows multiple hosts with the same name. Setting this to "yes" allows only one host per name. Useful for idempotence. default: False type: bool size: description: - This is the slug of the size you would like the droplet created with. aliases: ['size_id'] image: description: - This is the slug of the image you would like the droplet created with. aliases: ['image_id'] region: description: - This is the slug of the region you would like your server to be created in. aliases: ['region_id'] ssh_keys: description: - array of SSH key (numeric) ID that you would like to be added to the server. required: False private_networking: description: - add an additional, private network interface to droplet for inter-droplet communication. default: False type: bool user_data: description: - opaque blob of data which is made available to the droplet required: False ipv6: description: - enable IPv6 for your droplet. required: False default: False type: bool wait: description: - Wait for the droplet to be active before returning. If wait is "no" an ip_address may not be returned. required: False default: True type: bool wait_timeout: description: - How long before wait gives up, in seconds, when creating a droplet. default: 120 backups: description: - indicates whether automated backups should be enabled. required: False default: False type: bool monitoring: description: - indicates whether to install the DigitalOcean agent for monitoring. required: False default: False type: bool tags: description: - List, A list of tag names as strings to apply to the Droplet after it is created. Tag names can either be existing or new tags. required: False volumes: description: - List, A list including the unique string identifier for each Block Storage volume to be attached to the Droplet. required: False oauth_token: description: - DigitalOcean OAuth token. Can be specified in C(DO_API_KEY), C(DO_API_TOKEN), or C(DO_OAUTH_TOKEN) environment variables aliases: ['API_TOKEN'] required: True requirements: - "python >= 2.6" ''' EXAMPLES = ''' - name: create a new droplet digital_ocean_droplet: state: present name: mydroplet oauth_token: XXX size: 2gb region: sfo1 image: ubuntu-16-04-x64 wait_timeout: 500 register: my_droplet - debug: msg: "ID is {{ my_droplet.data.droplet.id }}, IP is {{ my_droplet.data.ip_address }}" - name: ensure a droplet is present digital_ocean_droplet: state: present id: 123 name: mydroplet oauth_token: XXX size: 2gb region: sfo1 image: ubuntu-16-04-x64 wait_timeout: 500 ''' RETURN = ''' # Digital Ocean API info https://developers.digitalocean.com/documentation/v2/#droplets data: description: a DigitalOcean Droplet returned: changed type: dict sample: { "ip_address": "104.248.118.172", "ipv6_address": "2604:a880:400:d1::90a:6001", "private_ipv4_address": "10.136.122.141", "droplet": { "id": 3164494, "name": "example.com", "memory": 512, "vcpus": 1, "disk": 20, "locked": true, "status": "new", "kernel": { "id": 2233, "name": "Ubuntu 14.04 x64 vmlinuz-3.13.0-37-generic", "version": "3.13.0-37-generic" }, "created_at": "2014-11-14T16:36:31Z", "features": ["virtio"], "backup_ids": [], "snapshot_ids": [], "image": {}, "volume_ids": [], "size": {}, "size_slug": "512mb", "networks": {}, "region": {}, "tags": ["web"] } } ''' import time import json from ansible.module_utils.basic import AnsibleModule, env_fallback from ansible.module_utils.digital_ocean import DigitalOceanHelper class DODroplet(object): def __init__(self, module): self.rest = DigitalOceanHelper(module) self.module = module self.wait = self.module.params.pop('wait', True) self.wait_timeout = self.module.params.pop('wait_timeout', 120) self.unique_name = self.module.params.pop('unique_name', False) # pop the oauth token so we don't include it in the POST data self.module.params.pop('oauth_token') def get_by_id(self, droplet_id): if not droplet_id: return None response = self.rest.get('droplets/{0}'.format(droplet_id)) json_data = response.json if response.status_code == 200: return json_data return None def get_by_name(self, droplet_name): if not droplet_name: return None page = 1 while page is not None: response = self.rest.get('droplets?page={0}'.format(page)) json_data = response.json if response.status_code == 200: for droplet in json_data['droplets']: if droplet['name'] == droplet_name: return {'droplet': droplet} if 'links' in json_data and 'pages' in json_data['links'] and 'next' in json_data['links']['pages']: page += 1 else: page = None return None def get_addresses(self, data): """ Expose IP addresses as their own property allowing users extend to additional tasks """ _data = data for k, v in data.items(): setattr(self, k, v) networks = _data['droplet']['networks'] for network in networks.get('v4', []): if network['type'] == 'public': _data['ip_address'] = network['ip_address'] else: _data['private_ipv4_address'] = network['ip_address'] for network in networks.get('v6', []): if network['type'] == 'public': _data['ipv6_address'] = network['ip_address'] else: _data['private_ipv6_address'] = network['ip_address'] return _data def get_droplet(self): json_data = self.get_by_id(self.module.params['id']) if not json_data and self.unique_name: json_data = self.get_by_name(self.module.params['name']) return json_data def create(self): json_data = self.get_droplet() droplet_data = None if json_data: droplet_data = self.get_addresses(json_data) self.module.exit_json(changed=False, data=droplet_data) if self.module.check_mode: self.module.exit_json(changed=True) data = self.module.params try: del data['id'] except KeyError: pass response = self.rest.post('droplets', data=data) json_data = response.json if response.status_code >= 400: self.module.fail_json(changed=False, msg=json_data['message']) if self.wait: json_data = self.ensure_power_on(json_data['droplet']['id']) droplet_data = self.get_addresses(json_data) self.module.exit_json(changed=True, data=droplet_data) def delete(self): json_data = self.get_droplet() if json_data: if self.module.check_mode: self.module.exit_json(changed=True) response = self.rest.delete('droplets/{0}'.format(json_data['droplet']['id'])) json_data = response.json if response.status_code == 204: self.module.exit_json(changed=True, msg='Droplet deleted') self.module.fail_json(changed=False, msg='Failed to delete droplet') else: self.module.exit_json(changed=False, msg='Droplet not found') def ensure_power_on(self, droplet_id): end_time = time.time() + self.wait_timeout while time.time() < end_time: response = self.rest.get('droplets/{0}'.format(droplet_id)) json_data = response.json if json_data['droplet']['status'] == 'active': return json_data time.sleep(min(2, end_time - time.time())) self.module.fail_json(msg='Wait for droplet powering on timeout') def core(module): state = module.params.pop('state') droplet = DODroplet(module) if state == 'present': droplet.create() elif state == 'absent': droplet.delete() def main(): module = AnsibleModule( argument_spec=dict( state=dict(choices=['present', 'absent'], default='present'), oauth_token=dict( aliases=['API_TOKEN'], no_log=True, fallback=(env_fallback, ['DO_API_TOKEN', 'DO_API_KEY', 'DO_OAUTH_TOKEN']) ), name=dict(type='str'), size=dict(aliases=['size_id']), image=dict(aliases=['image_id']), region=dict(aliases=['region_id']), ssh_keys=dict(type='list'), private_networking=dict(type='bool', default=False), backups=dict(type='bool', default=False), monitoring=dict(type='bool', default=False), id=dict(aliases=['droplet_id'], type='int'), user_data=dict(default=None), ipv6=dict(type='bool', default=False), volumes=dict(type='list'), tags=dict(type='list'), wait=dict(type='bool', default=True), wait_timeout=dict(default=120, type='int'), unique_name=dict(type='bool', default=False), ), required_one_of=( ['id', 'name'], ), required_if=([ ('state', 'present', ['name', 'size', 'image', 'region']), ]), supports_check_mode=True, ) core(module) if __name__ == '__main__': main() ================================================ FILE: playbooks/amazon.yml ================================================ --- - name: Provision the EC2 Server # ============================== hosts: localhost connection: local gather_facts: yes vars: # The region dict is generated from ./util/print-aws-regions.py regions: "1": "ap-east-1" "2": "ap-northeast-1" "3": "ap-northeast-2" "4": "ap-northeast-3" "5": "ap-south-1" "6": "ap-southeast-1" "7": "ap-southeast-2" "8": "ca-central-1" "9": "eu-central-1" "10": "eu-north-1" "11": "eu-west-1" "12": "eu-west-2" "13": "eu-west-3" "14": "sa-east-1" "15": "us-east-1" "16": "us-east-2" "17": "us-west-1" "18": "us-west-2" # These variable files are included so the ec2-security-group role # knows which ports to open vars_files: - roles/openconnect/defaults/main.yml - roles/openvpn/defaults/main.yml - roles/shadowsocks/defaults/main.yml - roles/ssh/defaults/main.yml - roles/lets-encrypt/vars/main.yml - roles/streisand-gateway/defaults/main.yml - roles/stunnel/defaults/main.yml - roles/tor-bridge/defaults/main.yml - roles/wireguard/defaults/main.yml vars_prompt: # The region prompt is generated from ./util/print-aws-regions.py # Don't forget to update the default if it changes. - name: "aws_region_var" prompt: | In what region should the server be located? 1. ap-east-1 Asia Pacific (Hong Kong) 2. ap-northeast-1 Asia Pacific (Tokyo) 3. ap-northeast-2 Asia Pacific (Seoul) 4. ap-northeast-3 Asia Pacific (Osaka-Local) 5. ap-south-1 Asia Pacific (Mumbai) 6. ap-southeast-1 Asia Pacific (Singapore) 7. ap-southeast-2 Asia Pacific (Sydney) 8. ca-central-1 Canada (Central) 9. eu-central-1 EU (Frankfurt) 10. eu-north-1 EU (Stockholm) 11. eu-west-1 EU (Ireland) 12. eu-west-2 EU (London) 13. eu-west-3 EU (Paris) 14. sa-east-1 South America (São Paulo) 15. us-east-1 US East (N. Virginia) 16. us-east-2 US East (Ohio) 17. us-west-1 US West (N. California) 18. us-west-2 US West (Oregon) Please choose the number of your region. Press enter for default (#16) region. default: "16" private: no - name: "aws_vpc_id_var" prompt: | In which VPC would you like to create the server and security group (e.g. vpc-89d740ee)? Press enter to use the default VPC. private: no - name: "aws_vpc_subnet_id_var" prompt: | From which subnet should the server receive an address (e.g. subnet-78d9a232)? Press enter to use the default subnet. private: no - name: "aws_instance_name" prompt: "\nWhat should the server be named? Press enter for default (streisand).\n" default: "streisand" private: no - name: "aws_access_key" prompt: "\n\nThe following information can be found in the IAM Management Console.\nhttps://console.aws.amazon.com/iam/home?#security_credential\n\nWhat is your AWS Access Key ID?\n" private: no - name: "aws_secret_key" prompt: "\nWhat is your AWS Secret Access Key?\n" private: no - name: "confirmation" prompt: "\nStreisand will now set up your server. This process usually takes around ten minutes. Press Enter to begin setup...\n" pre_tasks: - name: Set the AWS Region fact set_fact: aws_region: "{{ regions[aws_region_var] }}" - name: Set the AWS VPC ID fact set_fact: aws_vpc_id: "{{ aws_vpc_id_var }}" when: aws_vpc_id_var != "" - name: Set the AWS VPC Subnet ID fact set_fact: aws_vpc_subnet_id: "{{ aws_vpc_subnet_id_var }}" when: aws_vpc_subnet_id_var != "" roles: - genesis-amazon - import_playbook: ssh-setup.yml - import_playbook: cloud-status.yml - import_playbook: python.yml - import_playbook: ec2-metadata-instance.yml - import_playbook: streisand.yml ... ================================================ FILE: playbooks/azure.yml ================================================ --- - name: Provision the Azure Server (Resource Manager mode) # ======================================================== hosts: localhost connection: local gather_facts: yes vars: regions: "1": "eastus" "2": "eastus2" "3": "centralus" "4": "northcentralus" "5": "southcentralus" "6": "westcentralus" "7": "westus" "8": "westus2" "9": "usgovvirginia" "10": "usgoviowa" "11": "usdodeast" "12": "usdodcentral" "13": "canadaeast" "14": "canadacentral" "15": "brazilsouth" "16": "southeastasia" "17": "eastasia" "18": "chinaeast" "19": "chinanorth" "20": "japaneast" "21": "japanwest" "22": "koreacentral" "23": "koreasouth" "24": "centralindia" "25": "westindia" "26": "southindia" "27": "australiaeast" "28": "australiasoutheast" "29": "australiacentral" "30": "australiacentral2" "31": "northeurope" "32": "westeurope" "33": "germanycentral" "34": "germanynortheast" "35": "ukwest" "36": "uksouth" "37": "francecentral" "38": "francesouth" # These variable files are included so the azure-security-group role # knows which ports to open vars_files: - roles/openconnect/defaults/main.yml - roles/openvpn/defaults/main.yml - roles/shadowsocks/defaults/main.yml - roles/ssh/defaults/main.yml - roles/lets-encrypt/vars/main.yml - roles/streisand-gateway/defaults/main.yml - roles/stunnel/defaults/main.yml - roles/tor-bridge/defaults/main.yml - roles/wireguard/defaults/main.yml vars_prompt: - name: "azure_region_var" prompt: > What region should the server be located in? North America: 1: East US (Virginia) 2: East US 2 (Virginia) 3: Central US (Iowa) 4: North Central US (Illinois) 5: South Central US (Texas) 6: West Central US (West Central US) 7: West US (California) 8: West US 2 (West US 2) 9: US Gov Virginia (Virginia) 10: US Gov Iowa (Iowa) 11: US DoD East (US DoD East) 12: US DoD Central (US DoD Central) 13: Canada East (Quebec City) 14: Canada Central (Toronto) South America: 15: Brazil South (Sao Paulo State) Asia: 16: Southeast Asia (Singapore) 17: East Asia (Hong Kong) 18: China East (Shanghai) 19: China North (Beijing) 20: Japan East (Tokyo, Saitama) 21: Japan West (Osaka) 22: Korea Central (Seoul) 23: Korea South (Busan) 24: Central India (Pune) 25: West India (Mumbai) 26: South India (Chennai) Australia: 27: Australia East (New South Wales) 28: Australia Southeast (Victoria) 29: Austrailia Central (Canberra) 30: Austrailia Central 2 (Canberra) Europe: 31: North Europe (Ireland) 32: West Europe (Netherlands) 33: Germany Central (Frankfurt) 34: Germany Northeast (Magdeburg) 35: UK West (Cardiff) 36: UK South (London) 37: France Central (Paris) 38: France South (Marseille) Please choose the number of your region: Press enter for default (#1) region. default: "1" private: no - name: "azure_instance_name_var" prompt: "\nWhat should the server be named? Press enter for default (streisand).\n" default: "streisand" private: no - name: "confirmation_credentials" prompt: "\nEnsure that you have the azure credentials file: ~/.azure/credentials \nDetails on generating this can be found at https://github.com/StreisandEffect/streisand/blob/master/documentation/AZURE.md" - name: "confirmation" prompt: "\nStreisand will now set up your server: This process usually takes around ten minutes: Press Enter to begin setup...\n" pre_tasks: - name: Set the Azure Region fact set_fact: azure_region: "{{ regions[azure_region_var] }}" - name: Set the Azure Instance Name fact set_fact: azure_instance_name: "{{ azure_instance_name_var | regex_replace('\\s', '_') }}" roles: - genesis-azure - import_playbook: ssh-setup.yml - import_playbook: cloud-status.yml - import_playbook: streisand.yml ... ================================================ FILE: playbooks/cloud-status.yml ================================================ --- - name: Checking instance status # ========================================= hosts: streisand-host gather_facts: no remote_user: "root" become: true tasks: - name: Wait for cloud-init to complete raw: bash -c "for i in {1..30}; do if [ -f /var/lib/cloud/instance/boot-finished ]; then exit 0; fi; sleep 1; done; exit 1;" register: result changed_when: False failed_when: result.rc != 0 ... ================================================ FILE: playbooks/customize.yml ================================================ --- - name: Customize enabled Streisand services hosts: localhost gather_facts: no vars_prompt: - name: streisand_ssh_private_key prompt: "Enter the path to your SSH private key, or press enter for default " default: "~/.ssh/id_rsa" private: no - name: vpn_clients prompt: "How many VPN client profiles should be generated per-service (min: 1 max: 20)? Press enter for default " default: 10 private: no - name: streisand_ad_blocking_enabled prompt: "Enable DNS-based ad-blocking? Press enter for default " default: "no" private: no - name: streisand_openconnect_enabled prompt: "Enable OpenConnect? Press enter for default " default: "yes" private: no - name: streisand_openvpn_enabled prompt: "Enable OpenVPN? Press enter for default " default: "yes" private: no - name: streisand_stunnel_enabled prompt: "Enable stunnel service (only allowed for OpenVPN)? Press enter for default " default: "yes" private: no - name: streisand_shadowsocks_enabled prompt: "Enable Shadowsocks? Press enter for default " default: "yes" private: no - name: streisand_shadowsocks_v2ray_enabled prompt: "Enable v2ray-plugin for Shadowsocks? Press enter for default " default: "no" private: no - name: streisand_ssh_forward_enabled prompt: "Enable SSH Forward User? (Note: A SOCKS proxy only user will be added, no shell). Press enter for default " default: "yes" private: no - name: streisand_sshuttle_enabled prompt: "Enable sshuttle? (Note: A full shell access user will be added) Press enter for default " default: "no" private: no - name: streisand_tinyproxy_enabled prompt: "Enable tinyproxy? Press enter for default " default: "yes" private: no - name: streisand_tor_enabled prompt: "Enable Tor? Press enter for default " default: "no" private: no - name: streisand_wireguard_enabled prompt: "Enable WireGuard? Press enter for default " default: "yes" private: no - name: streisand_cloudflared_enabled prompt: "[BROKEN ON SOME PROVIDERS, including AWS] Enable DNS-over-HTTPS (cloudflared)? Press enter for default " default: "no" private: no tasks: - lineinfile: path: "{{ streisand_site_vars }}" regexp: "^streisand_ssh_private_key: .*$" line: "streisand_ssh_private_key: {{ streisand_ssh_private_key }}" - lineinfile: path: "{{ streisand_site_vars }}" regexp: "^vpn_clients: [\\d]+$" line: "vpn_clients: {{ vpn_clients }}" - lineinfile: path: "{{ streisand_site_vars }}" regexp: "^streisand_ad_blocking_enabled: (?:yes|no|True|False)$" line: "streisand_ad_blocking_enabled: {{ streisand_ad_blocking_enabled|bool }}" - lineinfile: path: "{{ streisand_site_vars }}" regexp: "^streisand_openconnect_enabled: (?:yes|no|True|False)$" line: "streisand_openconnect_enabled: {{ streisand_openconnect_enabled|bool }}" - lineinfile: path: "{{ streisand_site_vars }}" regexp: "^streisand_openvpn_enabled: (?:yes|no|True|False)$" line: "streisand_openvpn_enabled: {{ streisand_openvpn_enabled|bool }}" - lineinfile: path: "{{ streisand_site_vars }}" regexp: "^streisand_shadowsocks_enabled: (?:yes|no|True|False)$" line: "streisand_shadowsocks_enabled: {{ streisand_shadowsocks_enabled|bool }}" - lineinfile: path: "{{ streisand_site_vars }}" regexp: "^streisand_shadowsocks_v2ray_enabled: (?:yes|no|True|False)$" line: "streisand_shadowsocks_v2ray_enabled: {{ streisand_shadowsocks_v2ray_enabled|bool }}" - lineinfile: path: "{{ streisand_site_vars }}" regexp: "^streisand_ssh_forward_enabled: (?:yes|no|True|False)$" line: "streisand_ssh_forward_enabled: {{ streisand_ssh_forward_enabled|bool }}" - lineinfile: path: "{{ streisand_site_vars }}" regexp: "^streisand_sshuttle_enabled: (?:yes|no|True|False)$" line: "streisand_sshuttle_enabled: {{ streisand_sshuttle_enabled|bool }}" - lineinfile: path: "{{ streisand_site_vars }}" regexp: "^streisand_stunnel_enabled: (?:yes|no|True|False)$" line: "streisand_stunnel_enabled: {{ streisand_stunnel_enabled|bool }}" - lineinfile: path: "{{ streisand_site_vars }}" regexp: "^streisand_tinyproxy_enabled: (?:yes|no|True|False)$" line: "streisand_tinyproxy_enabled: {{ streisand_tinyproxy_enabled|bool }}" - lineinfile: path: "{{ streisand_site_vars }}" regexp: "^streisand_tor_enabled: (?:yes|no|True|False)$" line: "streisand_tor_enabled: {{ streisand_tor_enabled|bool }}" - lineinfile: path: "{{ streisand_site_vars }}" regexp: "^streisand_wireguard_enabled: (?:yes|no|True|False)$" line: "streisand_wireguard_enabled: {{ streisand_wireguard_enabled|bool }}" - lineinfile: path: "{{ streisand_site_vars }}" regexp: "^streisand_cloudflared_enabled: (?:yes|no|True|False)$" line: "streisand_cloudflared_enabled: {{ streisand_cloudflared_enabled|bool }}" ================================================ FILE: playbooks/digitalocean.yml ================================================ --- - name: Provision the DigitalOcean Server # ======================================= hosts: localhost connection: local gather_facts: yes vars: regions: "1": "ams2" "2": "ams3" "3": "blr1" "4": "fra1" "5": "lon1" "6": "nyc1" "7": "nyc2" "8": "nyc3" "9": "sfo1" "10": "sfo2" "11": "sgp1" "12": "tor1" vars_prompt: - name: "do_region" prompt: > What region should the server be located in? 1. Amsterdam (Datacenter 2) 2. Amsterdam (Datacenter 3) 3. Bangalore 4. Frankfurt 5. London 6. New York (Datacenter 1) 7. New York (Datacenter 2) 8. New York (Datacenter 3) 9. San Francisco (Datacenter 1) 10. San Francisco (Datacenter 2) 11. Singapore 12. Toronto Please choose the number of your region. Press enter for default (#2) region. default: "2" private: no - name: "do_server_name" prompt: "\nWhat should the server be named? Press enter for default (streisand).\n" default: "streisand" private: no - name: "do_access_token_entry" prompt: | Personal Access Tokens allow Streisand to create a droplet for you. New Personal Access Tokens can be generated in the DigitalOcean control panel. To generate a new token please do the following: * Go to https://cloud.digitalocean.com/settings/applications * Click 'Generate New Token' * Give the token a name (it is arbitrary) * Be sure to select the 'Write' scope as well (this is not optional) * Click 'Generate Token' * Copy the long string that is generated and paste it below. If this field is left blank, the environment variable DO_API_KEY will be used. What is your DigitalOcean Personal Access Token? private: no - name: "do_ssh_name" prompt: "\n\nThe following information can be found on your DigitalOcean control panel.\nhttps://cloud.digitalocean.com/settings/security\n\nWhat is the name of the DigitalOcean SSH key that you would like to use?\n * If you have never uploaded an SSH key to DigitalOcean then the default\n value will work!\n * This key should match your Streisand SSH key file (default: ~/.ssh/id_rsa.pub).\n\ * DigitalOcean requires SSH keys to be unique. You cannot upload multiple\n keys that have the same value under different names.\n\n If you see an error that says 'SSH Key failed to be created' once the setup\n process starts, then this is the problem. You can retry the setup process\n using the name of the existing SSH key from the DigitalOcean control panel\n that matches the contents of your RSA public key.\n" default: "streisand" private: no - name: "confirmation" prompt: "\nStreisand will now set up your server. This process usually takes around ten minutes. Press Enter to begin setup...\n" roles: - genesis-digitalocean - import_playbook: ssh-setup.yml - import_playbook: cloud-status.yml - import_playbook: streisand.yml ... ================================================ FILE: playbooks/ec2-metadata-instance.yml ================================================ --- - name: Drop packets to the Amazon EC2 metadata instance hosts: streisand-host gather_facts: no remote_user: "root" become: true tasks: - name: Copy the EC2 metadata instance oneshot unit file copy: src: "./roles/genesis-amazon/files/aws-metadata-instance.service" dest: "/etc/systemd/system/aws-metadata-instance.service" owner: root group: root mode: 0644 - name: Enable and reload the EC2 metadata instance systemd: name: aws-metadata-instance.service daemon_reload: yes enabled: yes state: restarted ================================================ FILE: playbooks/existing-server.yml ================================================ --- # existing-server.yml is an advanced provisioning option that doesn't use a genesis # role to create a new server and instead applies Streisand to an existing # remote server. - name: Register the genesis role in use hosts: localhost gather_facts: yes tasks: - set_fact: streisand_genesis_role: "existing-server" - include: ssh-setup.yml - name: Check SSH access to existing server hosts: streisand-host gather_facts: no remote_user: "{{ lookup('env', 'SSH_USER') }}" become: true tasks: - block: - raw: whoami args: executable: /bin/bash changed_when: False rescue: - fail: msg: "Unable to SSH to existing streisand-host.\nEnsure private key corresponding to \"{{ streisand_ssh_private_key }}\" is loaded in your SSH key agent.\nTry using `ssh-keygen -i {{ streisand_ssh_private_key }} to generate your key if it does not exist\n" # Ensure Python is installed on the system - import_playbook: python.yml # Try and detect the remote server's provider & apply required workarounds - import_playbook: provider-detect.yml - name: Prepare the remote server for Streisand # ========================================= hosts: streisand-host remote_user: "{{ lookup('env', 'SSH_USER') }}" become: true - import_playbook: streisand.yml ... ================================================ FILE: playbooks/google.yml ================================================ --- - name: Provision the GCE Server # ======================================= hosts: localhost connection: local gather_facts: yes vars: zones: "1": "us-central1-a" "2": "us-central1-b" "3": "us-central1-c" "4": "us-central1-f" "5": "us-east4-a" "6": "us-east4-b" "7": "us-east4-c" "8": "us-east1-b" "9": "us-east1-c" "10": "us-east1-d" "11": "us-west1-a" "12": "us-west1-b" "13": "us-west1-c" "14": "europe-west1-b" "15": "europe-west1-c" "16": "europe-west1-d" "17": "europe-west2-a" "18": "europe-west2-b" "19": "europe-west2-c" "20": "europe-west3-a" "21": "europe-west3-b" "22": "europe-west3-c" "23": "europe-west4-a" "24": "europe-west4-b" "25": "europe-west4-c" "26": "asia-east1-a" "27": "asia-east1-b" "28": "asia-east1-c" "29": "asia-east2-a" "30": "asia-east2-b" "31": "asia-east2-c" "32": "asia-northeast1-a" "33": "asia-northeast1-b" "34": "asia-northeast1-c" "35": "asia-south1-a" "36": "asia-south1-b" "37": "asia-south1-c" "38": "asia-southeast1-a" "39": "asia-southeast1-b" "40": "australia-southeast1-a" "41": "australia-southeast1-b" "42": "australia-southeast1-c" "43": "southamerica-east1-a" "44": "southamerica-east1-b" "45": "southamerica-east1-c" # These variable files are included so the gce-network role # knows which ports to open vars_files: - roles/openconnect/defaults/main.yml - roles/openvpn/defaults/main.yml - roles/shadowsocks/defaults/main.yml - roles/ssh/defaults/main.yml - roles/lets-encrypt/vars/main.yml - roles/streisand-gateway/defaults/main.yml - roles/stunnel/defaults/main.yml - roles/tor-bridge/defaults/main.yml - roles/wireguard/defaults/main.yml vars_prompt: - name: "gce_zone_idx" prompt: > What zone should the server be located in? 1. Central US (Iowa A) 2. Central US (Iowa B) 3. Central US (Iowa C) 4. Central US (Iowa F) 5. Eastern US (Northern Virginia A) 6. Eastern US (Northern Virginia B) 7. Eastern US (Northern Virginia C) 8. Eastern US (South Carolina B) 9. Eastern US (South Carolina C) 10. Eastern US (South Carolina D) 11. Western US (Oregon A) 12. Western US (Oregon B) 13. Western US (Oregon C) 14. Western Europe (Belgium B) 15. Western Europe (Belgium C) 16. Western Europe (Belgium D) 17. Western Europe (London A) 18. Western Europe (London B) 19. Western Europe (London C) 20. Western Europe (Frankfurt A) 21. Western Europe (Frankfurt B) 22. Western Europe (Frankfurt C) 23. Western Europe (Netherlands A) 24. Western Europe (Netherlands B) 25. Western Europe (Netherlands C) 26. East Asia (Taiwan A) 27. East Asia (Taiwan B) 28. East Asia (Taiwan C) 29. East Asia (Hong Kong A) 30. East Asia (Hong Kong B) 31. East Asia (Hong Kong C) 32. Northeast Asia (Tokyo A) 33. Northeast Asia (Tokyo B) 34. Northeast Asia (Tokyo C) 35. South Asia (Mumbai A) 36. South Asia (Mumbai B) 37. South Asia (Mumbai C) 38. Southeast Asia (Singapore A) 39. Southeast Asia (Singapore B) 40. Southeast Australia (Sydney A) 41. Southeast Australia (Sydney B) 42. Southeast Australia (Sydney C) 43. South America (São Paulo A) 44. South America (São Paulo B) 45. South America (São Paulo C) Please choose the number of your zone. Press enter for default (#3) zone. default: "3" when: gce_zone is not defined private: no - name: "gce_server_name" prompt: "\nWhat should the server be named? Press enter for default (streisand).\n" default: "streisand" private: no - name: "gce_json_file_location" prompt: "\n\nThe full path of your unique service account credentials file. Details on generating this can be found at \nhttps://docs.ansible.com/ansible/guide_gce.html#credentials\n and \nhttps://support.google.com/cloud/answer/6158849?hl=en&ref_topic=6262490#serviceaccounts\n" default: "{{ lookup('env','HOME') }}/streisand.json" private: no - name: "confirmation" prompt: "\nStreisand will now set up your server. This process usually takes around ten minutes. Press Enter to begin setup...\n" pre_tasks: - name: Set the Google Compute Engine Zone interactive set_fact: gce_zone: "{{ zones[gce_zone_idx] }}" when: gce_zone is not defined - name: Register JSON file contents command: cat {{ gce_json_file_location }} register: gce_json_file_contents changed_when: False - name: Set JSON file contents fact set_fact: gce_json_contents_fact: "{{ gce_json_file_contents.stdout | from_json }}" - name: Set the Google Compute Engine Service Account Email set_fact: gce_service_account_email: "{{ gce_json_contents_fact.client_email }}" - name: Set the Google Compute Engine Project ID set_fact: gce_project_id: "{{ gce_json_contents_fact.project_id }}" roles: - genesis-google - import_playbook: ssh-setup.yml - import_playbook: cloud-status.yml - import_playbook: streisand.yml ... ================================================ FILE: playbooks/group_vars/all ================================================ --- streisand_ci: no streisand_noninteractive: no ================================================ FILE: playbooks/lets-encrypt.yml ================================================ --- # lets-encrypt.yml asks questions about Streisand instance domain name, which # is used to request TLS certificates from Let's Encrypt. If user fails to # answer these questions, lets-encrypt role would not be run, and self-signed # certificates are generated and used instead. - name: Collect information about the Streisand domain # ========================================= hosts: streisand-host gather_facts: no vars_files: - roles/lets-encrypt/vars/main.yml vars_prompt: - name: "streisand_domain_var" prompt: | Do you have a fully qualified domain pointed at your Streisand server? This is an optional question. If you have a domain that points to your Streisand server, the installation scripts can request a Let's Encrypt HTTPS certificate for you automatically. If you do not provide one or the request fails, a self-signed certificate will be used instead. If you have just created a new cloud server in previous steps now is a good time to point your fully qualified domain to your server's public address. Make sure the fully qualified domain resolves to the correct IP address before proceeding. Please type your fully qualified domain below. Press enter to skip. private: no - name: "streisand_admin_email_var" prompt: | Which email address do you want to use as a contact for the Streisand server's Let's Encrypt certificate? This is an optional question. If you supply an email address Let's Encrypt will send you important (but infrequent) notifications about your certificate. These messages include any upcoming certificate expirations, and important changes to the Let's Encrypt service. The email provided will not be used for anything else or shared with the Streisand developers. Please type your contact email below. Press enter to skip. private: no pre_tasks: - name: Set Streisand domain set_fact: streisand_domain: "{{ streisand_domain_var }}" when: streisand_domain_var != "" - name: Set Streisand admin email set_fact: streisand_admin_email: "{{ streisand_admin_email_var }}" - name: Enable Let's Encrypt role set_fact: streisand_le_enabled: yes when: streisand_domain_var != "" - name: Disable Let's Encrypt role set_fact: streisand_le_enabled: no streisand_domain: "" streisand_admin_email: "" le_ok: False when: streisand_domain_var == "" ... ================================================ FILE: playbooks/linode.yml ================================================ --- - name: Provision the Linode Server # ================================= hosts: localhost connection: local gather_facts: yes vars: regions: "1": "ca-central" "2": "us-central" "3": "us-west" "4": "us-southeast" "5": "us-east" "6": "eu-west" "7": "ap-south" "8": "eu-central" "9": "ap-northeast" "10": "ap-west" "11": "ap-southeast" vars_prompt: - name: "linode_datacenter" prompt: > What region should the server be located in? 1. Toronto 2. Dallas 3. Fremont 4. Atlanta 5. Newark 6. London 7. Singapore 8. Frankfurt 9. Tokyo 10. Mumbai 11. Sydney Please choose the number of your region. Press enter for default (#7) region. default: "7" private: no - name: "linode_server_name" prompt: "\nWhat should the server be named? Press enter for default (streisand).\n" default: "streisand" private: no - name: "linode_api_token" prompt: "\n\nThe following information can be found in the Linode Manager:\nhttps://cloud.linode.com/profile/tokens\n\nWhat is your Linode API Token?\n" private: no - name: "confirmation" prompt: "\nStreisand will now set up your server. This process usually takes around ten minutes. Press Enter to begin setup...\n" roles: - genesis-linode - import_playbook: ssh-setup.yml - import_playbook: streisand.yml ... ================================================ FILE: playbooks/localhost.yml ================================================ --- # localhost.yml is an advanced provisioning option that doesn't use a genesis # role to create a new server and instead applies Streisand to the localhost. # Ensure Python is installed on the system - import_playbook: python.yml # Try and detect localhost's provider & apply required workarounds - import_playbook: provider-detect.yml - name: Prepare the localhost for Streisand # ========================================= hosts: streisand-host remote_user: "root" become: true tasks: - set_fact: streisand_genesis_role: "localhost" # If there's no streisand_ipv4_address set then we try our best using # the interface Ansible thinks is the default. - name: "Set the Streisand IPv4 address to the Ansible default: interface: {{ ansible_default_ipv4.alias }} address: {{ ansible_default_ipv4.address }}" set_fact: # The ansible_default_ipv4 address is calculated based on the default # ipv4 route to 8.8.8.8 for the system, and for local provisioning on # _most_ providers, seems to work well for finding the external facing IP. # See `provider-detect.yml` for cases where this approach doesn't work # (e.g. GCE) and workarounds. streisand_ipv4_address: "{{ ansible_default_ipv4.address }}" when: streisand_ipv4_address is not defined - import_playbook: streisand.yml ... ================================================ FILE: playbooks/provider-detect.yml ================================================ --- # provider-detect.yml is used for the advanced existing/localhost provisioning # to try and determine if a given host is from a specific cloud provider. If # the cloud provider is detected, some facts/workarounds may be set to aid in # provisioning in place of one of the geneiss roles. - name: Try to detect Cloud providers for specific overrides # ========================================= hosts: streisand-host remote_user: "root" become: true tasks: - name: "Install dmidecode to use for BIOS version detection" apt: package: dmidecode - name: "Try to determine localhost Cloud provider name from BIOS version" # Looking into ways to detect EC2/GCE instances without relying on # connections to metadata IPs this solution[0] seemed the most # straightforward & reliable. # https://serverfault.com/a/775063 command: dmidecode -s bios-version register: streisand_localhost_bios_name changed_when: False ignore_errors: True - name: "Set BIOS name fact from dmidecode if possible" set_fact: streisand_bios_name: "{{ streisand_localhost_bios_name.stdout }}" when: streisand_localhost_bios_name.rc == 0 - name: "...Otherwise set unknown BIOS fact" set_fact: streisand_bios_name: "Unknown" when: streisand_bios_name is undefined # GCE specific work-arounds: # * None of the interfaces have the external IP bound, so this must be set # from the Google Platform medatadata service. - block: - name: Warn about manual provisioning of GCE instances pause: prompt: "You are running Streisand in an advanced mode against an existing GCE instance. Unlike the standard GCE provisioning mode this means Streisand *CAN NOT* open ports on your behalf. You will need to manually create the correct VPC Network and firewall rules. See 'generated-docs/' for the firewall-information.html file at the end of installation for a list of ports to open. The Streisand maintainers are not able to support this configuration. Press [enter] to continue" when: not streisand_noninteractive - name: "Find the external GCE IP from Google Metadata" # NOTE: We use the command module and `curl` here because (AFAICT) # there isn't a way to `register` a `get_url` call and it seems # hackier overall to add an intermediate step using a file on disk # # Metadata URL & the required "Metadata-Flavor" are explained in the # Google Cloud Platform documentation[0]. # [0]: https://cloud.google.com/compute/docs/storing-retrieving-metadata#querying command: curl -H Metadata-Flavor:Google http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip register: streisand_gce_external_ip changed_when: False - name: "Set the Streiand IPv4 address to the GCE external IP: {{ streisand_gce_external_ip.stdout }}" set_fact: streisand_ipv4_address: "{{ streisand_gce_external_ip.stdout }}" when: "streisand_bios_name == 'Google'" # Amazon EC2 specific work-arounds: # * None of the interfaces have the external IP bound, so this must be set # from the EC2 medatadata service. - block: - name: Warn about manual provisioning of EC2 instances pause: prompt: "You are running Streisand in an advanced mode against an existing Amazon EC2 instance. Unlike the standard EC2 provisioning mode this means Streisand *CAN NOT* open ports on your behalf. You will need to manually assinging this machine to a security group with the correct firewall rules. See 'generated-docs/' for the firewall-information.html file at the end of installation for a list of ports to open. The Streisand maintainers are not able to support this configuration. Press [enter] to continue" when: not streisand_noninteractive # EC2 Instance Metadata API is explained in the EC2 docs[0]. # [0]: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html - name: "Find the external EC2 IP from Metadata" command: curl http://169.254.169.254/latest/meta-data/public-ipv4 register: streisand_ec2_external_ip changed_when: False - name: "Set the Streiand IPv4 address to the EC2 external IP: {{ streisand_ec2_external_ip.stdout }}" set_fact: streisand_ipv4_address: "{{ streisand_ec2_external_ip.stdout }}" when: "'amazon' in streisand_bios_name" ... ================================================ FILE: playbooks/python.yml ================================================ --- - name: Prepare the new server for Ansible # ========================================= hosts: streisand-host gather_facts: no remote_user: "root" become: true tasks: - name: Install Python using a raw SSH command to enable the execution of Ansible modules raw: apt update && apt install python -y args: executable: /bin/bash ... ================================================ FILE: playbooks/rackspace.yml ================================================ --- - name: Provision the Rackspace Server # ==================================== hosts: localhost connection: local gather_facts: yes vars: regions: "1": "ORD" "2": "DFW" "3": "HKG" "4": "IAD" "5": "SYD" vars_prompt: - name: "rackspace_region" prompt: > What region should the server be located in? 1. Chicago 2. Dallas 3. Hong Kong 4. Northern Virginia 5. Sydney Please choose the number of your region. Press enter for default (#1) region. default: "1" private: no - name: "rackspace_server_name" prompt: "\nWhat should the server be named? Press enter for default (streisand).\n" private: no default: "streisand" - name: "rackspace_username" prompt: "\nWhat is your Rackspace username?\n" private: no - name: "rackspace_api_key" prompt: "\n\nThe following information can be found in the Rackspace Cloud Control Panel.\nhttps://mycloud.rackspace.com/\n\nWhat is your Rackspace API key?\n" private: no - name: "confirmation" prompt: "\nStreisand will now set up your server. This process usually takes around ten minutes. Press Enter to begin setup...\n" roles: - genesis-rackspace - import_playbook: ssh-setup.yml - import_playbook: cloud-status.yml - import_playbook: streisand.yml ... ================================================ FILE: playbooks/roles/ad-blocking/files/download-blocklists ================================================ #!/bin/bash set -e # Now, if there were only some convenient way of storing etags from # those fetches. download_if_newer () { filename="$1" url="$2" if [ -e "$filename" ]; then curl --time-cond "$filename" -o "$filename" "$url" else curl -o "$filename" "$url" fi } mkdir -p /var/lib/blocklists download_if_newer /var/lib/blocklists/block-hostnames.txt 'https://raw.githubusercontent.com/notracking/hosts-blocklists/master/hostnames.txt' download_if_newer /var/lib/blocklists/block-domains.txt 'https://raw.githubusercontent.com/notracking/hosts-blocklists/master/domains.txt' transform-domain-list /etc/dnsmasq.d/block-domains.conf transform-host-list /etc/dnsmasq-block-hosts echo "addn-hosts=/etc/dnsmasq-block-hosts" >/etc/dnsmasq.d/block-hosts.conf # Sadly, "reload" doesn't work" systemctl restart dnsmasq.service ================================================ FILE: playbooks/roles/ad-blocking/files/download-blocklists.service ================================================ [Unit] Description=Download blocklists [Service] Type=oneshot ExecStart=/usr/local/bin/download-blocklists ================================================ FILE: playbooks/roles/ad-blocking/files/download-blocklists.timer ================================================ [Timer] OnActiveSec=0 OnBootSec=60 OnUnitActiveSec=1d RandomizedDelaySec=1h [Install] WantedBy=network.target ================================================ FILE: playbooks/roles/ad-blocking/files/transform-domain-list ================================================ #!/bin/bash awk ' BEGIN { FS="/"; print "# post-processed by transform-domain-list" } /^#/ { print $0; next; } /^address=\/.*\/0\.0\.0\.0$/ { next; } /^address=\/.*\/::$/ { if ($2 !~ /\./) { print "### no dot found in domain, skipping: " $0; next; } print "address=/" $2 "/0.0.0.0\naddress=/" $2 "/::"; next; } { print "### ERROR unprocessed line: " $0; } ' ================================================ FILE: playbooks/roles/ad-blocking/files/transform-host-list ================================================ #!/bin/bash awk ' BEGIN { print "# post-processed by transform-host-list"; } /^#/ { print $0; next; } /^0\.0\.0\.0 / { next; } /^:: / { if ($2 !~ /\./) { print "### no dot found in hostname, skipping: " $0; next; } print "0.0.0.0 " $2 "\n:: " $2; next; } { print "### ERROR unprocessed line: " $0; } ' ================================================ FILE: playbooks/roles/ad-blocking/tasks/main.yml ================================================ - name: "Install blocklist tools" copy: src: "{{ item }}" dest: /usr/local/bin/ mode: '0755' loop: - download-blocklists - transform-domain-list - transform-host-list - name: "Install blocklist systemd goo" copy: src: "{{ item }}" dest: /etc/systemd/system/ mode: '0644' loop: - download-blocklists.service - download-blocklists.timer - name: "Enable the blocklist download service" systemd: name: download-blocklists.service daemon_reload: true enabled: true state: started - name: "Enable and start the blocklist re-download timer" systemd: name: download-blocklists.timer enabled: true state: started ================================================ FILE: playbooks/roles/azure-security-group/meta/main.yml ================================================ --- allow_duplicates: yes ================================================ FILE: playbooks/roles/azure-security-group/tasks/main.yml ================================================ --- - name: Create Azure resource group azure_rm_resourcegroup: name: "{{ azure_resource_group_name }}" location: "{{ azure_region }}" - name: Create Azure virtual network azure_rm_virtualnetwork: resource_group: "{{ azure_resource_group_name }}" name: "{{ azure_resource_group_name }}" address_prefixes: "10.10.0.0/16" - name: Create Azure subnet azure_rm_subnet: resource_group: "{{ azure_resource_group_name }}" name: "{{ azure_resource_group_name }}" address_prefix: "10.10.0.0/24" virtual_network: "{{ azure_resource_group_name }}" - name: Create Azure public ip azure_rm_publicipaddress: resource_group: "{{ azure_resource_group_name }}" allocation_method: Static name: "{{ azure_resource_group_name }}" - name: Open all of the necessary ports across every service in the Azure security group azure_rm_securitygroup: name: "{{ azure_resource_group_name }}" resource_group: "{{ azure_resource_group_name }}" location: "{{ azure_region }}" rules: # Nginx # --- - name: "AllowNGINX" destination_port_range: "{{ nginx_port }}" protocol: Tcp access: Allow direction: Inbound priority: 101 # SSH # --- - name: "AllowSSHTCP" destination_port_range: "{{ ssh_port }}" protocol: Tcp access: Allow direction: Inbound priority: 108 # HTTP (Let's Encrypt) # --- - name: "AllowHTTP" destination_port_range: "{{ le_port }}" protocol: Tcp access: Allow direction: Inbound priority: 120 # Shadowsocks # --- - name: Open Shadowsocks ports in the Azure security group azure_rm_securitygroup: name: "{{ azure_resource_group_name }}" resource_group: "{{ azure_resource_group_name }}" location: "{{ azure_region }}" rules: # Shadowsocks TCP # --- - name: "AllowShadowsocksTCP" destination_port_range: "{{ shadowsocks_server_port }}" protocol: Tcp access: Allow direction: Inbound priority: 106 # Shadowsocks UDP # --- - name: "AllowShadowsocksUDP" destination_port_range: "{{ shadowsocks_server_port }}" protocol: Udp access: Allow direction: Inbound priority: 107 when: streisand_shadowsocks_enabled # WireGuard # --- - name: Open WireGuard ports in the Azure security group azure_rm_securitygroup: name: "{{ azure_resource_group_name }}" resource_group: "{{ azure_resource_group_name }}" location: "{{ azure_region }}" rules: - name: "AllowWireguard" destination_port_range: "{{ wireguard_port }}" protocol: Udp access: Allow direction: Inbound priority: 112 when: streisand_wireguard_enabled # OpenConnect (ocserv) # --- - name: Open the OpenConnect ports in the Azure security group azure_rm_securitygroup: name: "{{ azure_resource_group_name }}" resource_group: "{{ azure_resource_group_name }}" location: "{{ azure_region }}" rules: # OpenConnect TCP # --- - name: "AllowOCServTCP" destination_port_range: "{{ ocserv_port }}" protocol: Tcp access: Allow direction: Inbound priority: 102 # OpenConnect UDP # --- - name: "AllowOCServUDP" destination_port_range: "{{ ocserv_port }}" protocol: Udp access: Allow direction: Inbound priority: 103 when: streisand_openconnect_enabled # OpenVPN # --- - name: Open the OpenVPN ports in the Azure security group azure_rm_securitygroup: name: "{{ azure_resource_group_name }}" resource_group: "{{ azure_resource_group_name }}" location: "{{ azure_region }}" rules: # OpenVPN TCP # --- - name: "AllowOpenVPNTCP" destination_port_range: "{{ openvpn_port }}" protocol: Tcp access: Allow direction: Inbound priority: 104 # OpenVPN UDP # --- - name: "AllowOpenVPNUDP" destination_port_range: "{{ openvpn_port_udp }}" protocol: Udp access: Allow direction: Inbound priority: 105 when: streisand_openvpn_enabled # stunnel # --- - name: Open the stunnel port in the Azure security group azure_rm_securitygroup: name: "{{ azure_resource_group_name }}" resource_group: "{{ azure_resource_group_name }}" location: "{{ azure_region }}" rules: - name: "AllowStunnelTCP" destination_port_range: "{{ stunnel_remote_port }}" protocol: Tcp access: Allow direction: Inbound priority: 109 when: streisand_openvpn_enabled and streisand_stunnel_enabled # Tor # --- - name: Open Tor ports in the Azure security group azure_rm_securitygroup: name: "{{ azure_resource_group_name }}" resource_group: "{{ azure_resource_group_name }}" location: "{{ azure_region }}" rules: # Tor TCP # --- - name: "AllowTorTCP" destination_port_range: "{{ tor_orport }}" protocol: Tcp access: Allow direction: Inbound priority: 110 # Tor obfs4 TCP port # --- - name: "AllowOBFS4TCP" destination_port_range: "{{ tor_obfs4_port }}" protocol: Tcp access: Allow direction: Inbound priority: 111 when: streisand_tor_enabled - name: Create Azure network interface azure_rm_networkinterface: resource_group: "{{ azure_resource_group_name }}" name: "{{ azure_resource_group_name }}" virtual_network: "{{ azure_resource_group_name }}" subnet: "{{ azure_resource_group_name }}" public_ip_name: "{{ azure_resource_group_name }}" security_group: "{{ azure_resource_group_name }}" ================================================ FILE: playbooks/roles/azure-security-group/vars/main.yml ================================================ --- azure_resource_group_name: "streisand-{{ azure_instance_name }}" ================================================ FILE: playbooks/roles/certificates/defaults/main.yml ================================================ --- tls_key_country: "US" tls_key_province: "California" tls_key_city: "Beverly Hills" tls_key_org: "ACME CORPORATION" tls_key_ou: "Anvil Department" tls_days_valid: "1825" tls_default_md: "sha256" tls_key_size: "4096" # What type of certificates to be generated # must be explicitly set by playbooks that # include the certificates role generate_ca_server: no generate_client: no generate_pkcs: no ================================================ FILE: playbooks/roles/certificates/tasks/ca-server.yml ================================================ --- - name: "Generate the private keys for the CA and Server certificates" command: openssl genrsa -out {{ item }}.key {{ tls_key_size }} args: chdir: "{{ ca_path }}" creates: "{{ item }}.key" with_items: - ca - server - name: Set the proper permissions on all the private keys file: path: "{{ ca_path }}" recurse: yes state: directory owner: root group: root mode: 0600 - name: Generate CA certificate command: openssl req -nodes -batch -new -x509 -key {{ tls_ca }}.key -days {{ tls_days_valid }} -out {{ tls_ca }}.crt -subj "{{ tls_request_subject }}/CN=ca-certificate" args: creates: "{{ tls_ca }}.crt" - name: Generate a random server common name shell: "{{ streisand_word_gen.long_identifier | trim }} > {{ tls_server_common_name_file }}" args: creates: "{{ tls_server_common_name_file }}" - name: Set permissions on the TLS server common name file file: path: "{{ tls_server_common_name_file }}" owner: root group: root mode: 0600 - name: Register the TLS server common name command: cat "{{ tls_server_common_name_file }}" register: tls_server_common_name changed_when: False - name: Generate the OpenSSL configuration that will be used for the server certificate's req and ca commands template: src: openssl.cnf.j2 dest: "{{ ca_path }}/openssl.cnf" - name: Seed a blank database file that will be used when generating the Server's certificate file: path: "{{ ca_path }}/index.txt" state: touch - name: Seed a serial file that will be used when generating the Server's certificate copy: content: "01" dest: "{{ ca_path }}/serial" - name: Generate CSR for the Server command: openssl req -batch -extensions server -new -key server.key -out server.csr -config {{ ca_path }}/openssl.cnf args: chdir: "{{ ca_path }}" creates: server.csr - name: Generate certificate for the Server command: openssl ca -batch -extensions server -in server.csr -out server.crt -config openssl.cnf args: chdir: "{{ ca_path }}" creates: server.crt ================================================ FILE: playbooks/roles/certificates/tasks/client.yml ================================================ --- - name: Create directories for clients file: path: "{{ tls_client_path }}/{{ client_name.stdout }}" state: directory with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" - name: Generate the private keys for the client certificates command: openssl genrsa -out client.key {{ tls_key_size }} args: chdir: "{{ tls_client_path }}/{{ client_name.stdout }}" creates: client.key with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" - name: Set the proper permissions on all private client keys file: path: "{{ ca_path }}" recurse: yes state: directory owner: root group: root mode: 0600 - name: Generate CSRs for the clients command: openssl req -new -extensions client -key client.key -out client.csr -subj "{{ tls_request_subject }}/CN={{ client_name.stdout }}" -config {{ ca_path }}/openssl.cnf args: chdir: "{{ tls_client_path }}/{{ client_name.stdout }}" creates: client.csr with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" - name: Generate certificates for the clients command: openssl x509 -extensions client -CA {{ tls_ca }}.crt -CAkey {{ tls_ca }}.key -CAcreateserial -req -days {{ tls_days_valid }} -in client.csr -out client.crt -extfile {{ ca_path }}/openssl.cnf args: chdir: "{{ tls_client_path }}/{{ client_name.stdout }}" creates: client.crt with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" - name: Authorize certificates via /etc/allowed_vpn_certs template: src: allowed_vpn_certs.j2 dest: /etc/allowed_vpn_certs owner: root group: root mode: 0644 ================================================ FILE: playbooks/roles/certificates/tasks/main.yml ================================================ --- - import_tasks: ca-server.yml when: generate_ca_server - import_tasks: client.yml when: generate_client - import_tasks: pkcs.yml when: generate_pkcs ================================================ FILE: playbooks/roles/certificates/tasks/pkcs.yml ================================================ --- - name: "Generate a random password that will be used during the PKCS #12 conversion" shell: "{{ streisand_word_gen.weak_password | trim }} > {{ tls_client_path }}/{{ client_name.stdout }}/{{ vpn_name }}-{{ client_name.stdout }}-pkcs12-password" args: creates: "{{ tls_client_path }}/{{ client_name.stdout }}/{{ vpn_name }}-{{ client_name.stdout }}-pkcs12-password" with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" - name: "Set permissions on the PKCS #12 password file" file: path: "{{ tls_client_path }}/{{ client_name.stdout }}/{{ vpn_name }}-{{ client_name.stdout }}-pkcs12-password" owner: root group: root mode: 0600 with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" - name: "Register the PKCS #12 passwords" command: cat {{ tls_client_path }}/{{ client_name.stdout }}/{{ vpn_name }}-{{ client_name.stdout }}-pkcs12-password register: "vpn_client_pkcs12_password_list" with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" changed_when: False - name: "Convert the {{ vpn_name }} client keys and certificates into PKCS #12 format" command: > openssl pkcs12 -export -in {{ tls_client_path }}/{{ vpn_client_password.client_name.stdout }}/client.crt -inkey {{ tls_client_path }}/{{ vpn_client_password.client_name.stdout }}/client.key -name {{ vpn_client_password.client_name.stdout }} -out {{ tls_client_path }}/{{ vpn_client_password.client_name.stdout }}/{{ vpn_client_password.client_name.stdout }}.p12 -certfile {{ tls_client_path }}/ca.crt -passout pass:"{{ vpn_client_password.stdout }}" args: creates: "{{ tls_client_path }}/{{ vpn_client_password.client_name.stdout }}/{{ vpn_client_password.client_name.stdout }}.p12" with_items: "{{ vpn_client_pkcs12_password_list.results }}" loop_control: loop_var: "vpn_client_password" label: "{{ vpn_client_password.client_name.item }}" ================================================ FILE: playbooks/roles/certificates/templates/allowed_vpn_certs.j2 ================================================ # This file lists all the enabled VPN certificate names. Note that # it does not affect WireGuard. {% for client in vpn_client_names.results -%} {{ client.stdout }} {% endfor %} ================================================ FILE: playbooks/roles/certificates/templates/openssl.cnf.j2 ================================================ [ ca ] default_ca = CA_default [ CA_default ] dir = {{ ca_path }} certs = $dir crl_dir = $dir database = $dir/index.txt new_certs_dir = $dir certificate = {{ tls_ca }}.crt serial = $dir/serial crl = $dir/crl.pem private_key = {{ tls_ca }}.key RANDFILE = $dir/.rand copy_extensions = copy x509_extensions = server default_days = {{ tls_days_valid }} default_crl_days= 30 default_md = {{ tls_default_md }} preserve = no policy = policy_anything keyUsage = cRLSign, keyCertSign subjectKeyIdentifier=hash authorityKeyIdentifier=keyid:always,issuer:always [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied name = optional emailAddress = optional [ req ] distinguished_name = req_distinguished_name req_extensions = req_ext [ req_ext ] subjectAltName = @alt_names [ alt_names ] {% for item in tls_sans %} IP.{{ loop.index }} = {{ item }} {% endfor %} [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = {{ tls_key_country }} stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = {{ tls_key_province }} localityName = Locality Name (eg, city) localityName_default = {{ tls_key_city }} 0.organizationName = Organization Name (eg, company) 0.organizationName_default = {{ tls_key_org }} organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = {{ tls_key_ou }} commonName = Common Name (eg, your name or your server\'s hostname) commonName_default = {{ tls_server_common_name.stdout }} [ v3_ca ] keyUsage = digitalSignature, keyEncipherment [ server ] basicConstraints=CA:FALSE nsComment = "Ansible Generated Server Certificate" subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer:always extendedKeyUsage=serverAuth keyUsage = digitalSignature, keyEncipherment subjectAltName = @alt_names [ client ] basicConstraints=CA:FALSE nsComment = "Ansible Generated Client Certificate" subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer:always extendedKeyUsage=clientAuth keyUsage = digitalSignature ================================================ FILE: playbooks/roles/certificates/vars/main.yml ================================================ --- tls_request_subject: "/C={{ tls_key_country }}/ST={{ tls_key_province }}/L={{ tls_key_city }}/O={{ tls_key_org }}/OU={{ tls_key_ou }}" ================================================ FILE: playbooks/roles/cloudflared/defaults/main.yml ================================================ --- cloudflared_base_url: "https://bin.equinox.io/c/VdrWdbjqyF/" cloudflared_amd64_apt: "cloudflared-stable-linux-amd64.deb" cloudflared_amd64_yum: "cloudflared-stable-linux-amd64.rpm" cloudflared_amd64_binary: "cloudflared-stable-linux-amd64.tgz" cloudflared_arm_apt: "cloudflared-stable-linux-arm.deb" cloudflared_arm_yum: "cloudflared-stable-linux-arm.rpm" cloudflared_arm_binary: "cloudflared-stable-linux-arm.tgz" cloudflared_allow_firewall: false cloudflared_enable_service: true cloudflared_upstream1: "https://1.1.1.1/dns-query" cloudflared_upstream2: "https://1.0.0.1/dns-query" cloudflared_port: 5053 cloudflared_options: "proxy-dns --port {{ cloudflared_port }} --upstream {{ cloudflared_upstream1 }} --upstream {{ cloudflared_upstream2 }}" cloudflared_bin_location: /usr/local/bin ================================================ FILE: playbooks/roles/cloudflared/files/cloudflared.service ================================================ [Unit] Description=cloudflared service After=syslog.target network-online.target [Service] Type=simple User=cloudflared EnvironmentFile=/etc/default/cloudflared ExecStart=/usr/local/bin/cloudflared $CLOUDFLARED_OPTS Restart=on-failure RestartSec=10 KillMode=process [Install] WantedBy=multi-user.target ================================================ FILE: playbooks/roles/cloudflared/handlers/main.yml ================================================ --- - name: restart cloudflared service systemd: name: cloudflared.service state: restarted ================================================ FILE: playbooks/roles/cloudflared/meta/main.yml ================================================ --- dependencies: - { role: dnsmasq } - { role: ip-forwarding } ================================================ FILE: playbooks/roles/cloudflared/tasks/install_binary.yml ================================================ --- - name: build filename of file to be downloaded set_fact: cloudflared_file: "{{ vars['cloudflared_'+device_arch+'_binary'] }}" - name: download correct file for device get_url: url: "{{ cloudflared_base_url }}{{ cloudflared_file }}" dest: "/tmp/{{ cloudflared_file }}" #checksum: "{{ cloudflared_file_checksum }}" - name: extract cloudflared into /usr/local/bin unarchive: src: "/tmp/{{ cloudflared_file }}" dest: "{{ cloudflared_bin_location }}" remote_src: yes ================================================ FILE: playbooks/roles/cloudflared/tasks/install_package.yml ================================================ --- - name: build filename of file to be downloaded set_fact: cloudflared_file: "{{ vars['cloudflared_'+device_arch+'_'+ansible_pkg_mgr] }}" - name: download correct file for device get_url: url: "{{ cloudflared_base_url }}{{ cloudflared_file }}" dest: "/tmp/{{ cloudflared_file }}" #checksum: "{{ cloudflared_file_checksum }}" - name: Install a .deb package apt: deb: "/tmp/{{ cloudflared_file }}" state: present register: pkg_mgr_output ignore_errors: true when: ansible_pkg_mgr == 'apt' - name: Install a .rpm package yum: name: "/tmp/{{ cloudflared_file }}" state: present register: pkg_mgr_output ignore_errors: true when: ansible_pkg_mgr == 'yum' ================================================ FILE: playbooks/roles/cloudflared/tasks/main.yml ================================================ --- - stat: path: "{{ cloudflared_bin_location }}/cloudflared" register: cloudflared_binary - set_fact: cloudflared_installed: "{{ cloudflared_binary.stat.exists | default(false) }}" - name: set device architecture and package manager vars set_fact: device_arch: "{{ 'amd64' if ansible_architecture == 'x86_64' else 'arm' }}" - name: install package import_tasks: install_package.yml when: (not cloudflared_installed) and (ansible_pkg_mgr == 'yum' or ansible_pkg_mgr == 'apt') and (ansible_architecture == 'x86_64' or ansible_architecture == 'arm') - name: install binary import_tasks: install_binary.yml when: (not cloudflared_installed) and ((pkg_mgr_output is undefined or pkg_mgr_output is failed) or ansible_architecture == 'armv7l') - name: Set network capabilities for cloudflared capabilities: path: "{{ cloudflared_bin_location }}/cloudflared" capability: cap_net_bind_service+ep state: present when: cloudflared_port|int < 1024 - command: cloudflared update register: update_command changed_when: update_command.rc == '64' - name: create cloudflared nologin user become: yes user: name: cloudflared shell: /usr/sbin/nologin system: True create_home: False - name: set ownership of /usr/local/bin/cloudflared file: path: /usr/local/bin/cloudflared state: file owner: cloudflared group: cloudflared - name: template config file template: src: cloudflared.j2 dest: /etc/default/cloudflared owner: cloudflared group: cloudflared notify: restart cloudflared service tags: systemd - name: copy systemd service copy: src: cloudflared.service dest: /etc/systemd/system/ owner: root group: root mode: 0644 notify: restart cloudflared service register: service tags: systemd - name: enable systemd service service: name: cloudflared enabled: "{{ cloudflared_enable_service }}" when: service.changed tags: systemd - name: Allow port in firewall ufw: rule: allow port: "{{ cloudflared_port }}" comment: "allow cloudflared" when: cloudflared_allow_firewall # DNSMASQ - name: Remove existing upstream servers in dnsmasq replace: path: /etc/dnsmasq.conf regexp: "^server=(.*)$" - name: Set upstream DNS server to cloudflared proxy lineinfile: path: /etc/dnsmasq.conf state: present line: "server=127.0.0.1#{{ cloudflared_port }}" notify: Restart dnsmasq ================================================ FILE: playbooks/roles/cloudflared/templates/cloudflared.j2 ================================================ # Commandline args for cloudflared CLOUDFLARED_OPTS={{ cloudflared_options }} ================================================ FILE: playbooks/roles/cloudflared/vars/main.yml ================================================ --- # Cloudflared variables ================================================ FILE: playbooks/roles/common/files/english.txt ================================================ abandon ability able about above absent absorb abstract absurd abuse access accident account accuse achieve acid acoustic acquire across act action actor actress actual adapt add addict address adjust admit adult advance advice aerobic affair afford afraid again age agent agree ahead aim air airport aisle alarm album alcohol alert alien all alley allow almost alone alpha already also alter always amateur amazing among amount amused analyst anchor ancient anger angle angry animal ankle announce annual another answer antenna antique anxiety any apart apology appear apple approve april arch arctic area arena argue arm armed armor army around arrange arrest arrive arrow art artefact artist artwork ask aspect assault asset assist assume asthma athlete atom attack attend attitude attract auction audit august aunt author auto autumn average avocado avoid awake aware away awesome awful awkward axis baby bachelor bacon badge bag balance balcony ball bamboo banana banner bar barely bargain barrel base basic basket battle beach bean beauty because become beef before begin behave behind believe below belt bench benefit best betray better between beyond bicycle bid bike bind biology bird birth bitter black blade blame blanket blast bleak bless blind blood blossom blouse blue blur blush board boat body boil bomb bone bonus book boost border boring borrow boss bottom bounce box boy bracket brain brand brass brave bread breeze brick bridge brief bright bring brisk broccoli broken bronze broom brother brown brush bubble buddy budget buffalo build bulb bulk bullet bundle bunker burden burger burst bus business busy butter buyer buzz cabbage cabin cable cactus cage cake call calm camera camp can canal cancel candy cannon canoe canvas canyon capable capital captain car carbon card cargo carpet carry cart case cash casino castle casual cat catalog catch category cattle caught cause caution cave ceiling celery cement census century cereal certain chair chalk champion change chaos chapter charge chase chat cheap check cheese chef cherry chest chicken chief child chimney choice choose chronic chuckle chunk churn cigar cinnamon circle citizen city civil claim clap clarify claw clay clean clerk clever click client cliff climb clinic clip clock clog close cloth cloud clown club clump cluster clutch coach coast coconut code coffee coil coin collect color column combine come comfort comic common company concert conduct confirm congress connect consider control convince cook cool copper copy coral core corn correct cost cotton couch country couple course cousin cover coyote crack cradle craft cram crane crash crater crawl crazy cream credit creek crew cricket crime crisp critic crop cross crouch crowd crucial cruel cruise crumble crunch crush cry crystal cube culture cup cupboard curious current curtain curve cushion custom cute cycle dad damage damp dance danger daring dash daughter dawn day deal debate debris decade december decide decline decorate decrease deer defense define defy degree delay deliver demand demise denial dentist deny depart depend deposit depth deputy derive describe desert design desk despair destroy detail detect develop device devote diagram dial diamond diary dice diesel diet differ digital dignity dilemma dinner dinosaur direct dirt disagree discover disease dish dismiss disorder display distance divert divide divorce dizzy doctor document dog doll dolphin domain donate donkey donor door dose double dove draft dragon drama drastic draw dream dress drift drill drink drip drive drop drum dry duck dumb dune during dust dutch duty dwarf dynamic eager eagle early earn earth easily east easy echo ecology economy edge edit educate effort egg eight either elbow elder electric elegant element elephant elevator elite else embark embody embrace emerge emotion employ empower empty enable enact end endless endorse enemy energy enforce engage engine enhance enjoy enlist enough enrich enroll ensure enter entire entry envelope episode equal equip era erase erode erosion error erupt escape essay essence estate eternal ethics evidence evil evoke evolve exact example excess exchange excite exclude excuse execute exercise exhaust exhibit exile exist exit exotic expand expect expire explain expose express extend extra eye eyebrow fabric face faculty fade faint faith fall false fame family famous fan fancy fantasy farm fashion fat fatal father fatigue fault favorite feature february federal fee feed feel female fence festival fetch fever few fiber fiction field figure file film filter final find fine finger finish fire firm first fiscal fish fit fitness fix flag flame flash flat flavor flee flight flip float flock floor flower fluid flush fly foam focus fog foil fold follow food foot force forest forget fork fortune forum forward fossil foster found fox fragile frame frequent fresh friend fringe frog front frost frown frozen fruit fuel fun funny furnace fury future gadget gain galaxy gallery game gap garage garbage garden garlic garment gas gasp gate gather gauge gaze general genius genre gentle genuine gesture ghost giant gift giggle ginger giraffe girl give glad glance glare glass glide glimpse globe gloom glory glove glow glue goat goddess gold good goose gorilla gospel gossip govern gown grab grace grain grant grape grass gravity great green grid grief grit grocery group grow grunt guard guess guide guilt guitar gun gym habit hair half hammer hamster hand happy harbor hard harsh harvest hat have hawk hazard head health heart heavy hedgehog height hello helmet help hen hero hidden high hill hint hip hire history hobby hockey hold hole holiday hollow home honey hood hope horn horror horse hospital host hotel hour hover hub huge human humble humor hundred hungry hunt hurdle hurry hurt husband hybrid ice icon idea identify idle ignore ill illegal illness image imitate immense immune impact impose improve impulse inch include income increase index indicate indoor industry infant inflict inform inhale inherit initial inject injury inmate inner innocent input inquiry insane insect inside inspire install intact interest into invest invite involve iron island isolate issue item ivory jacket jaguar jar jazz jealous jeans jelly jewel job join joke journey joy judge juice jump jungle junior junk just kangaroo keen keep ketchup key kick kid kidney kind kingdom kiss kit kitchen kite kitten kiwi knee knife knock know lab label labor ladder lady lake lamp language laptop large later latin laugh laundry lava law lawn lawsuit layer lazy leader leaf learn leave lecture left leg legal legend leisure lemon lend length lens leopard lesson letter level liar liberty library license life lift light like limb limit link lion liquid list little live lizard load loan lobster local lock logic lonely long loop lottery loud lounge love loyal lucky luggage lumber lunar lunch luxury lyrics machine mad magic magnet maid mail main major make mammal man manage mandate mango mansion manual maple marble march margin marine market marriage mask mass master match material math matrix matter maximum maze meadow mean measure meat mechanic medal media melody melt member memory mention menu mercy merge merit merry mesh message metal method middle midnight milk million mimic mind minimum minor minute miracle mirror misery miss mistake mix mixed mixture mobile model modify mom moment monitor monkey monster month moon moral more morning mosquito mother motion motor mountain mouse move movie much muffin mule multiply muscle museum mushroom music must mutual myself mystery myth naive name napkin narrow nasty nation nature near neck need negative neglect neither nephew nerve nest net network neutral never news next nice night noble noise nominee noodle normal north nose notable note nothing notice novel now nuclear number nurse nut oak obey object oblige obscure observe obtain obvious occur ocean october odor off offer office often oil okay old olive olympic omit once one onion online only open opera opinion oppose option orange orbit orchard order ordinary organ orient original orphan ostrich other outdoor outer output outside oval oven over own owner oxygen oyster ozone pact paddle page pair palace palm panda panel panic panther paper parade parent park parrot party pass patch path patient patrol pattern pause pave payment peace peanut pear peasant pelican pen penalty pencil people pepper perfect permit person pet phone photo phrase physical piano picnic picture piece pig pigeon pill pilot pink pioneer pipe pistol pitch pizza place planet plastic plate play please pledge pluck plug plunge poem poet point polar pole police pond pony pool popular portion position possible post potato pottery poverty powder power practice praise predict prefer prepare present pretty prevent price pride primary print priority prison private prize problem process produce profit program project promote proof property prosper protect proud provide public pudding pull pulp pulse pumpkin punch pupil puppy purchase purity purpose purse push put puzzle pyramid quality quantum quarter question quick quit quiz quote rabbit raccoon race rack radar radio rail rain raise rally ramp ranch random range rapid rare rate rather raven raw razor ready real reason rebel rebuild recall receive recipe record recycle reduce reflect reform refuse region regret regular reject relax release relief rely remain remember remind remove render renew rent reopen repair repeat replace report require rescue resemble resist resource response result retire retreat return reunion reveal review reward rhythm rib ribbon rice rich ride ridge rifle right rigid ring riot ripple risk ritual rival river road roast robot robust rocket romance roof rookie room rose rotate rough round route royal rubber rude rug rule run runway rural sad saddle sadness safe sail salad salmon salon salt salute same sample sand satisfy satoshi sauce sausage save say scale scan scare scatter scene scheme school science scissors scorpion scout scrap screen script scrub sea search season seat second secret section security seed seek segment select sell seminar senior sense sentence series service session settle setup seven shadow shaft shallow share shed shell sheriff shield shift shine ship shiver shock shoe shoot shop short shoulder shove shrimp shrug shuffle shy sibling sick side siege sight sign silent silk silly silver similar simple since sing siren sister situate six size skate sketch ski skill skin skirt skull slab slam sleep slender slice slide slight slim slogan slot slow slush small smart smile smoke smooth snack snake snap sniff snow soap soccer social sock soda soft solar soldier solid solution solve someone song soon sorry sort soul sound soup source south space spare spatial spawn speak special speed spell spend sphere spice spider spike spin spirit split spoil sponsor spoon sport spot spray spread spring spy square squeeze squirrel stable stadium staff stage stairs stamp stand start state stay steak steel stem step stereo stick still sting stock stomach stone stool story stove strategy street strike strong struggle student stuff stumble style subject submit subway success such sudden suffer sugar suggest suit summer sun sunny sunset super supply supreme sure surface surge surprise surround survey suspect sustain swallow swamp swap swarm swear sweet swift swim swing switch sword symbol symptom syrup system table tackle tag tail talent talk tank tape target task taste tattoo taxi teach team tell ten tenant tennis tent term test text thank that theme then theory there they thing this thought three thrive throw thumb thunder ticket tide tiger tilt timber time tiny tip tired tissue title toast tobacco today toddler toe together toilet token tomato tomorrow tone tongue tonight tool tooth top topic topple torch tornado tortoise toss total tourist toward tower town toy track trade traffic tragic train transfer trap trash travel tray treat tree trend trial tribe trick trigger trim trip trophy trouble truck true truly trumpet trust truth try tube tuition tumble tuna tunnel turkey turn turtle twelve twenty twice twin twist two type typical ugly umbrella unable unaware uncle uncover under undo unfair unfold unhappy uniform unique unit universe unknown unlock until unusual unveil update upgrade uphold upon upper upset urban urge usage use used useful useless usual utility vacant vacuum vague valid valley valve van vanish vapor various vast vault vehicle velvet vendor venture venue verb verify version very vessel veteran viable vibrant vicious victory video view village vintage violin virtual virus visa visit visual vital vivid vocal voice void volcano volume vote voyage wage wagon wait walk wall walnut want warfare warm warrior wash wasp waste water wave way wealth weapon wear weasel weather web wedding weekend weird welcome west wet whale what wheat wheel when where whip whisper wide width wife wild will win window wine wing wink winner winter wire wisdom wise wish witness wolf woman wonder wood wool word work world worry worth wrap wreck wrestle wrist write wrong yard year yellow you young youth zebra zero zone zoo ================================================ FILE: playbooks/roles/common/files/footer.html ================================================ ================================================ FILE: playbooks/roles/common/files/header.html ================================================ STREISAND ================================================ FILE: playbooks/roles/common/tasks/detect-public-ip.yml ================================================ --- # detect-public-ip.yml will attempt to identify whether the server's public # IP address is different from what is visible on the host and, if # successfully detected, ask to update the address for documentation and # configuration profiles - name: "Install dns module" apt: package: dnsutils - name: "Initialize lookup variable" set_fact: external_ipv4_address: "presumed_failed" - name: "Check external IP Address through Google" command: dig -4 +short myip.opendns.com @resolver1.opendns.com A register: dig_output - name: "Set the variable to the value" set_fact: external_ipv4_address: "{{ dig_output.stdout | regex_replace('\"', '') }}" when: (dig_output.rc == 0) # Enter this block only when when the IPs are different and query user for updating # to public ip - block: - name: "Initialize the prompt" set_fact: prompt_external_ip: | We have found another public IP address of your server Some cloud providers use load balancers or SDN to make servers externally reachable. It seems - {{ external_ipv4_address }} is publicly visible - {{ streisand_ipv4_address }} is visible on the server Type 'yes' to use {{ external_ipv4_address }} for the VPN Hit 'enter' to skip and use {{ streisand_ipv4_address }}. Skip with 'enter' if you do not know {{ external_ipv4_address }} - name: "Ask user to update to public IP address" pause: prompt: "{{ prompt_external_ip }}" register: publish_external - name: "Change streisand_ipv4_address to public if requested" set_fact: streisand_ipv4_address: "{{ external_ipv4_address }}" when: ((publish_external.user_input == "yes") or (publish_external.user_input == "Yes") or (publish_external.user_input == "YES") or (publish_external.user_input == "Y") or (publish_external.user_input == "y")) when: (external_ipv4_address != "presumed_failed") and (streisand_ipv4_address != external_ipv4_address) ... ================================================ FILE: playbooks/roles/common/tasks/main.yml ================================================ --- - name: Warn users if the server's Linux distribution is not Ubuntu 16.04 pause: prompt: "Ubuntu 16.04 is the only officially supported distribution; the setup will probably fail. Press Enter if you still want to continue." when: not streisand_noninteractive and (ansible_distribution != "Ubuntu" or ansible_distribution_version != "16.04") # Set default variables - import_tasks: set-default-variables.yml - name: Ensure the APT cache is up to date apt: update_cache: yes cache_valid_time: 3600 - name: Install Streisand common packages apt: package: "{{ streisand_common_packages }}" - name: Purge unneeded services apt: package: "{{ streisand_unneeded_packages }}" state: "absent" purge: yes autoremove: yes - name: Perform a full system upgrade apt: upgrade: "safe" - name: Copy the English BIP-0039 wordlist copy: src: english.txt dest: /usr/share/dict owner: root group: root mode: 0644 - name: Generate random VPN client names shell: "{{ streisand_word_gen.identifier_max_len_15 }}" register: "vpn_client_names" with_sequence: count={{ vpn_clients }} - name: Ensure the Streisand gateway directory exists file: path: "{{ streisand_gateway_location }}" owner: www-data group: www-data mode: 0750 state: directory - name: Output the random VPN client names to disk for integration tests template: src: "test-client-inventory.j2" dest: "{{ streisand_gateway_location }}/test-client-inventory" when: streisand_client_test - name: Copy the HTML header and footer templates that are used during documentation generation copy: src: "{{ item.src }}" dest: "{{ item.dest }}" with_items: - { src: "header.html", dest: "{{ streisand_header_template }}" } - { src: "footer.html", dest: "{{ streisand_footer_template }}" } - name: Generate the unattended-upgrades templates to enable automatic security updates template: src: "{{ item.src }}" dest: "{{ item.dest }}" owner: root group: root mode: 0644 with_items: - { src: "20auto-upgrades.j2", dest: "/etc/apt/apt.conf.d/20auto-upgrades" } - { src: "50unattended-upgrades.j2", dest: "/etc/apt/apt.conf.d/50unattended-upgrades" } - name: Apply the custom sysctl values include_role: name: sysctl ================================================ FILE: playbooks/roles/common/tasks/set-default-variables.yml ================================================ --- # Some providers (e.g. Amazon EC2) assign internal 172.x.x.x IP # addresses to their virtual machines. It's important to use the real # public IP in the instructions Streisand generates, because the # internal addresses are not publicly accessible. # # Some providers (e.g. Amazon EC2 and Linode) do not use the name that # is provided in the API call as the server's hostname. It's important # to use the friendly name in the instructions rather than an ugly, # randomly generated hostname. # # Each 'genesis' role for the providers that Streisand natively supports # sets the streisand_ipv4_address and streisand_server_name variables in # an attempt to correct the limitations laid out above. However, it is # also necessary to fall back to default sane values if users choose to # run Streisand on their own servers or against other cloud providers. # # These conditionals are here for that very reason :) - name: Set the streisand_ipv4_address variable to the value provided by a 'genesis' role if one is defined set_fact: streisand_ipv4_address: "{{ hostvars['127.0.0.1']['streisand_ipv4_address'] }}" when: hostvars['127.0.0.1']['streisand_ipv4_address'] is defined - name: Set the streisand_ipv4_address variable to the default value if it doesn't already have one. The default is the value defined in the inventory file, which should be the IP address of the server that is being configured. set_fact: streisand_ipv4_address: "{{ inventory_hostname }}" when: streisand_ipv4_address is not defined - name: Set the streisand_server_name variable to the value provided by a 'genesis' role if one is defined set_fact: streisand_server_name: "{{ hostvars['127.0.0.1']['streisand_server_name'] }}" when: hostvars['127.0.0.1']['streisand_server_name'] is defined - name: Set the streisand_server_name variable to the default value if it doesn't already have one. The default is the value of the hostname retrieved from the server that is being configured. set_fact: streisand_server_name: "{{ ansible_hostname }}" when: streisand_server_name is not defined - import_tasks: detect-public-ip.yml when: (hostvars['127.0.0.1']['streisand_genesis_role'] is defined and ((hostvars['127.0.0.1']['streisand_genesis_role'] == "localhost") or (hostvars['127.0.0.1']['streisand_genesis_role'] == "existing-server"))) ================================================ FILE: playbooks/roles/common/templates/20auto-upgrades.j2 ================================================ APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1"; ================================================ FILE: playbooks/roles/common/templates/50unattended-upgrades.j2 ================================================ // Automatically upgrade packages from these (origin, archive) pairs Unattended-Upgrade::Allowed-Origins { // ${distro_id} and ${distro_codename} will be automatically expanded "${distro_id} stable"; "${distro_id} ${distro_codename}-security"; // Autoupdate Nginx "nginx:${distro_codename}"; {% if streisand_openvpn_enabled %} // Autoupdate OpenVPN "Freight:${distro_codename}"; {% endif %} {% if streisand_shadowsocks_enabled %} // Autoupdate shadowsocks-libev "LP-PPA-max-c-lv-shadowsocks-libev:${distro_codename}"; {% endif %} {% if streisand_tor_enabled %} // Autoupdate Tor "TorProject:${distro_codename}"; {% endif %} {% if streisand_wireguard_enabled %} // Autoupdate WireGuard "LP-PPA-wireguard-wireguard:${distro_codename}"; {% endif %} }; // List of packages to not update Unattended-Upgrade::Package-Blacklist { }; // Do automatic removal of new unused dependencies after the upgrade // (equivalent to apt-get autoremove) Unattended-Upgrade::Remove-Unused-Dependencies "true"; // Automatically reboot *WITHOUT CONFIRMATION* if a // the file /var/run/reboot-required is found after the upgrade Unattended-Upgrade::Automatic-Reboot "true"; // If automatic reboot is enabled and needed, reboot at the specific // time instead of immediately // Default: "now" Unattended-Upgrade::Automatic-Reboot-Time "00:00"; // Avoid conffile dpkg prompt by *always* leaving the modified configuration in // place and putting the new package configuration in a .dpkg-dist file Dpkg::Options { "--force-confdef"; "--force-confold"; }; ================================================ FILE: playbooks/roles/common/templates/test-client-inventory.j2 ================================================ {% for client_name in vpn_client_names.results %} {{ client_name.stdout }} {% endfor %} ================================================ FILE: playbooks/roles/common/vars/main.yml ================================================ --- streisand_common_packages: # Ensure that Apparmor is installed - apparmor # Enables support for the HTTPS protocol in APT sources - apt-transport-https # Used to perform a system upgrade - aptitude # Used to compile Libreswan and OpenConnect Server (ocserv) - build-essential # Used to perform API requests, including the version check for # the Tor Browser Bundle - curl # Enables automation of programs and scripts that ask for user input - expect # Legacy GPG is required by the Ansible apt_repository task - gnupg # Used to configure firewall rules - iptables # Used for documentation generation - markdown # Ensures the server's clock is set properly - ntp # Required to use the Ansible `expect` module - python-pexpect # Required for the apt_repository module - software-properties-common # Used to generate convenient QR codes for mobile clients in the # Shadowsocks, Tor, and WireGuard roles - qrencode # Used for automatically installing security updates - unattended-upgrades # A UUID generator with an explicit random function. # Used for generating UUIDs for mobileconfig files - uuid # Install git for source code retrieval and use with "go get" command - git # Services that are running by default but not needed by Streisand streisand_unneeded_packages: - lxd - snapd streisand_gateway_location: "/var/www/streisand" streisand_mirror_location: "{{ streisand_gateway_location }}/mirror" streisand_local_directory: "generated-docs" streisand_header_template: "/tmp/header.html" streisand_footer_template: "/tmp/footer.html" streisand_language_selector: "/tmp/languages.html" # By default concurrent executions of `iptables` will fail immediately if unable # to acquire a lock. We use the `--wait` argument to instead specify a maximum # number of seconds to wait before failing. This allows multiple services to add # iptables rules at once. See Issue #950 for longer term ideas on how to make # this var not required. streisand_iptables_wait: 120 # Internationalization streisand_languages: en: file_suffix: "" language_name: "English" tor_locale: "en-US" fr: file_suffix: "-fr" language_name: "Français" tor_locale: "fr" streisand_shuf: "shuf --random-source=/dev/urandom" # NOTE(@cpu): using the "folded block scalar" YAML syntax in the # `streisand_word_gen` entries seems to be the best way to both: # a) split up the long command lines # b) use templated variables like {{ striesand_shuf }}. # It has the unfortunate downside of leaving a trailing `\n` in the var content # which can cause problems in usage like: # `shell: {{ streisand_word_gen.identifier }} > foo` # The work-around/solution is to use: # `shell: {{ streisand_word_gen.identifier | trim }} > foo` # If you, clever reader, have a better idea please open a PR! :-) streisand_word_gen: gateway: > {{ streisand_shuf }} -n 6 /usr/share/dict/english.txt | paste -s -d '.' - identifier: > {{ streisand_shuf }} -n 2 /usr/share/dict/english.txt | paste -s -d '-' - identifier_max_len_15: > egrep '^.{1,7}$' /usr/share/dict/english.txt | {{ streisand_shuf }} -n 2 | paste -s -d '-' - # Tor only likes [A-Za-z0-9] for nicknames. # # The BIP words are maximum 8 characters, so we get two safely. # # Tor nicknames should be 19 characters or fewer; cut ours off at 18. # # Caps script from https://www.unix.com/302904939-post2.html tor_nickname: > {{ streisand_shuf }} -n 2 /usr/share/dict/english.txt | awk '{OFS=""; for(j=1;j<=NF;j++){ $j=toupper(substr($j,1,1)) substr($j,2) }}1' | tr -d '\n' | cut -b 1-18 long_identifier: > {{ streisand_shuf }} -n 3 /usr/share/dict/english.txt | paste -s -d '-' - weak_password: > {{ streisand_shuf }} -n 3 /usr/share/dict/english.txt | paste -s -d '.' - psk: > {{ streisand_shuf }} -n 5 /usr/share/dict/english.txt | paste -s -d '.' - # In online documentation, we recommand a URL for people to check # their effective IP address. streisand_my_ip_url: https://duckduckgo.com/?q=ip+address # We used to use Google; it has marginally better usability, but there # are reasons not to use it. If you're reading this, you know enough # to make your own decision. # # streisand_my_ip_url: https://encrypted.google.com/search?hl=en&q=my%20ip%20address # Ciphersuites recommended from Mozilla's Modern compatibility profile # https://wiki.mozilla.org/Security/Server_Side_TLS streisand_tls_ciphers: "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256" apt_repository_retries: 10 apt_repository_delay: 20 ================================================ FILE: playbooks/roles/diagnostics/tasks/main.yml ================================================ --- - name: "Determine the git revision of the current Streisand clone" command: git rev-parse HEAD register: streisand_diagnostics_git_rev changed_when: False # Credit to https://stackoverflow.com/a/3879077 for this approach - name: "Determine if there are untracked changes in the Streisand clone" shell: git diff-index --quiet HEAD -- && echo "no" || echo "yes"; register: streisand_diagnostics_git_untracked changed_when: False - name: "Produce the diagnostics markdown file to share if there is an error" template: src: streisand-diagnostics.md.j2 dest: ../streisand-diagnostics.md ================================================ FILE: playbooks/roles/diagnostics/templates/streisand-diagnostics.md.j2 ================================================ ### Ansible Information {% if 'localhost' in hostvars %} {% if 'ansible_version' in hostvars['localhost'] %} * Ansible version: {{ hostvars['localhost']['ansible_version']['full'] }} {% else %} * Ansible version: Unknown {% endif %} {% if 'ansible_system' in hostvars['localhost'] %} * Ansible system: {{ hostvars['localhost']['ansible_system'] }} {% else %} * Ansible system: Unknown {% endif %} {% if 'ansible_distribution' in hostvars['localhost'] %} * Host OS: {{ hostvars['localhost']['ansible_distribution'] }} {% else %} * Host OS: Unknown {% endif %} {% if 'ansible_distribution_version' in hostvars['localhost'] %} * Host OS version: {{ hostvars['localhost']['ansible_distribution_version'] }} {% else %} * Host OS version: Unknown {% endif %} {% if 'ansible_python_interpreter' in hostvars['localhost'] %} * Python interpreter: {{ hostvars['localhost']['ansible_python_interpreter'] }} {% else %} * Python interpreter: Unknown {% endif %} {% if 'ansible_python_version' in hostvars['localhost'] %} * Python version: {{ hostvars['localhost']['ansible_python_version'] }} {% else %} * Python version: Unknown {% endif %} {% else %} * No 'localhost' in hostvars. Ansible information unknown. {% endif %} ### Streisand Information * Streisand Git revision: {{ streisand_diagnostics_git_rev.stdout }} * Streisand Git clone has untracked changes: {{ streisand_diagnostics_git_untracked.stdout }} * Genesis role: {{ streisand_genesis_role | default("None") }} * Custom SSH key: {{ streisand_ssh_private_key != "~/.ssh/id_rsa" }} ### Enabled Roles * Shadowsocks enabled: {{ streisand_shadowsocks_enabled }} * Wireguard enabled: {{ streisand_wireguard_enabled }} * OpenVPN enabled: {{ streisand_openvpn_enabled }} * stunnel enabled: {{ streisand_stunnel_enabled }} * Tor enabled: {{ streisand_tor_enabled }} * Openconnect enabled: {{ streisand_openconnect_enabled }} * TinyProxy enabled: {{ streisand_tinyproxy_enabled }} * SSH forward user enabled: {{ streisand_ssh_forward_enabled }} * Configured number of VPN clients: {{ vpn_clients }} ================================================ FILE: playbooks/roles/dnsmasq/handlers/main.yml ================================================ --- - name: Restart dnsmasq systemd: name: dnsmasq.service state: restarted ================================================ FILE: playbooks/roles/dnsmasq/tasks/main.yml ================================================ --- - name: Ensure that BIND is not installed in order to avoid conflicts with dnsmasq apt: package: bind9 state: absent - name: Install dnsmasq apt: package: dnsmasq - name: Generate the dnsmasq configuration file template: src: dnsmasq.conf.j2 dest: /etc/dnsmasq.conf notify: Restart dnsmasq - name: Create the dnsmasq systemd drop-in configuration directory file: path: "{{ dnsmasq_systemd_service_path }}" state: directory - name: Generate the dnsmasq systemd drop-in service file template: src: dnsmasq.service.j2 dest: "{{ dnsmasq_systemd_service_path }}/10-restart-failure.conf" mode: 0644 - name: Enable the dnsmasq service systemd: daemon_reload: yes name: dnsmasq.service enabled: yes state: restarted ================================================ FILE: playbooks/roles/dnsmasq/templates/dnsmasq.conf.j2 ================================================ # Explicitly listen on localhost. This is required since config fragments in # `/etc/dnsmasq.d` may add additional `listen-address` values and the man page # says: # Note that if no --interface option is given, but --listen-address is, # dnsmasq will not automatically listen on the loopback interface. To achieve # this, its IP address, 127.0.0.1, must be explicitly given as # a --listen-address option. listen-address=127.0.0.1 # Never forward plain names (without a dot or domain part) domain-needed # Never forward addresses in the non-routed address spaces. bogus-priv # If you don't want dnsmasq to read /etc/resolv.conf or any other # file, getting its servers from this file instead (see below), then # uncomment this. no-resolv {% for item in upstream_dns_servers %} server={{ item }} {% endfor %} ================================================ FILE: playbooks/roles/dnsmasq/templates/dnsmasq.service.j2 ================================================ [Service] PrivateTmp=true RestartSec=5s Restart=on-failure ================================================ FILE: playbooks/roles/dnsmasq/vars/main.yml ================================================ --- dnsmasq_systemd_service_path: "/etc/systemd/system/dnsmasq.service.d" ================================================ FILE: playbooks/roles/download-and-verify/defaults/main.yml ================================================ --- signature_extension: "asc" ================================================ FILE: playbooks/roles/download-and-verify/tasks/main.yml ================================================ --- # Import the GPG playbook vars - include_vars: "../../gpg/vars/main.yml" - name: "Download the {{ project_name }} files" get_url: url: "{{ project_download_baseurl }}/{{ item.file }}" dest: "{{ project_download_location }}/{{ item.file }}" owner: www-data group: www-data mode: 0644 with_items: "{{ project_download_files }}" - name: "Download the {{ project_name }} signatures" get_url: url: "{{ project_download_baseurl }}/{{ item.sig }}" dest: "{{ project_download_location }}/{{ item.sig }}" owner: www-data group: www-data mode: 0644 with_items: "{{ project_download_files }}" - name: "Verify the {{ project_name }} download signatures with the Streisand GPG keyring" command: "gpgv2 --keyring {{ streisand_gpg_keyring }} {{ project_download_location }}/{{ item.sig }} {{ project_download_location }}/{{ item.file }}" environment: # We need to explicitly override the configured LANG, LC_MESSAGES and LC_ALL # env vars to ensure that we can match on English output even on systems # that have a non-english locale configured. It's _probably_ enough to # override just LANG but it sounds like there are some cases where LC_ALL # takes precedence and setting env vars is cheap! LANG: "en_US.UTF-8" LC_MESSAGES: "en_US.UTF-8" LC_ALL: "en_US.UTF-8" register: gpg_verification_results with_items: "{{ project_download_files }}" - name: "Verify the {{ project_name }} download signature checks all passed" assert: that: - "not item.failed" - "item.rc == 0" msg: "Verifying {{ item.item.file }} GPG signature failed!!" with_items: "{{ gpg_verification_results.results }}" loop_control: label: "{{ item.item.file }}" # NOTE(@cpu): Unfortunately because of an open Ansible issue[0] related to the # verbosity of `assert` tasks we need a `no_log: true` here to avoid creating # a TON of output in the "pass" case. This `no_log` will complicate debugging # and should be removed if the assertions are failing. # # [0]: https://github.com/ansible/ansible/issues/27124#issuecomment-376523736 no_log: true - name: "Verify the {{ project_name }} download signatures were from the correct keys" assert: that: # By default gpgv outputs to stderr. For a good signature the first line # always ends being like: # "gpgv: Signature made Fri 16 Mar 2018 11:16:40 PM UTC using RSA key ID # C3C07136" # Since we've already verified the gpgv2 return code we can just check for # the presence of the key ID we expect in the first line of stderr output # and be confident we saw a valid signature from the expected key ID and # not another unrelated key in the Streisand keyring. - "'key ID {{ project_signer_keyid }}' in '{{ item.stderr_lines[0] }}'" msg: "The GPG signature on {{ item.item.file }} was not from {{ project_signer_keyid }}" with_items: "{{ gpg_verification_results.results }}" loop_control: label: "{{ item.item.file }}" # See the above `assert`'s discussion on the purpose of `no_log`. no_log: true ================================================ FILE: playbooks/roles/ec2-security-group/tasks/main.yml ================================================ --- - name: Create the EC2 security group ec2_group: name: "{{ aws_security_group }}" description: Security group for Streisand region: "{{ aws_region }}" vpc_id: "{{ aws_vpc_id | default(omit) }}" aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" - name: Pause for fifteen seconds to ensure the EC2 security group has been created pause: seconds: 15 - name: Open necessary ports across supported services in the EC2 security group ec2_group: name: "{{ aws_security_group }}" description: Security group for Streisand region: "{{ aws_region }}" vpc_id: "{{ aws_vpc_id | default(omit) }}" aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" rules: # Nginx # --- - proto: tcp from_port: "{{ nginx_port }}" to_port: "{{ nginx_port }}" cidr_ip: 0.0.0.0/0 # SSH # --- - proto: tcp from_port: "{{ ssh_port }}" to_port: "{{ ssh_port }}" cidr_ip: 0.0.0.0/0 # HTTP (Let's Encrypt) # --- - proto: tcp from_port: "{{ le_port }}" to_port: "{{ le_port }}" cidr_ip: 0.0.0.0/0 rules_egress: - proto: all from_port: 1 to_port: 65535 cidr_ip: 0.0.0.0/0 # OpenConnect # --- - name: Open the OpenConnect ports in the EC2 security group ec2_group: name: "{{ aws_security_group }}" description: Security group for Streisand region: "{{ aws_region }}" vpc_id: "{{ aws_vpc_id | default(omit) }}" aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" purge_rules: no purge_rules_egress: no rules: # OpenConnect TCP # --- - proto: tcp from_port: "{{ ocserv_port }}" to_port: "{{ ocserv_port }}" cidr_ip: 0.0.0.0/0 # OpenConnect UDP # --- - proto: udp from_port: "{{ ocserv_port }}" to_port: "{{ ocserv_port }}" cidr_ip: 0.0.0.0/0 when: streisand_openconnect_enabled # OpenVPN # --- - name: Open the OpenVPN ports in the EC2 security group ec2_group: name: "{{ aws_security_group }}" description: Security group for Streisand region: "{{ aws_region }}" vpc_id: "{{ aws_vpc_id | default(omit) }}" aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" purge_rules: no purge_rules_egress: no rules: # OpenVPN TCP # --- - proto: tcp from_port: "{{ openvpn_port }}" to_port: "{{ openvpn_port }}" cidr_ip: 0.0.0.0/0 # OpenVPN UDP # --- - proto: udp from_port: "{{ openvpn_port_udp }}" to_port: "{{ openvpn_port_udp }}" cidr_ip: 0.0.0.0/0 when: streisand_openvpn_enabled # stunnel # --- - name: Open the stunnel port in the EC2 security group ec2_group: name: "{{ aws_security_group }}" description: Security group for Streisand region: "{{ aws_region }}" vpc_id: "{{ aws_vpc_id | default(omit) }}" aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" purge_rules: no purge_rules_egress: no rules: # Stunnel # --- - proto: tcp from_port: "{{ stunnel_remote_port }}" to_port: "{{ stunnel_remote_port }}" cidr_ip: 0.0.0.0/0 when: streisand_openvpn_enabled and streisand_stunnel_enabled # Shadowsocks # --- - name: Open the Shadowsocks ports in the EC2 security group ec2_group: name: "{{ aws_security_group }}" description: Security group for Streisand region: "{{ aws_region }}" vpc_id: "{{ aws_vpc_id | default(omit) }}" aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" purge_rules: no purge_rules_egress: no rules: # Shadowsocks TCP # --- - proto: tcp from_port: "{{ shadowsocks_server_port }}" to_port: "{{ shadowsocks_server_port }}" cidr_ip: 0.0.0.0/0 # Shadowsocks UDP # --- - proto: udp from_port: "{{ shadowsocks_server_port }}" to_port: "{{ shadowsocks_server_port }}" cidr_ip: 0.0.0.0/0 when: streisand_shadowsocks_enabled # Tor # --- - name: Open the Tor ports in the EC2 security group ec2_group: name: "{{ aws_security_group }}" description: Security group for Streisand region: "{{ aws_region }}" vpc_id: "{{ aws_vpc_id | default(omit) }}" aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" purge_rules: no purge_rules_egress: no rules: # Tor # --- - proto: tcp from_port: "{{ tor_orport }}" to_port: "{{ tor_orport }}" cidr_ip: 0.0.0.0/0 # Tor obfs4 # --- - proto: tcp from_port: "{{ tor_obfs4_port }}" to_port: "{{ tor_obfs4_port }}" cidr_ip: 0.0.0.0/0 when: streisand_tor_enabled # WireGuard # --- - name: Open the WireGuard ports in the EC2 security group ec2_group: name: "{{ aws_security_group }}" description: Security group for Streisand region: "{{ aws_region }}" vpc_id: "{{ aws_vpc_id | default(omit) }}" aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" purge_rules: no purge_rules_egress: no rules: - proto: udp from_port: "{{ wireguard_port }}" to_port: "{{ wireguard_port }}" cidr_ip: 0.0.0.0/0 when: streisand_wireguard_enabled ================================================ FILE: playbooks/roles/ec2-security-group/vars/main.yml ================================================ --- aws_security_group: "streisand-{{ aws_instance_name }}" ================================================ FILE: playbooks/roles/gce-network/tasks/main.yml ================================================ --- - name: Create network gce_net: name: "{{ gce_network }}" state: "present" mode: auto ipv4_range: "{{ gce_ipv4_range }}" credentials_file: "{{ gce_json_file_location }}" project_id: "{{ gce_project_id }}" service_account_email: "{{ gce_service_account_email }}" - name: Open the SSH port in the GCE firewall gce_net: name: "{{ gce_network }}" fwname: "streisand-{{ gce_server_name }}-ssh" allowed: "tcp:{{ ssh_port }}" state: "present" mode: auto src_range: 0.0.0.0/0 service_account_email: "{{ gce_service_account_email }}" credentials_file: "{{ gce_json_file_location }}" project_id: "{{ gce_project_id }}" - name: Open the Nginx port in the GCE firewall gce_net: name: "{{ gce_network }}" fwname: "streisand-{{ gce_server_name }}-nginx" allowed: "tcp:{{ nginx_port }}" state: "present" mode: auto src_range: 0.0.0.0/0 service_account_email: "{{ gce_service_account_email }}" credentials_file: "{{ gce_json_file_location }}" project_id: "{{ gce_project_id }}" - name: Open HTTP port for Let's Encrypt in the GCE firewall gce_net: name: "{{ gce_network }}" fwname: "streisand-{{ gce_server_name }}-letsencrypt" allowed: "tcp:{{ le_port }}" state: "present" mode: auto src_range: 0.0.0.0/0 service_account_email: "{{ gce_service_account_email }}" credentials_file: "{{ gce_json_file_location }}" project_id: "{{ gce_project_id }}" - name: Open necessary Tor ports in the GCE firewall gce_net: name: "{{ gce_network }}" fwname: "streisand-{{ gce_server_name }}-tor" allowed: "tcp:{{ tor_orport }},{{ tor_obfs4_port }}" state: "present" mode: auto src_range: 0.0.0.0/0 service_account_email: "{{ gce_service_account_email }}" credentials_file: "{{ gce_json_file_location }}" project_id: "{{ gce_project_id }}" when: streisand_tor_enabled - name: Open the OpenConnect (ocserv) port in the GCE firewall gce_net: name: "{{ gce_network }}" fwname: "streisand-{{ gce_server_name }}-openconnect" allowed: "tcp:{{ ocserv_port }};udp:{{ ocserv_port }}" state: "present" mode: auto src_range: 0.0.0.0/0 service_account_email: "{{ gce_service_account_email }}" credentials_file: "{{ gce_json_file_location }}" project_id: "{{ gce_project_id }}" when: streisand_openconnect_enabled - name: Open the OpenVPN ports in the GCE firewall gce_net: name: "{{ gce_network }}" fwname: "streisand-{{ gce_server_name }}-openvpn" allowed: "tcp:{{ openvpn_port }};udp:{{ openvpn_port_udp }}" state: "present" mode: auto src_range: 0.0.0.0/0 service_account_email: "{{ gce_service_account_email }}" credentials_file: "{{ gce_json_file_location }}" project_id: "{{ gce_project_id }}" when: streisand_openvpn_enabled - name: Open the OpenVPN stunnel port in the GCE firewall gce_net: name: "{{ gce_network }}" fwname: "streisand-{{ gce_server_name }}-openvpn-stunnel" allowed: "tcp:{{ stunnel_remote_port }}" state: "present" mode: auto src_range: 0.0.0.0/0 service_account_email: "{{ gce_service_account_email }}" credentials_file: "{{ gce_json_file_location }}" project_id: "{{ gce_project_id }}" when: streisand_openvpn_enabled and streisand_stunnel_enabled - name: Open the Shadowsocks ports in the GCE firewall gce_net: name: "{{ gce_network }}" fwname: "streisand-{{ gce_server_name }}-shadowsocks" allowed: "tcp:{{ shadowsocks_server_port }};udp:{{ shadowsocks_server_port }}" state: "present" mode: auto src_range: 0.0.0.0/0 service_account_email: "{{ gce_service_account_email }}" credentials_file: "{{ gce_json_file_location }}" project_id: "{{ gce_project_id }}" when: streisand_shadowsocks_enabled - name: Open the WireGuard port in the GCE firewall gce_net: name: "{{ gce_network }}" fwname: "streisand-{{ gce_server_name }}-wireguard" allowed: "udp:{{ wireguard_port }}" state: "present" mode: auto src_range: 0.0.0.0/0 service_account_email: "{{ gce_service_account_email }}" credentials_file: "{{ gce_json_file_location }}" project_id: "{{ gce_project_id }}" when: streisand_wireguard_enabled ================================================ FILE: playbooks/roles/gce-network/vars/main.yml ================================================ --- gce_network: "{{ gce_server_name }}-network" gce_ipv4_range: "10.240.16.0/24" ================================================ FILE: playbooks/roles/genesis-amazon/defaults/main.yml ================================================ --- aws_instance_type: "t2.micro" # Search AMIs owned by this owner. This is the Amazon owner ID. aws_ami_owner: "099720109477" # Find AMIs matching this name aws_ami_name: "ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-*" ================================================ FILE: playbooks/roles/genesis-amazon/files/aws-metadata-instance.service ================================================ [Unit] Description=Drop packets to the EC2 metadata instance After=network.target [Service] Type=oneshot RemainAfterExit=true ExecStart=/sbin/route add -host 169.254.169.254 reject [Install] WantedBy=multi-user.target ================================================ FILE: playbooks/roles/genesis-amazon/meta/main.yml ================================================ --- dependencies: - ec2-security-group ================================================ FILE: playbooks/roles/genesis-amazon/tasks/main.yml ================================================ --- - set_fact: streisand_genesis_role: "genesis-amazon" - name: "Get the {{ streisand_ssh_private_key }}.pub contents" command: "cat {{ streisand_ssh_private_key }}.pub" register: ssh_key changed_when: False - name: Remove the 'streisand' SSH key from Amazon if it already exists. This is to prevent problems if two people with two different keys are sharing the same AWS account. ec2_key: name: streisand-ssh state: absent aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" region: "{{ aws_region }}" wait: yes - name: Add the SSH key to Amazon under the name of 'streisand-ssh' ec2_key: name: streisand-ssh key_material: "{{ ssh_key.stdout }}" aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" region: "{{ aws_region }}" wait: yes - name: Determine which AMI to use ec2_ami_facts: aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" owners: "{{ aws_ami_owner }}" region: "{{ aws_region }}" filters: name: "{{ aws_ami_name }}" register: ami - name: Create the EC2 instance ec2: aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" instance_type: "{{ aws_instance_type }}" image: "{{ ami.images|sort(reverse=True,attribute='name')|map(attribute='image_id')|first }}" region: "{{ aws_region }}" vpc_subnet_id: "{{ aws_vpc_subnet_id | default(omit) }}" assign_public_ip: "{{ (aws_vpc_subnet_id is defined and aws_vpc_subnet_id != '') or omit }}" key_name: streisand-ssh group: "{{ aws_security_group }}" instance_tags: Name: "{{ aws_instance_name }}" wait: yes register: streisand_server - name: Create CloudWatch alarm to auto-recover instance ec2_metric_alarm: name: "autorecover-{{ aws_instance_name }}" description: "This alarm will auto-recover the EC2 instance on host failure" state: present aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" region: "{{ aws_region }}" namespace: "AWS/EC2" metric: StatusCheckFailed_System statistic: Minimum comparison: ">" threshold: 0.0 period: 60 evaluation_periods: 2 dimensions: InstanceId: "{{ streisand_server.instances[0].id }}" alarm_actions: - "arn:aws:automate:{{ aws_region }}:ec2:recover" when: aws_instance_type.startswith(("t2", "c3", "c4", "m3", "m4", "r3", "x1")) - name: Wait until the server has finished booting and OpenSSH is accepting connections wait_for: host: "{{ streisand_server.instances[0].public_ip }}" port: 22 search_regex: OpenSSH timeout: 600 - name: Allocate and associate Elastic IP ec2_eip: aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" region: "{{ aws_region }}" device_id: "{{ streisand_server.instances[0].id }}" in_vpc: "{{ aws_vpc_id is defined and aws_vpc_id != '' }}" register: instance_eip - name: Create the in-memory inventory group add_host: name: "{{ instance_eip.public_ip }}" groups: streisand-host ansible_user: ubuntu ansible_become: yes - name: Set the streisand_ipv4_address variable set_fact: streisand_ipv4_address: "{{ instance_eip.public_ip }}" - name: Set the streisand_server_name variable set_fact: streisand_server_name: "{{ aws_instance_name | regex_replace('\\s', '_') }}" ================================================ FILE: playbooks/roles/genesis-azure/defaults/main.yml ================================================ --- azure_instance_type: "Standard_B1s" azure_image_publisher: "Canonical" azure_image_offer: "UbuntuServer" azure_image_sku: "16.04-LTS" azure_image_version: "latest" ================================================ FILE: playbooks/roles/genesis-azure/meta/main.yml ================================================ --- dependencies: - azure-security-group ================================================ FILE: playbooks/roles/genesis-azure/tasks/main.yml ================================================ --- - set_fact: streisand_genesis_role: "genesis-azure" - name: "Get the {{ streisand_ssh_private_key }}.pub contents" command: "cat {{ streisand_ssh_private_key }}.pub" register: ssh_key changed_when: False - name: Create the Azure instance local_action: module: azure_rm_virtualmachine resource_group: "{{ azure_resource_group_name }}" network_interfaces: "{{ azure_resource_group_name }}" name: "{{ azure_instance_name }}" vm_size: "{{ azure_instance_type }}" location: "{{ azure_region }}" image: offer: "{{ azure_image_offer }}" publisher: "{{ azure_image_publisher }}" sku: "{{ azure_image_sku }}" version: "{{ azure_image_version }}" admin_username: ubuntu ssh_password_enabled: false ssh_public_keys: - path: /home/ubuntu/.ssh/authorized_keys key_data: "{{ ssh_key.stdout }}" register: streisand_server - name: Set the streisand_ipv4_address variable set_fact: streisand_ipv4_address: "{{ streisand_server.ansible_facts.azure_vm.properties.networkProfile.networkInterfaces[0].properties.ipConfigurations[0].properties.publicIPAddress.properties.ipAddress }}" - name: Wait until the server has finished booting and OpenSSH is accepting connections wait_for: host: "{{ streisand_ipv4_address }}" port: 22 search_regex: OpenSSH timeout: 600 - name: Create the in-memory inventory group add_host: name: "{{ streisand_ipv4_address }}" groups: streisand-host ansible_user: ubuntu ansible_become: yes - name: Set the streisand_server_name variable set_fact: streisand_server_name: "{{ azure_instance_name }}" ================================================ FILE: playbooks/roles/genesis-digitalocean/defaults/main.yml ================================================ --- do_ubuntu_x64_image_id: "ubuntu-16-04-x64" do_small_droplet_size_id: "s-1vcpu-1gb" ================================================ FILE: playbooks/roles/genesis-digitalocean/tasks/main.yml ================================================ --- - set_fact: streisand_genesis_role: "genesis-digitalocean" - name: "Get the {{ streisand_ssh_private_key }}.pub contents" command: "cat {{ streisand_ssh_private_key }}.pub" register: ssh_key changed_when: False - name: Set the DigitalOcean Access Token fact to the value that was entered, or attempt to retrieve it from the environment if the entry is blank set_fact: do_access_token: "{{ do_access_token_entry | default( lookup('env', 'DO_API_KEY') ) }}" - block: - name: Add the SSH key to DigitalOcean if it doesn't already exist digital_ocean_sshkey: name: "{{ do_ssh_name }}" ssh_pub_key: "{{ ssh_key.stdout }}" oauth_token: "{{ do_access_token }}" state: present register: do_ssh_key rescue: - fail: msg: "* The API Access Token might be incorrect or missing the Write Scope. OR * The SSH key may already exist in the DigitalOcean Control Panel under a different name." - block: - name: Create the new Droplet digital_ocean_droplet: name: "{{ do_server_name }}" ssh_keys: - "{{ do_ssh_key.data.ssh_key.id }}" size: "{{ do_small_droplet_size_id }}" region: "{{ regions[do_region] }}" image: "{{ do_ubuntu_x64_image_id }}" unique_name: yes wait: yes oauth_token: "{{ do_access_token }}" state: present register: streisand_server rescue: - fail: msg: "Unable to create the Droplet. Please ensure that the API Access Token you provided has the Write Scope enabled." - name: Wait until the server has finished booting and OpenSSH is accepting connections wait_for: host: "{{ streisand_server.data.ip_address }}" port: 22 search_regex: OpenSSH timeout: 600 - name: Create the in-memory inventory group add_host: name: "{{ streisand_server.data.ip_address }}" groups: streisand-host - name: Set the streisand_ipv4_address variable set_fact: streisand_ipv4_address: "{{ streisand_server.data.ip_address }}" - name: Set the streisand_server_name variable set_fact: streisand_server_name: "{{ do_server_name | regex_replace('\\s', '_') }}" ================================================ FILE: playbooks/roles/genesis-google/defaults/main.yml ================================================ --- gce_machine_type: "f1-micro" gce_image: "ubuntu-1604" ================================================ FILE: playbooks/roles/genesis-google/meta/main.yml ================================================ --- dependencies: - gce-network ================================================ FILE: playbooks/roles/genesis-google/tasks/main.yml ================================================ --- - set_fact: streisand_genesis_role: "genesis-google" - name: "Get the {{ streisand_ssh_private_key }}.pub contents" command: "cat {{ streisand_ssh_private_key }}.pub" register: ssh_key changed_when: False - name: Create the GCE instance gce: name: "{{ gce_server_name }}" network: "{{ gce_network }}" zone: "{{ gce_zone }}" machine_type: "{{ gce_machine_type }}" credentials_file: "{{ gce_json_file_location }}" project_id: "{{ gce_project_id }}" service_account_email: "{{ gce_service_account_email }}" image: "{{ gce_image }}" tags: "{{ gce_server_name }}" metadata: '{"ssh-keys":"ubuntu:{{ ssh_key.stdout }}"}' register: streisand_server - name: Wait until the server has finished booting and OpenSSH is accepting connections wait_for: host: "{{ streisand_server.instance_data[0].public_ip }}" port: 22 search_regex: OpenSSH timeout: 600 - name: Create the in-memory inventory group add_host: name: "{{ streisand_server.instance_data[0].public_ip }}" groups: streisand-host ansible_user: ubuntu ansible_become: yes - name: Set the streisand_ipv4_address variable set_fact: streisand_ipv4_address: "{{ streisand_server.instance_data[0].public_ip }}" - name: Set the streisand_server_name variable set_fact: streisand_server_name: "{{ gce_server_name | regex_replace('\\s', '_') }}" ================================================ FILE: playbooks/roles/genesis-linode/defaults/main.yml ================================================ --- # Setting to most minimal linode plan size. # For a most recent list of types: curl https://api.linode.com/v4/linode/types linode_plan_id: "g6-nanode-1" linode_distribution_id: "linode/ubuntu16.04lts" ### Preserving these varsfor when we can set these with the ansible linode apiv4 module: # linode_kernel_id: 210 # GRUB2 to utilize the distribution's kernel for compatibility # Threshold for receiving CPU usage alerts. Each CPU core adds 100% to total. # Since by default Streisand provisions a Linode 1024 with one core a value of # 90% seems ~reasonable # linode_alert_cpu_threshold: 90 # Other values left as the defaults from the Linode module. See # https://github.com/StreisandEffect/streisand/issues/626 for more detail. # linode_alert_diskio_threshold: 10000 # linode_alert_bwin_threshold: 10 # linode_alert_bwout_threshold: 10 # linode_alert_bwquota_threshold: 80 ================================================ FILE: playbooks/roles/genesis-linode/tasks/main.yml ================================================ --- - set_fact: streisand_genesis_role: "genesis-linode" - name: "Get the {{ streisand_ssh_private_key }}.pub contents" command: "cat {{ streisand_ssh_private_key }}.pub" register: ssh_key changed_when: False - name: "Create the server" linode_v4: access_token: "{{ linode_api_token }}" label: "{{ linode_server_name }}" type: "{{ linode_plan_id }}" region: "{{ regions[linode_datacenter] }}" image: "{{ linode_distribution_id }}" authorized_keys: "{{ ssh_key.stdout }}" state: present register: streisand_server - name: "Wait until the server has finished booting and OpenSSH is accepting connections" wait_for: host: "{{ streisand_server.instance.ipv4[0] }}" port: 22 search_regex: OpenSSH timeout: 600 - name: "Create the in-memory inventory group" add_host: name: "{{ streisand_server.instance.ipv4[0] }}" groups: streisand-host - name: "Set the streisand_ipv4_address variable" set_fact: streisand_ipv4_address: "{{ streisand_server.instance.ipv4[0] }}" - name: "Set the streisand_server_name variable" set_fact: streisand_server_name: "{{ linode_server_name | regex_replace('\\s', '_') }}" ================================================ FILE: playbooks/roles/genesis-rackspace/defaults/main.yml ================================================ --- rackspace_flavor: 2 rackspace_image: "Ubuntu 16.04 LTS (Xenial Xerus) (PVHVM)" ================================================ FILE: playbooks/roles/genesis-rackspace/tasks/main.yml ================================================ --- - set_fact: streisand_genesis_role: "genesis-rackspace" - name: "Get the {{ streisand_ssh_private_key }}.pub contents" command: "cat {{ streisand_ssh_private_key }}.pub" register: ssh_key changed_when: False - name: Create the server rax: api_key: "{{ rackspace_api_key }}" username: "{{ rackspace_username }}" name: "{{ rackspace_server_name }}" flavor: "{{ rackspace_flavor }}" image: "{{ rackspace_image }}" region: "{{ regions[rackspace_region] }}" files: /root/.ssh/authorized_keys: "{{ streisand_ssh_private_key }}.pub" wait: yes register: streisand_server - name: Wait until the server has finished booting and OpenSSH is accepting connections wait_for: host: "{{ streisand_server.instances[0].rax_accessipv4 }}" port: 22 search_regex: OpenSSH timeout: 600 - name: Create the in-memory inventory group add_host: name: "{{ streisand_server.instances[0].rax_accessipv4 }}" groups: streisand-host - name: Set the streisand_ipv4_address variable set_fact: streisand_ipv4_address: "{{ streisand_server.instances[0].rax_accessipv4 }}" - name: Set the streisand_server_name variable set_fact: streisand_server_name: "{{ rackspace_server_name | regex_replace('\\s', '_') }}" ================================================ FILE: playbooks/roles/gpg/files/2D8330C2.daniel@binaryparadox.net.asc ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- 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 =Do3m -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: playbooks/roles/gpg/files/2F2B01E7.security@openvpn.net.asc ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2 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 U9coVPCcdBfWUAxoPoOCPH/eNug4 =Q4FQ -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: playbooks/roles/gpg/files/4AE8DA82.putty@projects.tartarus.org.asc ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- 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 =5Mif -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: playbooks/roles/gpg/files/7F343FA7.nmav@redhat.com.asc ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2 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 fx08azxnYdWAMp+HWVuMX5HUja5uy7Y= =W7zY -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: playbooks/roles/gpg/files/93298290.torbrowser@torproject.org.asc ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2 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 NX0K81ErDIJw6oeLpicQjR3B363tXNDzhHBBmyjzafimsB2pE6cajMz3x+bUzSL3 X7yGcAyWVkPIEir34u+z2+jfwfnK1XX2TjFlXLo7BtM4dyooeB5CM0s5nHjfoDkD eazxd5lKkCPeJMbG88EZcC30/FJSOhtEpvnG5Lm3ZhV+254biQLtBBMBCgDXBQJV AMSiBYMJZgGATxSAAAAAAB4AKHZlcmlmaWVkQHBhdHRlcm5zaW50aGV2b2lkLm5l dEVGNkUyODZEREE4NUVBMkE0QkE3REU2ODRFMkM2RTg3OTMyOTgyOTBLFIAAAAAA GgAoaXNpc0BwYXR0ZXJuc2ludGhldm9pZC5uZXQwQTZBNThBMTRCNTk0NkFCREUx OEUyMDdBM0FEQjY3QTJDREI4QjM1LhpodHRwczovL2Jsb2cucGF0dGVybnNpbnRo ZXZvaWQubmV0L3BvbGljeS50eHQACgkQo622eizbizVqrw/9GOt7yBURbGtG2Pef EovIK3WJJHouIEd1q8FtaPSbs+NQfXv0o8F1Kz6S1J+/707Vx5dQZeHiyRXuMbJr BmyENWGx3P5s6eYmTMB/dAE6odwXrep13oOMYNlhLlsnKNU7vM8D9kzWDBI5U+Gk 4LXjNs228B6MgsBjYzRAbUfP6e8JPOxNABWi+zBnMlDOoa9XgbcCsIxQ5FaGIVpV JId962IwG7wgBL/ZQT0FxnZ8e9+KBDHhy4weZ79mm10p4CdtCw6v82Di4Q1HRbkW LrBukt9L64OAwuiBe0K0J654bu8HZujwAgXmJjrjdw3SwaNh6Yv64IWwkODCQ1Li eF+fH0j4fDrR3se+kzOTMsUceysqhwJ11JRJA6cUeEEwtMmeBNf+aRl7gbfEoPQk mkEDX4/wN5dZOuOimavjghn2xLYyEgff1na9WXHcIT/zLwscG7Or4XEt8iGCFcmj oWuZQZbjGYrvrR394ywvKf/rWAOeFBkQ84f1ErizXhQo6/28bG+DpjmhESIaxXyR DpU+qgadWyjo70s8ihrl2JDJVIYjk0Y7RukEndE/Z61szeg14QccsShdTwptyoYu RvzCc8ooeVsbLr654P8HGz46yP0VzZ2eNv65+1ZdgU8qO40276Lhr2S32LxYBdjF OATANKD3I4lmmHfO0v/dwJIULtqJAu0EEwEKANcFAlfVSBIFgwlmAYBPFIAAAAAA HgAodmVyaWZpZWRAcGF0dGVybnNpbnRoZXZvaWQubmV0RUY2RTI4NkREQTg1RUEy QTRCQTdERTY4NEUyQzZFODc5MzI5ODI5MEsUgAAAAAAaAChpc2lzQHBhdHRlcm5z aW50aGV2b2lkLm5ldDBBNkE1OEExNEI1OTQ2QUJERTE4RTIwN0EzQURCNjdBMkNE QjhCMzUuGmh0dHBzOi8vYmxvZy5wYXR0ZXJuc2ludGhldm9pZC5uZXQvcG9saWN5 LnR4dAAKCRCjrbZ6LNuLNZzfD/4niHzydJAeq0mKa2IrO2/pCZJWCC6wH47xkuQJ mGyxw+PVRxEBXWNMm2swt2vwuFVRt7ljLWQlEUJsrWQlmEoCV0KcNup7DIcLrJOR sHp8cLdx3PCepSvSz0qApkQ1/5iOhK8UsLCKrNg8/ErMTRxeMESlzivxB5aHxqSy T352vTAkic0DHc3/szgb84XufbmHc22K4zdZRhSFh5Tv3Mk/xLH4nHYWqPznoIeo RV96Jst0gP3GQ2GQNmzqaf3fvKjNUch28B9WlcvYr1vRJkY76degqCDRaWaClS2F tHUNU1qXF07uwFpk1CqkD9+7/ocKo4gJlXOHRgGrtnDBCLXmaNNLMrBEBYekMt81 fppo1MQQe74s9CGWDQelhl/TBzHhD59wjr5Q0fm4ccM50GkWcMYafUuLV3F7xIFb huUtzSSqONzDPwNVNmvfxcsbQpcd/c50gFrKOFwypcTNfogd59A5dEP7vO153NKI 6tiZMBvhfSSOKasI1zYcXimLaF211BxKJkGSqgko/rOGqhIXPCHPmrTYaemBIRHp vp7P/W1I9dsWhkIkvxCd1/+TJTAL8malkQS8F4X7xpczcmpZTBgIUKttwfYrk0QB TbO3PWmpDQAdDsUL53cO2vkQ/kYliJUDxp0yL4DQ40yEWqzbsYJSCuDWzCMqMfj8 9VitT4kEPQQSAQoAJwUCVthxySAaaHR0cHM6Ly9taWtlLnRpZy5hcy9wZ3AvcG9s aWN5LwAKCRCpk+cVbg6ZI2fbH/0Uz0bi9P78MscBIEyQKvkEkV9iD4v8jJcBzbRm vRw+RJ7aDRsPfnK+AWLfNGR6I0oqF0Z6+Si4xSME2Wi3ycaax4n5oeGXz+eM2/iH cM4SPq5hyFTnvqla9eSbvly5e8y0B0eONP1XR3/2onokOrKEyHHxtGrxD7V19siA 7ZtikPJa5Eb3hjilhy8y98IdoWNksHtge0sHrI2585t5IjctXNrgJTIQzTn9aBU4 7IA5LmYTsPVCOfK5C1dK88IjerZqiBn4MeJ/xYqiuSHRaEHrd+/c5vniDDPjaPBx i0xsfRvrRrEoVtuHZiPRNTyAuYoRLKsjeQA/CJP85+0rpdtusL/ImnSDZNAIFLyo 3D4mpk7ysxlniKSr3j9cRUEq1JLE0ICYjP749H4NrTfjGMC8JLNSfVMdAWoyUXLq emh1Lxha3DV0lxCURDCjz38L6pZC1nZikTLG/GIwlaOKJQk8hjywkQpfeVy2xSj5 TIHc1bPYtAg5iGBAP/iiybUNaHnH2nEKuDtuoGuzAMmqXFPt+Xxqn3D28CuQthAz oN6rUi2rCXq/Y7/gBW4EUzO4HgQyZ7ihK0yWIlnB1nU2xZEdZ1azb829f06x17y3 TSm5pfDbLKHWOJHC0g5B9txF5VlBxL3hW1QeWFI8no5yy7euGK1ZCjwV5BNX3oY5 RhW7HxvlVhcKocN270YwF3gA32za6Lq8quVT4VjuJmEXXk67XvinAypavEmKqM35 lBM6VaGwxjRBMCcDMhRYXgA33hVFkVnj2Jv8XcAHDPLEGcNU8QFDTyZTHW5ukGBu dndo6OL3oPQoXOdOqGFXI0O1qjSv0Pl3SLboYnWTRqZRDb0xfs7QvT0g4+eHj14A HfsQaVTn6HWDVfFfCBdMzqPX6t3PS5udIlctq9FXMxOxS+G54in7agDjo5ay7kSc wvwZVpbFQUE9rOJOzqCNrB60XhDlz7E2sk3yfohjtH4/hTtmy7eI5wFjuTohhuTg vcK6SO0XHqSxV9piwFjw8niy5Y6iDK+/4idPTuaOn53MBPJjBV+p96FX/ScfntAJ l/5LiX7Bju0qTuY+cw83Lpxy5Ic5jC82+7l9dcnIg8WENBJgEjU8kT/Fh3jctriy oBypQa6m67p5xClyJcfo+oNB9lWBZRgfDPNtntkR7kSfKkt7CpCFlmkAMg783mGx xijtYkbkwuLSCRs2NaOGgKkxFk2PkYHvclW2CxSPdss9o1q6BQPOi5hgFPItfczx FgS7XtPsm0d461qGLVzmgxWr90/w+Rg9cV1qVGcOMYMnT/G3ind4QMMhtjsSFuuV daEkKzxI9pOEJ259nuCSwe2Vtcn2Pun+ITsCcw4XdevZbYmoiQQ9BBIBCgAnBQJW 2HHJIBpodHRwczovL21pa2UudGlnLmFzL3BncC9wb2xpY3kvAAoJEKmT5xVuDpkj Z9sf/RTPRuL0/vwyxwEgTJAq+QSRX2IPi/yMlwHNtGa9HD5EntoNGw9+cr4BYt80 ZHojSioXRnr5KLjFIwTZaLfJxprHifmh4ZfP54zb+IdwzhI+rmHIVOe+qVr15Ju+ XLl7zLQHR440/VdHf/aieiQ6soTIcfG0avEPtXX2yIDtm2KQ8lr///////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////+5Ag0EVI6v+wEQAMqjOEpP9zV1se4h/gfeSKB2 rpWy80aUMolBqRtMm4wrFVFzie7iPYxSGBrCXO81BaJQR/kILlf8GpTnB0vXJuGE V3GxeCQIt4WnGKbV34Uh/S8G/1d0H5nkO/jDvQJ6bDiozYeJfJoqW3ATJzRTtk3z D49aannoz73IOBjBafy+Lsa1sAS6BoX1zSO+WYrkLSXpcpQsyv4Ky1hF0zILUDrK F7dq9a7u5RBebA8tSPebDPJlJhTSxP0rDYuYn7A5UllKDEh8YW/0JEDOvGXghmfb azwViifFT/0PLu93ZEHYT3NHH+Gd/2Sy9OFyBr1sJX2MRrTSstXUxMjaKqwt2j+E 6EUxgMX+RPSVJqRyTVE6ydSEiYyptcddtBeapMStuVZlsdK6l3H6IzY64VCGeFwy XY1HHUM0Kpk1niHm87/bJ8ZmOQmKDVsAplj0kHoWtf3xiDVtLVWJMwTVTfICZB7F o5frjmiW6r9kUbgIm3a6LNt79ev6H7uTbRj+Cgw7dBvbyP+DOtyhRWYgS220FAUF RvjLz9R0Q8rDX3JYXIwm0FEoMAVvp/BPH/rQMZpf+4j+aHZu3WcLIfW45Ok7u8C9 4vFGcy9Nk1V5jn1JuFa/KD34vElXkM4aFg3+szAmRKIMUrEoBm1lmwG7nNOhSJn/ ThiwENhWoQEMl3q1bdQXABEBAAGJBEQEGAEKAA8CGwIFAlXdobsFCQURWLACKcFd IAQZAQoABgUCVI6v+wAKCRBwF63O9lwgNsnqEADFuK5Ql1CAt0AvPS83yUkEOHHj xh2pK86fYAwAOPr4RO8LehWGnVXi+IIjgFDKO5e4bQC/axXm57c4qZ4IUQ+eIcDf 6zuO3SSa6GcMildfyQcXTBW2sIjig/ImSdTPsLsOyWwiprq1fPCyARYpXzNa4Tiv 4rTypP0YNzhPSnjrJtAEmcBm/X0lH39cn/PNwahyBujGnuJTnwYcCMzXJMP+Dnj2 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 AlYZCWAFgwWjmoAnGmdpdDovL2dpdGh1Yi5jb20vaW5maW5pdHkwL3B1YmtleXMu Z2l0AAoJEBMY76xfu9vO6cAP/jFXHWEyPQoEWLgNxOZ/oI5Br7UUFbs+MpJkFb2t Lh+KfJybu35xNi8vPFt5FXmdyE0xJNNPLMGbqQ8/kh8BFsSejRN84qREbXHhp5B2 e2rg6RN62sWaMTYKHsiSW+Z7GxoFYixw1r43FA2JGNPj+4TuE/FvW83KCTzHsRZG yNOKKzhWAWgEJjiqtzVg5dXd7dmCWQ+on1/5npw3+zErythQqv+3vaaRDACAEreH laRuaAY+kYuSMkr6F4eMW6Zt78LC3ZxfB2okOmdqI7+zQj82Zj3OAshi/6vS7po3 slcuEVjeMhzaF8Gt6Lyxe/KZW+Vf2AB/FzRMibLepZV+v8uOwrpoeLufrscqoZrk mjc00iUeQbfGnwUhvCH8P1+D5HE0JBOsdF06JOrewFocaMNg1TuAqmrkjUMSwL5t jQPejOHCeg8BK2ULAOq3bB+E0lQ3L7Sc985tGL1jQpgSYLdNw1IncjJCgHBEKDyb O4CFo9V37An5jejG77gxNX0K81ErDIJw6oeLpicQjR3B363tXNDzhHBBmyjzafim sB2pE6cajMz3x+bUzSL3X7yGcAyWVkPIEir34u+z2+jfwfnK1XX2TjFlXLo7BtM4 dyooeB5CM0s5nHjfoDkDeazxd5lKkCPeJMbG88EZcC30/FJSOhtEpvnG5Lm3ZhV+ 254biQLtBBMBCgDXBQJVAMSiBYMJZgGATxSAAAAAAB4AKHZlcmlmaWVkQHBhdHRl cm5zaW50aGV2b2lkLm5ldEVGNkUyODZEREE4NUVBMkE0QkE3REU2ODRFMkM2RTg3 OTMyOTgyOTBLFIAAAAAAGgAoaXNpc0BwYXR0ZXJuc2ludGhldm9pZC5uZXQwQTZB NThBMTRCNTk0NkFCREUxOEUyMDdBM0FEQjY3QTJDREI4QjM1LhpodHRwczovL2Js b2cucGF0dGVybnNpbnRoZXZvaWQubmV0L3BvbGljeS50eHQACgkQo622eizbizVq rw/9GOt7yBURbGtG2PefEovIK3WJJHouIEd1q8FtaPSbs+NQfXv0o8F1Kz6S1J+/ 707Vx5dQZeHiyRXuMbJrBmyENWGx3P5s6eYmTMB/dAE6odwXrep13oOMYNlhLlsn KNU7vM8D9kzWDBI5U+Gk4LXjNs228B6MgsBjYzRAbUfP6e8JPOxNABWi+zBnMlDO oa9XgbcCsIxQ5FaGIVpVJId962IwG7wgBL/ZQT0FxnZ8e9+KBDHhy4weZ79mm10p 4CdtCw6v82Di4Q1HRbkWLrBukt9L64OAwuiBe0K0J654bu8HZujwAgXmJjrjdw3S waNh6Yv64IWwkODCQ1LieF+fH0j4fDrR3se+kzOTMsUceysqhwJ11JRJA6cUeEEw tMmeBNf+aRl7gbfEoPQkmkEDX4/wN5dZOuOimavjghn2xLYyEgff1na9WXHcIT/z LwscG7Or4XEt8iGCFcmjoWuZQZbjGYrvrR394ywvKf/rWAOeFBkQ84f1ErizXhQo 6/28bG+DpjmhESIaxXyRDpU+qgadWyjo70s8ihrl2JDJVIYjk0Y7RukEndE/Z61s zeg14QccsShdTwptyoYuRvzCc8ooeVsbLr654P8HGz46yP0VzZ2eNv65+1ZdgU8q O40276Lhr2S32LxYBdjFOATANKD3I4lmmHfO0v/dwJIULtqJAu0EEwEKANcFAlfV SBIFgwlmAYBPFIAAAAAAHgAodmVyaWZpZWRAcGF0dGVybnNpbnRoZXZvaWQubmV0 RUY2RTI4NkREQTg1RUEyQTRCQTdERTY4NEUyQzZFODc5MzI5ODI5MEsUgAAAAAAa AChpc2lzQHBhdHRlcm5zaW50aGV2b2lkLm5ldDBBNkE1OEExNEI1OTQ2QUJERTE4 RTIwN0EzQURCNjdBMkNEQjhCMzUuGmh0dHBzOi8vYmxvZy5wYXR0ZXJuc2ludGhl dm9pZC5uZXQvcG9saWN5LnR4dAAKCRCjrbZ6LNuLNZzfD/4niHzydJAeq0mKa2Ir O2/pCZJWCC6wH47xkuQJmGyxw+PVRxEBXWNMm2swt2vwuFVRt7ljLWQlEUJsrWQl mEoCV0KcNup7DIcLrJORsHp8cLdx3PCepSvSz0qApkQ1/5iOhK8UsLCKrNg8/ErM TRxeMESlzivxB5aHxqSyT352vTAkic0DHc3/szgb84XufbmHc22K4zdZRhSFh5Tv 3Mk/xLH4nHYWqPznoIeoRV96Jst0gP3GQ2GQNmzqaf3fvKjNUch28B9WlcvYr1vR JkY76degqCDRaWaClS2FtHUNU1qXF07uwFpk1CqkD9+7/ocKo4gJlXOHRgGrtnDB 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 =m01L -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: playbooks/roles/gpg/files/96865171.nmav@gnutls.org.asc ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2 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 ipUmbs4A/1iZlpnIDl9JaFrMlvSdOwJahurU7A7R5fA7yokrrEcBiF4EEBEIAAYF Ak1P6jUACgkQnUKBHfuLs3Z3QwEAtklBLDDU4slBNg7aIa4EAZ2K4/Go+F13JS16 re/N5O4BALPgNAB+0qLgDg/UQmDmyFjIO1r5vtp97BeAdSwm1Y64iF4EEBEIAAYF Ak1U+1IACgkQ7o02PRaHlzjKfQD+IAQsHP/uh//SpCno/dgUf3W5uD7vCHstanq7 ubxJiYIA/jvS+U1ZrGt/EVPbMpoSlbwwr8g+R1nWsF+s6tqKkLhniGUEExEIACUF Ak1WxLgeGmh0dHA6Ly93d3cuZ290aGdvb3NlLm5ldC9wZ3AvAAoJELR14ge6tYIp l7gAnjkbjnRS6lFVa09WXgGzX0+K3niEAJ4sxP/qlOOoHUjzTh0MZN9yb/TAmohl BBMRCAAlBQJNVsS7HhpodHRwOi8vd3d3LmdvdGhnb29zZS5uZXQvcGdwLwAKCRCU j9ag4Q9QLintAJ4tis8eBtXw05q3qRSW0GGQHeFrTwCfcNHWvfiXCLW8QAlQSIAC GESRUc6IdAQSEQIANAUCTU/RXS0aaHR0cDovL3d3dy5hMnguY2gvZGUva29udGFr dC9wZ3AtcG9saWN5Lmh0bWwACgkQcW1EEz2MIi1OvwCfQjG0eHr/kHanCZLFuf6S X19OdUkAn06K3zrS/gYyMLrJegK+/Lku9eO5iHQEEhECADQFAk1P0W8tGmh0dHA6 Ly93d3cuYTJ4LmNoL2RlL2tvbnRha3QvcGdwLXBvbGljeS5odG1sAAoJEFbVKT7J egZU584An1wa2HTUiR9GsvdO1OR8/q/a0Fq7AJ9WtmrUp/EI0hq0IGy14TPqWHdi W4h1BBAWCAAdFiEEEaFmtlnT6CXpZS+hG7icBgI2dEkFAlihkW0ACgkQG7icBgI2 dEnSHgD+NKvpKk2Pyt5/S7NdDhP3EwMe6a1ayGEp9fLIdymXHnkA/38Btt8ZnoI/ G6m9H+oZy/+qPGmz9Xl3yKl10v6iRZYBiH0EExECAD0FAk1g1F4yGmh0dHA6Ly93 d3cubmljLW5hYy1wcm9qZWN0Lm9yZy9+YmxhYXAvcG9saWN5Lmh0bWwDBQF4AAoJ EHLU3/jUw/GX7UEAn1q0nJa4PKV0iXBk6itVnzK6ZHi4AJ0T7BdWBAOW+pvBMXYD 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 /lL4fNapao8+Yj/N6oT8HBcA/2PB62hLyyIQwGSqovcYC3R5QWxcpEg0+FXBq52p rCj6iF4EEBEIAAYFAk1P6jUACgkQnUKBHfuLs3Y2iwD6AzC0tTOHa4fKisvAoWlN Ejqxbgs/wyfdvaeyAMKDZoIA/3PgfPsF1dYqm2WiUUzS6fE7ral+ULtA6DfS2s6B V219iF4EEBEIAAYFAk1U+1IACgkQ7o02PRaHlziNaQD/Va8yZU0v/UbrbB7uZ6TS rfzvf6SUx9Wnm0kiOTcOGoQA/3BcTLSDeiEWG/hbI2vaQjuWHCisRdbFx0cxSEFj xeE1iGUEExEIACUFAk1WxLgeGmh0dHA6Ly93d3cuZ290aGdvb3NlLm5ldC9wZ3Av AAoJELR14ge6tYIpBx8AoNRcJvGN+QmiOqxZSBZkFy7zawNAAKCjBJUz1dzYDiDl RDdJEkIIP6R52ohlBBMRCAAlBQJNVsS7HhpodHRwOi8vd3d3LmdvdGhnb29zZS5u ZXQvcGdwLwAKCRCUj9ag4Q9QLp3gAKCIe0+D6ZYXfdISEWMDxWVwuFfliACfVp70 kqaGLsOR3duqU7JNrp9ikVSIdAQSEQIANAUCTU/RXS0aaHR0cDovL3d3dy5hMngu Y2gvZGUva29udGFrdC9wZ3AtcG9saWN5Lmh0bWwACgkQcW1EEz2MIi1zsgCgtTqN /X4Kby8uY1RrB/pUH+4BB74An03/W8DJ5jfWt6pNhrbZcoesyGfHiHQEEhECADQF Ak1P0W8tGmh0dHA6Ly93d3cuYTJ4LmNoL2RlL2tvbnRha3QvcGdwLXBvbGljeS5o dG1sAAoJEFbVKT7JegZUk0cAoNfv3bXzPDca17Hk8hyGGw/dv+GZAKDmTUP08lt+ lspGi2mannl6iSHCcoh1BBAWCAAdFiEEEaFmtlnT6CXpZS+hG7icBgI2dEkFAlih kXQACgkQG7icBgI2dEm1YwEA0FqDfQlOVUqxei7La0ranvEfp5frFAA9RPeF0zIT dHgBAMUFHxO7Y5sAfhtv+8tG6UJB3iP6UtU7Y57wJ9lD6XcEiH0EExECAD0FAk1g 1F4yGmh0dHA6Ly93d3cubmljLW5hYy1wcm9qZWN0Lm9yZy9+YmxhYXAvcG9saWN5 Lmh0bWwDBQF4AAoJEHLU3/jUw/GXE8IAn1BlJ+kOR2BNPIMfCs4nLpIWy7AFAJ0W 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 QIOJfhI1jBAAlDT5AHza1U2fSTJ4KfRHbGBDyozihiWI+wl8L9DQ45iv//////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////+JAhwEEAEC AAYFAk1Sb+MACgkQKukB5ccCGNJYKBAAzsVGoGNds0Mz+LMsKypZqwAiDGi4hOpj KU9V40sjp4DKkM/x8ieZU+UJgOR/13EHy0vTD22NFT7sC29p1VWbobdk+jluQAJq 0IgNZAqNGkFA2jvV1zRj5o8rJ26+G6A95Eyiq0NnrKDDy0bsO8nVtM6GBmgSY6MU KIvGcDCvOewDz3B98IoEet6J+7qxEvoCg8fPlwKzDv7H1NjOkpTq7+TUnv4rvPRZ 3eIcRXIfRn4cgfKk13K+k8YZTiMtl/2yt06PtC9S1vZzrYpKBIEAy+pDWRz2aZJn XaWOrz5aNZNLiqvjc9hmMN0uDXl93XXBI/SFlFEX/r0+6/9LPPUvwBdet8I6X0QU hsvW3Ahw8NoyXZHTE6auOotoirsDdLS8z/TuAZ58YnMEtJswOayhzn1MimprywS6 9S9/jb5BOHW/lR2VB/UUfM+Tv/2+hrGJCAcAI5m2biqV1sHYhHoRVOWvW4OLaQ7u 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 +Rf44bJMHaxnzi5x =F8jg -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: playbooks/roles/gpg/files/A697A56F.corban@raunco.co.asc ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- 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 =IEyv -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: playbooks/roles/gpg/files/AF16234E.alimakki@gmail.com.asc ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- mG8EWrT8mhMFK4EEACIDAwTdpZNOKCX/kAf9MAU1PM7N1BKcpPdBZFJsG/xVeJRv OAtFw6oqKYlZbR/GF1N+FL5kHJ98rKippyzgbGlrJ7ybgL4IzpMW1e1DdpgnG6If ZlfbGntuLVRzTp01+Smz5U20HkFsaSBNYWtraSA8YWxpbWFra2lAZ21haWwuY29t Poi2BBMTCQA+FiEEpFXaOWgX6MbiyBm/rxYjTsk9zfsFAlq0/JoCGyMFCQtJ/+YF CwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQrxYjTsk9zfschwF/eUEa+z+2wqhe 6VAEZgaqchxmQpjMGP1H8l4MheWZVBQc1qtfdXl9s+QDCyYfzNDXAYCw1GIm8DUT GT3loxmuMYuyzUWohMLY7yEgXn6U8k2NB/CRAcfsz5AT3LAndNuf8Zu4cwRatPya EgUrgQQAIgMDBCH9Rs++/u1zpHuFKZ/9nj21hHC3bFfOfqcVdru92ES+tr8PCwZT yXercMUb3179Ej6VEZbJMoZ+7lKwVj77UzlMHvYBNRQX0DS7wztI+yEvbcckxySO Pxvk2X9px/awmAMBCQmIngQYEwkAJhYhBKRV2jloF+jG4sgZv68WI07JPc37BQJa tPyaAhsMBQkLSf/mAAoJEK8WI07JPc37JVcBfRLZW0ESnzcSpkfnt2w9hucXT2xo B/xvKiSl6NBmeO8xmV6giTt+bbiGnGsdMQ5ebgGAqcBpBSXIjNB1onV/wq6k1jr6 05KnSv5lIRwKQvdDpU3mr70j8Rw5L1tem0NPjkyO =CFDD -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: playbooks/roles/gpg/files/CDF6583E.josh@joshlund.com.asc ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- mQENBFGauS0BCAC+3c+kVPOsMmWQ1IWobTn2WDzbMpgzH9YBVNCs7Rihv1cBufjC qCetEp+GFfoWdNWl/GFFhdousBSOOOPervnYUiLdIW+BXc/FTbysrXy7qJfCofUf JB7LOJw261eUYPglHES/IM6F6rmtkbfcnD+NPfJ3t/cEFMWDYid1BZcJAGdQ6pES ydIG7RW4cbQHJPgzjubwEa/JsnSiGORsaqAjXwcD9ii4q/UkahMpfnmk/iLBh/Sl Y03Cob9xGx2nW1agzjFbRFUFd5iqKN4RpUwutPsvQDy3gmiEX5abpwZ8nWHlb3Ed Gu8acwrbMDebwJ/HsC5PA2+0bpNEmE9LqsF9ABEBAAG0H0pvc2h1YSBMdW5kIDxq b3NoQGpvc2hsdW5kLmNvbT6JATgEEwECACIFAlGauS0CGwMGCwkIBwMCBhUIAgkK CwQWAgMBAh4BAheAAAoJEM32WD6no1bWnZQH/1EHoD/bbnQfgY/ceUi1pFQF0vls 00Ish1Wyva/VVoBXj7x72WW2GNPA+FLKRolFtnNo+BUNWFwDQV6mN3sjHuonOCyB 4GpoX/W30+PKo2VCnml3mheFlAoD6i5aikQcqO/VetFljLl7enfmpd67nUJ0Tw7L PI6g21zy45RDCXip32XJF1bNDGONN35U5j8fga17w3K2vWV//Rl3SbCO+3erbBhw O8oAuc93MNtXmOrNoHiMUjdJ18jGMpJ0WUGdjzUuyNXhHYtkrf9KE/W7gk2+Wp85 zumWGiZFU/OKs3U98ujDNS9vk1urWpkrucIwdSbGB+ad3AK7L2NtqQwuGZK5AQ0E UZq5LQEIALh3+2B3AlWBT+H8BmolIGSz/AHF+CwJkDPqf/BeKrcQxVMjGMHI0LBN RwcXIVqPnNx5tO80Eu99kIXqUmcpgnq3sS/dbHx5pBPWLwC3PJHpsKO+zlIVkj/q pN3MpuABy3LfBI97DoC4AwLe5HwNcBjLGY7d7viNnnzk1sA1NJF6kZQ0Y+9wcxKL U1EYMiFJH2MPurQzS8BHJV3aVp7zCM59YrBUhF0aL0jPs0b/zm+LTvqSn9+72iIq 1GYV0voBX8ogxBPqf/kSKFuxdox2EN9e3MKwRMcSck6Y4e1rGn5I9QlpCGBCoCBj gWtlylurM1vgTPhUwOMtgrxbEXoo9y0AEQEAAYkBHwQYAQIACQUCUZq5LQIbDAAK CRDN9lg+p6NW1pzpB/4lIKZh2vH5p1vKPNUNBL7remReaUJjWS9U5SdQPSB6DWFh MfEwK3dfcHF6bAPyiWf6JSPGcJSgmcOv4FW1wBuiK51+N8x3fAblp0DrlewFox95 ClWkr94rvHqIPhuKv636Wgo0a0/4A3jPGBqKpkQcxpZ/vW43dxrsfP6e+LZyfASo 3ft7So0908f0Ij5lKIZO5DgjQJsNaqEK93Onu1++CxEwSMwbFxmDSrwXujaOtMgu 7LVm/WuZFtfpvzr9bG8n0oyW0cQXJW5jmBsGx6ulrMdvtnEwOEXq+OmhSqW0EMWA /MJViGcd0lsmd03uuugIp92I2ZorHGFU2H3IELKi =2maT -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: playbooks/roles/gpg/files/DD3AAAA3.Michal.Trojnara@stunnel.org.asc ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2 mQINBFTU6YwBEAC6PP7E4J6cRZQsJlFE+o3zdQYo7Mg2sVxDR6K9Cha52wn7P0t0 hHUd0CSmWyfjmYUy3/7jYjgKe4oiGzeSCVK8b3TiX3ylHi/nW3mixwpDPwFmr5Cf ce55Ro3TdIeslRGigK8Hl+/l4n9c9z/AiTvcdAEQ34BJhERce4/KFx+/omiaxe7S fzzU/+52zy+v4FfnclgRQrzrD8sxNag6CQOaQ8lTMczNkBkDlhQTOPYkfNf76PUY kbWpcH7n9N50nddjEaLf7DPjOETc4OH/g5a99FSEJL7jyEgn+C8RX7RpbbAxCNlX 1231NZoresLmxSulB6fRWLmhJ8pES3sRxE1IfwUfPpUZuTPzwXEFJY6StY5OCVy8 rNFpkYlEePuVn74XkGbvv7dkkisq4Hp59zfIUaNVRod0Xk2rM8Rx8d5IK801Ywsn RyzCE02zt3N2O4IdXI1qQ1gMJNyaE/k2Qk8buh8BsKJzZca34WGocHOxz2O5s7FN Q1pLNpLmuHZIdyvYqcsenLz5EV8X2LztRmJ3Se4ag/XyXPYwS6lXX1YUGVxZpk0E sQDRdJvYCsGcUy253w+W7Nm/BtjKi6/PJmjEEU7ieHppR9Yp+LI3lyzNBeZAIVqk 4Hco05l4GUKtEDFfOQ58sULDqJWmpH4T72DHeCpfRB0guaPa5TYY7B0umQARAQAB tCtNaWNoYcWCIFRyb2puYXJhIDxNaWNoYWwuVHJvam5hcmFAbWlydC5uZXQ+iEoE EBECAAoFAlTVFpIDBQJ4AAoJEPzVPp10xzLRWKMAoN6R8pzjT7vwHYDHYFlZqnEm 7Ty5AJ9RsArkFD6YLaw4vlTboX0V1kuxNYkBMwQQAQgAHRYhBDxu+k4KK2cNXBlH +J1+uqtZhJK2BQJakQc7AAoJEJ1+uqtZhJK2qbMH/00biTSOHDZ5Zs/1itJwLkcK cOb8SuL6QmXdkd23OG8TBcCvEyjU/pR3EdNFP5oonoA3OjCk9lEkyxhm9RutCtJn QGQMvu1ZD3C1ZXYvLl+yTXhFlojXlVyZBGCUxPR4zLbp53IOz9bkfwhuE7Exi1c9 0fVYTXXtBTe/oUqkSafbNwc+drDL6bL5GMXQlf5E411+hlzUnwtCAKecs+4lq9yb tZdsTq6srd0aLKKAEQt8769XOTLFPRyFVEQCQdKXwFIInXJu1AMSiR+HRh6vTq4Y jZoXyTEQvZvw/BDxl8zsH1J5K9XvSquORZOaAXplMFLSpXv9u5wWHR78n7T7XOKJ AjgEEwECACIFAlTU6YwCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJELEE iTLdOqqjj/UP/jDb+7B3MdcuMR+IsrD5oy/gAU6J2XlCFv49TJYL3vI8bWuwKbg5 tFiIDEHf6VoIxvPIP9oUrNQ0ZrqOu6T/r95fh4JoUGqkHvV6GkB8soH6w8BhCgIw OfvuoYPPFxeRjbnIVKGNsBwiwcrsXWmk/cA0lBh84/lZUGHGlaq8v7vPsEYbsa/P Wr4e53m49g8NapCLkevsLmmAFNfZms85nZwy4f962B0+71Sbe+dSR8seH5ybSiO3 g1saUhn4XCut0lrR06CSoOI23SGTDkVVbznraUWz7xfKqEd9i2Unxf6zybTYVoHL C1AqWZ+hMhuo4oMfJoWNlrqUTR7rUhEvqijX3wjIu326UH5eMCas5TcSSStcxrtq MdueZfn1yXkCYe3773DYnrkAKSoTi/pjs9X8kZ1Icfxoxp3DpgoGZXUyxBfA3h7r pbl4BZQ1yXF+qaIe78rd7ideEgsoldXC515gqMV0ZkDZW+M0xqAL3zsPwxR9xM0O OHSv2dRziHyXmCC8XviT9hPEzV1L6/8HJNdskPJGqhcx0kAXFftEuPfqSKaCuO+S 1azrOkvSY4TLeFlX1+D2PiBPMBp2cicNYLz13ZNT/fJR0fxb9Fhv5Fonpqw1zzNb Y5yGrOhBps8U/4ssBheLXbFR74hE1YERqSh4BLQf4cLAnhlEwJhw210EtC5NaWNo YcWCIFRyb2puYXJhIDxNaWNoYWwuVHJvam5hcmFAc3R1bm5lbC5vcmc+iEoEEBEC AAoFAlTVFpIDBQJ4AAoJEPzVPp10xzLRtFkAn3XOJUuQUnruzeO2oa4+BJMBsmJL AJ4pc6dtGRcoQrDqUk/vZyliX8Hb0okBMwQQAQgAHRYhBDxu+k4KK2cNXBlH+J1+ uqtZhJK2BQJakQc7AAoJEJ1+uqtZhJK2mQYH/2XCL/4AP87o6fkgwcW7YPBvWxyi R6C3WKbYREYAt9YTJpPQTzJCPGXTszkhvIzmgj7RDl/TqaFCibNEoLnZxpNZMxqq ijMJ4hSHr+0JSh5VrR9NGlKqdI1csXnS5rnMywFXj2GD9HGYnZl+ZlcML0WlS4i0 gCafK6Ao1U6sI4+VnfrrbqmCQ6LxnG/5Ldld6ZcuNqiXKHVij3MIsnluOsToTtvj J6EgLSN3x3b05HgQBS7UQW/7NO82H+Rk4kUgv5KejFGCzP7EiLhNGv3arJTZHebT HrIbsDay1GQTJKEbsMXSj7n2DG+rmketmZkt0liXLKFOd61cHE7L+xN9ueyJAjgE EwECACIFAlTVDWUCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJELEEiTLd OqqjvVMQAJY7fVkg6uScKyCYT3RDTIE9gPwIDxoz04s72pVhFH5IpDpUAWf+pYKg b/Pdow5QkkUMyYENNKIZnjeNF5BVzU++g1QoVeQPXe+sM0aD4gyv6N5+PL9GQULm t0gA7OXqcxEUfkP8oO2+5dQZ+No302isLK/lpw5roaKdMUA0dhbv0NK+3R1pMbRE WKZVMq0CHyjHEEYZS9n/rRIlPkjenAopfm3q34tpSBTP/nmnmPfp3KFlOaRfqNXJ zOR+MHDL2LaMPyRnXD4RA48bLWEcIdpo3mS7cGHiGJmuWHydymKk4x44nljvULN7 8lE7K7eORH/n0u48kttSTA09le4wz4PgRjX01YqpYWByyNoXmO5LfrGBuVCh0A5c FlyfMfXhvGVESVY0uEyJv6mIgvQForZymmXAn5vNBGTJbOquXnG78B4T99AF15HE vWODh707N6Lg+TPRpyjEiDaZ1HpvfpSk5Yq8FBhnybhNuxYFGsONzEp66Xs5uyh0 WgZO15HGo32CWE136flskB6wbNQ3zp7c2YKjEJRAxoqWswRVQzBppI5VZg0J/BdJ 7jZ0rZmtXimwhgjB+S1OQXrQ3cp4FtOzv8eFL9Hbb36VAIv3RtwpGK/ZLNy+pHq/ vzGducA2bRJf9vozvE0F/yVZfBL1OVH0DMDhcLsWo0thMwZXnpOYtC9NaWNoYcWC IFRyb2puYXJhIDxNaWNoYWwuVHJvam5hcmFAbW9iaS1jb20ubmV0PohKBBARAgAK BQJU1RaSAwUCeAAKCRD81T6ddMcy0YdJAKCqUM57aBB0MKtB7I258foylNoLowCf WMXExoUnz3AspRzdCi22vIeMNBaJATMEEAEIAB0WIQQ8bvpOCitnDVwZR/idfrqr WYSStgUCWpEHOwAKCRCdfrqrWYSStgepB/9MLcUR0BdC+jvipNOqSNFAjgtTleoM Qixns2qsgFc8tsHDBQNfpvC8CMkaSRUSqVDaMyh5AnJAHqN70eia3Ng/lEYx321E Vc0Z7GuTV7vi1ZopZecTONEID3N1FJ05y2VBSfbP6H3S4opCnLnfAr3I2R1sjT/0 xA+C3tgaAlQywEJXZWik8+Ahlxc0jYhQbabOHvNrzlTPufYDxm/9lpb2/Nu9UJrS HJwhcsAH6wm6Y+SpQbEG/RLCXMkHnhRs2Zk6mpn6wHvCvflABKbtWaHj5YsK46Er MNJ+QJ/7likoL60AHiMcYX1nyuEfq9eKtadozBbITRyLAb9K77051WLIiQI4BBMB AgAiBQJU1Q03AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRCxBIky3Tqq oyVkEACtMHa7x5PQ0ZNJ8TrvVd/VrT5USuHwwFwnnsYUNzSc95gSwSEaPC3xwgs9 cX3VRmOwb3IiCQQ7R0EamH/ydmZnlesbCsnamLl6dEmzS284lnnMd5X0wep2qq3S lS1z+5wW4ZnoodX98E7RyecjMYPLH+uAqGqg3nHG8eOpoSDMvIOJtOIvDc9Y6tbN sBbeKbOCyB7A08TMzVqayQvXzm6QShHTicra69oqIzhmu2zII3ZWVwkfEGweuN0v docoXiqrentcyF3KLUX/LooDzdCAxuoJdovg41E69rXEWF//IP5XBT0LUDTzqwmB e7nOfoJF2RAHn3ySogdL6WNSGaH5B5NK1jGflj/Hr/HBHIYYx820P4aEXSyxbLQW 1F0HWlAAQ9+EmjJssbv7cIq2DV2Ls3AOeY0GAWhTdvUVdVpOG+TuWRUi61XwjWPf vrJDH8MEoLRb2MhNRffle8hSdF8TP4CO1TCxtSFs0NXT1I/HazvacHzvbXspFDJv bYJsy+pRvOsf2QCcY5xb633duU60+IHJ9GMOV/ZqQR744wAxu+e/ZHpa2+mpI9Vp TMuBTMFCOQKbiLacsDJtFqsenZAyhcTU4DPFa0bkMO67Gwl0skuk2x8/0R3EgJ9J vNlsEz6vBaHpWhEddU1m6FMKKZkfo0xnyFr/WPT6zti9iKTnIbkCDQRU1OvDARAA 8gIC641dK6ap9W1K3EkqRn0z6zizdVGr/jvf8xFXeUq+auxixZ0tEY6NM5CBSya5 BCK9IGVWmJNbazyWUa4llA6EvmUxcTeGE7ppQA4Kl1bzvUq5upo+8+0VuqvLC/bV z0DUnFSWJYHAZrPZ+yO0yMq8vaGTo5kwKixQ4Ni+N+1EiALKZex1g6UW9d0HAcYE a/lTWhz3J0V1yyY4Vov30gtoo67KkSC/SswZzIR00CQGrz3twlGuB73Sm1YfqDqb Y8dQLJeyU0ovIeU95VI5cQF6D1H8YdaMWQm6MtVAfIX5WMoH+eq4Ank9hilReGAN kIWNSqM21Drdu3crbGIYiZPEadKfGxwquwvRDTEgD4gjqMvEdxA2W6s4WR36SwMk eOtESj21MiR2YDcbIzIbUh9p0P8DZGvQcVh45jCgdOcL5th9R076npXHn8FIe2If AZnX1OnpsKn/YqJ0wNFhGYWxV/yZA10NbFKFXhD1FGqrOz6lSqmqDz00tXofF432 ae+7PzTP9n4cij4k0SYG1l/LThnOYL3SNUCG3rCASeWoXmhxCYRGi0Xw3IJrcpVN mNQD+SLLTjVB94AlDjSlx1q0V+9ymhGHi51wsBSajMwDexaSI/WM1y9lROwl7eeA D41fPArzTleAqT89akWLevTBLWvj59mku9vZAW26/1UAEQEAAYkCHwQYAQIACQUC VNTrwwIbDAAKCRCxBIky3Tqqo2NCEACHJ7e0l8NhS4slfzej1AAXOwL1wDexn6th pgexAyqZLIaibqhIybhSo1LOL1NY/55ytscbOQL7NliRAXVN6F9lcer+qzxL5Jgx zUU6dryapNZYs06u3wfr8ZtSbvIAON/w89tm9tHxoNUIYZZUZROFBW6fn8RkhboQ s0hJFxWfWghOxhS0TXJ8/MZ4YcfDy+Ew6LIAym3A1XY+++2VMEHqKcyhU95W5sqA sfO5MkRWa0E9JTS2dWTteNTWPonywJGX/mSVVMZgOZF6o32Vb9LTnB676YQaPiMl u2qg+vRkRM/zyGjvPx7hilf68CWxZcIHslfp5gJV6RvtlK+muEvIkSmNYyi8hQp1 Y5C6uWb9JWt/9ISJ+Xz+n+5nAHEUzW/LeEDyhjVlS9vOoAAy18r47mQybzJ2q2zO Ho9zl3fKOJ2S4SFBKGHuIhPOxG2CruhxN9U5+RwTDqKECeuCZROMYQLzlmIP2vM/ NuFVhQm8iNhbTvEenh4mWD4IuOHJkqvzKKzAXllosuUK4B0kblh4GaOVmEjaXGw8 789rOlQzD5566SgKPDNUtom5/eIcy6/UYBoFd7lLltIVSSCA1VUMU4MWJgjwa9gk 6MxoNe8dcJ197oQMfhZNjJ80S5C+a2al4wrR2vL/3hXhy2M2kG73RLSzxEiVoJsG +hbzNtfIa7kCDQRU1O5ZARAA1pGrQ1V3YMXF3DzwvA/uWb912pwqUvMAAKvYCDiE LIOP07c32+z04N/bOXjiZ2Jb8AuICj4v92tXAygtf18zxwoU8AOXiuScP3wy1Zpr Bw8k71dNy0XmEXbiX7tkLoe0OzWlCaNTajSXTELT+nYHTOkBsrC4T+y7AwYueQJY UaRkJR/5Tc68UnRSO295pgJd7EoWWAky3bdH+TKN0MsagCJwa+RrXFGtIKjU0XAK sddTxQKx2SUGF0QVdNZ/14Duo73btoXtHgB0oxewnsiJp5XKWYm57RSNLv1LKr26 iSUtUM1CAIZALuGMAyQXVEo7OmzuZmN0yRYM7FSnpG4rIDnDxYhDTaa+xWb738V8 uLQDZAVnAuBEhq1RQEDrRM/XLbibvVBzpd+JI9WneNEp0ehq5sEC6FbKYz0HqVk2 SH1Dpb0tgrtxz3c7rPs7vRdmFMxTuYctSzuqNHpKX+C6rgyAW2sxEKD0ys8OYEa3 hvrQFSAznM/j3X8dge1DriHIQd/Dt4+LMdPcsQk3vty7pYxZIDRa9hl7ngaesQSZ /7PV/cj7U7qieTr1ulO1Gc5GcyS2Hu4P9109HX1tBEQvGHpbqe9Lc2d0VKgHVjG9 vDLrE1h/qXKbmn0LF1YR4djaM+sYCfYOO+WzZKUACPdMq3Lid/3oQ71p6eNgu6lQ cgEAEQEAAYkEPgQYAQIACQUCVNTuWQIbAgIpCRCxBIky3Tqqo8FdIAQZAQIABgUC VNTuWQAKCRAu/H/w1BbgFNx6EACR7CKB3Mv2lNaRRraVRwjNrumyODqsnX/oe3la d04iCBb9JxGyNyTGF0s6teoaocXxIeZ50bF7GuYcnepMGpniMCkE2ymlM6ruFNNT UYC02FsrowKQboC7S5DN2l7lb4nlgyDX7nOlOMmhTc3D/QsduMyS9H5kjFFKtzLY OwREV/RHI/wQUyTyze8qs/BxpT3/HsSJuGZybLSd/fmeM43xghcdfDgKTaGkFkhh W7UWgtOhQtYxr0VD4HEw4C+nMyksqKAIFMBjJAqtsuWeSgavVrbU8KrzlcJFHSro vZ7Pi0mKMYHGomPstZcZxwr15t3BhDvogMSRscU1mLUigLEGiWxPVxtQlmHTZfMn s4Cy04S7jK4Gix0PN4Xi/9rOcLFCb5zddcLVrqiuT+dt/O/TPKUKHTvLL1gF4Dly pbu8TQWtO7xDSPy7wSdPWUN5GBjsxbZfVlWpvvVMmGUuygIl0LkrJLKGxk36AnNp EPqsQ9e9Rsgu5dP9lGPz3igxE3p+UlhWo5eqJqZwAfEFb+0PQzKSQ6zIFQAf50eS I/pWf+Xp9XOT47d4y8aWzHA7T/ja9tbyd+eg71ZOqOFtVP8zFWvmPnoosxrBR7qK /RBY5/PXKhfG10yEYXSjTap4dmsy430l8Mcuqo55iixgT5vxZfTeyFjTjHmjuHD1 rTTfpXk4D/9GI9cIfrWczhrbWN8BoP66ImMXpVhZzDt6S5u9dHSNJdqivDzCkktb /psXILvvu3qLmb1nJbsNzN9GJm6LoduzCJ4SqaodjhMkNi/Tc95dx0n2cCP2Rh/j vzo7zrqQO09c8at/pFEiF8LgUlc5QaB/GNhXBqJog2yOzUPGKq0OMy/wttW42TCe 7V+J8fnn16xfGhnVwmiWRQaqdCiFDY2IiOHhnRwfJVANrddfuU/AJ8vY8XXzrxI7 YZL43V530Wich1VB00XLFU8aj08FsjdFvR77AAxFU+Cd6sH6yq6jsRXppQ0BOO15 aR+wopEvtKwDdRu3TaweC1XMLLQ4XuN9Ql0bMH0d626uMG2zUfZGO1jNTOS4sUhE qJsImbsL/hgNDKYvfo0wSHPWmQo9njw7aG8Mey77I3fL1ELj/Tfa86njPpJ/tmFM LV9ntWACcW/c3tojdcP278rTw/4zk+Sr2Zv+3bP1yjJd0z4B3gYYz2BUYTU7dyiA 41Kgk4ZfV1n2NUAxQJYzvEIAZcMEWA3rOTb+AjcBVXX89Gk0BEykVmA9G808tbmI +4DUd2c/+d1xeufb43TGOiwKqwY+Os9iey3FbsnoYuzKPsd5LByJFEudbMB152h9 5u/NysaM0AjC+yPtlpSLUIaDUW75VAlQKPWj1Ag5uVpc2ScMEjevQQ== =uMki -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: playbooks/roles/gpg/files/F67DA905.nop@nop.com.asc ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- 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 =E0RQ -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: playbooks/roles/gpg/tasks/main.yml ================================================ --- - name: "Install GnuPG 2, dirmngr and gpgv2" apt: package: - gnupg2 - dirmngr - gpgv2 - name: "Create the GPG directory" file: path: "{{ root_gpg_dir }}" state: directory owner: root group: root mode: 0750 - name: "Create the Streisand GPG directory" file: path: "{{ streisand_gpg_dir }}" state: directory owner: root group: root mode: 0750 - name: "Create the Streisand GPG keys directory" file: path: "{{ streisand_gpg_dir }}/keys" state: directory owner: root group: root mode: 0750 - name: "Write the Streisand GPG dirmngr config" template: src: "dirmngr.conf.j2" dest: "{{ root_gpg_dir }}/dirmngr.conf" owner: root group: root mode: 0750 - name: "Ensure a GPG agent is running" command: "gpgconf --launch gpg-agent" - name: "Reload gpg-agent to pick up configuration changes" command: "gpgconf --reload gpg-agent" # It turns out that "--reload" doesn't work on dirmngr. - name: "Kill any existing dirmngr" command: "gpgconf --kill dirmngr" - name: "Start a new dirmngr with our config changes" command: "gpgconf --launch dirmngr" - name: "Wait for the GPG agent and dirmngr control sockets" wait_for: path: "{{ root_gpg_dir }}/{{ item }}" state: present sleep: 5 timeout: 60 with_items: - "S.dirmngr" - "S.gpg-agent" - name: "Create the Streisand GPG keyring" command: "gpg2 {{ streisand_default_gpg_flags }} --fingerprint" args: creates: "{{ streisand_gpg_keyring }}" - name: "Copy the bootstrap GPG public keys to the Streisand instance" copy: src: "{{ item }}" dest: "{{ streisand_gpg_dir }}/keys/{{ item }}" with_items: "{{ streisand_bootstrap_gpg_keys }}" - name: "Import the bootstrap GPG public keys to the Streisand GPG keyring" command: "gpg2 {{ streisand_default_gpg_flags }} --import {{ streisand_gpg_dir }}/keys/{{ item }}" with_items: "{{ streisand_bootstrap_gpg_keys }}" - name: "Refresh the Streisand GPG keyring with keyserver information" command: "gpg2 {{ streisand_default_gpg_flags }} {{ streisand_default_key_import_flags }} --refresh" register: gpg2_refresh_result until: "gpg2_refresh_result is success" retries: 10 delay: 5 # NOTE(@cpu): We skip the keyring refresh in CI so that when the static keys # in the repo become too stale to be used without successsful refresh the # maintainers will notice failed builds and fix them by refreshing their own # keyrings and updating the static repo keys until the build passes again. when: not streisand_ci - name: "Set up a daily cronjob to refresh the Streisand GPG keyring" template: src: "streisand-gpg-refresh.j2" dest: "/etc/cron.daily/streisand-gpg-refresh" owner: root group: root mode: 0755 # There's no point installing a cronjob in CI when: not streisand_ci ================================================ FILE: playbooks/roles/gpg/templates/dirmngr.conf.j2 ================================================ keyserver {{ streisand_gpg_keyserver_address }} hkp-cacert /etc/ssl/certs/{{ streisand_gpg_keyserver_root_ca }} ================================================ FILE: playbooks/roles/gpg/templates/streisand-gpg-refresh.j2 ================================================ #!/bin/sh # Refresh the Streisand GPG keyring /usr/bin/gpg2 {{ streisand_default_gpg_flags }} {{ streisand_default_key_import_flags }} --refresh >/dev/null 2>&1 ================================================ FILE: playbooks/roles/gpg/vars/main.yml ================================================ --- root_gpg_dir: "/root/.gnupg" # Keep Streisand's GPG cruft out of the way streisand_gpg_dir: "{{ root_gpg_dir }}/streisand" # GPG Keyserver's Root CA Cert # Currently the keyserver is using an Amazon certificate, whose root CA is signed # by "Starfield Services", which should be available by default in /etc/ssl/certs streisand_gpg_keyserver_root_ca: "Starfield_Services_Root_Certificate_Authority_-_G2.pem" # Where is the Streisand specific GPG keyring kept? streisand_gpg_keyring: "{{ streisand_gpg_dir }}/pubring.gpg" # By default use the Streisand GPG keyring streisand_default_gpg_flags: "--no-default-keyring --keyring {{ streisand_gpg_keyring }}" # NOTE(@cpu): Since GNUPG 2.1.11 (the version Ubuntu 16.04 installs meets this # criteria) the CA certificate for the HKPS SKS-Keyservers.net pool has been # built into the GNUPG distribution, so we don't need to specify a CA cert # explicitly in a dirmngr config if we stick with this particular pool. # By default use HKP over HTTPS to the SKS Keyserver pool streisand_gpg_keyserver_address: "hkps://gpg.mozilla.org" # The default timeout is 30s, we use something larger streisand_gpg_timeout: "120" # Specify some options when searching for keys in the keyserver streisand_default_key_import_flags: "--keyserver-options timeout={{ streisand_gpg_timeout }}" # Which keys from files/ do we add to the keyring in first setup? These keys are # trusted ultimately to verify software signatures during Streisand provisioning. streisand_bootstrap_gpg_keys: # OpenVPN release signing key - 2F2B01E7.security@openvpn.net.asc # Openconnect - 7F343FA7.nmav@redhat.com.asc - 96865171.nmav@gnutls.org.asc # Tor browser release signing key - 93298290.torbrowser@torproject.org.asc # PuTTY release signing key - 4AE8DA82.putty@projects.tartarus.org.asc # Stunnel release signing key - DD3AAAA3.Michal.Trojnara@stunnel.org.asc # Streisand maintainer - Github @cpu - 2D8330C2.daniel@binaryparadox.net.asc # Streisand maintainer - Github @jlund - CDF6583E.josh@joshlund.com.asc # Streisand maintainer - Github @nopdotcom - F67DA905.nop@nop.com.asc # Streisand maintainer - Github @CorbanR - A697A56F.corban@raunco.co.asc # Streisand maintainer - Github @alimakki (Commented out for now because it # breaks CI) #- AF16234E.alimakki@gmail.com.asc ================================================ FILE: playbooks/roles/i18n-docs/defaults/main.yml ================================================ --- # The jina2 template filename (without extensions) input_template_name: "instructions" # The output file name (without extensions) output_file_name: "index" ================================================ FILE: playbooks/roles/i18n-docs/tasks/main.yml ================================================ --- - name: "Generate the {{ title }} Markdown page" template: src: "{{ input_template_name }}{{ item.value.file_suffix }}.md.j2" dest: "{{ i18n_location }}/{{ output_file_name }}{{ item.value.file_suffix }}.md" with_dict: "{{ streisand_languages }}" loop_control: label: "{{ item.value.language_name }}" - name: "Convert the {{ title }} Markdown page into HTML" shell: > markdown {{ output_file_name }}{{ item.value.file_suffix }}.md | \ cat {{ streisand_header_template }} - {{ streisand_footer_template }} > {{ output_file_name }}{{ item.value.file_suffix }}.html args: chdir: "{{ i18n_location }}" with_dict: "{{ streisand_languages }}" loop_control: label: "{{ item.value.language_name }}" ================================================ FILE: playbooks/roles/i18n-docs/templates/languages.md.j2 ================================================ - - - {% for key, value in streisand_languages.items() %} [{{ value.language_name }}]({{ output_file_name }}{{ value.file_suffix }}.html)  {% endfor %} - - - ================================================ FILE: playbooks/roles/ip-forwarding/files/streisand-ipforward.sh ================================================ #!/bin/sh ### BEGIN INIT INFO # Provides: streisand-ipforward # Required-Start: $network $remote_fs $local_fs # Required-Stop: $network $remote_fs $local_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Persist IP forwarding settings for Streisand ### END INIT INFO echo 1 > /proc/sys/net/ipv4/ip_forward echo 0 | tee /proc/sys/net/ipv4/conf/*/*_redirects exit 0 ================================================ FILE: playbooks/roles/ip-forwarding/tasks/main.yml ================================================ --- - name: "Enable IPv4 traffic forwarding" sysctl: name: net.ipv4.ip_forward value: 1 when: ansible_virtualization_type != 'lxc' - name: "Add IPv4 traffic forwarding persistence service to init" copy: src: streisand-ipforward.sh dest: /etc/init.d/streisand-ipforward mode: 0755 - name: "Enable the streisand-ipforward init service" service: name: streisand-ipforward enabled: yes ================================================ FILE: playbooks/roles/lets-encrypt/files/01-reload-nginx.sh ================================================ #!/bin/sh # Deploy hook that runs after a successful certbot renewal (not each attempt) # Reload nginx to gracefully shut down old worker processes and serve the new cert. systemctl reload nginx ================================================ FILE: playbooks/roles/lets-encrypt/tasks/firewall.yml ================================================ - name: Ensure incoming HTTP traffic is allowed ufw: to_port: "{{ le_port }}" proto: tcp rule: allow ================================================ FILE: playbooks/roles/lets-encrypt/tasks/install.yml ================================================ - name: Enable the Universe repository apt_repository: repo: "deb http://archive.ubuntu.com/ubuntu {{ ansible_distribution_release }} universe" state: present register: le_add_apt_repository until: not le_add_apt_repository.failed retries: "{{ apt_repository_retries }}" delay: "{{ apt_repository_delay }}" - name: Add the certbot PPA apt_repository: repo: "ppa:certbot/certbot" register: le_add_certbot_ppa until: not le_add_certbot_ppa.failed retries: "{{ apt_repository_retries }}" delay: "{{ apt_repository_delay }}" - name: Install certbot apt: package: certbot ================================================ FILE: playbooks/roles/lets-encrypt/tasks/main.yml ================================================ - import_tasks: install.yml - import_tasks: firewall.yml # Now we try to request a certificate. If it fails. We print a warning and # fall back to self-signed certificates. - block: - set_fact: le_email_flag: "--email {{ streisand_admin_email }}" when: streisand_admin_email != "" - set_fact: le_email_flag: "--register-unsafely-without-email" when: streisand_admin_email == "" - name: Request initial certificate from Let's Encrypt # Use certbot's "standalone" plugin to listen on port 80 for the initial # challenge request/response. After this role runs, the nginx config # handles port 80 requests, and whitelists the ACME challenge response dir. command: |- certbot certonly --standalone -d {{ streisand_domain }} {{ le_email_flag }} --agree-tos --non-interactive --rsa-key-size {{ le_rsa_key_size }} --server {{ le_api_endpoint }} args: creates: "{{ le_certificate }}" # We modify certbot's renewal config file to use the "webroot" plugin. # By default, certbot uses the same options during the initial cert request # when doing renewals. This would cause port conflicts since nginx will # be listening on 80 (for 80->443 redirects, and allowing ACME challenges). # We could technically let certbot restart/modify nginx temporarily for # port 80 during renewal, but using "webroot" helps avoid downtime, since # it just needs to write to the webroot directory to complete the challenge. - name: Modify certbot renewal options to use webroot authenticator ini_file: path: "/etc/letsencrypt/renewal/{{ streisand_domain }}.conf" section: renewalparams option: authenticator value: webroot - name: Point webroot to the right directory where nginx will serve challenge responses. ini_file: path: "/etc/letsencrypt/renewal/{{ streisand_domain }}.conf" section: renewalparams option: webroot_path value: "{{ nginx_default_html_path }}" - name: Setup deploy hook to reload nginx after successful auto renewal copy: src: 01-reload-nginx.sh dest: "{{ le_base }}/renewal-hooks/deploy/01-reload-nginx.sh" owner: root group: root mode: 0750 - set_fact: le_ok: True rescue: - set_fact: le_ok: False - name: Failed to get Let's Encrypt certificates. We are falling back to use self-signed ones. You could re-run the playbook to try again. pause: seconds: 10 ================================================ FILE: playbooks/roles/lets-encrypt/vars/main.yml ================================================ --- le_base: /etc/letsencrypt le_port: 80 # RSA key size to request for SSL certificate le_rsa_key_size: 4096 le_api_endpoint: "https://acme-v02.api.letsencrypt.org/directory" le_certificate: "{{ le_base }}/live/{{ streisand_domain }}/fullchain.pem" le_private_key: "{{ le_base }}/live/{{ streisand_domain }}/privkey.pem" le_chain: "{{ le_base }}/live/{{ streisand_domain }}/chain.pem" ================================================ FILE: playbooks/roles/nginx/files/nginx.conf ================================================ user www-data; worker_processes 4; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; tcp_nopush on; tcp_nodelay on; server_names_hash_bucket_size 128; types_hash_max_size 4096; client_max_body_size 10M; server_tokens off; keepalive_timeout 65; gzip on; gzip_types text/css text/xml text/plain application/x-javascript application/atom+xml application/rss+xml; autoindex on; index index.html index.htm; include /etc/nginx/sites-enabled/*; } ================================================ FILE: playbooks/roles/nginx/files/nginx_signing.key ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2.0.22 (GNU/Linux) mQENBE5OMmIBCAD+FPYKGriGGf7NqwKfWC83cBV01gabgVWQmZbMcFzeW+hMsgxH W6iimD0RsfZ9oEbfJCPG0CRSZ7ppq5pKamYs2+EJ8Q2ysOFHHwpGrA2C8zyNAs4I QxnZZIbETgcSwFtDun0XiqPwPZgyuXVm9PAbLZRbfBzm8wR/3SWygqZBBLdQk5TE fDR+Eny/M1RVR4xClECONF9UBB2ejFdI1LD45APbP2hsN/piFByU1t7yK2gpFyRt 97WzGHn9MV5/TL7AmRPM4pcr3JacmtCnxXeCZ8nLqedoSuHFuhwyDnlAbu8I16O5 XRrfzhrHRJFM1JnIiGmzZi6zBvH0ItfyX6ttABEBAAG0KW5naW54IHNpZ25pbmcg a2V5IDxzaWduaW5nLWtleUBuZ2lueC5jb20+iQE+BBMBAgAoAhsDBgsJCAcDAgYV CAIJCgsEFgIDAQIeAQIXgAUCV2K1+AUJGB4fQQAKCRCr9b2Ce9m/YloaB/9XGrol kocm7l/tsVjaBQCteXKuwsm4XhCuAQ6YAwA1L1UheGOG/aa2xJvrXE8X32tgcTjr KoYoXWcdxaFjlXGTt6jV85qRguUzvMOxxSEM2Dn115etN9piPl0Zz+4rkx8+2vJG F+eMlruPXg/zd88NvyLq5gGHEsFRBMVufYmHtNfcp4okC1klWiRIRSdp4QY1wdrN 1O+/oCTl8Bzy6hcHjLIq3aoumcLxMjtBoclc/5OTioLDwSDfVx7rWyfRhcBzVbwD oe/PD08AoAA6fxXvWjSxy+dGhEaXoTHjkCbz/l6NxrK3JFyauDgU4K4MytsZ1HDi MgMW8hZXxszoICTTiQEcBBABAgAGBQJOTkelAAoJEKZP1bF62zmo79oH/1XDb29S YtWp+MTJTPFEwlWRiyRuDXy3wBd/BpwBRIWfWzMs1gnCjNjk0EVBVGa2grvy9Jtx JKMd6l/PWXVucSt+U/+GO8rBkw14SdhqxaS2l14v6gyMeUrSbY3XfToGfwHC4sa/ Thn8X4jFaQ2XN5dAIzJGU1s5JA0tjEzUwCnmrKmyMlXZaoQVrmORGjCuH0I0aAFk RS0UtnB9HPpxhGVbs24xXZQnZDNbUQeulFxS4uP3OLDBAeCHl+v4t/uotIad8v6J SO93vc1evIje6lguE81HHmJn9noxPItvOvSMb2yPsE8mH4cJHRTFNSEhPW6ghmlf Wa9ZwiVX5igxcvaIRgQQEQIABgUCTk5b0gAKCRDs8OkLLBcgg1G+AKCnacLb/+W6 cflirUIExgZdUJqoogCeNPVwXiHEIVqithAM1pdY/gcaQZmIRgQQEQIABgUCTk5f YQAKCRCpN2E5pSTFPnNWAJ9gUozyiS+9jf2rJvqmJSeWuCgVRwCcCUFhXRCpQO2Y Va3l3WuB+rgKjsQ= =EWWI -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: playbooks/roles/nginx/tasks/main.yml ================================================ --- - name: Ensure that the Apache web server is not installed in order to avoid conflicts with Nginx apt: package: "{{ apache_packages_to_remove }}" state: absent - name: "Add the official Nginx APT key; hiding 25 lines of log..." apt_key: id: 7BD9BF62 data: "{{ item }}" with_file: nginx_signing.key no_log: True - name: Add the official Nginx repository apt_repository: repo: "deb https://nginx.org/packages/{{ ansible_distribution|lower }}/ {{ ansible_lsb.codename }} nginx" register: nginx_add_apt_repository until: not nginx_add_apt_repository.failed retries: "{{ apt_repository_retries }}" delay: "{{ apt_repository_delay }}" - name: Install Nginx apt: package: nginx - name: Update Nginx configuration copy: src: nginx.conf dest: /etc/nginx/nginx.conf owner: root group: root mode: 0644 - name: Set up Nginx vhost directories file: path: /etc/nginx/{{ item }} state: directory with_items: - sites-available - sites-enabled - name: Create the nginx systemd configuration directory file: path: "{{ nginx_systemd_service_path }}" state: directory - name: Generate the nginx systemd service file template: src: nginx.service.j2 dest: "{{ nginx_systemd_service_path }}/10-restart-failure.conf" mode: 0644 # This directory is used to create ACME challenge responses when using letsencrypt. # N.B.: this should be different from streisand's gateway path to avoid any # chance of leaking gateway files (VPN credentials, configs, etc.) over port 80. # The nginx rules do redirect http to https, with the only exception # being requests to /.well-known/acme-challenge for letsencrypt. # So there is probably a low risk of that ever happening, # but still, keeping them separate can't hurt. - name: Ensure the default nginx HTML directory is empty file: state: absent path: "{{ nginx_default_html_path }}/" when: streisand_le_enabled - file: state: directory path: "{{ nginx_default_html_path }}" when: streisand_le_enabled - name: Enable the nginx service systemd: daemon_reload: yes name: nginx.service enabled: yes state: restarted ================================================ FILE: playbooks/roles/nginx/templates/nginx.service.j2 ================================================ [Service] PrivateTmp=true RestartSec=5s Restart=on-failure ================================================ FILE: playbooks/roles/nginx/vars/main.yml ================================================ --- apache_packages_to_remove: - apache2 - apache2.2-bin - apache2.2-common - apache2-mpm-event - apache2-mpm-itk - apache2-mpm-prefork - apache2-mpm-worker nginx_systemd_service_path: /etc/systemd/system/nginx.service.d nginx_default_html_path: /usr/share/nginx/html ================================================ FILE: playbooks/roles/openconnect/defaults/main.yml ================================================ --- ocserv_ca_cn: "Streisand" ocserv_ca_organization: "Streisand Effect Automated Signing, Inc." ocserv_port: "4443" ocserv_server_organization: "Streisand Effect Servers, Inc." ocserv_key_ou: "nogroup" ================================================ FILE: playbooks/roles/openconnect/files/ocserv-pam ================================================ account required pam_listfile.so \ sense=allow item=user file=/etc/allowed_vpn_certs auth required pam_listfile.so \ sense=allow item=user file=/etc/allowed_vpn_certs password required pam_deny.so session required pam_deny.so other required pam_deny.so ================================================ FILE: playbooks/roles/openconnect/files/openconnect.conf ================================================ # # Filter out all OpenConnect entries to prevent IP address logging. # :msg, contains, "ocserv" then stop ================================================ FILE: playbooks/roles/openconnect/handlers/main.yml ================================================ --- - name: Restart ocserv service: name: ocserv state: restarted - name: Restart rsyslog for OpenConnect service: name: rsyslog state: restarted ================================================ FILE: playbooks/roles/openconnect/meta/main.yml ================================================ --- dependencies: - { role: ufw } - { role: ip-forwarding } ================================================ FILE: playbooks/roles/openconnect/tasks/docs.yml ================================================ --- - name: Create the OpenConnect/AnyConnect Gateway directory file: path: "{{ ocserv_gateway_location }}" owner: www-data group: www-data mode: 0750 state: directory - name: Copy the CA certificate file to the OpenConnect Gateway directory command: cp {{ ocserv_ca_certificate_file }} {{ ocserv_gateway_location }} args: creates: "{{ ocserv_gateway_location }}/ca-cert.pem" - name: "Copy the client PKCS #12 files to the OpenConnect Gateway directory" command: cp {{ ocserv_path }}/{{ ocserv_client_password.client_name.stdout }}/{{ ocserv_client_password.client_name.stdout }}.p12 {{ ocserv_gateway_location }} args: creates: "{{ ocserv_gateway_location }}/{{ ocserv_client_password.client_name.stdout }}.p12" with_items: "{{ vpn_client_pkcs12_password_list.results }}" loop_control: loop_var: "ocserv_client_password" label: "{{ ocserv_client_password.client_name.item }}" - name: "Copy the client .mobileconfig files to the OpenConnect Gateway directory" command: cp {{ ocserv_path }}/{{ ocserv_client_password.client_name.stdout }}/{{ ocserv_client_password.client_name.stdout }}.mobileconfig {{ ocserv_gateway_location }} args: creates: "{{ ocserv_gateway_location }}/{{ ocserv_client_password.client_name.stdout }}.mobileconfig" with_items: "{{ vpn_client_pkcs12_password_list.results }}" loop_control: loop_var: "ocserv_client_password" label: "{{ ocserv_client_password.client_name.item }}" - include_role: name: i18n-docs vars: title: "OpenConnect/AnyConnect" input_template_name: "instructions" i18n_location: "{{ ocserv_gateway_location }}" ================================================ FILE: playbooks/roles/openconnect/tasks/firewall.yml ================================================ --- - name: Ensure UFW allows DNS requests from OpenConnect clients ufw: to_port: "53" proto: "udp" rule: "allow" from_ip: "192.168.1.0/24" - name: Ensure UFW allows OpenConnect (ocserv) ufw: to_port: "{{ ocserv_port }}" proto: "any" rule: "allow" - name: Install the ocserv iptables service file template: src: ocserv-iptables.service.j2 dest: /etc/systemd/system/ocserv-iptables.service mode: 0644 - name: Enable the ocserv-iptables service systemd: daemon_reload: yes name: ocserv-iptables.service enabled: yes state: started ================================================ FILE: playbooks/roles/openconnect/tasks/install.yml ================================================ --- # It *shouldn't* be necessary to run this particular apt_repository # call in a "retry" loop; enabling Universe doesn't reach out to the # network, so this shouldn't have transient failures. For the sake of # consistency with the other apt_repository calls, it does retry. - name: Enable the Universe repository apt_repository: repo: "deb http://archive.ubuntu.com/ubuntu {{ ansible_distribution_release }} universe" state: present register: openconnect_add_apt_repository until: not openconnect_add_apt_repository.failed retries: "{{ apt_repository_retries }}" delay: "{{ apt_repository_delay }}" - name: Install ocserv apt: package: ocserv - name: Create the OpenConnect rsyslog configuration directory file: path: /etc/rsyslog.d/openconnect.d/ owner: root group: root mode: 0644 state: directory - name: Copy the modified rsyslog configuration into place that prevents OpenConnect traffic from being logged copy: src: openconnect.conf dest: /etc/rsyslog.d/openconnect.d/openconnect.conf owner: root group: root mode: 0644 notify: Restart rsyslog for OpenConnect ================================================ FILE: playbooks/roles/openconnect/tasks/main.yml ================================================ --- # Download, compile and install ocserv and its dependencies - import_tasks: install.yml - name: Create ocserv's PAM control copy: src: ocserv-pam dest: /etc/pam.d/ocserv owner: root group: root mode: 0644 - name: Create the ocserv configuration directory file: path: "{{ ocserv_path }}" owner: root group: root mode: 0750 state: directory - include_role: name: certificates vars: ca_path: "{{ ocserv_path }}" tls_ca: "{{ ocserv_ca }}" tls_client_path: "{{ ocserv_path }}" tls_key_ou: "{{ ocserv_key_ou }}" vpn_name: "ocserv" generate_ca_server: yes generate_client: yes generate_pkcs: yes tls_server_common_name_file: "{{ ocserv_server_common_name_file }}" tls_sans: - "{{ streisand_ipv4_address }}" - name: Base64 encode the client PKCS12 file(s) command: "openssl base64 -in {{ ocserv_path }}/{{ client_name.stdout }}/{{ client_name.stdout }}.p12" register: ocserv_clients_base64 changed_when: False with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" - name: Generate a UUID for .mobileconfig client PKCS12 certificate command: uuid -v4 register: ocserv_client_mobileconfig_uuid changed_when: False - name: Generate a UUID for .mobileconfig vpn payload identifier command: uuid -v4 register: ocserv_payload_mobileconfig_uuid changed_when: False - name: Generate a UUID for .mobileconfig config payload identifier command: uuid -v4 register: ocserv_config_mobileconfig_uuid changed_when: False - name: Generate a UUID for .mobileconfig global identifier command: uuid -v4 register: ocserv_global_mobileconfig_uuid changed_when: False - name: Generate the iOS client mobileconfig file(s) template: src: client.mobileconfig.j2 dest: "{{ ocserv_path }}/{{ client_content[0].stdout }}/{{ client_content[0].stdout }}.mobileconfig" owner: root group: root mode: 0600 with_together: - "{{ vpn_client_names.results }}" - "{{ ocserv_clients_base64.results }}" - "{{ vpn_client_pkcs12_password_list.results }}" loop_control: loop_var: "client_content" label: "{{ client_content[0].item }}" - name: Generate a random ocserv password shell: "{{ streisand_word_gen.psk | trim }} > {{ ocserv_password_file }}" args: creates: "{{ ocserv_password_file }}" - name: Set permissions on the unhashed ocserv password file file: path: "{{ ocserv_password_file }}" owner: root group: root mode: 0600 - name: Register the ocserv password command: cat {{ ocserv_password_file }} register: ocserv_password changed_when: False - name: Create an ocpasswd credentials file expect: command: ocpasswd -c {{ ocserv_hashed_password_file }} streisand responses: "Enter password": "{{ ocserv_password.stdout }}" "Re-enter password": "{{ ocserv_password.stdout }}" creates: "{{ ocserv_hashed_password_file }}" - name: Generate the ocserv configuration file template: src: config.j2 dest: "{{ ocserv_config_file }}" owner: root group: root mode: 0600 - name: Generate the ocserv systemd service file template: src: ocserv.service.j2 dest: /etc/systemd/system/ocserv.service mode: 0644 - name: Stop and disable ocserv.socket systemd: name: ocserv.socket state: stopped enabled: no - name: Enable the ocserv service systemd: daemon_reload: yes name: ocserv.service enabled: yes state: restarted # Set up the openconnect firewall rules - import_tasks: firewall.yml # Generate Gateway documentation - import_tasks: docs.yml # Mirror the OpenConnect clients - import_tasks: mirror.yml ================================================ FILE: playbooks/roles/openconnect/tasks/mirror.yml ================================================ --- - name: Include the OpenConnect mirror variables include_vars: mirror.yml - name: Make the directory where the OpenConnect mirrored files will be stored file: path: "{{ openconnect_mirror_location }}" owner: www-data group: www-data mode: 0755 state: directory - block: - name: Mirror the OpenConnect clients get_url: url: "{{ item.url }}" dest: "{{ openconnect_mirror_location }}" checksum: "{{ item.checksum }}" owner: www-data group: www-data mode: 0644 with_items: "{{ openconnect_download_urls }}" rescue: - name: "{{ streisand_mirror_warning }}" pause: seconds: "{{ streisand_mirror_warning_seconds }}" - include_role: name: i18n-docs vars: title: "OpenConnect mirror" i18n_location: "{{ openconnect_mirror_location }}" input_template_name: "mirror" ================================================ FILE: playbooks/roles/openconnect/templates/client.mobileconfig.j2 ================================================ PayloadContent Password {{ client_content[2].stdout }} PayloadCertificateFileName {{ client_content[0].stdout }}.p12 PayloadContent {{ client_content[1].stdout }} PayloadDescription Client PKCS12 certificate PayloadDisplayName {{ client_content[0].stdout }}.p12 PayloadIdentifier com.apple.security.pkcs12.{{ ocserv_client_mobileconfig_uuid.stdout | upper }} PayloadType com.apple.security.pkcs12 PayloadUUID {{ ocserv_client_mobileconfig_uuid.stdout | upper }} PayloadVersion 1 IPv4 OverridePrimary 0 PayloadDescription Configures VPN settings PayloadDisplayName VPN PayloadIdentifier com.apple.vpn.managed.{{ ocserv_payload_mobileconfig_uuid.stdout | upper }} PayloadType com.apple.vpn.managed PayloadUUID {{ ocserv_payload_mobileconfig_uuid.stdout | upper }} PayloadVersion 1 Proxies HTTPEnable 0 HTTPSEnable 0 UserDefinedName {{ client_content[0].stdout }} VPN AuthName {{ client_content[0].stdout }} AuthenticationMethod Certificate PayloadCertificateUUID {{ ocserv_client_mobileconfig_uuid.stdout | upper }} RemoteAddress {{ streisand_ipv4_address }}:{{ ocserv_port }} VPNSubType com.cisco.anyconnect VPNType VPN VendorConfig Group {{ ocserv_key_ou }} PayloadDisplayName {{ client_content[0].stdout }} PayloadIdentifier streisand.{{ ocserv_config_mobileconfig_uuid.stdout | upper }} PayloadRemovalDisallowed PayloadType Configuration PayloadUUID {{ ocserv_global_mobileconfig_uuid.stdout | upper }} PayloadVersion 1 ================================================ FILE: playbooks/roles/openconnect/templates/config.j2 ================================================ auth = "plain[passwd={{ ocserv_hashed_password_file }}]" enable-auth = "certificate" tcp-port = {{ ocserv_port }} udp-port = {{ ocserv_port }} run-as-user = nobody run-as-group = {{ ocserv_key_ou }} socket-file = {{ ocserv_socket_file }} server-cert = {{ ocserv_server_certificate_file }} server-key = {{ ocserv_server_key_file }} ca-cert = {{ ocserv_ca_certificate_file }} isolate-workers = true # The anyconnect client issues a bye and expects for the session to # be usable afterwards, however ocserv on a bye packet invalidates the # session. Use 'persistent-cookies = true' to force the server keep # these sessions even after disconnect. persistent-cookies = true keepalive = 32400 dpd = 240 mobile-dpd = 1800 try-mtu-discovery = true cert-user-oid = 2.5.4.3 cert-group-oid = 2.5.4.11 # https://gnutls.org/manual/gnutls.html#Priority-Strings # SECURE192: All known to be secure ciphersuites that offer a security level 192-bit or more. # %SERVER_PRECEDENCE: The ciphersuite will be selected according to server priorities and not the client’s. # %LATEST_RECORD_VERSION: Use the latest TLS version record version in client hello. # -VERS-ALL: Disable all TLS/DTLS versions # +VERS-TLS1.2: Allow TLSv1.2 # +VERS-DTLS1.2: Allow DLSv1.2 tls-priorities = "SECURE192:%SERVER_PRECEDENCE:%LATEST_RECORD_VERSION:-VERS-ALL:+VERS-TLS1.2:+VERS-DTLS1.2" auth-timeout = 40 min-reauth-time = 300 max-ban-score = 50 ban-reset-time = 300 cookie-timeout = 300 cookie-rekey-time = 14400 deny-roaming = false rekey-time = 3600 rekey-method = ssl use-occtl = true pid-file = {{ ocserv_pid_file }} device = vpns default-domain = example.com ipv4-network = 192.168.1.0 ipv4-netmask = 255.255.255.0 ping-leases = false cisco-client-compat = true max-clients = {{ vpn_clients + 1 }} # Limit the number of identical clients (i.e., users connecting # multiple times). Unset or set to zero for unlimited. max-same-clients = 1 # Whether to tunnel all DNS queries via the VPN. This is the default # when a default route is set. tunnel-all-dns = true {% for item in upstream_dns_servers %} dns = {{ item }} {% endfor %} acct = pam ================================================ FILE: playbooks/roles/openconnect/templates/instructions-fr.md.j2 ================================================ {% include "languages.md.j2" %} OpenConnect / Cisco AnyConnect ------------------------------ OpenConnect est un serveur VPN extrêmement performant et léger qui offre une compatibilité totale avec les clients officiels Cisco AnyConnect. Le [protocole] (http://www.infradead.org/ocserv/technical.html) repose sur des normes telles que HTTP, TLS et DTLS, et c'est l'une des technologies VPN les plus populaires et largement utilisées parmi les grands multi-nationales. En raison de son utilisation dans les grandes multinationales, cela signifie souvent qu'au niveau du protocole, il est peu probable d'être ciblé pour la censure. --- * Plateformes * [Certificats](#clientcerts) * [Windows](#windows) * [macOS](#macos) * [OpenConnect GUI](#macos-openconnect-gui) * [OpenConnect CLI](#macos-openconnect-cli) * [Linux](#linux) * [Android](#android) * [iOS](#ios) ### Certificats du serveur et des clients (macOS, Android) ### Les certificats clients sont un mécanisme par lequel les clients peuvent s'authentifier de manière sécurisée avec un serveur. 1. Votre serveur OpenConnect émet son propre __certificat serveur__. Ceci est utilisé par le logiciel du client de votre appareil (tel que AnyConnect pour iOS) afin de s'identifier de manière sécurisée avec le serveur VPN. Téléchargez le certificat de ce serveur. * [ca.crt](/openconnect/ca.crt) 1. Chaque appareil que vous souhaitez configurer nécessite un __certificat client__ en plus du certificat serveur ci-dessus. Un certificat client est utilisé pour identifier et authentifier de manière sécurisée votre appareil sur le serveur VPN. Deux appareils ne peuvent pas utiliser le même certificat de client et être connectés simultanément (un certificat de client pour chaque appareil). Chaque certificat client est protégé par une mot de passe, qui sera nécessaire pour le déverrouiller une fois que vous l'importez dans votre appareil. {% for client in vpn_client_pkcs12_password_list.results %} * [{{ client.client_name.stdout }}.p12](/openconnect/{{ client.client_name.stdout }}.p12), mot de passe: `{{ client.stdout }}` {% endfor %} ### Windows ### 1. Téléchargez [l'installateur OpenConnect GUI](/mirror/index-fr.html#openconnect). 1. Lancez le programme d'installation OpenConnect GUI. 1. Compléter l'assistant de configuration TAP-Windows. * Choisissez les options par défaut et permettez au pilote TAP du projet OpenVPN d'être installé. 1. Lancez l'application OpenConnect. 1. Cliquez sur l'icône *Edit* (Engrenage). 1. Saisissez `{{ streisand_server_name }}` pour le *Name* (Nom). 1. Saisissez `{{ streisand_ipv4_address }}:{{ ocserv_port }}` pour la *Gateway* (passerelle). 1. Saisissez `streisand` pour le *Username* (nom d'utilisateur) et cliquez *Save* (Enregisterer). 1. Cliquez *Connect* (Connexion). 1. Une invite apparaîtra pendant la connexion initiale en vous demandant de faire confiance au certificat du serveur. Cliquez *The information is accurate* (l'information est précise) et le serveur sera automatiquement vérifié pour toutes les connexions suivantes. 1. Saisissez `{{ ocserv_password.stdout }}` pour le *Password* (mot de passe) et cliquez *OK*. 1. Cliquez *Non* lorsque l'invite de Windows apparaît en vous demandant *Voulez-vous permettre à votre PC d'être découvrable...*. 1. La version bêta actuelle de l'interface graphique OpenConnect [ne supporte pas la modification automatique des paramètres DNS](https://github.com/openconnect/openconnect-gui/issues/48). Afin d'éviter les fuites de DNS, les étapes suivantes doivent être effectuées: 1. Faites un clic droit sur le bouton Démarrer de Windows. 1. Cliquez *Connexions réseau*. 1. Cliquez-driot sur le appareil que vous utilisez pour vous connecter (Ethernet ou Wifi) et cliquez sur *Propriétés*. 1. Double-cliquez *Protocol Internet Version 4 (TCP/IPv4)*. 1. Sélectionnez *Utiliser les adresses de serveur DNS suivante* et saisissez: {% for item in upstream_dns_servers %} * `{{ item }}` {% endfor %} 1. Cliquez *OK*. 1. Cliquez *OK* pour fermer les propriétés de connexion. 1. Vous devriez être prêt à partir! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. ### macOS ### #### OpenConnect GUI 1. Téléchargez [l'installateur OpenConnect GUI](/mirror/index-fr.html#openconnect). 1. Lancez le programme d'installation OpenConnect GUI. 1. Lancez l'application OpenConnect. 1. Cliquez sur l'icône *Edit* (Engrenage). 1. Saisissez `{{ streisand_server_name }}` pour le *Name* (Nom). 1. Saisissez `{{ streisand_ipv4_address }}:{{ ocserv_port }}` pour la *Gateway* (passerelle). 1. Saisissez `streisand` pour le *Username* (nom d'utilisateur) et cliquez *Save* (Enregisterer). 1. Cliquez *Connect* (Connexion). 1. Une invite apparaîtra pendant la connexion initiale en vous demandant de faire confiance au certificat du serveur. Cliquez *The information is accurate* (l'information est précise) et le serveur sera automatiquement vérifié pour toutes les connexions suivantes. 1. Saisissez `{{ ocserv_password.stdout }}` pour le *Password* (mot de passe) et cliquez *OK*. 1. Vous devriez être prêt à partir! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. #### OpenConnect CLI 1. Installer [Homebrew](https://brew.sh/), si vous ne l'avez pas. 1. Installer OpenConnect en utilisant Homebrew: `brew install openconnect` * Si l'installation de Homebrew n'est pas une option, vous pouvez également télécharger et compiler le [code source OpenConnect](/mirror/index-fr.html#openconnect). 1. Téléchargez le fichier [ca.crt](/openconnect/ca.crt) et un [fichier de certificat client](#clientcerts) dans la liste ci-dessus. 1. Placez les fichiers téléchargés dans un dossier distinct (par exemple `{{ streisand_server_name }}-openconnect`), ouvrez votre terminal, et `cd` dans la directoire. 1. Lancez OpenConnect: `sudo openconnect --cafile ca.crt --certificate votre-certificat-client.p12 --key-password 'mot-de-passe-du-certificat' --pfs {{ streisand_ipv4_address }}:{{ ocserv_port }}` 1. Vous devriez être prêt à partir! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. ### Linux ### 1. Installer le plugin OpenConnect pour NetworkManager. `sudo apt-get install network-manager-openconnect-gnome` 1. Téléchargez le certificat serveur: * [ca.crt](/openconnect/ca.crt) 1. Ouvrez votre *Paramètres du système*. 1. Cliquez sur l'icône *Réseau*. 1. Cliquez le bouton *+* dans le coin inférieur gauche de la fenêtre. 1. Sélectionnez *VPN* à partir de la liste déroulante et cliquez *Créer*. 1. Sélectionnez *VPN compatible Cisco AnyConnect (openconnect)* et cliquez *Créer*. 1. Saisissez `{{ streisand_server_name }}` pour le *Nom du connexion*. 1. Saisissez `{{ streisand_ipv4_address }}:{{ ocserv_port }}` pour la *Gateway* (passerelle). 1. Sélectionnez le fichier `ca.crt` Que vous venez de télécharger pour *Certificat CA*. 1. Cliquez *Enregisterer*. 1. Sélectionnez le VPN dans le menu à gauche, et faites glisser l'interrupteur vers la position *ON*. Vous pouvez également activer/désactiver le VPN en cliquant sur l'icône WiFi/Réseau dans la barre de menu, défiler vers *Connexions VPN* et en cliquant sur son nom. 1. Saisissez `streisand` pour le champ *Nom d'utilisateur* puis cliquez *Connexion*. 1. Saisissez `{{ ocserv_password.stdout }}` pour le champ *Mot de passe*, cocher *Enregistrer mots de passe*, et cliquez *Connexion*. 1. Vous devriez être prêt à partir! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. ### Android ### 1. Téléchargez [Cisco AnyConnect](https://play.google.com/store/apps/details?id=com.cisco.anyconnect.vpn.android.avf&hl=fr) depuis Google Play. 1. Lancez l'application. 1. Tapez *OK* pour accepter "Supplemental End User License Agreement for AnyConnect® Secure Mobility Client vx.x and other VPN-related Software". 1. Tapez sur l'icône du menu et sélectionnez *Paramètres*. 1. Décochez l'option *Bloquer les serveurs non fiables*. * Le certificat du serveur sera importé pendant la connexion initiale et vérifié automatiquement pour toutes les connexions ultérieures. 1. Tapez sur le bouton arrière. 1. Tapez *Connexion* en suite tapez *Ajouter une nouvelle connexion VPN...*. 1. Tapez *Description* et saisissez `{{ streisand_server_name }}`. 1. Tapez *Adresse du server* et saisissez `{{ streisand_ipv4_address }}:{{ ocserv_port }}`. 1. Tapez *Préférences avancées*. 1. Tapez *Certificat*. * Chaque profil peut être téléchargé sur l'appareil lui-même à l'aide des [liens ci-dessus](#clientcerts), ou copié depuis votre ordinateur via USB. 1. Tapez *Import*, tapez *Système de fichiers*, et sélectionnez un [fichier certificat client](#clientcerts) 1. Vérifiez le dossier `Téléchargements` si vous avez téléchargé le fichier directement sur l'appareil. C'est là que Chrome place ses fichiers, par exemple. 1. Saisissez votre mot de passe de certificat client lorsque l'invite *Mot de passe* s'affiche, en suite tapez *Se Connecter*. 1. Vous verrez une coche à côté du certificat nouvellement importé. Tapez sur le bouton de retour. 1. Tapez *Terminer* deux fois pour sauvegarder la connexion. 1. Tapez sur le bouton retour pour revenir à l'écran principal. Vous devriez voir `{{ streisand_server_name }}` dans la section *Connexion*. 1. Faites glisser l'interrupteur *VPN* vers la position Ouvert. 1. Tapez *Détails* avertissement de securite est affiche when the Security Warning is displayed. 1. Tapez *Import and Continue* sommaire du certificat est achhife when the Certificate Summary is displayed. 1. Tapez *Se Connecter* dans l'écran de selection de groupe. Le défaut correct a déjà été choisi. 1. Si c'est la première fois que vous utilisez AnyConnect, vous devrez accepter la boîte de dialogue de la demande de connexion affichée par Android. 1. Vous devriez être prêt à partir! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. #### Avez-vous été demandé pour le nom d'utilisateur? #### Certains utilisateurs [ont signalé](https://github.com/StreisandEffect/streisand/issues/847) que leurs clients Android AnyConnect demandent un nom d'utilisateur et un mot de passe. C'est un bug connu que nous n'avons pas résolu. Voir la liste de [questions ouvertes Streisand AnyConnect](https://github.com/StreisandEffect/streisand/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20anyconnect). Si vous êtes affecté, vous pouvez nous aider à comprendre le bug en signalant vos informations en utilisant le modèle du bouton *New issue* de la liste des problèmes. Les correctifs sont également acceptés. Si vous êtes affecté, vous pouvez utiliser cette solution alternatif: 1. Lorsque vous êtes invité à entrer un utilisateur, entrez `streisand` 1. Lorsque vous êtes invité à entrer un mot de passe, utilisez `{{ ocserv_password.stdout }}` ### iOS ### #### Note: Lorsque vous utilisez AnyConnect pour la première fois, vous pouvez être invité à entrer un mot de passe avant d'être connecté. Saisissez `streisand` comme nom de l'utilisateur et `{{ocserv_password.stdout}}` pour le mot de pass et continuez. Les tentatives de connexion suivantes ne vous demanderont plus. #### Note: Un seul profil AnyConnect peut être configuré à tout moment. Pour supprimer un profil existant, naviguez vers *Règlages* -> *Général* -> *Profil*, tapez sur le profil que vous souhaitez supprimer, puis tapez sur *Supprimer le profil*. 1. Transférer un fichier .mobileconfig à chaque appareil que vous souhaitez configurer: {% for client in vpn_client_pkcs12_password_list.results %} * [{{ client.client_name.stdout }}.mobileconfig](/openconnect/{{ client.client_name.stdout }}.mobileconfig) {% endfor %} * Le profil peut être envoyé par courrier électronique sur votre appareil (simplement appuyez sur la pièce jointe), transféré via l'utilitaire [Apple Configurator](https://itunes.apple.com/us/app/apple-configurator/id434433123?mt=12), ou téléchargé directement en regardant ces instructions sur l'appareil lui-même. 1. Suivez les instructions à l'écran. 1. Vous serez invité à entrer le mot de passe ou le code NIP de votre appareil. 1. Téléchargez l'application [Cisco AnyConnect](https://itunes.apple.com/fr/app/cisco-anyconnect/id1135064690) depuis l'App Store. 1. Lancez l'application. 1. Tapez *OK* pour activer le logiciel lorsque la boîte de dialogue apparaît. 1. Tapez *Paramètres*. 1. Éteignez l'interrupteur *Bloquer les serveurs non fiable*. * Le certificat du serveur sera importé pendant la connexion initiale et vérifié automatiquement pour toutes les connexions ultérieures. 1. Faites glisser l'interrupteur *VPN* vers la position ON. 1. Tapez *Détails* lorsque l'avertissement de sécurité s'affiche. 1. Tapez *Importer* dans le coin supérieur droit. 1. Tapez *Se Connecter* dans l'écran de selection de groupe. Le défaut correct a déjà été choisi. 1. Vous devriez être prêt à partir! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. ================================================ FILE: playbooks/roles/openconnect/templates/instructions.md.j2 ================================================ {% include "languages.md.j2" %} OpenConnect / Cisco AnyConnect ------------------------------ OpenConnect is an extremely high-performance and lightweight VPN server that also features full compatibility with the official Cisco AnyConnect clients. The [protocol](http://www.infradead.org/ocserv/technical.html) is built on top of standards like HTTP, TLS, and DTLS, and it's one of the most popular and widely used VPN technologies. Due to its use among large multi-national corporations, it often means that at the protocol level, it is seldom targetted for censorship. --- * Platforms * [Certificates](#clientcerts) * [Windows](#windows) * [macOS](#macos) * [OpenConnect GUI](#macos-openconnect-gui) * [OpenConnect CLI](#macos-openconnect-cli) * [Linux](#linux) * [Android](#android) * [iOS](#ios) ### Server and client Certificates (macOS, Android) ### Client certificates are a mechanism by which clients can authenticate themselves securely with the server. 1. Your OpenConnect server issues its own __server certificate__. This is used by your device's client software (such as AnyConnect for iOS) to securely identify the VPN server. Download this server's certificate. * [ca.crt](/openconnect/ca.crt) 1. Each device you wish to configure needs a __client certificate__ in addition to the server certificate above. A client certificate is used to securely identify and authenticate your device to the VPN server. Two devices can't use the same client certificate and be logged in at the same time (one client certificate per device). Each client certificate is protected by a password, which will be needed to unlock it once you import it into your device. {% for client in vpn_client_pkcs12_password_list.results %} * [{{ client.client_name.stdout }}.p12](/openconnect/{{ client.client_name.stdout }}.p12), password: `{{ client.stdout }}` {% endfor %} ### Windows ### 1. Download the [OpenConnect GUI installer](/mirror/#openconnect). 1. Run the OpenConnect GUI installer. 1. Complete the TAP-Windows Setup Wizard. * Choose the default options, and allow the TAP driver from the OpenVPN project to be installed. 1. Launch the OpenConnect application. 1. Click the *Edit* icon (gear) and select 'New profile advanced'. 1. Enter `{{ streisand_server_name }}` for the *Name*. 1. Enter `{{ streisand_ipv4_address }}:{{ ocserv_port }}` for the *Gateway*. 1. Enter `streisand` for the *Username* and click *Save*. 1. Click *Connect*. 1. A prompt will appear during the initial connection asking you to trust the server's certificate. Click *The information is accurate* and the server will be automatically verified for all future connections. 1. Enter `{{ ocserv_password.stdout }}` for the *Password* and click *OK*. 1. Click *No* when the Windows prompt appears asking *Do you want to allow your PC to be discoverable...*. 1. The current beta version of the OpenConnect GUI [does not support automatically changing the DNS settings](https://github.com/openconnect/openconnect-gui/issues/48). In order to avoid DNS leaks, the following steps must be performed: 1. Right-click on the Windows Start Button. 1. Click *Network Connections*. 1. Right-click on the device that you are using to connect (Ethernet or Wi-Fi) and click *Properties*. 1. Double-click *Internet Protocol Version 4 (TCP/IPv4)*. 1. Select *Use the following DNS server addresses* and enter: {% for item in upstream_dns_servers %} * `{{ item }}` {% endfor %} 1. Click *OK*. 1. Click *OK* to close the connection properties. 1. You should be good to go! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### macOS ### #### OpenConnect GUI 1. Download the [OpenConnect GUI installer](/mirror/#openconnect). 1. Run the OpenConnect GUI installer. 1. Launch the OpenConnect application. 1. Click the *Edit* icon (gear) and select 'New profile advanced'. 1. Enter `{{ streisand_server_name }}` for the *Name*. 1. Enter `{{ streisand_ipv4_address }}:{{ ocserv_port }}` for the *Gateway*. 1. Enter `streisand` for the *Username* and click *Save*. 1. Click *Connect*. 1. A prompt will appear during the initial connection asking you to trust the server's certificate. Click *The information is accurate* and the server will be automatically verified for all future connections. 1. Enter `{{ ocserv_password.stdout }}` for the *Password* and click *OK*. 1. You should be good to go! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. #### OpenConnect CLI 1. Install [Homebrew](https://brew.sh/), if you haven't already. 1. Install OpenConnect using Homebrew: `brew install openconnect` * If installing Homebrew is not an option, you can also download and compile the [OpenConnect source code](/mirror/#openconnect). 1. Download the [server certificate](/openconnect/ca.crt) file, and a [client certificate file](#clientcerts) from the list above. 1. Place the downloaded server certificate and a selected client certificate into a separate folder (e.g. `{{ streisand_server_name }}-openconnect`), open your Terminal, and `cd` to that directory. 1. Run OpenConnect: `sudo openconnect --cafile ca.crt --certificate your-client-certificate.p12 --key-password 'your-client-certificate-password' --pfs {{ streisand_ipv4_address }}:{{ ocserv_port }}` 1. You should be good to go! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### Linux ### 1. Install the OpenConnect plugin for NetworkManager. `sudo apt-get install network-manager-openconnect-gnome` 1. Download the server certificate file: * [ca.crt](/openconnect/ca.crt) 1. Open your *System Settings*. 1. Click the *Network* icon. 1. Click the *+* button in the lower-left of the window. 1. Select *VPN* from the Interface drop-down and click *Create*. 1. Select *Cisco AnyConnect Compatible VPN (openconnect)* and click *Create*. 1. Enter `{{ streisand_server_name }}` for the *Connection name*. 1. Enter `{{ streisand_ipv4_address }}:{{ ocserv_port }}` for the *Gateway*. 1. Select the `ca.crt` file that you just downloaded for the *CA Certificate*. 1. Click *Save*. 1. Select the VPN in the left-hand menu, and flip the switch to *ON*. You can also enable/disable the VPN by clicking on the WiFi/Network icon in the menu bar, scrolling to *VPN Connections*, and clicking on its name. 1. Enter `streisand` for the *Username* and click *Login*. 1. Enter `{{ ocserv_password.stdout }}` for the *Password*, check *Save passwords*, and click *Login*. 1. You should be good to go! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### Android ### 1. Download [Cisco AnyConnect](https://play.google.com/store/apps/details?id=com.cisco.anyconnect.vpn.android.avf) from Google Play. 1. Launch the application. 1. Tap *OK* to accept the "Supplemental End User License Agreement for AnyConnect® Secure Mobility Client vx.x and other VPN-related Software". 1. Tap the menu icon and select *Settings*. 1. Uncheck the *Block Untrusted Servers* option. * The server certificate will be imported during the initial login and automatically verified for all future connections. 1. Tap the back button. 1. Tap *Connection* and then tap *Add New VPN Connection...*. 1. Tap *Description* and enter `{{ streisand_server_name }}`. 1. Tap *Server Address* and enter `{{ streisand_ipv4_address }}:{{ ocserv_port }}`. 1. Tap *Advanced Preferences*. 1. Tap *Certificate*. * Each profile can be downloaded on the device itself using the [links above](#clientcerts), or copied from your computer via USB. 1. Check the `Download` folder if you downloaded the file directly to the device. This is where Chrome places its files, for example. 1. Tap *Import*, tap *File System*, and select a [client certificate file](#clientcerts) from the list above that you transferred. 1. Enter your client certificate password when the *Password* prompt is displayed, and tap *Connect*. 1. You'll see a checkmark next to the newly imported certificate. Tap the back button. 1. Tap *Done* twice to save the connection. 1. Tap the back button to return to the main screen. You should see `{{ streisand_server_name }}` in the *Connection* section. 1. Slide the *AnyConnect VPN* switch On. 1. Tap *Details* when the Security Warning is displayed. 1. Tap *Import and Continue* when the Certificate Summary is displayed. 1. Tap *Connect* on the group selection screen. The correct default has already been chosen. 1. If this is your first time using AnyConnect, you will need to accept the Connection Request dialog that Android displays. 1. You should be good to go! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. #### Prompted for username? #### Some users [have reported](https://github.com/StreisandEffect/streisand/issues/847) that their Android AnyConnect clients prompt for a username and password. This is a known bug we don't understand. See the list of [Streisand AnyConnect open issues](https://github.com/StreisandEffect/streisand/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20anyconnect). If you're affected, you could help us understand the bug by reporting your details using the issue list's *New issue* button's template. Fixes are gratefully accepted too. If you're affected, you can use this workaround: 1. When prompted for a user, enter `streisand` 1. When prompted for a password, use `{{ ocserv_password.stdout }}` ### iOS ### #### Note: When using AnyConnect for the first time, you may be prompted for a password prior to being connected. Enter `streisand` for the username, `{{ ocserv_password.stdout }}` for the password and continue. Subsequent connections will not prompt you again. #### Note: Only one AnyConnect profile can be configured at any give time. To remove an existing profile, go to *Settings* -> *General* -> *Profile*, tap on the profile you wish to remove, then tap on *Remove Profile*. 1. Transfer a .mobileconfig file for each device you wish to configure: {% for client in vpn_client_pkcs12_password_list.results %} * [{{ client.client_name.stdout }}.mobileconfig](/openconnect/{{ client.client_name.stdout }}.mobileconfig) {% endfor %} * The profile can be emailed to your device (simply tap the attachment), transferred via the [Apple Configurator](https://itunes.apple.com/us/app/apple-configurator/id434433123?mt=12) utility, or downloaded directly by viewing these instructions on the device itself. 1. Follow the on-screen instructions. 1. You will be prompted to enter your device password or pin. 1. Download [Cisco AnyConnect](https://itunes.apple.com/us/app/cisco-anyconnect/id1135064690) from the App Store. 1. Launch the application. 1. Tap *OK* to enable the software when the dialog box appears. 1. Tap *Settings*. 1. Turn off the *Block Untrusted Servers* switch. * The server certificate will be imported during the initial login and automatically verified for all future connections. 1. Tap *Home*. 1. Slide the *AnyConnect VPN* switch on. 1. Tap *Details* when the Security Warning is displayed. 1. Tap *Import* in the top-right corner. 1. Tap *Connect* on the group selection screen. The correct default has already been chosen. 1. You should be good to go! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ================================================ FILE: playbooks/roles/openconnect/templates/mirror-fr.md.j2 ================================================ {% include "languages.md.j2" %} ### OpenConnect ### **Source** * [{{ openconnect_source_filename }}]({{ openconnect_source_href }}) * Somme de contrôle: *{{ openconnect_source_checksum }}* **Windows** * [{{ openconnect_windows_filename }}]({{ openconnect_windows_href }}) * Somme de contrôle: *{{ openconnect_windows_checksum }}* **macOS** * [{{ openconnect_macos_filename }}]({{ openconnect_macos_href }}) * Somme de contrôle: *{{ openconnect_macos_checksum }}* ================================================ FILE: playbooks/roles/openconnect/templates/mirror.md.j2 ================================================ {% include "languages.md.j2" %} ### OpenConnect ### **Source** * [{{ openconnect_source_filename }}]({{ openconnect_source_href }}) * Checksum: *{{ openconnect_source_checksum }}* **Windows** * [{{ openconnect_windows_filename }}]({{ openconnect_windows_href }}) * Checksum: *{{ openconnect_windows_checksum }}* **macOS** * [{{ openconnect_macos_filename }}]({{ openconnect_macos_href }}) * Checksum: *{{ openconnect_macos_checksum }}* ================================================ FILE: playbooks/roles/openconnect/templates/ocserv-iptables.service.j2 ================================================ [Unit] Description=Set the firewall rules required for ocserv After=network.target Before=ocserv.service [Service] Type=oneshot RemainAfterExit=true ExecStart=/sbin/{{ ocserv_firewall_rule }} [Install] WantedBy=multi-user.target ================================================ FILE: playbooks/roles/openconnect/templates/ocserv.service.j2 ================================================ [Unit] Description=OpenConnect SSL VPN server Documentation=man:ocserv(8) After=network-online.target After=dbus.service [Service] PrivateTmp=true PIDFile={{ ocserv_pid_file }} ExecStart=/usr/sbin/ocserv --foreground --pid-file {{ ocserv_pid_file }} --config {{ ocserv_config_file }} ExecReload=/bin/kill -HUP $MAINPID [Install] WantedBy=multi-user.target ================================================ FILE: playbooks/roles/openconnect/vars/main.yml ================================================ --- ocserv_path: "/etc/ocserv" ocserv_ca: "{{ ocserv_path }}/ca" ocserv_config_file: "{{ ocserv_path }}/ocserv.conf" ocserv_firewall_rule: "iptables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -j MASQUERADE" ocserv_days_valid: "1825" ocserv_pid_file: "/var/run/ocserv.pid" ocserv_socket_file: "/var/run/ocserv-socket" ocserv_ca_certificate_file: "{{ ocserv_path }}/ca.crt" ocserv_ca_key_file: "{{ ocserv_path }}/ca.key" ocserv_server_certificate_file: "{{ ocserv_path }}/server.crt" ocserv_server_key_file: "{{ ocserv_path }}/server.key" ocserv_hashed_password_file: "{{ ocserv_path }}/ocpasswd" ocserv_password_file: "{{ ocserv_path }}/ocserv-password" ocserv_server_common_name_file: "{{ ocserv_path }}/ocserv_server_common_name" ocserv_client_name: "streisand-openconnect-{{ streisand_ipv4_address }}" ocserv_gateway_location: "{{ streisand_gateway_location }}/openconnect" ================================================ FILE: playbooks/roles/openconnect/vars/mirror.yml ================================================ --- # OpenConnect Download variables # ------------------------------ openconnect_mirror_location: "{{ streisand_mirror_location }}/openconnect" openconnect_mirror_href_base: "/mirror/openconnect" # Source openconnect_source_version: "7.08" openconnect_source_filename: "openconnect-{{ openconnect_source_version }}.tar.gz" openconnect_source_href: "{{ openconnect_mirror_href_base }}/{{ openconnect_source_filename }}" # This CDN mirrors the OpenConnect source code, and helps mitigate # connection errors that were occurring when using the project's # official download location. openconnect_source_url: "https://d25kfp60e9u1dw.cloudfront.net/{{ openconnect_source_filename }}" openconnect_source_checksum: "sha256:1c44ec1f37a6a025d1ca726b9555649417f1d31a46f747922b84099ace628a03" # Windows openconnect_windows_version: "v1.5.3" openconnect_windows_filename: "openconnect-gui-1.5.3-win32.exe" openconnect_windows_href: "{{ openconnect_mirror_href_base }}/{{ openconnect_windows_filename }}" openconnect_windows_url: "https://github.com/openconnect/openconnect-gui/releases/download/{{ openconnect_windows_version }}/{{ openconnect_windows_filename }}" openconnect_windows_checksum: "sha256:b1d4bd76b41f32d08287bf043b3dc8c798a145c02319217d45a74b0d9545a23d" # macOS openconnect_macos_version: "v1.5.1" openconnect_macos_filename: "openconnect-gui-1.5.1-Darwin.dmg" openconnect_macos_href: "{{ openconnect_mirror_href_base }}/{{ openconnect_macos_filename }}" openconnect_macos_url: "https://github.com/openconnect/openconnect-gui/releases/download/{{ openconnect_macos_version }}/{{ openconnect_macos_filename }}" openconnect_macos_checksum: "sha256:b2c338cfe9d0725bee98893225449e27cf7e337d43b0f8b08aec96de6f761f08" openconnect_download_urls: - { url: "{{ openconnect_source_url }}", checksum: "{{ openconnect_source_checksum }}" } - { url: "{{ openconnect_windows_url }}", checksum: "{{ openconnect_windows_checksum }}" } - { url: "{{ openconnect_macos_url }}", checksum: "{{ openconnect_macos_checksum }}" } ================================================ FILE: playbooks/roles/openvpn/defaults/main.yml ================================================ --- openvpn_key_country: "US" openvpn_key_province: "California" openvpn_key_city: "Beverly Hills" openvpn_key_org: "ACME CORPORATION" openvpn_key_ou: "Anvil Department" openvpn_port: "636" openvpn_port_udp: "8757" openvpn_port_sslh: "443" openvpn_key_size: "4096" openvpn_cipher: "AES-256-CBC" openvpn_ncp_ciphers: "AES-256-GCM:AES-128-GCM" openvpn_auth_digest: "SHA256" openvpn_connect_timeout: 10 #seconds # If stunnel is enabled generating OpenVPN stunnel profiles requires these # variables to be defined stunnel_local_port: "41194" stunnel_remote_port: "993" ================================================ FILE: playbooks/roles/openvpn/files/openvpn_signing.key ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- mQENBE45PsIBCAC2K2LRZPQIUmJlCDKcncfR6vok2wowDpGpHZffvEEoUj/DoocR LLpPHR5RB1zMWIs2IjF8vOtXMCBguDgtEvQTh6p6DM3D1fTnYp3pPlQyyzAuC81v CQo44h09R4Nh2e38oMRVztmAnacC4g5aiSEamrZ4PbWdAdPc4uZdCPOGmUDJw8+q aAYvL/8pM7YqEu05FqE+aNcG02K+mDhA2bqRLLKoLEFpeMSO6vV8BrE7Vw1Rs1PM VLDJt9HdXmC6vP+WWqDuj7/qfRb2wwlSIp5+aFyRHOUNyFKnWZYIObeV3+Y6oG6h gmBtU1673mHDqVy26TwfjpJeudMKHVCrKXVXABEBAAG0QVNhbXVsaSBTZXBww6Ru ZW4gKE9wZW5WUE4gVGVjaG5vbG9naWVzLCBJbmMpIDxzYW11bGlAb3BlbnZwbi5u ZXQ+iQFVBBMBAgA/AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgBYhBDDr9Oc8 zmPu4STdJ45tqLThWMVpBQJZeJ2tBQkQ4vlrAAoJEI5tqLThWMVpPDUH/RdLsdG/ 4kmal/rfbso3YVxZXGp2fHKrptvCVrUWluYs6H/XBV4x6aMe8Q6K7Qa7BSLA9jZ8 v+UN/4aA+urBcs6Ted/XbP3mKU47tOotW24nA1LRjd4gUSEXCaEOBbCSyw3uw6Vz U1wr1gEmkC7kvBziL+Pcbt5tKTRhUfgbcjYNNdp/nAwn3Pm3OFRaBt/qDU2aYAOH +k191x/ovDRO/UiU2CVvrdfv/VMZfo/rwxe8IiirxQ4k5DR2Vyu0DMNzlNTqRk8l rUH0FBdl0rOiefH0m6ubKstpYCaOUYsh/FaW53O6qqrTlZqPtAav1cRog8zb8mhT sFFAarhnZcQ/DG+5AQ0ETjk+wgEIAOg+Bjk8Wnb7fbbwBDDUalLsIEgFUhsrSLD5 VVYB8tOq7djshckp/3LwfkSsmUzEtXMXxIbDUON1vbCQXZlQDe7E7uY5KFNWyi4+ UJwLMrs+oqfeduUzDxQ+voq/6NGl+2olqd6vT/c/uPb/RPZpOdgoEkqFEOTMRVz0 DZwAyzyYEWBrwDECNbEtqefMLPIaUGUzZvUc80I+MYL6AzRe/utIWcBnZ2nydZ0S vWKRJ0lOs69e6KoFVeE5QXzmTXkjzSbR9eN3ADm2j0EjLnpt/zR4hF8s4l4HLdRd Sn47tAdvahsNfgWmOfiQD8btnu8DiMiJMd8IpVsZX/zCJbSUChcAEQEAAYkBPAQY AQIAJgIbDBYhBDDr9Oc8zmPu4STdJ45tqLThWMVpBQJZeJ2DBQkQ4vlBAAoJEI5t qLThWMVpCCIH+QFqEY+Xk5gJc10lbJUZEhJIknS/3GEd+3WBHgBtBaQCeK7+bFQP ZagTN4SJLiwYcQDV04mZTpFOJV1k9AYaz7ENEjHe51mGhPM9sm5Ix7KwMNo0lHJ+ ryZ0zyie28IbGz+rYa7OdkhE2EmcQkezYNWC03G8yR9yGk3QZ3CtPPO/xYP2tBGc OocqWUkVuR7KpitT9QnOZ4af26b83Vr/+qJ1FdSfW6/VAbyboVWya4oEnKSUusBm 0WCQzaLH15EpzgcdB/x8KVOTS1dAA5GNyRyhbRfP6yBXgBruCkPa4/np78/72jjW vbAvOhOEMnfzWmf3VZq+q6hhIJf6Sp+dcoU= =P3ax -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: playbooks/roles/openvpn/handlers/main.yml ================================================ --- - name: Restart OpenVPN systemd: name: "{{ item }}" state: restarted with_items: - "{{ openvpn_service_names }}" ================================================ FILE: playbooks/roles/openvpn/meta/main.yml ================================================ --- dependencies: # OpenVPN needs to be added to the firewall - { role: ufw } - { role: dnsmasq } - { role: ip-forwarding } ================================================ FILE: playbooks/roles/openvpn/tasks/docs.yml ================================================ --- - name: Create the OpenVPN Gateway directory file: path: "{{ openvpn_gateway_location }}" owner: www-data group: www-data mode: 0750 state: directory - name: Copy the client files to the OpenVPN Gateway directory command: cp --recursive {{ openvpn_path }}/{{ client_name.stdout }} {{ openvpn_gateway_location }} args: creates: "{{ openvpn_gateway_location }}/{{ client_name.stdout }}/ca.crt" with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" - include_role: name: i18n-docs vars: title: "OpenVPN" input_template_name: "instructions" i18n_location: "{{ openvpn_gateway_location }}" - include_role: name: i18n-docs vars: title: "OpenVPN stunnel" input_template_name: "stunnel-instructions" output_file_name: "stunnel" i18n_location: "{{ openvpn_gateway_location }}" when: streisand_stunnel_enabled ================================================ FILE: playbooks/roles/openvpn/tasks/firewall.yml ================================================ --- - name: Allow OpenVPN through the firewall command: "{{ item }}" with_items: "{{ openvpn_firewall_rules }}" - name: Ensure UFW allows DNS requests from OpenVPN clients ufw: to_port: "53" proto: "udp" rule: "allow" from_ip: "10.8.0.0/24" - name: Ensure UFW allows DNS requests from OpenVPN UDP clients ufw: to_port: "53" proto: "udp" rule: "allow" from_ip: "10.9.0.0/24" - name: Ensure UFW allows OpenVPN ufw: to_port: "{{ openvpn_port }}" proto: "tcp" rule: "allow" - name: Ensure UFW allows OpenVPN over UDP ufw: to_port: "{{ openvpn_port_udp }}" proto: "udp" rule: "allow" - name: Install the OpenVPN iptables service file template: src: openvpn-iptables.service.j2 dest: /etc/systemd/system/openvpn-iptables.service mode: 0644 - name: Enable the openvpn-iptables service systemd: daemon_reload: yes name: openvpn-iptables.service enabled: yes state: started ================================================ FILE: playbooks/roles/openvpn/tasks/install.yml ================================================ --- - name: "Add the official OpenVPN APT key; hiding 25 lines of log..." apt_key: id: E158C569 data: "{{ item }}" with_file: openvpn_signing.key no_log: True - name: Add the official OpenVPN repository apt_repository: repo: 'deb https://build.openvpn.net/debian/openvpn/stable {{ ansible_lsb.codename }} main' state: present register: openvpn_add_apt_repository until: not openvpn_add_apt_repository.failed retries: "{{ apt_repository_retries }}" delay: "{{ apt_repository_delay }}" - name: Install OpenVPN and its dependencies from APT apt: package: - openvpn - udev ================================================ FILE: playbooks/roles/openvpn/tasks/main.yml ================================================ --- # Add the apt key and install OpenVPN - import_tasks: install.yml - name: "Configure DNSMasq to listen on {{ dnsmasq_openvpn_tcp_ip }}:53 and {{ dnsmasq_openvpn_udp_ip }}:53" template: src: openvpn_dnsmasq.conf.j2 dest: /etc/dnsmasq.d/openvpn.conf notify: Restart dnsmasq - include_role: name: certificates vars: ca_path: "{{ openvpn_path }}" tls_ca: "{{ openvpn_ca }}" tls_client_path: "{{ openvpn_path }}" generate_ca_server: yes generate_client: yes tls_request_subject: "{{ openvpn_request_subject }}" tls_server_common_name_file: "{{ openvpn_server_common_name_file }}" tls_sans: - "{{ streisand_ipv4_address }}" - name: Register the OpenVPN server common name command: cat "{{ openvpn_server_common_name_file }}" register: openvpn_server_common_name changed_when: False - name: Generate HMAC firewall key command: openvpn --genkey --secret {{ openvpn_hmac_firewall }} args: creates: "{{ openvpn_hmac_firewall }}" - name: Register CA certificate contents command: cat ca.crt args: chdir: "{{ openvpn_path }}" register: openvpn_ca_contents changed_when: False - name: Register client certificate contents command: cat client.crt args: chdir: "{{ openvpn_path }}/{{ client_name.stdout }}" with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" register: openvpn_client_certificates changed_when: False - name: Register client key contents command: cat client.key args: chdir: "{{ openvpn_path }}/{{ client_name.stdout }}" with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" register: openvpn_client_keys changed_when: False - name: Register HMAC firewall contents command: cat ta.key args: chdir: "{{ openvpn_path }}" register: openvpn_hmac_firewall_contents changed_when: False - name: Create the client configuration profiles that will be used when connecting directly template: src: client-direct.ovpn.j2 dest: "{{ openvpn_path }}/{{ item[0].stdout }}/{{ openvpn_direct_profile_filename }}" with_together: - "{{ vpn_client_names.results }}" - "{{ openvpn_client_certificates.results }}" - "{{ openvpn_client_keys.results }}" loop_control: label: "{{ item[0].item }}" - name: Create the client configuration profiles that will be used when connecting directly via UDP template: src: client-direct-udp.ovpn.j2 dest: "{{ openvpn_path }}/{{ item[0].stdout }}/{{ openvpn_direct_udp_profile_filename }}" with_together: - "{{ vpn_client_names.results }}" - "{{ openvpn_client_certificates.results }}" - "{{ openvpn_client_keys.results }}" loop_control: label: "{{ item[0].item }}" - name: Create the client configuration profiles that will be used when connecting via sslh template: src: client-sslh.ovpn.j2 dest: "{{ openvpn_path }}/{{ item[0].stdout }}/{{ openvpn_sslh_profile_filename }}" with_together: - "{{ vpn_client_names.results }}" - "{{ openvpn_client_certificates.results }}" - "{{ openvpn_client_keys.results }}" loop_control: label: "{{ item[0].item }}" - name: Create the client configuration profiles that will be used when connecting via stunnel template: src: client-stunnel.ovpn.j2 dest: "{{ openvpn_path }}/{{ item[0].stdout }}/{{ openvpn_stunnel_profile_filename }}" with_together: - "{{ vpn_client_names.results }}" - "{{ openvpn_client_certificates.results }}" - "{{ openvpn_client_keys.results }}" loop_control: label: "{{ item[0].item }}" when: streisand_stunnel_enabled - name: Create the combined client configuration profiles that will be used to connect from the fastest to the most compatible template: src: client-combined.ovpn.j2 dest: "{{ openvpn_path }}/{{ item[0].stdout }}/{{ openvpn_combined_profile_filename }}" with_together: - "{{ vpn_client_names.results }}" - "{{ openvpn_client_certificates.results }}" - "{{ openvpn_client_keys.results }}" loop_control: label: "{{ item[0].item }}" - name: Copy OpenVPN configuration file into place template: src: etc_openvpn_server.conf.j2 dest: "{{ openvpn_path }}/server.conf" - name: Copy OpenVPN UDP configuration file into place template: src: etc_openvpn_server_udp.conf.j2 dest: "{{ openvpn_path }}/server-udp.conf" - name: Stop and disable the bundled openvpn.service systemd: name: openvpn.service state: stopped enabled: no - name: Copy the OpenVPN system unit files template: src: openvpn.service.j2 dest: /etc/systemd/system/{{ item }} mode: 0644 with_items: - "{{ openvpn_service_names }}" - name: Enable the OpenVPN services systemd: daemon_reload: yes name: "{{ item }}" enabled: yes state: restarted with_items: - "{{ openvpn_service_names }}" - name: Copy the ca.crt and ta.key files that clients will need in order to connect to the OpenVPN server command: cp {{ openvpn_path }}/{{ item[1] }} {{ openvpn_path }}/{{ item[0].stdout }} with_nested: - "{{ vpn_client_names.results }}" - ["ca.crt", "ta.key"] loop_control: label: "{{ item[0].item }}" # Set up the OpenVPN firewall rules - import_tasks: firewall.yml # Generate Gateway documentation - import_tasks: docs.yml # Mirror the OpenVPN clients - import_tasks: mirror.yml # Install stunnel # NOTE (@alimakki) - must be done after OpenVPN's Gateway task # since it depends on the OpenVPN gateway directory being present - include_role: name: stunnel when: streisand_stunnel_enabled ================================================ FILE: playbooks/roles/openvpn/tasks/mirror.yml ================================================ --- - name: Include the OpenVPN mirror variables include_vars: mirror.yml - name: Make the directory where OpenVPN's mirrored files will be stored file: path: "{{ openvpn_mirror_location }}" owner: www-data group: www-data mode: 0755 state: directory - block: - name: Mirror Tunnelblick for macOS get_url: url: "{{ tunnelblick_url }}" dest: "{{ openvpn_mirror_location }}" checksum: "{{ tunnelblick_checksum }}" owner: www-data group: www-data mode: 0644 - include_role: name: download-and-verify vars: project_name: "OpenVPN Community" project_download_baseurl: "{{ openvpn_base_download_url }}" project_download_files: "{{ openvpn_download_files }}" project_download_location: "{{ openvpn_mirror_location }}" project_signer_keyid: "{{ openvpn_gpg_keyid }}" rescue: - name: "{{ streisand_mirror_warning }}" pause: seconds: "{{ streisand_mirror_warning_seconds }}" - include_role: name: i18n-docs vars: title: "OpenVPN mirror" i18n_location: "{{ openvpn_mirror_location }}" input_template_name: "mirror" ================================================ FILE: playbooks/roles/openvpn/templates/client-combined.ovpn.j2 ================================================ client remote {{ openvpn_server }} {{ openvpn_port_udp }} udp remote {{ openvpn_server }} {{ openvpn_port }} tcp connect-timeout {{ openvpn_connect_timeout }} remote {{ openvpn_server }} {{ openvpn_port_sslh }} tcp connect-timeout {{ openvpn_connect_timeout }} {% include "client-common.j2" %} ================================================ FILE: playbooks/roles/openvpn/templates/client-common.j2 ================================================ dev tun cipher {{ openvpn_cipher }} auth {{ openvpn_auth_digest }} resolv-retry infinite nobind persist-key persist-tun remote-cert-tls server verify-x509-name {{ openvpn_server_common_name.stdout }} name tls-version-min 1.2 compress verb 3 route {{ streisand_ipv4_address }} 255.255.255.255 net_gateway {{ openvpn_ca_contents.stdout }} {{ item[1].stdout }} {{ item[2].stdout }} {{ openvpn_hmac_firewall_contents.stdout }} ================================================ FILE: playbooks/roles/openvpn/templates/client-direct-udp.ovpn.j2 ================================================ client remote {{ openvpn_server }} {{ openvpn_port_udp }} proto udp {% include "client-common.j2" %} ================================================ FILE: playbooks/roles/openvpn/templates/client-direct.ovpn.j2 ================================================ client remote {{ openvpn_server }} {{ openvpn_port }} proto tcp {% include "client-common.j2" %} ================================================ FILE: playbooks/roles/openvpn/templates/client-sslh.ovpn.j2 ================================================ client remote {{ openvpn_server }} {{ openvpn_port_sslh }} proto tcp {% include "client-common.j2" %} ================================================ FILE: playbooks/roles/openvpn/templates/client-stunnel.ovpn.j2 ================================================ client remote 127.0.0.1 {{ stunnel_local_port }} proto tcp {% include "client-common.j2" %} ================================================ FILE: playbooks/roles/openvpn/templates/etc_openvpn_server.conf.j2 ================================================ server 10.8.0.0 255.255.255.0 push "dhcp-option DNS {{ dnsmasq_openvpn_tcp_ip }}" proto tcp port {{ openvpn_port }} {% include "etc_openvpn_server_common.j2" %} ================================================ FILE: playbooks/roles/openvpn/templates/etc_openvpn_server_common.j2 ================================================ dev tun ca ca.crt cert server.crt key server.key # This file should be kept secret dh none ifconfig-pool-persist ipp.txt push "redirect-gateway def1" # Fix for the Windows 10 DNS leak described here: # https://community.openvpn.net/openvpn/ticket/605 push block-outside-dns client-to-client remote-cert-tls client # Allow multiple clients with the same common name to concurrently connect. # In the absence of this option, OpenVPN will disconnect a client instance # upon connection of a new client having the same common name. # duplicate-cn keepalive 1800 3600 tls-crypt ta.key # This file is secret cipher {{ openvpn_cipher }} ncp-ciphers {{ openvpn_ncp_ciphers }} # limit the tls ciphers tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384 auth {{ openvpn_auth_digest }} tls-version-min 1.2 compress user nobody group nogroup persist-key persist-tun verb 0 script-security 2 tls-verify "/usr/share/openvpn/verify-cn /etc/allowed_vpn_certs" ================================================ FILE: playbooks/roles/openvpn/templates/etc_openvpn_server_udp.conf.j2 ================================================ server 10.9.0.0 255.255.255.0 push "dhcp-option DNS {{ dnsmasq_openvpn_udp_ip }}" proto udp port {{ openvpn_port_udp }} {% include "etc_openvpn_server_common.j2" %} ================================================ FILE: playbooks/roles/openvpn/templates/instructions-fr.md.j2 ================================================ {% include "languages.md.j2" %} OpenVPN ------- {% if streisand_stunnel_enabled %} Si les connexions OpenVPN sont bloquées dans votre pays, reportez-vous aux instructions [OpenVPN (stunnel)](/openvpn/stunnel-fr.html) qui vous aideront à envelopper votre trafic OpenVPN dans un tunnel crypté afin qu'il ressemble à du trafic TLS standard. {% endif %} #### Une note sur les fichiers clients #### Pour des raisons de sécurité, ce serveur OpenVPN a été configuré pour n'autoriser qu'une seule connexion client par paire de certificats. Toute tentative de ré-utiliser les certificats clients entraînera une session client existante de se déconnecter. --- * Plateformes * [Windows](#windows) * [macOS](#macos) * [Linux](#linux) * [Linux (Ubuntu 16.04)](#linux-ubuntu-16.04) * [Linux (Ubuntu 17.10)](#linux-ubuntu-17.10) * [Linux (Ubuntu 18.04)](#linux-ubuntu-18.04) * [Android](#android) * [iOS](#ios) ### Windows ### 1. Téléchargez et exécutez [l'installateur Windows](/mirror/index-fr.html#openvpn) OpenVPN. 1. Cliquez sur *Suivant* et acceptez le contrat de licence en sélectionnant *J'accepte*. 1. Cliquez *Suivant* sur l'écran *Choose Components* (Choisir les composants). Laissez toutes les options par défaut cochées. 1. Notez le dossier de destination. C'est là que vous placerez le profil de configuration client `{{ openvpn_direct_profile_filename }}` après l'installation. Cliquez sur *Install* (Installer). 1. Un avis de sécurité Windows apparaîtra et demande *Voulez-vous installer ce logiciel de périphérique?*. Cliquer *Installer*. 1. Cliquez *Next* sur l'écran *Installation Complete* (installation complète). 1. Décochez *Show Readme* puis cliquez *Finish* (finir). 1. Cliquez-droit sur l'icône OpenVPN GUI et choisissez *Propriétés*. 1. Allez à l'onglet *Compatibilité* et cochez la case *Exécuter ce programme en tant qu'administrateur* dans la section *Niveau du privilège*. 1. Double-cliquez sur l'icône OpenVPN GUI situé sur votre bureau afin de lancer l'application. 1. Téléchargez l'un de ces profils OpenVPN unifiés : {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_direct_profile_filename }}) {% endfor %} * *[Profils alternatifs](#sslh-profiles) sont disponibles si vous êtes sur un réseau qui ne permet l'accès au port `443`.* * *[Profils UDP](#udp-profiles) sont disponibles.* * *[Profils combinés](#combined-profiles) sont maintenant disponibles.* 1. Ouvrez le répertoire *config* situé dans le dossier de destination. Pour la plupart des utilisateurs, ça sera *C:\Programmes\OpenVPN\config* ou *C:\Program Files (x86)\OpenVPN\config*. Vous verrez un seul fichier README dans ce répertoire. 1. Faites glisser et déposez le `{{ openvpn_direct_profile_filename }}` téléchargé a ci-dessus, et placez-le dans le même répertoire que le fichier README. 1. Faites un clic droit sur l'icône OpenVPN dans la barre des tâches et choisissez *Connecter*. 1. Vous verrez des journaux se déplacent à mesure que la connexion est établie, suivie d'une notification de la barre des tâches indiquant votre adresse IP attribuée. 1. Succès ! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{ streisand_ipv4_address }}*. ### macOS ### 1. Téléchargez et lancez [Tunnelblick](/mirror/index-fr.html#openvpn). 1. Saisissez votre mot de passe pour permettre le processus d'installation de finir, et cliquez ensuite sur *OK*. 1. Cliquez *Lancer* une fois l'installation terminée. 1. Cliquez *J'ai des fichiers de configuration*. 1. Téléchargez l'un des profils unifiés d'OpenVPN: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_direct_profile_filename }}) {% endfor %} * *[Profils alternatifs](#sslh-profiles) sont disponibles si vous êtes sur un réseau qui ne permet l'accès au port `443`.* * *[Profils UDP](#udp-profiles) sont disponibles.* * *[Profils combinés](#combined-profiles) sont maintenant disponibles.* 1. Double-cliquez le profil OpenVPN. 1. Vous devrez choisir si le profil doit être disponible pour tous les utilisateurs ou seulement pour l'utilisateur actuel. Après avoir effectué votre sélection, vous devrez entrer votre mot de passe. 1. Recherchez l'icône Tunnelblick dans votre barre de menus. Cliquez dessus et choisissez *Connecter*. 1. Succès ! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{ streisand_ipv4_address }}*. ### Linux ### 1. Installer OpenVPN: `sudo apt-get install openvpn` où `sudo yum install openvpn` où `gestionnaire-de-paquetage-esoterique hipster openvpn` * Si l'installation de OpenVPN via votre gestionnaire de paquet n'est pas une option, vous pouvez également télécharger et compiler le [code source OpenVPN](/mirror/index-fr.html#openvpn). 1. Téléchargez l'un de ces profils OpenVPN unifiés : {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_direct_profile_filename }}) {% endfor %} * *[Profils alternatifs](#sslh-profiles) sont disponibles si vous êtes sur un réseau qui permet uniquement l'accès au port `443`.* * *[Profils UDP](#udp-profiles) sont disponibles.* * *[Profils combinés](#combined-profiles) sont maintenant disponibles.* 1. Copiez le fichier `{{ openvpn_direct_profile_filename }}` téléchargé vers l'emplacement de votre choix. */etc/openvpn/* est une bonne option. 1. Sur certaines distributions, l'option DNS DHCP poussée du serveur OpenVPN sera ignorée. Cela signifie que vos requêtes DNS seront toujours acheminées via les serveurs de votre FAI qui les rend vulnérables à ce que l'on appelle une fuite DNS. * Vous pouvez tester si votre DNS ou non est en train de fuir [ici](https://dnsleaktest.com/). * Vous pouvez vous assurer que les serveurs DNS corrects sont utilisés en ajoutant les lignes suivantes au début du fichier `{{ openvpn_direct_profile_filename }}`: * `script-security 2` * `up /etc/openvpn/update-resolv-conf` * `down /etc/openvpn/update-resolv-conf` 1. Exécutez OpenVPN et passez le profil .ovpn en option. `sudo openvpn {{ openvpn_direct_profile_filename }}` 1. Succès ! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{ streisand_ipv4_address }}*. ### Linux (Ubuntu 16.04) ### En raison d'un problème lié à NetworkManager d'Ubuntu 16.04, vous ne pouvez pas utiliser le plug-in OpenVPN. De plus, vous ne pouvez pas utiliser la version de OpenVPN qui est dans les dépôts par défaut. Pour résoudre ce problème, nous devons télécharger OpenVPN directement depuis le dépôt de projets. 1. Téléchargez l'un de ces profils OpenVPN unifiés : {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_direct_profile_filename }}) {% endfor %} * [Profils alternatifs](#sslh-profiles) sont disponibles si vous êtes sur un réseau qui permet uniquement l'accès au port `443`.* * [Profils UDP](#udp-profiles) sont disponibles.* * [Profils combinés](#combined-profiles) sont maintenant disponibles.* 1. Copiez le fichier téléchargé {{ openvpn_direct_profile_filename }} `à l'emplacement de votre choix. */etc/openvpn/* est une bonne option. 1. Ajouter le dépôt APT OpenVPN à vos sources en exécutant les commandes suivantes : `curl -s https://swupdate.openvpn.net/repos/repo-public.gpg | apt-key add` `echo "deb http://build.openvpn.net/debian/openvpn/stable xenial main" > /etc/apt/sources.list.d/openvpn-aptrepo.list` 1. Mettre à jour et installez OpenVPN avec la commande suivante : `sudo apt update && sudo apt install openvpn` 1. Une fois l'installation terminée, vérifiez que vous disposez de la version OpenVPN 2.4+ avec la commande suivante : `openvpn --version` 1. Dans certaines distributions, l'option DNS DHCP poussée du serveur OpenVPN sera ignorée. Cela signifie que vos requêtes DNS seront toujours acheminés par les serveurs de votre fournisseur de services Internet qui les rend vulnérables à ce qu'on appelle une fuite DNS. * Vous pouvez tester si votre DNS fuit ou non [ici](https://dnsleaktest.com/). * Vous pouvez vous assurer que les serveurs DNS corrects sont utilisés en ajoutant les lignes suivantes au début du fichier `{{ openvpn_direct_profile_filename }}` : * `script-security 2` * `up /etc/openvpn/update-resolv-conf` * `down /etc/openvpn/update-resolv-conf` 1. Exécutez OpenVPN et transmettez-lui le profil .ovpn en option. `sudo openvpn {{ openvpn_direct_profile_filename }}` 1. Succès ! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{ streisand_ipv4_address }}*. ### Linux (Ubuntu) ### Il est préférable de configurer Ubuntu en utilisant le plugin OpenVPN pour NetworkManager. Cela vous donne une jolie petite interface pour la connexion, et il gère correctement les modifications DNS nécessaires lors de la connexion / déconnexion. Malheureusement, le plugin ne prend pas en charge les profils .ovpn, les étapes sont donc un peu plus compliquée. 1. Premièrement, téléchargez le certificat CA d'OpenVPN, le certificat client `.crt`, la clé privée du client `.key`, et finalement la clé d'authentification TLS `ta.key` pour l'un ces profils en dessous: {% for client in vpn_client_names.results %} * {{ client.stdout }} * CA Cert: [ca.crt](/openvpn/{{ client.stdout }}/ca.crt) * Client Cert: [{{ client.stdout }}.crt](/openvpn/{{ client.stdout }}/client.crt) * Client Clé: [{{ client.stdout }}.key](/openvpn/{{ client.stdout }}/client.key) * TA Clé: [ta.key](/openvpn/{{ client.stdout }}/ta.key) {% endfor %} 1. Installez le plugin OpenVPN pour NetworkManager. `sudo apt-get install network-manager-openvpn-gnome` 1. Ouvrez votre *Paramètres système*. 1. Cliquez sur l'icône *Réseau*. 1. Cliquez sur le bouton *+* en bas à gauche de la fenêtre. 1. Sélectionnez *VPN* dans la liste déroulante Interface et cliquez sur *Créer*. 1. Sélectionnez *OpenVPN* et cliquez *Créer*. 1. Saisissez `{{ streisand_server_name }}` pour la *Nom de la connexion*. 1. Saisissez `{{ openvpn_server }}` pour le *Gateway*. 1. Assurez-vous que *Certificates (TLS)* est sélectionné pour le *Type*. 1. Sélectionnez le fichier `client.crt` de votre choix pour le *User Certificate*. 1. Sélectionnez le fichier `ca.crt` pour le *CA Certificate*. 1. Sélectionnez le ficher `client.key` pour le *Private Key*. 1. Cliquez le bouton *Advanced*. 1. Accédez à l'onglet *General*. * Vérifier *Use custom gateway port* et entrer `{{ openvpn_port }}` comme sa valeur. * Port `443` est disponible en alternative si vous êtes sur un réseau qui ne permet l'accès au le port HTTPS standard. * Vous pouvez également utiliser le port `{{ openvpn_port_udp }}` pour une connexion UDP. * Le profil combiné qui parcourt le port UDP `{{ openvpn_port_udp }}`, le port TCP `{{ openvpn_port }}`et `{{ openvpn_port_sslh }}` est également disponible. * Cochez *Use a TCP connection* sauf si vous avez choisi d'utiliser le port UDP ou le profil combiné. 1. Accédez à l'onglet *Security*. * Sélectionnez `{{ openvpn_cipher }}` pour le *Cipher*. * Sélectionnez `{{ openvpn_auth_digest }}` pour le *HMAC Authentication*. 1. Accédez à l'onglet *TLS Authentication*. * Dans le menu déroulant *Server Certificate Check* selectionnez `verify name exactly` et saisissez `{{ openvpn_server_common_name.stdout }}` pour sa valeur. * Cochez *Verify peer (server) certificate usage signature*. * Cochez *Additional TLS authentication or encryption*. * Sélectionnez `TLS-Crypt` pour *Mode*. * Sélectionnez le fichier `ta.key` que vous avez téléchargé depuis le répertoire client-files pour le *Key File*. * Cliquez *Valider*. 1. Cliquez *Enregistrer* 1. Sélectionnez le VPN dans le menu à gauche, et faites glisser l'interrupteur sur *ON*. Vous pouvez également activer/désactiver le VPN en cliquant sur l'icône WiFi/Réseau dans la barre de menu, défiler vers *Connexions VPN* et en cliquant sur son nom. 1. Succès ! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{ streisand_ipv4_address }}*. ### Linux (Ubuntu) ### Il est préférable de configurer Ubuntu en utilisant le plugin OpenVPN pour NetworkManager. Cela vous donne une jolie petite interface pour la connexion, et il gère correctement les modifications DNS nécessaires lors de la connexion / déconnexion. Malheureusement, le plugin ne prend pas en charge les profils .ovpn, les étapes sont donc un peu plus compliquée. 1. Premièrement, téléchargez le certificat CA d'OpenVPN, le certificat client `.crt`, la clé privée du client `.key`, et finalement la clé d'authentification TLS `ta.key` pour l'un ces profils en dessous: {% for client in vpn_client_names.results %} * {{ client.stdout }} * CA Cert: [ca.crt](/openvpn/{{ client.stdout }}/ca.crt) * Client Cert: [{{ client.stdout }}.crt](/openvpn/{{ client.stdout }}/client.crt) * Client Clé: [{{ client.stdout }}.key](/openvpn/{{ client.stdout }}/client.key) * TA Clé: [ta.key](/openvpn/{{ client.stdout }}/ta.key) {% endfor %} 1. Installez le plugin OpenVPN pour NetworkManager. `sudo apt-get install network-manager-openvpn-gnome` 1. Ouvrez votre *Paramètres système*. 1. Cliquez sur l'icône *Réseau*. 1. Cliquez sur le bouton *+* en bas à gauche de la fenêtre. 1. Sélectionnez *VPN* dans la liste déroulante Interface et cliquez sur *Créer*. 1. Sélectionnez *OpenVPN* et cliquez *Créer*. 1. Saisissez `{{ streisand_server_name }}` pour la *Nom de la connexion*. 1. Saisissez `{{ openvpn_server }}` pour le *Gateway*. 1. Assurez-vous que *Certificates (TLS)* est sélectionné pour le *Type*. 1. Sélectionnez le fichier `client.crt` de votre choix pour le *User Certificate*. 1. Sélectionnez le fichier `ca.crt` pour le *CA Certificate*. 1. Sélectionnez le ficher `client.key` pour le *Private Key*. 1. Cliquez le bouton *Advanced*. 1. Accédez à l'onglet *General*. * Vérifier *Use custom gateway port* et entrer `{{ openvpn_port }}` comme sa valeur. * Port `443` est disponible en alternative si vous êtes sur un réseau qui ne permet l'accès au le port HTTPS standard. * Vous pouvez également utiliser le port `{{ openvpn_port_udp }}` pour une connexion UDP. * Le profil combiné qui parcourt le port UDP `{{ openvpn_port_udp }}`, le port TCP `{{ openvpn_port }}`et `{{ openvpn_port_sslh }}` est également disponible. * Cochez *Use a TCP connection* sauf si vous avez choisi d'utiliser le port UDP ou le profil combiné. 1. Accédez à l'onglet *Security*. * Sélectionnez `{{ openvpn_cipher }}` pour le *Cipher*. * Sélectionnez `{{ openvpn_auth_digest }}` pour le *HMAC Authentication*. 1. Accédez à l'onglet *TLS Authentication*. * Dans le menu déroulant *Server Certificate Check* selectionnez `verify name exactly` et saisissez `{{ openvpn_server_common_name.stdout }}` pour sa valeur. * Cochez *Verify peer (server) certificate usage signature*. * Cochez *Additional TLS authentication or encryption*. * Sélectionnez `TLS-Crypt` pour *Mode*. * Sélectionnez le fichier `ta.key` que vous avez téléchargé depuis le répertoire client-files pour le *Key File*. * Cliquez *Valider*. 1. Cliquez *Enregistrer* 1. Sélectionnez le VPN dans le menu à gauche, et faites glisser l'interrupteur sur *ON*. Vous pouvez également activer/désactiver le VPN en cliquant sur l'icône WiFi/Réseau dans la barre de menu, défiler vers *Connexions VPN* et en cliquant sur son nom. 1. Succès ! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{ streisand_ipv4_address }}*. ### Android ### 1. Installez [OpenVPN pour Android](https://play.google.com/store/apps/details?id=de.blinkt.openvpn&hl=fr). 1. Téléchargez l'un de ces profils OpenVPN unifiés : {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_direct_profile_filename }}) {% endfor %} * *[Profils alternatifs](#sslh-profiles) sont disponibles si vous êtes sur un réseau qui permet uniquement l'accès au port `443`.* * *[Profils UDP](#udp-profiles) sont disponibles.* * *[Profils combinés](#combined-profiles) sont maintenant disponibles.* 1. Copiez le fichier `{{ openvpn_direct_profile_filename }}` sur votre téléphone. 1. Lancez OpenVPN pour Android. 1. Appuyez sur l'icône du dossier en bas à droite de l'écran. 1. Sélectionnez le profil `{{ openvpn_direct_profile_filename }}` que vous avez copié sur votre téléphone. 1. Tapez sur l'icône de sauvegarde (disquette) en bas à droite de l'écran une fois l'importation est terminée. 1. Tapez le profil. 1. Acceptez l'avertissement de connexion VPN Android. 1. Succès ! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{ streisand_ipv4_address }}*. ### iOS ### 1. Téléchargez [OpenVPN Connect](https://itunes.apple.com/fr/app/openvpn-connect/id590379981) et lancez-le. 1. Téléchargez l'un de ces profils OpenVPN unifiés: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_direct_profile_filename }}) {% endfor %} * *[Profils alternatifs](#sslh-profiles) sont disponibles si vous êtes sur un réseau qui permet uniquement l'accès au port `443`.* * *[Profils UDP](#udp-profiles) sont disponibles.* * *[Profils combinés](#combined-profiles) sont maintenant disponibles.* 1. Ouvrez iTunes sur votre ordinateur et connectez votre téléphone. 1. Sélectionnez votre téléphone, cliquez sur l'onglet *Apps* et trouvez OpenVPN dans la section *Partage de fichiers*. 1. Faites glisser et déposez le fichier `{{ openvpn_direct_profile_filename }}` téléchargé dans la fenêtre de partage de fichiers. 1. OpenVPN sur votre téléphone va vous dire *1 new OpenVPN profile is available for import*. 1. Appuyez sur le bouton vert *+* pour importer le profil. 1. Tapez sur le l'interrupteur pour vous connecter au serveur OpenVPN. 1. Succès! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{ streisand_ipv4_address }}*. ### Profils alternatifs unifiés pour l'accès via le port 443 ### {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_sslh_profile_filename }}) {% endfor %} ### Profils alternatifs unifiés pour accéder via le port UDP {{ openvpn_port_udp }} ### {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_direct_udp_profile_filename }}) {% endfor %} ### Profil alternatif qui parcourra le port UDP {{ openvpn_port_udp }}, le port TCP {{ openvpn_port }}, et le port TCP {{ openvpn_port_sslh }} ### {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_combined_profile_filename }}) {% endfor %} ================================================ FILE: playbooks/roles/openvpn/templates/instructions.md.j2 ================================================ {% include "languages.md.j2" %} OpenVPN ------- {% if streisand_stunnel_enabled %} If OpenVPN connections are being blocked in your country, please refer to the [OpenVPN (stunnel)](/openvpn/stunnel.html) instructions instead which will help you wrap your OpenVPN traffic in an encrypted tunnel so it looks like standard TLS traffic. {% endif %} #### A note on client files #### For security reasons, this OpenVPN has been configured to only allow one client connection per certificate pair. Attempting to re-use client certificates will cause an existing client session to disconnect. --- * Platforms * [Windows](#windows) * [macOS](#macos) * [Linux](#linux) * [Linux (Ubuntu 16.04)](#linux-ubuntu-16.04) * [Linux (Ubuntu 17.10)](#linux-ubuntu-17.10) * [Linux (Ubuntu 18.04/18.10)](#linux-ubuntu-18.04) * [Android](#android) * [iOS](#ios) ### Windows ### 1. Download and run the OpenVPN [Windows Installer](/mirror/#openvpn). 1. Click *Next* and accept the license agreement by selecting *I Agree*. 1. Click *Next* on the *Choose Components* screen. Leave all of the default options checked. 1. Make note of the Destination Folder. This is where you will place the `{{ openvpn_direct_profile_filename }}` client configuration profile after installation. Click *Install*. 1. A Windows Security notice will appear and ask *Would you like to install this device software?*. Click *Install*. 1. Click *Next* on the *Installation Complete* screen. 1. Uncheck *Show Readme* and click *Finish*. 1. Right-click on the OpenVPN GUI desktop icon and choose *Properties*. 1. Go to the *Compatibility* tab and click the *Run this program as an administrator* checkbox in the *Privilege Level* section. 1. Double-click the OpenVPN GUI desktop icon to launch the application. 1. Download one of these unified OpenVPN profiles: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_direct_profile_filename }}) {% endfor %} * *[Alternate profiles](#sslh-profiles) are available if you are on a network that only allows access to port `443`.* * *[UDP profiles](#udp-profiles) available.* * *[Combined profiles](#combined-profiles) are now available.* 1. Open the *config* directory that is located in the Destination Folder. For most users, this will either be in *C:\Program Files\OpenVPN\config* or *C:\Program Files (x86)\OpenVPN\config*. You will see a single README file in this directory. 1. Drag and drop the downloaded `{{ openvpn_direct_profile_filename }}` file to this location alongside the README. 1. Right-click on the OpenVPN icon in your taskbar and choose *Connect*. 1. You will see a log scroll by as the connection is established, followed by a taskbar notification indicating your assigned IP. 1. Success! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### macOS ### 1. Download and open [Tunnelblick](/mirror/#openvpn). 1. Type your password to allow the installation process to complete, and click *OK*. 1. Click *Launch* after the installation is finished. 1. Click *I have configuration files*. 1. Download one of these unified OpenVPN profiles: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_direct_profile_filename }}) {% endfor %} * *[Alternate profiles](#sslh-profiles) are available if you are on a network that only allows access to port `443`.* * *[UDP profiles](#udp-profiles) available.* * *[Combined profiles](#combined-profiles) are now available.* 1. Double-click the OpenVPN profile. 1. You will be asked to choose whether the profile should be available for all users or only the current user. After making your selection, you will be asked to enter your password. 1. Look for the Tunnelblick icon in your menu bar. Click on it, and choose *Connect*. 1. Success! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### Linux ### 1. Install OpenVPN: `sudo apt-get install openvpn` OR `sudo yum install openvpn` OR `esoteric-package-manager hipster openvpn` * If installing OpenVPN via your package manager is not an option, you can also download and compile the [OpenVPN source code](/mirror/#openvpn). 1. Download one of these unified OpenVPN profiles: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_direct_profile_filename }}) {% endfor %} * *[Alternate profiles](#sslh-profiles) are available if you are on a network that only allows access to port `443`.* * *[UDP profiles](#udp-profiles) available.* * *[Combined profiles](#combined-profiles) are now available.* 1. Copy the downloaded `{{ openvpn_direct_profile_filename }}` file to the location of your choice. */etc/openvpn/* is a decent option. 1. On some distributions, the pushed DHCP DNS option from the OpenVPN server will be ignored. This means that your DNS queries will still be routed through your ISP's servers which makes them vulnerable to what is known as a DNS leak. * You can test whether or not your DNS is leaking [here](https://dnsleaktest.com/). * You can ensure that the correct DNS servers are used by adding the following lines to the top of the `{{ openvpn_direct_profile_filename }}` file: * `script-security 2` * `up /etc/openvpn/update-resolv-conf` * `down /etc/openvpn/update-resolv-conf` 1. Execute OpenVPN, and pass it the .ovpn profile as an option. `sudo openvpn {{ openvpn_direct_profile_filename }}` 1. Success! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### Linux (Ubuntu 16.04) ### Due to an issue related to Ubuntu 16.04's NetworkManager you cannot use the OpenVPN plugin. Additionally, you cannot use the version of OpenVPN which is in the default repositories. To fix this issue we must download OpenVPN directly from the projects repository. 1. Download one of these unified OpenVPN profiles: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_direct_profile_filename }}) {% endfor %} * *[Alternate profiles](#sslh-profiles) are available if you are on a network that only allows access to port `443`.* * *[UDP profiles](#udp-profiles) available.* * *[Combined profiles](#combined-profiles) are now available.* 1. Copy the downloaded `{{ openvpn_direct_profile_filename }}` file to the location of your choice. */etc/openvpn/* is a decent option. 1. Add the OpenVPN APT repository to your sources by running the following commands: `curl -s https://swupdate.openvpn.net/repos/repo-public.gpg | apt-key add` `echo "deb http://build.openvpn.net/debian/openvpn/stable xenial main" > /etc/apt/sources.list.d/openvpn-aptrepo.list` 1. Update and install OpenVPN with the following command: `sudo apt update && sudo apt install openvpn` 1. After the install is complete, ensure you have OpenVPN version 2.4+ with the following command: `openvpn --version` 1. On some distributions, the pushed DHCP DNS option from the OpenVPN server will be ignored. This means that your DNS queries will still be routed through your ISP's servers which makes them vulnerable to what is known as a DNS leak. * You can test whether or not your DNS is leaking [here](https://dnsleaktest.com/). * You can ensure that the correct DNS servers are used by adding the following lines to the top of the `{{ openvpn_direct_profile_filename }}` file: * `script-security 2` * `up /etc/openvpn/update-resolv-conf` * `down /etc/openvpn/update-resolv-conf` 1. Execute OpenVPN, and pass it the .ovpn profile as an option. `sudo openvpn {{ openvpn_direct_profile_filename }}` 1. Success! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### Linux (Ubuntu 17.10) ### It's preferable to configure Ubuntu using the OpenVPN plugin for NetworkManager. This gives you a nice little interface for connecting, and it properly handles the necessary DNS changes when you connect/disconnect. Unfortunately, the plugin does not support .ovpn profiles, so the list of steps is a little more involved. 1. First, download the OpenVPN CA certificate, the certificate `.crt` file, private key `.key` file, and TLS authentication key `ta.key` file for one of the client profiles below: {% for client in vpn_client_names.results %} * {{ client.stdout }} * CA Cert: [ca.crt](/openvpn/{{ client.stdout }}/ca.crt) * Client Cert: [{{ client.stdout }}.crt](/openvpn/{{ client.stdout }}/client.crt) * Client Key: [{{ client.stdout }}.key](/openvpn/{{ client.stdout }}/client.key) * TA key: [ta.key](/openvpn/{{ client.stdout }}/ta.key) {% endfor %} 1. Install the OpenVPN plugin for NetworkManager. `sudo apt-get install network-manager-openvpn-gnome` 1. Open your *System Settings*. 1. Click the *Network* tab on the left side. 1. Click the *+* button under *VPN*. 1. Select *OpenVPN*. 1. Enter `{{ streisand_server_name }}` for the *Name*. 1. Enter `{{ openvpn_server }}` for the *Gateway*. 1. Make sure *Certificates (TLS)* is selected for the *Type*. 1. Select the `client.crt` file you downloaded for the *User Certificate*. 1. Select the `ca.crt` file you downloaded for the *CA Certificate*. 1. Select the `client.key` file you downloaded for the *Private Key*. 1. Click the *Advanced* button. 1. Go to the *General* tab. * Check *Use custom gateway port* and enter `{{ openvpn_port }}` as its value. * Port `443` is available as an alternative if you are on a network that only allows access to the standard HTTPS port. * You can also use port `{{ openvpn_port_udp }}` for a UDP connection. * A combined profile which cycles through UDP port `{{ openvpn_port_udp }}`, TCP port `{{ openvpn_port }}` and `{{ openvpn_port_sslh }}` is also available. * Check *Use a TCP connection* unless you have chosen to use the UDP port or the combined profile. 1. Go to the *Security* tab. * Select `{{ openvpn_cipher }}` as the *Cipher*. * Select `{{ openvpn_auth_digest }}` as the *HMAC Authentication*. 1. Go to the *TLS Authentication* tab. * Under *Server Certificate Check* choose `verify name exactly` and enter `{{ openvpn_server_common_name.stdout }}` as its value. * Check *Verify peer (server) certificate usage signature*. * Go to *Additional TLS authentication or encryption*. * Select `TLS-Crypt` as the *Mode*. * Select the `ta.key` file you downloaded from the client-files directory for the *Key File*. * Click *OK*. 1. Click *Add* 1. Select the VPN in the left-hand menu, and flip the switch to *ON*. You can also enable/disable the VPN by clicking on the WiFi/Network icon in the menu bar, scrolling to *VPN Connections*, and clicking on its name. 1. Success! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### Linux (Ubuntu 18.04/18.10) ### It's preferable to configure Ubuntu using the OpenVPN plugin for NetworkManager. This gives you a nice little interface for connecting, and it properly handles the necessary DNS changes when you connect/disconnect. Unfortunately, the plugin does not support .ovpn profiles, so the list of steps is a little more involved. 1. First, download the OpenVPN CA certificate, the certificate `.crt` file, private key `.key` file, and TLS authentication key `ta.key` file for one of the client profiles below: {% for client in vpn_client_names.results %} * {{ client.stdout }} * CA Cert: [ca.crt](/openvpn/{{ client.stdout }}/ca.crt) * Client Cert: [{{ client.stdout }}.crt](/openvpn/{{ client.stdout }}/client.crt) * Client Key: [{{ client.stdout }}.key](/openvpn/{{ client.stdout }}/client.key) * TA key: [ta.key](/openvpn/{{ client.stdout }}/ta.key) {% endfor %} 1. Install the OpenVPN plugin for NetworkManager. `sudo apt-get install network-manager-openvpn-gnome` 1. Open your *System Settings*. 1. Click the *Network* tab on the left side. 1. Click the *+* button under *VPN*. 1. Select *OpenVPN*. 1. Enter `{{ streisand_server_name }}` for the *Connection name*. 1. Enter `{{ openvpn_server }}` for the *Gateway*. 1. Make sure *Certificates (TLS)* is selected for the *Type*. 1. Select the `ca.crt` file you downloaded for the *CA Certificate*. 1. Select the `client.crt` file you downloaded for the *User Certificate*. 1. Select the `client.key` file you downloaded for the *User Private Key*. 1. Click the *Advanced* button. 1. Go to the *General* tab. * Check *Use custom gateway port* and enter `{{ openvpn_port }}` as its value. * Port `443` is available as an alternative if you are on a network that only allows access to the standard HTTPS port. * You can also use port `{{ openvpn_port_udp }}` for a UDP connection. * A combined profile which cycles through UDP port `{{ openvpn_port_udp }}`, TCP port `{{ openvpn_port }}` and `{{ openvpn_port_sslh }}` is also available. * Check *Use a TCP connection* unless you have chosen to use the UDP port or the combined profile. 1. Go to the *Security* tab. * Select `{{ openvpn_cipher }}` as the *Cipher*. * Select `{{ openvpn_auth_digest }}` as the *HMAC Authentication*. 1. Go to the *TLS Authentication* tab. * Under *Server Certificate Check* choose `verify name exactly` and enter `{{ openvpn_server_common_name.stdout }}` as its value. * Check *Verify peer (server) certificate usage signature*. * Go to *Additional TLS authentication or encryption*. * Select `TLS-Crypt` as the *Mode*. * Select the `ta.key` file you downloaded from the client-files directory for the *Key File*. * Click *OK*. 1. Click *Add* 1. Find the VPN and flip the switch to *ON*. You can also enable/disable the VPN by clicking on the WiFi/Network icon in the menu bar, scrolling to *VPN Connections*, and clicking on its name. 1. Success! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### Android ### 1. Install [OpenVPN for Android](https://play.google.com/store/apps/details?id=de.blinkt.openvpn). 1. Download one of these unified OpenVPN profiles: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_direct_profile_filename }}) {% endfor %} * *[Alternate profiles](#sslh-profiles) are available if you are on a network that only allows access to ports `80` and `443`.* * *[UDP profiles](#udp-profiles) available.* * *[Combined profiles](#combined-profiles) are now available.* 1. Copy the `{{ openvpn_direct_profile_filename }}` file to your phone. 1. Launch OpenVPN for Android. 1. Tap the folder icon in the lower-right of the screen. 1. Select the `{{ openvpn_direct_profile_filename }}` profile that you copied to your phone. 1. Tap the save icon (floppy disk) in the lower-right of the screen after the import is complete. 1. Tap the profile. 1. Accept the Android VPN connection warning. 1. Success! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### iOS ### 1. Download [OpenVPN Connect](https://itunes.apple.com/us/app/openvpn-connect/id590379981) and launch it. 1. Download one of these unified OpenVPN profiles: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_direct_profile_filename }}) {% endfor %} * *[Alternate profiles](#sslh-profiles) are available if you are on a network that only allows access to ports `80` and `443`.* * *[UDP profiles](#udp-profiles) available.* * *[Combined profiles](#combined-profiles) are now available.* 1. Open iTunes on your computer and connect your phone. 1. Select your phone, click on the *Apps* tab, and find OpenVPN under the *File Sharing* section. 1. Drag and drop the downloaded `{{ openvpn_direct_profile_filename }}` file into the file sharing window. 1. OpenVPN on your phone will say that *1 new OpenVPN profile is available for import*. 1. Tap the green *+* button to import the profile. 1. Tap the slider to connect to the OpenVPN server. 1. Success! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### Alternate unified profiles for access via port 443 ### {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_sslh_profile_filename }}) {% endfor %} ### Alternate unified profiles for access via UDP port {{ openvpn_port_udp }} ### {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_direct_udp_profile_filename }}) {% endfor %} ### Alternate profile that will cycle through UDP port {{ openvpn_port_udp }}, TCP port {{ openvpn_port }}, and TCP port {{ openvpn_port_sslh }} ### {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_combined_profile_filename }}) {% endfor %} ================================================ FILE: playbooks/roles/openvpn/templates/mirror-fr.md.j2 ================================================ {% include "languages.md.j2" %} ### OpenVPN ### **Source** * [{{ openvpn_source_filename }}]({{ openvpn_source_href }}) ([sig]({{ openvpn_source_sig_href }})) **macOS** * [Tunnelblick\_{{ tunnelblick_version }}.dmg]({{ tunnelblick_href }}) * Somme de contrôle: *{{ tunnelblick_checksum }}* **Windows** * [{{ openvpn_windows_installer_filename }}]({{ openvpn_windows_installer_href }}) ([sig]({{ openvpn_windows_installer_sig_href }})) ================================================ FILE: playbooks/roles/openvpn/templates/mirror.md.j2 ================================================ {% include "languages.md.j2" %} ### OpenVPN ### **Source** * [{{ openvpn_source_filename }}]({{ openvpn_source_href }}) ([sig]({{ openvpn_source_sig_href }})) **macOS** * [Tunnelblick\_{{ tunnelblick_version }}.dmg]({{ tunnelblick_href }}) * Checksum: *{{ tunnelblick_checksum }}* **Windows** * [{{ openvpn_windows_installer_filename }}]({{ openvpn_windows_installer_href }}) ([sig]({{ openvpn_windows_installer_sig_href }})) ================================================ FILE: playbooks/roles/openvpn/templates/openvpn-iptables.service.j2 ================================================ [Unit] Description=Firewall rules required for OpenVPN After=network.target Before={{ openvpn_service_names | join(' ') }} [Service] Type=oneshot RemainAfterExit=true {% for rule in openvpn_firewall_rules %} ExecStart=/sbin/{{ rule }} {% endfor %} [Install] WantedBy=multi-user.target ================================================ FILE: playbooks/roles/openvpn/templates/openvpn.service.j2 ================================================ # source: https://github.com/OpenVPN/openvpn/blob/master/distro/systemd/openvpn-server%40.service.in [Unit] Description=OpenVPN service for %I After=syslog.target network-online.target Wants=network-online.target Documentation=man:openvpn(8) Documentation=https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage Documentation=https://community.openvpn.net/openvpn/wiki/HOWTO [Service] Type=notify PrivateTmp=true WorkingDirectory={{ openvpn_path }} ExecStart=/usr/sbin/openvpn --config %i.conf CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_OVERRIDE LimitNPROC=30 DeviceAllow=/dev/null rw DeviceAllow=/dev/net/tun rw ProtectSystem=true ProtectHome=true KillMode=process RestartSec=5s Restart=on-failure [Install] Alias=openvpn@%i.target WantedBy=multi-user.target ================================================ FILE: playbooks/roles/openvpn/templates/openvpn_dnsmasq.conf.j2 ================================================ # Listen on the OpenVPN TCP and UDP addresses listen-address={{ dnsmasq_openvpn_tcp_ip }},{{ dnsmasq_openvpn_udp_ip }} ================================================ FILE: playbooks/roles/openvpn/templates/stunnel-instructions-fr.md.j2 ================================================ {% include "languages.md.j2" %} OpenVPN (enveloppé dans stunnel) -------------------------------- Ces instructions ne doivent être utilisées que si les connexions OpenVPN sont bloquées activement dans votre pays ou par votre FAI. La performance sera meilleure si vous pouvez vous connecter directement, mais certains pays (notamment la Chine et l'Iran) emploient 'Deep Packet Inspection' pour détecter et affronter les connexions OpenVPN. Certaines entreprises font de même. Si vous ne rentrez pas dans cette catégorie, reportez-vous à la norme [instructions OpenVPN](/openvpn/index-fr.html) à la place. Suite à ces étapes, vous enveloppez votre trafic OpenVPN dans un tunnel crypté, de sorte qu'il ressemblera comme du trafic SSL standard soit dirigé vers un port où ce type de trafic serait attendu. Cela empêchera le 'DPI' d'identifier la nature veritable de vos paquets, ce qui vous permetera d'utiliser OpenVPN librement. #### Une note sur les fichiers clients #### Pour des raisons de sécurité, les clients OpenVPN ont généralement leurs propres certificats et clés privées. Le serveur auquel vous vous connectez a été configuré pour permettre aux clients de partager le même certificat et la même clé privée, mais vous pouvez éventuellement donner à votre téléphone et votre ordinateur portable des clés différentes, par exemple. --- * Plateformes * [Windows](#windows) * [macOS](#macos) * [Linux](#linux) * [Android](#android) * [iOS](#ios) ### Windows ### #### Installation Stunnel #### 1. Téléchargez et exécutez [l'installateur stunnel](/mirror/index-fr.html#stunnel). 1. Téléchargez le fichier `stunnel.conf` qui a été personnalisé pour fonctionner avec ce serveur: * [stunnel.conf](/openvpn/stunnel.conf) 1. Ouvrez le répertoire où vous avez installé stunnel. Pour la plupart des utilisateurs, cela sera dans *C:\Program Files\stunnel* ou *C:\Program Files (x86)\stunnel*. 1. Faites glisser et déposez le fichier `stunnel.conf` téléchargé dans ce répertoire. 1. Double-cliquez sur *stunnel.exe* dans le répertoire d'installation pour lancer le service. Maintenant, vous êtes prêt à installer OpenVPN et à le configurer pour acheminer son trafic via Stunnel. Un profil .ovpn personnalisé préconfiguré pour fonctionner à côté du fichier `stunnel.conf` rendra cela facile. #### Installation OpenVPN #### 1. Téléchargez et exécutez [l'installateur Windows](/mirror/index-fr.html#openvpn) OpenVPN. 1. Cliquez sur *Suivant* et acceptez le contrat de licence en sélectionnant *J'accepte*. 1. Cliquez *Suivant* sur l'écran *Choose Components* (Choisir les composants). Laissez toutes les options par défaut cochée. 1. Notez le dossier de destination. C'est là que vous placerez le profil de configuration client `{{ openvpn_direct_profile_filename }}` après l'installation. Cliquez sur *Install* (Installer). 1. Un avis de sécurité Windows apparaîtra et demande *Voulez-vous installer ce logiciel de périphérique?*. Cliquer *Installer*. 1. Cliquez *Next* sur l'écran *Installation Complete* (installation complète). 1. Décochez *Show Readme* puis cliquez *Finish* (finir). 1. Cliquez-droit sur l'icône OpenVPN GUI et choisissez *Propriétés*. 1. Allez à l'onglet *Compatibilité* et cliquez sur la coche *Exécuter ce programme en tant qu'administrateur* dans la section *Niveau du privilège*. 1. Double-cliquez sur l'icône OpenVPN GUI situé sur votre bureau afin de lancer l'application. 1. Téléchargez l'un de ces profils OpenVPN unifiés: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_stunnel_profile_filename }}) {% endfor %} 1. Ouvrez le directoire *config* situé dans le dossier de destination. Pour la plupart des utilisateurs, ça sera *C:\Programmes\OpenVPN\config* ou *C:\Program Files (x86)\OpenVPN\config*. Vous verrez un seul fichier README dans ce répertoire. 1. Faites glisser et déposez le `{{ openvpn_stunnel_profile_filename }}` téléchargé a cette location juste à côté du fichier README. 1. Faites un clic droit sur l'icône OpenVPN dans la barre des tâches et choisissez *Connecter*. 1. Vous verrez des journaux se déplacent à mesure que la connexion est établie, suivie d'une notification de la barre des tâches indiquant votre adresse IP attribuée. 1. Succès ! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. ### macOS ### #### Installation Stunnel #### 1. Installer [Homebrew](https://brew.sh/), si vous ne l'avez pas. 1. Installer stunnel utilisant Homebrew: `brew install stunnel` * Si l'installation de Homebrew n'est pas une option, vous pouvez également télécharger et compiler le [code source stunnel](/mirror/index-fr.html#stunnel). 1. Téléchargez le fichier `stunnel.conf` qui a été personnalisé pour fonctionner avec ce serveur: * [stunnel.conf](/openvpn/stunnel.conf) 1. Remplacez le fichier défaut stunnel.conf avec la version personnalisée. Assurez-vous de mettre à jour l'emplacement de la source si vous avez téléchargé le fichier dans un répertoire différent. `cp ~/Downloads/stunnel.conf /usr/local/etc/stunnel/` 1. Lancez stunnel: `stunnel` Maintenant, vous êtes prêt à installer OpenVPN et à le configurer pour acheminer son trafic via Stunnel. Un profil .ovpn personnalisé préconfiguré pour fonctionner à côté du fichier `stunnel.conf` rendra cela facile. #### Installation OpenVPN #### 1. Téléchargez et lancez [Tunnelblick](/mirror/index-fr.html#openvpn). 1. Saisissez votre mot de passe pour permettre le processus d'installation de finir, en suite cliquez *OK*. 1. Cliquez *Lancer* une fois l'installation est terminée. 1. Cliquez *J'ai des fichiers de configuration*. 1. Téléchargez l'un des profils unifiés d'OpenVPN: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_stunnel_profile_filename }}) {% endfor %} 1. Double-cliquez le profile OpenVPN. 1. Vous devrez choisir si le profil doit être disponible pour tous les utilisateurs ou seulement pour l'utilisateur actuel. Après avoir effectué votre sélection, vous devrez entrer votre mot de passe. 1. Recherchez l'icône Tunnelblick dans votre barre de menus. Cliquez dessus et choisissez *Connecter*. 1. Succès! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. ### Linux ### #### Installation Stunnel #### 1. Assurez-vous que stunnel est installé: `sudo apt-get install stunnel` où `sudo yum install stunnel` où `gestionnaire-de-paquetage-esoterique hipster stunnel` * Si l'installation de stunnel via votre gestionnaire de paquets n'est pas une option, vous pouvez également télécharger et compiler le [code source stunnel](/mirror/index-fr.html#stunnel).. 1. Téléchargez le fichier `stunnel.conf` qui a été personnalisé pour fonctionner avec ce serveur: * [stunnel.conf](/openvpn/stunnel.conf) 1. Copiez `stunnel.conf` vers la bonne destination. Assurez-vous de mettre à jour l'emplacement de la source si vous avez déplacé le répertoire ailleurs. `cp ~/Downloads/stunnel.conf /etc/stunnel/` 1. Les utilisateurs d'Ubuntu doivent ajuster le fichier `/etc/default/stunnel4` et assurez-vous que `ENABLED` est défini sur `1`. 1. Redémarrez le service Stunnel: `sudo service stunnel4 restart` ou `sudo service stunnel restart` Maintenant, vous êtes prêt à installer OpenVPN et à le configurer pour acheminer son trafic via Stunnel. Un profil .ovpn personnalisé préconfiguré pour fonctionner à côté du fichier `stunnel.conf` rendra cela facile. #### Installation OpenVPN #### 1. Installer OpenVPN : `sudo apt-get install openvpn` où `sudo yum install openvpn` où `gestionnaire-de-paquetage-esoterique hipster openvpn` * Si l'installation de OpenVPN via votre gestionnaire de paquetage n'est pas une option, vous pouvez également télécharger et compiler le [code source OpenVPN](/mirror/index-fr.html#openvpn). 1. Téléchargez l'un de ces profils OpenVPN unifiés : {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_stunnel_profile_filename }}) {% endfor %} 1. Copiez le fichier `{{ openvpn_direct_profile_filename }}` téléchargé vers l'emplacement de votre choix. */etc/openvpn/* est une bonne option. 1. Sur certaines distributions, l'option DNS DHCP poussée du serveur OpenVPN sera ignorée. Cela signifie que vos requêtes DNS seront toujours acheminées via les serveurs de votre FAI qui les rend vulnérables à ce que l'on appelle une fuite DNS. * Vous pouvez tester si votre DNS ou non est en train de fuir [içi](https://dnsleaktest.com/). * Vous pouvez vous assurer que les serveurs DNS corrects sont utilisés en ajoutant les lignes suivantes au début du fichier `{{ openvpn_stunnel_profile_filename }}`: * `script-security 2` * `up /etc/openvpn/update-resolv-conf` * `down /etc/openvpn/update-resolv-conf` 1. Exécutez OpenVPN et passez le profil .ovpn en option. `sudo openvpn {{ openvpn_stunnel_profile_filename }}` 1. Succès ! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. Le plugin OpenVPN pour NetworkManager semble devenir très confus à la manière dont il doit acheminer le trafic lorsque vous vous connectez à un serveur OpenVPN via une connexion stunnel. L'ajout manuelle d'informations sur l'itinéraire ne semble pas aider. Par conséquent, contrairement aux [instructions non stunnel](/openvpn/index-fr.html), les étapes ci-dessus sont également la méthode de connexion recommandée pour Ubuntu. ### Android ### #### Installation SSLDroid #### 1. Téléchargez la clé PKCS #12 stunnel * [stunnel.p12](/openvpn/stunnel.p12) 1. Copiez le fichier `stunnel.p12` sur votre téléphone. 1. Installez [SSLDroid](https://play.google.com/store/apps/details?id=hu.blint.ssldroid&hl=fr) et lancez-le. 1. Appuyez sur le bouton de menu. 1. Tapez *Add tunnel*. 1. Tapez *Tunnel name* et saisissez `{{ streisand_server_name }}`. 1. Tapez *Local port* et saisissez `{{ stunnel_local_port }}`. 1. Tapez *Remote host* et saisissez `{{ openvpn_server }}`. 1. Tapez *Remote port* et saisissez `{{ stunnel_remote_port }}`. 1. Appuyez sur le bouton 'browse' (Parcourir) à côté du fichier *PKCS12* et sélectionnez le fichier `stunnel.p12` que vous avez copié sur votre téléphone durant la première étape. Vous pouvez laisser le champ *PKCS12 pass* vide. 1. Tapez *Apply*. Maintenant, vous êtes prêt à installer OpenVPN pour Android et à le configurer pour acheminer son trafic via SSLDroid, qui est connecté au port Stunnel sur le serveur distant. Un profil .ovpn personnalisé qui est préconfiguré pour fonctionner avec SSLDroid rendra cela facile. ### Installation OpenVPN ### 1. Installez [OpenVPN pour Android](https://play.google.com/store/apps/details?id=de.blinkt.openvpn&hl=fr). 1. Téléchargez l'un de ces profils OpenVPN unifiés : {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_stunnel_profile_filename }}) {% endfor %} 1. Copiez le fichier `{{ openvpn_stunnel_profile_filename }}` sur votre téléphone. 1. Lancez OpenVPN pour Android. 1. Appuyez sur l'icône du dossier en bas à droite de l'écran. 1. Sélectionnez le profil `{{ openvpn_stunnel_profile_filename }}` que vous avez copié sur votre téléphone. 1. Tapez sur l'icône de sauvegarde (disquette) en bas à droite de l'écran une fois l'importation est terminée. 1. Tapez le profil. 1. Acceptez l'avertissement de connexion VPN Android. 1. Succès ! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. iOS --- Il n'y a pas d'applications de tunneling compatibles dans l'App Store en ce moment. ================================================ FILE: playbooks/roles/openvpn/templates/stunnel-instructions.md.j2 ================================================ {% include "languages.md.j2" %} OpenVPN (wrapped in stunnel) ---------------------------- These instructions should only be used if OpenVPN connections are being actively blocked in your country or by your ISP. Performance will be better if you are able to connect directly, but certain countries (notably China and Iran) employ Deep Packet Inspection to detect and thwart OpenVPN connections. Some companies do the same. If you do not fall into this category, please refer to the standard [OpenVPN instructions](/openvpn/) instead. Following these steps will wrap your OpenVPN traffic in an encrypted tunnel so it looks like standard SSL traffic being directed to a port where this type of traffic would be expected. This prevents DPI from identifying the true nature of the packets, thereby allowing you to freely use OpenVPN. #### A note on client files #### For security reasons, OpenVPN clients typically have their own unique certificate and private key. The server you will be connecting to has been configured to allow clients to share the same certificate and private key, but you may still wish to give your phone and laptop different keys, for example. --- * Platforms * [Windows](#windows) * [macOS](#macos) * [Linux](#linux) * [Android](#android) * [iOS](#ios) ### Windows ### #### Stunnel Setup #### 1. Download and run the [stunnel installer](/mirror/#stunnel). 1. Download the `stunnel.conf` file that has been customized to work with this server: * [stunnel.conf](/openvpn/stunnel.conf) 1. Open the directory where you installed stunnel. For most users, this will either be in *C:\Program Files\stunnel* or *C:\Program Files (x86)\stunnel*. 1. Drag and drop the downloaded `stunnel.conf` file into this directory. 1. Double click *stunnel.exe* in the installation directory to start the service. Now you are ready to install OpenVPN and configure it to route its traffic through stunnel. A custom .ovpn profile that is preconfigured to work alongside the `stunnel.conf` file will make this easy. #### OpenVPN Setup #### 1. Download and run the OpenVPN [Windows Installer](/mirror/#openvpn). 1. Click *Next* and accept the license agreement by clicking *I Agree*. 1. Click *Next* on the *Choose Components* screen. Leave all of the default options checked. 1. Make note of the Destination Folder. This is where you will place the `{{ openvpn_stunnel_profile_filename }}` client configuration profile after installation. Click *Install*. 1. A Windows Security notice will appear and ask *Would you like to install this device software?*. Click *Install*. 1. Click *Next* on the *Installation Complete* screen. 1. Uncheck *Show Readme* and click *Finish*. 1. Right-click on the OpenVPN GUI desktop icon and choose *Properties*. 1. Go to the *Compatibility* tab and click the *Run this program as an administrator* checkbox in the *Privilege Level* section. 1. Double-click the OpenVPN GUI desktop icon to launch the application. 1. Download one of these unified OpenVPN profiles: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_stunnel_profile_filename }}) {% endfor %} 1. Open the *config* directory that is located in the Destination Folder. For most users, this will either be in *C:\Program Files\OpenVPN\config* or *C:\Program Files (x86)\OpenVPN\config*. You will see a single README file in this directory. 1. Drag and drop the downloaded `{{ openvpn_stunnel_profile_filename }}` file to this location alongside the README. 1. Right-click on the OpenVPN icon in your taskbar and choose *Connect*. 1. You will see a log scroll by as the connection is established, followed by a taskbar notification indicating your assigned IP. 1. Success! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### macOS ### #### Stunnel Setup #### 1. Install [Homebrew](http://brew.sh/), if you haven't already. 1. Install stunnel using Homebrew: `brew install stunnel` * If installing Homebrew is not an option, you can also download and compile the [stunnel source code](/mirror/#stunnel). 1. Download the `stunnel.conf` file that has been customized to work with this server: * [stunnel.conf](/openvpn/stunnel.conf) 1. Replace the default stunnel.conf file with the customized version. Be sure to update the source location if you downloaded the file to a different directory. `cp ~/Downloads/stunnel.conf /usr/local/etc/stunnel/` 1. Start stunnel: `stunnel` Now you are ready to install OpenVPN and configure it to route its traffic through stunnel. A custom .ovpn profile that is preconfigured to work alongside the `stunnel.conf` file will make this easy. #### OpenVPN Setup #### 1. Download and open [Tunnelblick](/mirror/#openvpn). 1. Type your password to allow the installation process to complete, and click *OK*. 1. Click *Launch* after the installation is finished. 1. Click *I have configuration files*. 1. Download one of these unified OpenVPN profiles: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_stunnel_profile_filename }}) {% endfor %} 1. Double-click the OpenVPN profile. 1. You will be asked to choose whether the profile should be available for all users or only the current user. After making your selection, you will be asked to enter your password. 1. Look for the Tunnelblick icon in your menu bar. Click on it, and choose *Connect*. 1. Success! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### Linux ### #### Stunnel Setup #### 1. Make sure stunnel is installed: `sudo apt-get install stunnel` OR `sudo yum install stunnel` OR `esoteric-package-manager hipster stunnel` * If installing stunnel via your package manager is not an option, you can also download and compile the [stunnel source code](/mirror/#stunnel). 1. Download the `stunnel.conf` file that has been customized to work with this server: * [stunnel.conf](/openvpn/stunnel.conf) 1. Copy `stunnel.conf` to the right destination. Be sure to update the source location if you have moved the directory elsewhere. `cp ~/Downloads/stunnel.conf /etc/stunnel/` 1. Ubuntu users should adjust the `/etc/default/stunnel4` file and make sure `ENABLED` is set to `1`. 1. Restart the stunnel service: `sudo service stunnel4 restart` OR `sudo service stunnel restart` Now you are ready to install OpenVPN and configure it to route its traffic through stunnel. A custom .ovpn profile that is preconfigured to work alongside the `stunnel.conf` file will make this easy. #### OpenVPN Setup #### 1. Install OpenVPN: `sudo apt-get install openvpn` OR `sudo yum install openvpn` OR `esoteric-package-manager hipster openvpn` * If installing OpenVPN via your package manager is not an option, you can also download and compile the [OpenVPN source code](/mirror/#openvpn). 1. Download one of these unified OpenVPN profiles: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_stunnel_profile_filename }}) {% endfor %} 1. Copy the downloaded `{{ openvpn_stunnel_profile_filename }}` file to the location of your choice. */etc/openvpn/* is a decent option. 1. On some distributions, the pushed DHCP DNS option from the OpenVPN server will be ignored. This means that your DNS queries will still be routed through your ISP's servers which makes them vulnerable to what is known as a DNS leak. * You can test whether or not your DNS is leaking [here](https://dnsleaktest.com/). * You can ensure that the correct DNS servers are used by adding the following lines to the top of the `{{ openvpn_stunnel_profile_filename }}` file: * `script-security 2` * `up /etc/openvpn/update-resolv-conf` * `down /etc/openvpn/update-resolv-conf` 1. Execute OpenVPN, and pass it the .ovpn profile as an option: `sudo openvpn {{ openvpn_stunnel_profile_filename }}` 1. Success! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. The OpenVPN plugin for NetworkManager appears to become very confused as to how it should route traffic when you connect to an OpenVPN server through a wrapped stunnel connection. Manually adding route information does not seem to help. Therefore, unlike in the [non-stunnel instructions](/openvpn), the steps above are the recommended connection method for Ubuntu as well. ### Android ### #### SSLDroid Setup #### 1. Download the stunnel PKCS #12 formatted key: * [stunnel.p12](/openvpn/stunnel.p12) 1. Copy the `stunnel.p12` file to your phone. 1. Install [SSLDroid](https://play.google.com/store/apps/details?id=hu.blint.ssldroid) and launch it. 1. Tap the menu button. 1. Tap *Add tunnel*. 1. Tap *Tunnel name* and enter `{{ streisand_server_name }}`. 1. Tap *Local port* and enter `{{ stunnel_local_port }}`. 1. Tap *Remote host* and enter `{{ openvpn_server }}`. 1. Tap *Remote port* and enter `{{ stunnel_remote_port }}`. 1. Tap the browse button next to *PKCS12 file* and select the `stunnel.p12` file that you copied to your phone during the first step. You can leave the *PKCS12 pass* field blank. 1. Tap *Apply*. Now you are ready to install OpenVPN for Android and configure it to route its traffic through SSLDroid, which is connected to the stunnel port on the remote server. A custom .ovpn profile that is preconfigured to work alongside SSLDroid will make this easy. #### OpenVPN Setup #### 1. Install [OpenVPN for Android](https://play.google.com/store/apps/details?id=de.blinkt.openvpn). 1. Download one of these unified OpenVPN profiles: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/openvpn/{{ client.stdout }}/{{ openvpn_stunnel_profile_filename }}) {% endfor %} 1. Copy the `{{ openvpn_stunnel_profile_filename }}` file to your phone. 1. Launch OpenVPN for Android. 1. Tap the folder icon in the lower-right of the screen. 1. Select the `{{ openvpn_stunnel_profile_filename }}` profile that you copied to your phone. 1. Tap the save icon (floppy disk) in the lower-right of the screen after the import is complete. 1. Tap the profile. 1. Accept the Android VPN connection warning. 1. Success! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. iOS --- There are no stunnel-compatible tunneling applications available in the App Store at this time. ================================================ FILE: playbooks/roles/openvpn/vars/main.yml ================================================ --- openvpn_days_valid: "1825" openvpn_request_subject: "/C={{ openvpn_key_country }}/ST={{ openvpn_key_province }}/L={{ openvpn_key_city }}/O={{ openvpn_key_org }}/OU={{ openvpn_key_ou }}" openvpn_path: "/etc/openvpn" openvpn_ca: "{{ openvpn_path }}/ca" openvpn_hmac_firewall: "{{ openvpn_path }}/ta.key" openvpn_server: "{{ streisand_ipv4_address }}" openvpn_server_common_name_file: "{{ openvpn_path }}/openvpn_server_common_name" openvpn_direct_profile_filename: "{{ openvpn_server }}-direct.ovpn" openvpn_direct_udp_profile_filename: "{{ openvpn_server }}-direct-udp.ovpn" openvpn_sslh_profile_filename: "{{ openvpn_server }}-sslh.ovpn" openvpn_stunnel_profile_filename: "{{ openvpn_server }}-stunnel.ovpn" openvpn_combined_profile_filename: "{{ openvpn_server }}-combined.ovpn" dnsmasq_openvpn_tcp_ip: "10.8.0.1" dnsmasq_openvpn_udp_ip: "10.9.0.1" openvpn_firewall_rules: - "iptables --wait {{ streisand_iptables_wait }} -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT" - "iptables --wait {{ streisand_iptables_wait }} -A FORWARD -s 10.8.0.0/24 -j ACCEPT" - "iptables --wait {{ streisand_iptables_wait }} -A FORWARD -s 10.9.0.0/24 -j ACCEPT" - "iptables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -s 10.8.0.0/24 -o {{ ansible_default_ipv4.interface }} -j MASQUERADE" - "iptables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -s 10.9.0.0/24 -o {{ ansible_default_ipv4.interface }} -j MASQUERADE" openvpn_gateway_location: "{{ streisand_gateway_location }}/openvpn" openvpn_service_names: - "openvpn@server.service" - "openvpn@server-udp.service" ================================================ FILE: playbooks/roles/openvpn/vars/mirror.yml ================================================ --- # OpenVPN Download variables # -------------------------- openvpn_mirror_location: "{{ streisand_mirror_location }}/openvpn" openvpn_mirror_href_base: "/mirror/openvpn" openvpn_base_download_url: "https://build.openvpn.net/downloads/releases/latest" openvpn_source_filename: "openvpn-latest-stable.tar.gz" openvpn_source_sig_filename: "{{ openvpn_source_filename }}.asc" openvpn_source_href: "{{ openvpn_mirror_href_base }}/{{ openvpn_source_filename }}" openvpn_source_sig_href: "{{ openvpn_mirror_href_base }}/{{ openvpn_source_sig_filename }}" openvpn_windows_installer_filename: "openvpn-install-latest-stable-win10.exe" openvpn_windows_installer_sig_filename: "{{ openvpn_windows_installer_filename }}.asc" openvpn_windows_installer_href: "{{ openvpn_mirror_href_base }}/{{ openvpn_windows_installer_filename }}" openvpn_windows_installer_sig_href: "{{ openvpn_mirror_href_base }}/{{ openvpn_windows_installer_sig_filename }}" openvpn_gpg_keyid: "5ACFEAC6" openvpn_download_files: - { "file": "{{ openvpn_source_filename }}", "sig": "{{ openvpn_source_sig_filename }}" } - { "file": "{{ openvpn_windows_installer_filename }}", "sig": "{{ openvpn_windows_installer_sig_filename }}" } # macOS tunnelblick_version: "3.8.1" tunnelblick_build: "5400" tunnelblick_filename: "Tunnelblick_{{ tunnelblick_version }}_build_{{ tunnelblick_build }}.dmg" tunnelblick_href: "{{ openvpn_mirror_href_base }}/{{ tunnelblick_filename }}" tunnelblick_url: "https://tunnelblick.net/release/{{ tunnelblick_filename }}" tunnelblick_checksum: "sha256:a619a1c01a33a8618fc2489a43241e95c828dcdb7f7c56cfc883dcbb22644693" ================================================ FILE: playbooks/roles/service-net/files/10-service0.netdev ================================================ [NetDev] Name=service0 Kind=dummy ================================================ FILE: playbooks/roles/service-net/files/10-service0.network ================================================ [Match] Name=service0 [Link] RequiredForOnline=yes [Network] Address=10.10.10.10/24 ================================================ FILE: playbooks/roles/service-net/files/service-net.conf ================================================ # Listen on the service network's IP listen-address=10.10.10.10 ================================================ FILE: playbooks/roles/service-net/meta/main.yml ================================================ --- dependencies: - role: dnsmasq ================================================ FILE: playbooks/roles/service-net/tasks/main.yml ================================================ --- - name: Install service0 network configuration copy: src: "{{ item }}" dest: /etc/systemd/network/ owner: root group: root mode: 0644 with_items: - 10-service0.netdev - 10-service0.network - name: Enable and start systemd networking systemd: daemon_reload: yes name: systemd-networkd.service enabled: yes state: restarted - name: Install dnsmasq for service0 network copy: src: service-net.conf dest: /etc/dnsmasq.d/ owner: root group: root mode: 0644 # NOTE(@nop): From wireguard/tasks/main.yml: # NOTE(@cpu): We don't use a `notify` to "Restart dnsmasq" here because it seems # that in some conditions Ansible mistakenly believes the dnsmasq restart can be # skipped. We also don't use "reloaded" instead of "restarted" here because # dnsmasq doesn't seem to reload _new_ config files in that case, just existing # ones. A full restart is required in practice (sigh) - name: "Restart DNSMasq to pick up the new configuration" service: name: dnsmasq state: restarted ================================================ FILE: playbooks/roles/shadowsocks/defaults/main.yml ================================================ --- shadowsocks_server_port: "8530" shadowsocks_local_port: "1080" shadowsocks_timeout: "600" shadowsocks_encryption_method: "chacha20-ietf-poly1305" shadowsocks_tcp_fast_open: "true" shadowsocks_v2ray_plugin: "v2ray-plugin" shadowsocks_v2ray_cover_domain: "github.com" shadowsocks_v2ray_plugin_options: "host={{ shadowsocks_v2ray_cover_domain }}" shadowsocks_v2ray_plugin_protocol: "http" ================================================ FILE: playbooks/roles/shadowsocks/handlers/main.yml ================================================ --- - name: Restart shadowsocks-libev systemd: name: shadowsocks-libev.service state: restarted ================================================ FILE: playbooks/roles/shadowsocks/meta/main.yml ================================================ --- dependencies: # Shadowsocks needs to be added to the firewall - { role: ufw } ================================================ FILE: playbooks/roles/shadowsocks/tasks/docs.yml ================================================ --- - name: Create the Shadowsocks Gateway directory file: path: "{{ shadowsocks_gateway_location }}" owner: www-data group: www-data mode: 0750 state: directory - include_role: name: i18n-docs vars: title: "Shadowsocks" i18n_location: "{{ shadowsocks_gateway_location }}" input_template_name: "instructions" - name: Generate the Shadowsocks QR code # The ss:// URI format is documented here: # http://shadowsocks.org/en/config/quick-guide.html shell: echo -n '{{ shadowsocks_encryption_method }}:{{ shadowsocks_password.stdout }}@{{ streisand_ipv4_address }}:{{ shadowsocks_server_port }}' | base64 --wrap=0 | sed 's/^/ss:\/\//' | qrencode -s 8 -o {{ shadowsocks_qr_code }} ================================================ FILE: playbooks/roles/shadowsocks/tasks/firewall.yml ================================================ --- - name: Ensure UFW allows Shadowsocks ufw: to_port: "{{ shadowsocks_server_port }}" proto: "any" rule: "allow" ================================================ FILE: playbooks/roles/shadowsocks/tasks/main.yml ================================================ --- - name: Apply the sysctl value to enable TCP Fast Open sysctl: name: net.ipv4.tcp_fastopen value: 3 state: present when: ansible_virtualization_type != 'lxc' - name: Add the Shadowsocks PPA apt_repository: repo: 'ppa:max-c-lv/shadowsocks-libev' register: shadowsocks_add_apt_repository until: not shadowsocks_add_apt_repository.failed retries: "{{ apt_repository_retries }}" delay: "{{ apt_repository_delay }}" - name: Install shadowsocks-libev apt: package: "shadowsocks-libev" - name: Create the shadowsocks-libev config directory file: path: "{{ shadowsocks_location }}" owner: root # The nobody user in nogroup needs to be able to read the config file group: nogroup # It is safe to make this directory world read only because the password # file and config file are not mode: 0755 state: directory - name: Populate the shadowsocks-libev systemd defaults template: src: shadowsocks-libev.default.j2 dest: "/etc/default/shadowsocks-libev" mode: 0644 - name: Generate a random Shadowsocks password shell: openssl rand -base64 48 > {{ shadowsocks_password_file }} args: creates: "{{ shadowsocks_password_file }}" - name: Set permissions on the Shadowsocks password file file: path: "{{ shadowsocks_password_file }}" owner: root group: root mode: 0600 - name: Register Shadowsocks password command: cat {{ shadowsocks_password_file }} register: shadowsocks_password changed_when: False # Add V2ray support - import_tasks: v2ray.yml when: streisand_shadowsocks_v2ray_enabled|bool - name: Generate Shadowsocks config file template: src: config.json.j2 dest: "{{ shadowsocks_location }}/config.json" # The nobody user in nogroup needs to be able to read the config file group: nogroup mode: 0640 - name: Create the shadowsocks systemd configuration directory file: path: "{{ shadowsocks_systemd_service_path }}" state: directory - name: Generate the nginx systemd service file template: src: shadowsocks-libev.service.j2 dest: "{{ shadowsocks_systemd_service_path }}/10-restart-failure.conf" mode: 0644 - name: Enable the Shadowsocks service so it starts at boot, and bring it up systemd: name: shadowsocks-libev.service daemon_reload: yes enabled: yes state: restarted # For some providers (e.g. GCE/AWS) the streisand_ipv4_address != the # ansible_default_ipv4.address, and the Shadowsocks service is bound to the # latter instead of the former. We check both, tolerating errors checking the # former. This mess should probably be addressed in a more forward facing # manner. - block: - name: "Check that the Shadowsocks service started ({{ streisand_ipv4_address }})" wait_for: host: "{{ streisand_ipv4_address }}" port: "{{ shadowsocks_server_port }}" state: started timeout: 30 ignore_errors: yes rescue: - name: "Check that the Shadowsocks service started ({{ ansible_default_ipv4.address }})" wait_for: host: "{{ ansible_default_ipv4.address }}" port: "{{ shadowsocks_server_port }}" state: started timeout: 30 # Apply the Shadowsocks firewall rules - import_tasks: firewall.yml # Generate the Shadowsocks gateway docs & client QR code - import_tasks: docs.yml # Mirror the Shadowsocks clients - import_tasks: mirror.yml ================================================ FILE: playbooks/roles/shadowsocks/tasks/mirror.yml ================================================ --- - name: Include the Shadowsocks mirror variables include_vars: mirror.yml - name: Make the directory where the Shadowsocks mirrored files will be stored file: path: "{{ shadowsocks_mirror_location }}" owner: www-data group: www-data mode: 0755 state: directory - block: - name: Mirror the Shadowsocks clients get_url: url: "{{ item.url }}" dest: "{{ shadowsocks_mirror_location }}" checksum: "{{ item.checksum }}" owner: www-data group: www-data mode: 0644 with_items: "{{ shadowsocks_download_urls }}" rescue: - name: "{{ streisand_mirror_warning }}" pause: seconds: "{{ streisand_mirror_warning_seconds }}" - include_role: name: i18n-docs vars: title: "Shadowsocks mirror" i18n_location: "{{ shadowsocks_mirror_location }}" input_template_name: "mirror" ================================================ FILE: playbooks/roles/shadowsocks/tasks/simple-obfs.yml ================================================ --- - name: Clone the simple-obfs source code at tag {{ simple_obfs_version }} git: repo: "https://github.com/shadowsocks/simple-obfs" dest: "{{ simple_obfs_compilation_directory }}" version: "v{{simple_obfs_version}}" - name: Update the simple-obfs source code submodules command: git submodule update --init --recursive args: chdir: "{{ simple_obfs_compilation_directory }}" # if one of the submodules (libcork) has been populated we assume all of # the submodules were updated. creates: "{{ simple_obfs_compilation_directory }}/libcork/Makefile.am" - name: Autogen simple-obfs {{ simple_obfs_version }} source command: ./autogen.sh args: chdir: "{{ simple_obfs_compilation_directory }}" creates: "{{ simple_obfs_compilation_directory }}/configure" - name: Configure simple-obfs {{ simple_obfs_version }} source command: ./configure {{ simple_obfs_configure_flags }} args: chdir: "{{ simple_obfs_compilation_directory }}" creates: "{{ simple_obfs_compilation_directory }}/Makefile" - name: Compile simple-obfs {{ simple_obfs_version }} source command: make -j{{ ansible_processor_cores }} args: chdir: "{{ simple_obfs_compilation_directory }}" creates: "{{ simple_obfs_compilation_directory }}/src/obfs-server" - name: Install simple-obfs {{ simple_obfs_version }} binaries command: make install args: chdir: "{{ simple_obfs_compilation_directory }}" creates: "/usr/local/bin/obfs-server" ... ================================================ FILE: playbooks/roles/shadowsocks/tasks/v2ray.yml ================================================ --- - name: Add the repo for getting the latest version of Go apt_repository: repo: 'ppa:longsleep/golang-backports' register: golang_add_apt_repository until: not golang_add_apt_repository.failed retries: "{{ apt_repository_retries }}" delay: "{{ apt_repository_delay }}" - name: Install golang-go apt: package: "golang-go" # Temporary workaround for insecure v2ray.com redirection # see https://github.com/StreisandEffect/streisand/issues/1642 # for more info. We clone the v2ray-core repository to avoid # the "go get" command failing with an "insecure redirect" error. - file: path: "{{ go_path }}/src/v2ray.com" state: directory - name: "[Temporary] Clone v2ray-core repository manually to GOPATH" git: repo: "{{ v2ray_core_github }}" dest: "{{ go_path }}/src/v2ray.com/core" - name: Get V2Ray-plugin shell: "go get {{ v2ray_github }}" environment: GOPATH: "{{ go_path }}" - name: Copying v2ray-plugin to shadowsocks-libev directory shell: "cp -rf {{ v2ray_location }}/v2ray-plugin {{ shadowsocks_location }}" ... ================================================ FILE: playbooks/roles/shadowsocks/templates/config.json.j2 ================================================ { "server":"{{ ansible_default_ipv4.address }}", "server_port":{{ shadowsocks_server_port }}, "local_port":{{ shadowsocks_local_port }}, "password":"{{ shadowsocks_password.stdout }}", "timeout":{{ shadowsocks_timeout }}, "method":"{{ shadowsocks_encryption_method }}", "fast_open":{{ shadowsocks_tcp_fast_open }} {%- if streisand_shadowsocks_v2ray_enabled -%} , "plugin":"{{ shadowsocks_location }}/v2ray-plugin", "plugin_opts":"{{ v2ray_options }}" {% endif %} } ================================================ FILE: playbooks/roles/shadowsocks/templates/instructions-fr.md.j2 ================================================ {% include "languages.md.j2" %} Shadowsocks ----------- --- * Plateformes * [Windows](#windows) * [macOS](#macos) * [Linux](#linux) * [Android](#android) * [iOS](#ios) {%- if streisand_shadowsocks_v2ray_enabled -%} * Plugins * [v2ray-plugin](#V2ray-plugin) {% endif %} ### Windows ### 1. Téléchargez [Shadowsocks pour Windows](/mirror/shadowsocks/index-fr.html). 1. Extrayez l'archive et double-cliquez sur le fichier Shadowsocks.exe. 1. Assurez-vous que le code QR ci-dessous est centré et complètement visible, cliquez avec le bouton droit de la souris sur l'icône de la barre d'état système de Shadowsocks, puis accédez à *Servers* > *Scan QRCode from Screen...* ![Shadowsocks QR code](/shadowsocks/shadowsocks-qr-code.png) 1. Vous pouvez également aller à *Servers* > *New server* et procédez manuellement aux étapes suivantes: 1. Saisissez `{{ streisand_ipv4_address }}` pour le *Server IP*. 1. Saisissez `{{ shadowsocks_server_port }}` pour le *Server Port*. 1. Saisissez `{{ shadowsocks_password.stdout }}` pour le *Password*. 1. Le *SOCKS 5 Proxy Port* devrait être `{{ shadowsocks_local_port }}` et le *Encryption Method* devrait être `{{ shadowsocks_encryption_method }}`. 1. Assurez-vous que le support de l'authentification unique (OTA) est activé. 1. Cliquez *Save*. 1. Cliquez sur l'icône de la barre de menus Shadowsocks et sélectionnez *Enable*. 1. C'est tout! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. ### macOS ### 1. Téléchargez [ShadowsocksX-NG](/mirror/shadowsocks/index-fr.html). 1. Double-cliquez sur le DMG et faites glisser l'icône dans votre dossier Applications. 1. Lancez ShadowsocksX-NG. Vous serez invité à saisir votre mot de passe afin que vos paramètres de proxy système puissent être modifiés. 1. Tapez sur l'icône du l'avion en papier rond dans la barre inférieure gauche. 1. Assurez-vous que le code QR ci-dessous est centré et complètement visible, et choisissez *Scan QR Code from Screen...* ![Shadowsocks QR code](/shadowsocks/shadowsocks-qr-code.png) 1. Vous pouvez également configurer la connexion en allant sur *Servers*, sélectionnant *Open Server Preferences...*, et en cliquant le bouton *+* sur la barre latérale: 1. Saisissez `{{ streisand_ipv4_address }}` et `{{ shadowsocks_server_port }}` dans le champ *Address*. 1. Assurez-vous que `{{ shadowsocks_encryption_method }}` est sélectionné pour la valeur *Encryption*. 1. Saisissez `{{ shadowsocks_password.stdout }}` pour le *Password*. 1. Chocher `Enable OTA`. 1. Cliquez *OK*. 1. Cliquez à nouveau sur l'icône Shadowsocks dans la barre de menu, puis choisissez *Global Mode*. 1. Vous pouvez utiliser l'icône Shadowsocks pour activer/désactiver le VPN. La couleur de l'icône changera en fonction de son activation ou pas. 1. C'est tout! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. ### Linux ### Téléchargez [shadowsocks2-linux-x64.gz](/mirror/shadowsocks/index-fr.html) et extrayez-le: `gunzip shadowsocks2-linux-x64.gz` Modifier le binaire qu'il soit exécutable `chmod +x shadowsocks2-linux-x64` Démarrez le proxy SOCKS shadowsocks: `./shadowsocks2-linux-x64 -c {{ streisand_ipv4_address }}:{{ shadowsocks_server_port }} -password "{{ shadowsocks_password.stdout }}" -socks localhost:{{ shadowsocks_local_port }} -verbose -cipher {{ shadowsocks_encryption_method }}` Pour référence, voici les informations de configuration dont vous aurez besoin: Server: {{ streisand_ipv4_address }} Port: {{ shadowsocks_server_port }} Password: {{ shadowsocks_password.stdout }} Encryption Method: {{ shadowsocks_encryption_method }} Une fois que vous avez Shadowsocks fonctionnant localement, vous devrez transférer le trafic de votre navigateur via le proxy SOCKS qu'il fournit. #### Configuration de Firefox pour se connecter via un proxy SOCKS #### 1. Cliquez sur le bouton *Menu* à côté de l'icône *Accueil* à droite de la barre d'adresse. 1. Cliquez *Préférences*. 1. Cliquez l'icône *Avancé*. 1. Allez à l'onglet *Réseau*. 1. Cliquez le bouton *Paramètres* pour *Configurer la façon dont Firefox se connecte à Internet*. 1. Choisissez *Configuration manuelle de proxy*. 1. Saisissez `127.0.0.1` et port `{{ shadowsocks_local_port }}` pour la ligne *Hôte SOCKS*. 1. Sélectionnez *Utiliser un DNS distant lorsque SOCKS v5 est actif*. Cela configure Firefox pour envoyer toutes les requêtes DNS via le proxy SOCKS. Cela vous protégera contre l'empoisonnement du DNS et vous garantira que les fausses entrées DNS ne peuvent pas être utilisées pour censurer votre accès. 1. Cliquez *OK*. 1. Cliquez *OK* encore pour fermer la fenêtre de paramètres de Firefox.. 1. C'est tout! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{ streisand_ipv4_address }}*. ### Android ### 1. Téléchargez Shadowsocks pour Android à partir de [Google Play](https://play.google.com/store/apps/details?id=com.github.shadowsocks&hl=fr). Si Google Play est bloqué dans votre pays, vous pouvez [télécharger une copie en miroir](/mirror/shadowsocks/index-fr.html) de ce serveur! 1. Lancez l'application. 1. Tapez sur l'icône Ajouter un profil +, en haut à droite de l'écran. 1. Tapez l'icône *+* en bas à droite. 1. Sélectionnez *Scan QR code*. 1. Utilisez votre téléphone pour numériser cette image, ou si vous utilisez votre téléphone _maintenant_, vous pouvez choisir la *Manual Settings* et copiez/collez les informations à partir de la section Linux ci-dessus: ![Shadowsocks QR code](/shadowsocks/shadowsocks-qr-code.png) 1. Tapez sur l'icône de l'avion en haut. 1. Acceptez l'avertissement de connexion VPN Android. 1. Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. ### iOS ### 1. Téléchargez [Shadowrocket](https://itunes.apple.com/fr/app/shadowrocket/id932747118?mt=8) et lancez-le. * Vous serez invité à vous connecter à l'iTunes Store la première fois que vous exécutez Shadowrocket. > **Note:** Shadowrocket n'est pas disponible dans l'App Store chinois 1. Appuyez sur l'icône d'importation du code QR en haut à gauche de l'écran. 1. Utilisez votre téléphone pour numériser cette image, ou si vous utilisez votre téléphone _maintenant_, vous pouvez ajouter manuellement un nouveau proxy à partir de l'onglet *Home* et copier et coller les informations de la section Linux ci-dessus: ![Shadowsocks QR code](/shadowsocks/shadowsocks-qr-code.png) 1. Mettez le commutateur de connexion situé à droite du texte *Not connected* sur l'onglet *Home*. * Si c'est la première fois que vous utilisez Shadowrocket, iOS vous demandera de vérifier que l'application devrait avoir la permission d'ajouter des configurations VPN. Tapez *Permettre* et suivez les instructions. 1. Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. {%- if streisand_shadowsocks_v2ray_enabled -%} ### v2ray-plugin pour les réseaux peu fiables/hostiles ### Pour les utilisateurs sur des réseaux peu fiables ou hostiles (en particulier la limitation de la qualité de service (QOS)), l'utilisation du plugin simple-obfs peut vous aider à atténuer ces problèmes. La configuration supplémentaire du client Shadowsocks pour utiliser le plugin v2ray-plugin peut être effectuée via la configuration suivante: Server: {{ streisand_ipv4_address }} Port: {{ shadowsocks_server_port }} Password: {{ shadowsocks_password.stdout }} Encryption Method: {{ shadowsocks_encryption_method }} Plugin: {{ shadowsocks_v2ray_plugin }} Plugin_Options: {{ shadowsocks_v2ray_plugin_options }} Les utilisateurs d'Android devront d'abord télécharger l'application [V2ray-plugin](https://play.google.com/store/apps/details?id=com.github.shadowsocks.plugin.v2ray), puis modifier le profil existant de Streisand sur votre client pour utiliser ce plugin. Vous pouvez le faire en appuyant sur le bouton d'édition (edit) à côté du profil, en tapant l'option Plugin en bas du profil et en sélectionnant le plugin "V2ray-plugin" dans le menu. Votre trafic Shadowsocks sera maintenant obscurci en tant que {{ shadowsocks_v2ray_plugin_protocol }} trafic vers `{{ shadowsocks_v2ray_cover_domain }}`. {% endif %} ================================================ FILE: playbooks/roles/shadowsocks/templates/instructions.md.j2 ================================================ {% include "languages.md.j2" %} Shadowsocks ----------- --- * Platforms * [Windows](#windows) * [macOS](#macos) * [Linux](#linux) * [Android](#android) * [iOS](#ios) {%- if streisand_shadowsocks_v2ray_enabled -%} * Plugins * [v2ray-plugin](#V2ray-plugin) {% endif %} ### Windows ### 1. Download [Shadowsocks for Windows](/mirror/shadowsocks/). 1. Extract the archive, and double-click on the Shadowsocks.exe file. 1. Make sure the QR code below is centered and completely visible, right-click on the Shadowsocks system tray icon, and go to *Servers* > *Scan QRCode from Screen...* ![Shadowsocks QR code](/shadowsocks/shadowsocks-qr-code.png) 1. You may also go to *Servers* > *New server* and manually complete the following steps: 1. Enter `{{ streisand_ipv4_address }}` as the *Server IP*. 1. Enter `{{ shadowsocks_server_port }}` as the *Server Port*. 1. Enter `{{ shadowsocks_password.stdout }}` as the *Password*. 1. The *SOCKS 5 Proxy Port* should be `{{ shadowsocks_local_port }}` and the *Encryption Method* should be `{{ shadowsocks_encryption_method }}`. 1. Click *Save*. 1. Click on the Shadowsocks menu tray icon and select *Enable*. 1. That's it! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### macOS ### 1. Download [ShadowsocksX-NG](/mirror/shadowsocks/). 1. Double-click the ZIP, and extract the application into your Applications folder. 1. Launch ShadowsocksX-NG. You will be prompted to enter your password so that your system proxy settings can be modified. 1. Look for the Shadowsocks paper plane icon in your menu bar and click on it. 1. Make sure the QR code below is centered and completely visible, and choose *Scan QR Code from Screen...* ![Shadowsocks QR code](/shadowsocks/shadowsocks-qr-code.png) 1. You may also configure the connection by going to *Servers*, selecting *Open Server Preferences...*, and clicking the *+* button on the sidebar: 1. Enter `{{ streisand_ipv4_address }}` and `{{ shadowsocks_server_port }}` in the *Address* fields. 1. Make sure `{{ shadowsocks_encryption_method }}` is selected for the *Encryption* value. 1. Enter `{{ shadowsocks_password.stdout }}` as the *Password*. 1. Click *OK*. 1. Click on the Shadowsocks icon in the menu bar again, and choose *Global Mode*. 1. You can use the Shadowsocks icon to enable/disable the VPN. The color of the icon will change based on whether or not it is active. 1. That's it! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### Linux ### Download [shadowsocks2-linux-x64.gz](/mirror/shadowsocks/) and extract it: `gunzip shadowsocks2-linux-x64.gz` Make the binary executable: `chmod +x shadowsocks2-linux-x64` Start the shadowsocks SOCKS proxy: `./shadowsocks2-linux-x64 -c {{ streisand_ipv4_address }}:{{ shadowsocks_server_port }} -password "{{ shadowsocks_password.stdout }}" -socks localhost:{{ shadowsocks_local_port }} -verbose -cipher {{ shadowsocks_encryption_method }}` For reference, this is the configuration specified: Server: {{ streisand_ipv4_address }} Port: {{ shadowsocks_server_port }} Password: {{ shadowsocks_password.stdout }} Encryption Method: {{ shadowsocks_encryption_method }} Once you have Shadowsocks running locally, you'll need to forward your browser traffic through the SOCKS proxy it provides ### Testing with Curl You can quickly test using curl: `curl -I --socks5 localhost:1080 google.com` This should return a 301 Found response **not** a connection refused error. #### Configuring Firefox to connect through a SOCKS proxy #### 1. Click the *Menu* button next to the *Home* icon to the right of the address bar. 1. Click *Options*. 1. Click the *Advanced* icon. 1. Go to the *Network* tab. 1. Click the *Settings* button to *Configure how Firefox connects to the Internet*. 1. Choose *Manual proxy configuration*. 1. Enter `127.0.0.1` and Port `{{ shadowsocks_local_port }}` on the *SOCKS Host* line. 1. Select *Remote DNS*. This configures Firefox to send all DNS requests through the SOCKS proxy. This will protect you against DNS poisoning and ensure that false DNS entries cannot be used to censor your access. 1. Click *OK*. 1. Click *OK* again to close the Firefox preferences window. 1. You should be good to go! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### Android ### 1. Download Shadowsocks for Android from [Google Play](https://play.google.com/store/apps/details?id=com.github.shadowsocks). If Google Play is blocked in your country, you can [download a mirrored copy](/mirror/shadowsocks/) from this server! 1. Launch the application. 1. Tap the Add Profile *+* icon, second from the top-right of the screen. 1. Select *Scan QR code*. 1. Use your phone to scan this image, or if you are using your phone _right now_ you can choose the *Manual Settings* option and copy and paste the information from the Linux section above: ![Shadowsocks QR code](/shadowsocks/shadowsocks-qr-code.png) 1. Tap the round paper plane icon along the bottom-left bar. 1. Accept the Android VPN connection warning. 1. You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### iOS ### 1. Download [Shadowrocket](https://itunes.apple.com/us/app/shadowrocket/id932747118?mt=8) and launch it. > **Note:** Shadowrocket is not available in the China App Store. 1. Tap the QR code import icon in the top-left of the screen. 1. Use your phone to scan this image, or if you are using your phone _right now_ you can manually add a new proxy by tapping *Add Server* and using the information from the Linux section above: ![Shadowsocks QR code](/shadowsocks/shadowsocks-qr-code.png) 1. Toggle the connection switch located to the right of the *Not Connected* text on the *Home* tab. * If this is your first time running Shadowrocket, iOS will ask you to verify that the application should have permission to add VPN configurations. Tap *Allow* and follow the instructions. 1. You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. {%- if streisand_shadowsocks_v2ray_enabled -%} ### v2ray-plugin for unreliable/hostile networks ### For users on unreliable or hostile networks (esp. experiencing quality-of-service (QOS) throttling) using the [v2ray-plugin](https://github.com/shadowsocks/v2ray-plugin) may help alleviate these issues. Further configuration of the Shadowsocks client to use the v2ray-plugin can be carried out via the following configuration: Server: {{ streisand_ipv4_address }} Port: {{ shadowsocks_server_port }} Password: {{ shadowsocks_password.stdout }} Encryption Method: {{ shadowsocks_encryption_method }} Plugin: {{ shadowsocks_v2ray_plugin }} Plugin_Options: {{ shadowsocks_v2ray_plugin_options }} Android users will first need to download the [V2ray-plugin](https://play.google.com/store/apps/details?id=com.github.shadowsocks.plugin.v2ray) plugin app, and then modify the existing Streisand profile on your client to use this plugin. You can do this by hitting the edit button next to the profile, tapping the `Plugin` option at the bottom of the profile and selecting the "V2ray-plugin" plugin from the menu. Your Shadowsocks traffic will now be obfuscated as `{{ shadowsocks_v2ray_plugin_protocol }}` traffic to `{{ shadowsocks_v2ray_cover_domain }}`. {% endif %} ================================================ FILE: playbooks/roles/shadowsocks/templates/mirror-fr.md.j2 ================================================ {% include "languages.md.j2" %} ### Shadowsocks ### **Linux x64** * [{{ shadowsocks_go_filename }}]({{ shadowsocks_go_href }}) * Somme de contrôle: *{{ shadowsocks_go_checksum }}* **Android** * [{{ shadowsocks_android_filename }}]({{ shadowsocks_android_href }}) * Somme de contrôle: *{{ shadowsocks_android_checksum }}* **macOS** * [{{ shadowsocks_x_ng_filename }}]({{ shadowsocks_x_ng_href }}) * Somme de contrôle: *{{ shadowsocks_x_ng_checksum }}* **Windows** * [{{ shadowsocks_gui_filename }}]({{ shadowsocks_gui_href }}) * Somme de contrôle: *{{ shadowsocks_gui_checksum }}* ================================================ FILE: playbooks/roles/shadowsocks/templates/mirror.md.j2 ================================================ {% include "languages.md.j2" %} ### Shadowsocks ### **Linux x64** * [{{ shadowsocks_go_filename }}]({{ shadowsocks_go_href }}) * Checksum: *{{ shadowsocks_go_checksum }}* **Android** * [{{ shadowsocks_android_filename }}]({{ shadowsocks_android_href }}) * Checksum: *{{ shadowsocks_android_checksum }}* **macOS** * [{{ shadowsocks_x_ng_filename }}]({{ shadowsocks_x_ng_href }}) * Checksum: *{{ shadowsocks_x_ng_checksum }}* **Windows** * [{{ shadowsocks_gui_filename }}]({{ shadowsocks_gui_href }}) * Checksum: *{{ shadowsocks_gui_checksum }}* ================================================ FILE: playbooks/roles/shadowsocks/templates/shadowsocks-libev.default.j2 ================================================ # Defaults for shadowsocks systemd service # See /lib/systemd/system/shadowsocks-libev.service # installed by Streisand from template # Configuration file CONFFILE="{{ shadowsocks_location }}/config.json" # Extra command line arguments DAEMON_ARGS="{{ shadowsocks_daemon_args }}" ================================================ FILE: playbooks/roles/shadowsocks/templates/shadowsocks-libev.service.j2 ================================================ [Service] PrivateTmp=true Restart=on-failure RestartSec=5s ================================================ FILE: playbooks/roles/shadowsocks/vars/main.yml ================================================ --- shadowsocks_location: "/etc/shadowsocks-libev" shadowsocks_password_file: "{{ shadowsocks_location }}/shadowsocks-password.txt" # V2ray-plugin go_path: "{{ ansible_env.HOME }}/go" v2ray_github: "github.com/shadowsocks/v2ray-plugin" v2ray_core_github: "https://github.com/v2ray/v2ray-core" v2ray_location: "{{ go_path }}/bin" v2ray_options: "server;host={{ shadowsocks_v2ray_cover_domain }}" # Add -v for verbose mode to help with debugging shadowsocks_daemon_args: "-u" shadowsocks_gateway_location: "{{ streisand_gateway_location }}/shadowsocks" shadowsocks_qr_code: "{{ shadowsocks_gateway_location }}/shadowsocks-qr-code.png" shadowsocks_systemd_service_path: "/etc/systemd/system/shadowsocks-libev.service.d" ================================================ FILE: playbooks/roles/shadowsocks/vars/mirror.yml ================================================ --- # Shadowsocks Download variables # ------------------------------ shadowsocks_mirror_location: "{{ streisand_mirror_location }}/shadowsocks" shadowsocks_mirror_href_base: "/mirror/shadowsocks" # Android shadowsocks_android_version: "4.3.2" shadowsocks_android_filename: "shadowsocks-nightly-{{ shadowsocks_android_version }}.apk" shadowsocks_android_href: "{{ shadowsocks_mirror_href_base }}/{{ shadowsocks_android_filename }}" shadowsocks_android_url: "https://github.com/shadowsocks/shadowsocks-android/releases/download/v{{ shadowsocks_android_version }}/shadowsocks-nightly-{{ shadowsocks_android_version }}.apk" shadowsocks_android_checksum: "sha256:333833ed934a22767e19ebf468f51e59fff16f9d12ca2cf223b8d1e0eedd5895" # Windows shadowsocks_gui_version: "4.1.9.2" shadowsocks_gui_filename: "Shadowsocks-{{ shadowsocks_gui_version }}.zip" shadowsocks_gui_href: "{{ shadowsocks_mirror_href_base }}/{{ shadowsocks_gui_filename }}" shadowsocks_gui_url: "https://github.com/shadowsocks/shadowsocks-windows/releases/download/{{ shadowsocks_gui_version }}/{{ shadowsocks_gui_filename }}" shadowsocks_gui_checksum: "sha256:7a52b4827a4dac14ccd0c8a05a46c7debafca33672285e7630ee8f8e54387738" # macOS shadowsocks_x_ng_version: "1.9.4" shadowsocks_x_ng_filename: "ShadowsocksX-NG.{{ shadowsocks_x_ng_version }}.zip" shadowsocks_x_ng_href: "{{ shadowsocks_mirror_href_base }}/{{ shadowsocks_x_ng_filename }}" shadowsocks_x_ng_url: "https://github.com/shadowsocks/ShadowsocksX-NG/releases/download/v{{ shadowsocks_x_ng_version }}/{{ shadowsocks_x_ng_filename }}" shadowsocks_x_ng_checksum: "sha256:dc06a995b63f8e32be9b86c265fd2979a6d73d4742d0ff16e1b2bb8f538d77a3" # Linux (x64) # NOTE(@cpu): if 32bit Linux clients are to be supported then we should add `shadowsocks2-linux-x86.gz` shadowsocks_go_version: "0.0.9" shadowsocks_go_filename: "shadowsocks2-linux-x64.gz" shadowsocks_go_href: "{{ shadowsocks_mirror_href_base }}/{{ shadowsocks_go_filename }}" shadowsocks_go_url: "https://github.com/riobard/go-shadowsocks2/releases/download/v{{ shadowsocks_go_version }}/{{ shadowsocks_go_filename }}" shadowsocks_go_checksum: "sha256:9c08118a0caa60acdb6764112fd181513dbaa2c85d63e7b4b333895b7fe225e9" shadowsocks_download_urls: - { url: "{{ shadowsocks_android_url }}", checksum: "{{ shadowsocks_android_checksum }}" } - { url: "{{ shadowsocks_gui_url }}", checksum: "{{ shadowsocks_gui_checksum }}" } - { url: "{{ shadowsocks_x_ng_url }}", checksum: "{{ shadowsocks_x_ng_checksum }}" } - { url: "{{ shadowsocks_go_url }}", checksum: "{{ shadowsocks_go_checksum }}" } ================================================ FILE: playbooks/roles/ssh/defaults/main.yml ================================================ --- ssh_port: 22 ================================================ FILE: playbooks/roles/ssh/files/sshd_config ================================================ # Adapted from the "Modern" configuration detailed on the Mozilla Security # Guidelines wiki (https://wiki.mozilla.org/Security/Guidelines/OpenSSH). # # Three notable changes were made from that initial configuration: # 1) All logs are disabled. # 2) Root logins are allowed. This is the default way that most users # connect to a new VPS. Brute-force attacks against the root user # are mitigated because public-key authentication is required. # 3) PAM support is enabled. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. # Supported HostKey algorithms by order of preference. HostKey /etc/ssh/ssh_host_ed25519_key HostKey /etc/ssh/ssh_host_rsa_key HostKey /etc/ssh/ssh_host_ecdsa_key KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256 Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com # Password based logins are disabled - only public key based logins are allowed. AuthenticationMethods publickey PermitRootLogin Yes # Disable logging. LogLevel QUIET # Ensure that logging is disabled for SFTP connections as well. Subsystem sftp /usr/lib/openssh/sftp-server -f AUTHPRIV -l QUIET # Use kernel sandbox mechanisms where possible in unprivileged processes # Systrace on OpenBSD, Seccomp on Linux, seatbelt on macOS/Darwin, rlimit elsewhere. UsePrivilegeSeparation sandbox # Enable PAM. PasswordAuthentication no ChallengeResponseAuthentication no UsePAM yes ================================================ FILE: playbooks/roles/ssh/handlers/main.yml ================================================ --- - name: Restart SSH service: name: ssh state: restarted ================================================ FILE: playbooks/roles/ssh/tasks/main.yml ================================================ --- - name: Reconfigure OpenSSH with enhanced security settings copy: src: sshd_config dest: /etc/ssh/sshd_config owner: root group: root mode: 0644 notify: Restart SSH - name: Generate a stronger RSA host key shell: "rm {{ ssh_rsa_host_private_key }} {{ ssh_rsa_host_public_key }} && ssh-keygen -h -t rsa -b {{ ssh_rsa_host_key_size }} -f {{ ssh_rsa_host_private_key }} -N '' && touch {{ ssh_rsa_host_key_change_verification }}" args: creates: "{{ ssh_rsa_host_key_change_verification }}" warn: no notify: Restart SSH - name: Ensure missing host keys are generated command: ssh-keygen -A - name: Register the server's SSH fingerprints command: ssh-keygen -lf /etc/ssh/{{ item }} register: ssh_server_fingerprints with_items: - ssh_host_ecdsa_key.pub - ssh_host_rsa_key.pub ================================================ FILE: playbooks/roles/ssh/vars/main.yml ================================================ --- ssh_rsa_host_key_size: "3072" ssh_rsa_host_key_change_verification: "/etc/ssh/ssh_host_rsa_key_was_regenerated" ssh_rsa_host_private_key: "/etc/ssh/ssh_host_rsa_key" ssh_rsa_host_public_key: "{{ ssh_rsa_host_private_key }}.pub" ================================================ FILE: playbooks/roles/ssh-forward/defaults/main.yml ================================================ --- ssh_default_socks_port: 8080 ================================================ FILE: playbooks/roles/ssh-forward/tasks/docs.yml ================================================ --- - name: Create the SSH Gateway directory file: path: "{{ ssh_gateway_location }}" owner: www-data group: www-data mode: 0750 state: directory - include_role: name: i18n-docs vars: title: "SSH instructions" i18n_location: "{{ ssh_gateway_location }}" input_template_name: "instructions" - name: Copy the SSH private key that can be used to connect as the 'forward' user to the SSH Gateway directory command: cp {{ forward_location }}/.ssh/id_rsa {{ ssh_rsa_key }} args: creates: "{{ ssh_rsa_key }}" - name: Install the putty-tools package to facilitate converting the standard OpenSSH key into PuTTY's unique .ppk format apt: package: putty-tools - name: Convert the OpenSSH key into a PuTTY .ppk command: puttygen {{ forward_location }}/.ssh/id_rsa -o {{ ssh_putty_rsa_key }} args: creates: "{{ ssh_putty_rsa_key }}" - name: Generate a SSH known hosts file shell: 'ssh-keyscan {{ streisand_server_name }} {{ streisand_ipv4_address }} > {{ ssh_known_hosts }}' args: creates: '{{ ssh_known_hosts }}' ================================================ FILE: playbooks/roles/ssh-forward/tasks/main.yml ================================================ --- - name: Add the SSH forwarding user and generate a key user: name: forward generate_ssh_key: yes ssh_key_bits: "{{ ssh_rsa_host_key_size }}" ssh_key_comment: streisand home: "{{ forward_location }}" shell: /bin/bash comment: "SSH Forwarding User" - name: Register the forwarding user's public SSH key command: cat {{ forward_location }}/.ssh/id_rsa.pub register: ssh_forward_key changed_when: False - name: Authorize the forward users's key for accessing the forward user authorized_key: user: forward key: 'command="{{ ssh_force_command }}" {{ ssh_forward_key.stdout }}' - name: Add the sshuttle user and generate a key user: name: sshuttle generate_ssh_key: yes ssh_key_bits: "{{ ssh_rsa_host_key_size }}" ssh_key_comment: streisand-sshuttle home: "{{ sshuttle_location }}" shell: /bin/bash comment: "sshuttle User" when: streisand_sshuttle_enabled - name: Register the sshuttle user's public SSH key command: cat {{ sshuttle_location }}/.ssh/id_rsa.pub register: sshuttle_forward_key changed_when: False when: streisand_sshuttle_enabled - name: Authorize the sshuttle users's key for accessing the sshuttle user authorized_key: user: sshuttle # NOTE(@cpu): Unlike the forward user the sshuttle user does not have # a ssh_force_comamnd in the ssh authorized keys file. This means the # sshuttle user is a *full* shell user and can SSH in. They don't have root # but this is a more significant foothold on a server than is offered by the # ssh fowrarding user. This is why by default Streisand no longer enables # sshuttle by default. key: '{{ ssh_forward_key.stdout }}' when: streisand_sshuttle_enabled # Generate the gateway documentation for the SSH forward user - import_tasks: docs.yml # Mirror Putty for SSH forwarding from Windows - import_tasks: mirror.yml ================================================ FILE: playbooks/roles/ssh-forward/tasks/mirror.yml ================================================ --- - name: Include the SSH mirror variables include_vars: mirror.yml - name: Make the directory where the SSH client mirrored files will be stored file: path: "{{ ssh_mirror_location }}" owner: www-data group: www-data mode: 0755 state: directory - block: - name: Mirror shuttle if enabled get_url: url: "{{ sshuttle_url }}" dest: "{{ ssh_mirror_location }}/{{ sshuttle_filename }}" checksum: "{{ sshuttle_checksum }}" owner: www-data group: www-data mode: 0644 when: streisand_sshuttle_enabled - include_role: name: download-and-verify vars: project_name: "PuTTY" project_download_baseurl: "{{ putty_base_download_url }}" project_download_files: "{{ putty_download_files }}" project_download_location: "{{ ssh_mirror_location }}" project_signer_keyid: "{{ putty_gpg_keyid }}" rescue: - name: "{{ streisand_mirror_warning }}" pause: seconds: "{{ streisand_mirror_warning_seconds }}" - include_role: name: i18n-docs vars: title: "SSH mirror" i18n_location: "{{ ssh_mirror_location }}" input_template_name: "mirror" ================================================ FILE: playbooks/roles/ssh-forward/templates/instructions-fr.md.j2 ================================================ {% include "languages.md.j2" %} Tunnel SSH ---------- --- * Plateformes * [Windows](#windows) * [Linux et macOS](#linux-and-macos) {% if streisand_tinyproxy_enabled %} * [Android](#android) {% endif %} * [Notes](#notes) ### Windows ### 1. Téléchargez [PuTTY](/mirror/ssh/index-fr.html) et lancez-le. 1. Allez à la section *Session*. 1. Saisissez `{{ streisand_ipv4_address }}` pour le champ *Host Name*. 1. Saisissez `{{ ssh_port }}` pour le chaml *Port*. * Le port `443` est disponible en option de recharge si vous êtes sur un réseau qui restreint l'accès au port SSH par défaut. 1. Allez à Connection --> Data. 1. Saisissez `forward` dans le champ *Auto-login username*. 1. Allez à Connection --> SSH. 1. Cochez `Don't start a shell or command at all`. 1. Allez à Connection --> SSH --> Tunnels. 1. Saisissez `{{ ssh_default_socks_port }}` dans le champ *Source port*. 1. Assurez-vous que les boutons radio `Auto` et `Dynamic` sont sélectionnés. 1. Cliquez sur *Add* pour ajouter le tunnel. Vous devriez voir `D {{ ssh_default_socks_port }}` dans la section *Forwarded ports*. 1. Allez à Connection --> SSH --> Auth. 1. Téléchargez la clé privée `streisand_rsa.ppk` utilisée pour authentifier la connexion SSH. Cliquez-droit; Enregistrer la cible sous... * [streisand\_rsa.ppk](/ssh/streisand_rsa.ppk) 1. Cliquez le bouton *Browse*. 1. Cliquez le *PuTTY Private Key Files* menu déroulant à côté du champ *File name* et sélectionnez *All Files*. 1. Sélectionnez le fichier `streisand_rsa.ppk` téléchargé et cliquez sur *Open*. 1. Revenez à *Session* (le premier élément dans le menu de gauche). 1. Saisissez `{{ streisand_server_name }}` dans la première boite *Saved Sessions* et cliques le bouton *Save*. La prochaine fois que vous lancez PuTTY, vous pouvez choisir la session et cliquer sur *Load* pour restaurer tous ces paramètres. 1. Cliquez *Open* pour vous connecter! PuTTY vous demandera de confirmer l'empreinte digitale. Vérifiez que les empreintes numériques correspondent à l'un des éléments ci-dessous: `{{ ssh_server_fingerprints.results[0].stdout }}` `{{ ssh_server_fingerprints.results[1].stdout }}` Vous êtes maintenant connecté à un proxy SOCKS qui est prêt à transférer votre trafic chiffré via SSH. L'étape suivante consiste à configurer votre navigateur Web pour l'utiliser. #### Configuration de Firefox pour se connecter via un proxy SOCKS #### 1. Cliquez sur le bouton *Menu* à côté de l'icône *Accueil* à droite de la barre d'adresse. 1. Cliquez *Paramètres*. 1. Cliquez l'icône *Avancé*. 1. Allez à l'onglet *Réseau*. 1. Cliquez le bouton *Paramètres* pour *Configurer la façon dont Firefox se connecte à Internet*. 1. Choisissez *Configuration manuelle de proxy*. 1. Saisissez `127.0.0.1` et port `{{ ssh_default_socks_port }}` pour la ligne *Hôte SOCKS*. 1. Sélectionnez *Utiliser un DNS distant lorsque SOCKS v5 est actif*. Cela configure Firefox pour envoyer toutes les requêtes DNS via le proxy SOCKS. Cela vous protégera contre l'empoisonnement du DNS et vous garantira que les fausses entrées DNS ne peuvent pas être utilisées pour censurer votre accès. 1. Cliquez *OK*. 1. Cliquez *OK* encore pour fermer la fenêtre de paramètres de Firefox.. 1. C'est tout! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{ streisand_ipv4_address }}*. ### Linux et macOS ### #### sshuttle #### Sshuttle est une simple solution de tunneling VPN qui fonctionne sur le transport SSH. C'est rapide, facile à installer et offre de bonnes performances. 1. Téléchargez la clé privée `streisand_rsa` qui sert à authentifier la connexion SSH: * [streisand\_rsa](/ssh/streisand_rsa) 1. Copiez le fichier `streisand_rsa` dans une répertoire de votre choix. 1. Définissez les autorisations correctes sur le fichier clé RSA: * `chmod 600 streisand_rsa` 1. Ajoutez une nouvelle entrée à votre fichier `.ssh / config`. Il devrait ressembler à ceci.Le port `443` est disponible en option de recharge si vous êtes sur un réseau qui restreint l'accès au port SSH par défaut. Assurez-vous d'ajuster l'emplacement du IdentityFile: Host {{ streisand_server_name }} User sshuttle Port {{ ssh_port }} HostName {{ streisand_ipv4_address }} IdentitiesOnly yes IdentityFile ~/.ssh/streisand_rsa 1. Téléchargez [sshuttle](https://github.com/sshuttle/sshuttle) en exécutant la commande suivante dans une répertoire de votre choix: `git clone https://github.com/sshuttle/sshuttle.git` * Si l'accès à GitHub a été bloqué, vous pouvez télécharger une copie en miroir [içi](/mirror/ssh/index-fr.html)! * sshuttle est également disponible via [Homebrew](http://brew.sh/) pour les utilisateurs de OS X: `brew install sshuttle` 1. Entrer dans la répertoire: `cd sshuttle` 1. Exécutez sshuttle et connectez-vous au serveur: `./run --dns -r sshuttle@{{ streisand_server_name }} 0/0 -vv` 1. Vérifiez que les empreintes numériques correspondent à l'un des éléments ci-dessous: `{{ ssh_server_fingerprints.results[0].stdout }}` `{{ ssh_server_fingerprints.results[1].stdout }}` 1. Tout votre trafic, y compris DNS, est maintenant transmis de manière transparente via une connexion SSH cryptée. Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{ streisand_ipv4_address }}*. #### SSH forward #### 1. Téléchargez la clé privée `streisand_rsa` qui sert à authentifier la connexion SSH: * [streisand\_rsa](/ssh/streisand_rsa) 1. Copiez le fichier `streisand_rsa` dans une répertoire de votre choix. 1. Ajoutez une nouvelle entrée à votre fichier `.ssh / config`. Il devrait ressembler à ceci.Le port `443` est disponible en option de recharge si vous êtes sur un réseau qui restreint l'accès au port SSH par défaut. Assurez-vous d'ajuster l'emplacement du IdentityFile: Host {{ streisand_server_name }} User forward Port {{ ssh_port }} HostName {{ streisand_ipv4_address }} IdentitiesOnly yes IdentityFile ~/.ssh/streisand_rsa 1. `SSH`ez dans le serveur et transférer un port SOCKS dynamique: `ssh -vND {{ ssh_default_socks_port }} forward@{{ streisand_server_name }}` 1. Vérifiez que les empreintes numériques correspondent à l'un des éléments ci-dessous: `{{ ssh_server_fingerprints.results[0].stdout }}` `{{ ssh_server_fingerprints.results[1].stdout }}` 1. Vous êtes maintenant connecté à un proxy SOCKS qui est prêt à transférer votre trafic chiffré via SSH. L'étape suivante consiste à configurer votre navigateur Web pour l'utiliser. Vous pouvez suivre les mêmes instructions contenues dans la section Windows ci-dessus pour configurer Firefox pour acheminer son trafic via le proxy SOCKS. {% if streisand_tinyproxy_enabled %} ### Android ### 1. Installez [SSH persistent tunnels](https://play.google.com/store/apps/details?id=org.ayal.SPT&hl=fr) par Shai Ayal. Cette application permet de transférer facilement plusieurs ports via un tunnel SSH, et fonctionne assez bien pour s'assurer que les tunnels restent actifs même lorsque vous changez régulièrement entre LTE, 3G et WiFi. L'application est [open source](https://bitbucket.org/ayal_org/ssh-persistent-tunnel/) et peut être compilée gratuitement, mais la version Play Store coûte $1,50 (USD). 1. Téléchargez la clé privée `streisand_rsa` qui sert à authentifier la connexion SSH: * [streisand\_rsa](/ssh/streisand_rsa) 1. Copiez le fichier `streisand_rsa` dans une répertoire de votre choix. 1. Démarrez l'application. Il sera répertorié comme *SPT* dans votre lanceur, et son icône ressemble à un tunnel de train. 1. Tapez sur l'icône du menu en bas à droite de votre écran. 1. Tapez *Settings*. 1. Tapez *Host name* et saisissez `{{ streisand_ipv4_address }}`. 1. Tapez *User Name* et saisissez `forward`. 1. Tapez *Port* et saisissez `{{ ssh_port }}`. * Le port `443` est disponible en option de recharge si vous êtes sur un réseau qui restreint l'accès au port SSH par défaut. 1. Tapez *Private Key File* et sélectionnez le fichier `streisand_rsa` que vous avez copié sur votre téléphone. 1. Tapez *Dynamic Forward Port* et saisissez `1080`. 1. Tapez *Forwards* et saisissez `L8888=localhost:8888`. 1. Retourner et tapez le bouton *Connect Tunnel*. 1. Vérifiez que les empreintes numériques correspondent à l'un des éléments ci-dessous: `{{ ssh_server_fingerprints.results[0].stdout }}` `{{ ssh_server_fingerprints.results[1].stdout }}` Vous êtes maintenant prêt à configurer vos applications pour acheminer leur trafic via le tunnel SSH fourni par SPT. #### Configuration d'Android pour acheminer la plupart du trafic via SPT #### Ces étapes ne fonctionneront que si vous êtes connecté via WiFi. Ils doivent également être appliqués individuellement sur tous les réseaux WiFi auxquels vous vous connectez. La plupart des applications respecteront ces paramètres, y compris le navigateur par défaut, Chrome, YouTube et bien d'autres. 1. Ouvrez les paramètres de votre téléphone. 2. Tapez *Wi-Fi* dans la section *Sans fil et réseaux*. 3. Appuyez longuement sur le réseau WiFi auquel vous êtes actuellement connecté. Un menu contextuel apparaîtra. 4. Tapez *Modifier le réseau*. 5. Tapez *Options avancées*. 6. Sélectionnez *Manuel* dans la section proxy. 7. Tapez *Nom d'hôte du proxy* et saisissez `127.0.0.1`. 8. Tapez *Port du proxy* et saisissez `8888`. 9. Tapez *Enregistrer*. Certaines applications vous permettent de rendre ces paramètres persistants pour tous les réseaux. Twitter pour Android et Firefox pour Android peut acheminer son trafic via le tunnel SPT SSH indépendamment de votre connexion actuelle (WiFi, 3G, HSPA +, LTE, etc.). #### Configuration de Twitter pour Android pour utiliser SPT #### 1. Ouvrez Twitter. 2. Appuyez sur les trois points en haut à droite de l'écran. 3. Sélectionnez *Paramètres*. 4. Tapez *Général*. 5. Tapez *Proxy*. 6. Cocher *Activer le proxy HTTP*. 7. Tapez *Hôte du proxy* et saisissez `127.0.0.1`. 8. Tapez *Port du proxy* et saisissez `8888`. #### Configuration de Firefox pour Android pour utiliser SPT #### 1. Ouvrez Firefox. 2. Tapez `about:config` dans la barre d'adresse et appuyez sur le bouton 'Go' de votre clavier. 3. Tapez `proxy` dans la barre de recherche. 4. Définissez la valeur de *network.proxy.socks* à `127.0.0.1`. 5. Définissez la valeur de *network.proxy.socks\_port* à `1080`. 6. Définissez la valeur de *network.proxy.socks\_remote\_dns* à `true`. 7. Définissez la valeur de *network.proxy.type* à `1`. {% endif %} 1. Si vous voyez le message "This account is for port forwarding only", assurez-vous de configurer votre client SSH pour ne pas exécuter les commandes shell sur le serveur distant (`-N`). ================================================ FILE: playbooks/roles/ssh-forward/templates/instructions.md.j2 ================================================ {% include "languages.md.j2" %} SSH Tunnel ---------- --- * Platforms * [Windows](#windows) * [Linux and macOS](#linux-and-macos) {% if streisand_tinyproxy_enabled %} * [Android](#android) {% endif %} * [Notes](#notes) ### Windows ### 1. Download [PuTTY](/mirror/ssh/) and run it. 1. Go to the *Session* section. 1. Enter `{{ streisand_ipv4_address }}` in the Host Name field. 1. Enter `443` in the Port field. * Port {{ ssh_port }} is available as an option if your network does not block it. 1. Go to Connection --> Data. 1. Enter `forward` in the *Auto-login username* field. 1. Go to Connection --> SSH. 1. Check `Don't start a shell or command at all`. 1. Go to Connection --> SSH --> Tunnels. 1. Enter `{{ ssh_default_socks_port }}` in the *Source port* field. 1. Make sure the `Auto` and `Dynamic` radio buttons are selected. 1. Click *Add* to add the tunnel. You should see `D{{ ssh_default_socks_port }}` in the Forwarded ports box. 1. Go to Connection --> SSH --> Auth. 1. Download the `streisand_rsa.ppk` private key that is used to authenticate the SSH connection. Right-click; Save target as... * [streisand\_rsa.ppk](/ssh/streisand_rsa.ppk) 1. Click the *Browse* button. 1. Click the *PuTTY Private Key Files* drop down next to the *File name* field and choose *All Files*. 1. Select the downloaded `streisand_rsa.ppk` file and click *Open*. 1. Go back to *Session* (the very first item in the left-hand menu). 1. Enter `{{ streisand_server_name }}` in the first *Saved Sessions* box and click the *Save* button. The next time you launch PuTTY you can choose the session and click *Load* to restore all of these settings. 1. Click *Open* to connect! PuTTY will ask you to confirm the fingerprint. Make sure it matches one of these: `{{ ssh_server_fingerprints.results[0].stdout }}` `{{ ssh_server_fingerprints.results[1].stdout }}` You are now connected and have a SOCKS proxy up and running that is ready to forward encrypted traffic through SSH. The next step is to configure your web browser to use it. #### Configuring Firefox to connect through a SOCKS proxy #### 1. Click the *Menu* button next to the *Home* icon to the right of the address bar. 1. Click *Options*. 1. Click the *Advanced* icon. 1. Go to the *Network* tab. 1. Click the *Settings* button to *Configure how Firefox connects to the Internet*. 1. Choose *Manual proxy configuration*. 1. Enter `127.0.0.1` and Port `{{ ssh_default_socks_port }}` on the *SOCKS Host* line. 1. Select *Remote DNS*. This configures Firefox to send all DNS requests through the SOCKS proxy. This will protect you against DNS poisoning and ensure that false DNS entries cannot be used to censor your access. 1. Click *OK*. 1. Click *OK* again to close the Firefox preferences window. 1. You should be good to go! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. ### Linux and macOS ### #### SSH Forwarding #### 1. Download the `streisand_rsa` private key that is used to authenticate the SSH connection: * [streisand\_rsa](/ssh/streisand_rsa) 1. Copy the `streisand_rsa` file to the directory of your choice. 1. Set the correct permissions on the RSA key file: * `chmod 600 streisand_rsa` 1. Add a new entry to your `.ssh/config` file. It should look like this. Port {{ ssh_port }} is available if your network does not block it. Be sure to adjust the location of the IdentityFile: Host {{ streisand_server_name }} User forward Port 443 HostName {{ streisand_ipv4_address }} IdentitiesOnly yes IdentityFile ~/.ssh/streisand_rsa 1. SSH into the server and forward a dynamic SOCKS port: `ssh -vND {{ ssh_default_socks_port }} forward@{{ streisand_server_name }}` 1. Verify that the fingerprint matches one of these: `{{ ssh_server_fingerprints.results[0].stdout }}` `{{ ssh_server_fingerprints.results[1].stdout }}` 1. You are now connected and have a SOCKS proxy up and running that is ready to forward encrypted traffic through SSH. The next step is to configure your web browser to use it. You can follow the same instructions contained in the Windows section above to configure Firefox to route its traffic through the SOCKS proxy. {% if streisand_sshuttle_enabled %} #### sshuttle Sshuttle is a simple VPN tunnelling solution that operates over the SSH transport. It's fast, easy to set up, and offers great performance. 1. Download the `streisand_rsa` private key that is used to authenticate the SSH connection: * [streisand\_rsa](/ssh/streisand_rsa) 1. Copy the `streisand_rsa` file to the directory of your choice. 1. Set the correct permissions on the RSA key file: * `chmod 600 streisand_rsa` 1. Add a new entry to your `.ssh/config` file. It should look like this. Port {{ ssh_port }} is available if your network does not block it. Be sure to adjust the location of the IdentityFile: Host {{ streisand_server_name }} User sshuttle Port 443 HostName {{ streisand_ipv4_address }} IdentitiesOnly yes IdentityFile ~/.ssh/streisand_rsa 1. Download [sshuttle](https://github.com/sshuttle/sshuttle) by running the following command in the directory of your choice: `git clone https://github.com/sshuttle/sshuttle.git` * If access to GitHub has been blocked, you can download a mirrored copy [here](/mirror/ssh/)! * sshuttle is also available via [Homebrew](http://brew.sh/) for OS X users: `brew install sshuttle` 1. Enter the directory: `cd sshuttle` 1. Run sshuttle and connect to the server: `./run --dns -r sshuttle@{{ streisand_server_name }} 0/0 -vv` 1. Verify that the fingerprint matches one of these: `{{ ssh_server_fingerprints.results[0].stdout }}` `{{ ssh_server_fingerprints.results[1].stdout }}` 1. All of your traffic, including DNS, is now being transparently forwarded through an encrypted SSH connection. You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. {% endif %} {% if streisand_tinyproxy_enabled %} ### Android ### 1. Install [SSH persistent tunnels](https://play.google.com/store/apps/details?id=org.ayal.SPT) by Shai Ayal. This application makes it easy to forward multiple ports through an SSH tunnel, and it does a decent job of ensuring that the tunnels remain active even when you switch back and forth regularly between LTE, 3G, and WiFi. The app is [open source](https://code.google.com/p/ssh-persistent-tunnel/) and can be compiled for free, but the Play Store version costs $1.50. 1. Download the `streisand_rsa` private key that is used to authenticate the SSH connection: * [streisand\_rsa](/ssh/streisand_rsa) 1. Copy the `streisand_rsa` file to the root directory of your phone. 1. Start the application. It will be listed as *SPT* in your launcher, and its icon looks like a train tunnel. 1. Tap the menu icon in the lower-right of your screen. 1. Tap *Settings*. 1. Tap *Host name* and enter `{{ streisand_ipv4_address }}`. 1. Tap *User Name* and enter `forward`. 1. Tap *Port* and enter `443`. * Port {{ ssh_port }} is available if your network does not block it. 1. Tap *Private Key File* and select the `streisand_rsa` file that you copied to your phone. 1. Tap *Dynamic Forward Port* and enter `1080`. 1. Tap *Forwards* and enter `L8888=localhost:8888`. 1. Go back and tap the *Connect Tunnel* button. 1. Verify that the fingerprint matches one of these: `{{ ssh_server_fingerprints.results[0].stdout }}` `{{ ssh_server_fingerprints.results[1].stdout }}` You are now ready to configure your applications to route their traffic through the SSH tunnel provided by SPT. #### Configuring Android to route most of its traffic through SPT #### These steps will only work when you are connected via WiFi. They also must be applied individually to every WiFi network you connect to. Most applications will respect these settings, including the default browser, Chrome, YouTube, and many more. 1. Open your phone's Settings. 2. Tap *Wi-Fi* in the *Wireless & Networks* section. 3. Long-press on the WiFi network you are currently connected to. A pop-up menu will appear. 4. Tap *Modify network*. 5. Tap *Show advanced options*. 6. Select *Manual* in the Proxy section. 7. Tap *Proxy hostname* and enter `127.0.0.1`. 8. Tap *Proxy port* and enter `8888`. 9. Tap *Save*. Some applications allow you to make these settings persistent for all networks. Twitter for Android and Firefox for Android can route their traffic through the SPT SSH tunnel regardless of your current connection (WiFi, 3G, HSPA+, LTE, etc.). #### Configuring Twitter for Android to use SPT #### 1. Open Twitter. 2. Tap the three dots in the upper-right of the screen to open the menu. 3. Choose *Settings*. 4. Tap *General*. 5. Tap *Proxy*. 6. Check the *Enable HTTP Proxy* checkbox. 7. Tap *Proxy Host* and enter `127.0.0.1`. 8. Tap *Proxy Port* and enter `8888`. #### Configuring Firefox for Android to use SPT #### 1. Open Firefox. 2. Type `about:config` into the address bar and tap the 'Go' button on your keyboard. 3. Type `proxy` into the search box. 4. Set the value of *network.proxy.socks* to `127.0.0.1`. 5. Set the value of *network.proxy.socks\_port* to `1080`. 6. Set the value of *network.proxy.socks\_remote\_dns* to `true`. 7. Set the value of *network.proxy.type* to `1`. {% endif %} 1. If you see the message "This account is for port forwarding only", make sure you configure your SSH client to not execute shell commands on remote server (`-N`). ================================================ FILE: playbooks/roles/ssh-forward/templates/mirror-fr.md.j2 ================================================ {% include "languages.md.j2" %} ### SSH ### **Windows** * [{{ putty_filename }}]({{ putty_href }}) ([sig]({{ putty_sig_href }})) {% if streisand_sshuttle_enabled %} **Linux and OS X** * [{{ sshuttle_filename }}]({{ sshuttle_href }}) * Checksum: *{{ sshuttle_checksum }}* {% endif %} ================================================ FILE: playbooks/roles/ssh-forward/templates/mirror.md.j2 ================================================ {% include "languages.md.j2" %} ### SSH ### **Windows** * [{{ putty_filename }}]({{ putty_href }}) ([sig]({{ putty_sig_href }})) {% if streisand_sshuttle_enabled %} **Linux and OS X** * [{{ sshuttle_filename }}]({{ sshuttle_href }}) * Checksum: *{{ sshuttle_checksum }}* {% endif %} ================================================ FILE: playbooks/roles/ssh-forward/vars/main.yml ================================================ --- forward_location: "/home/forward" ssh_force_command: "echo 'This account is for port forwarding only.'" sshuttle_location: "/home/sshuttle" ssh_gateway_location: "{{ streisand_gateway_location }}/ssh" ssh_rsa_key: "{{ ssh_gateway_location }}/streisand_rsa" ssh_putty_rsa_key: "{{ ssh_gateway_location }}/streisand_rsa.ppk" ssh_known_hosts: "{{ ssh_gateway_location }}/streisand.known_hosts" ================================================ FILE: playbooks/roles/ssh-forward/vars/mirror.yml ================================================ --- # SSH Client Download variables # ----------------------------- ssh_mirror_location: "{{ streisand_mirror_location }}/ssh" ssh_mirror_href_base: "/mirror/ssh" putty_base_download_url: "https://the.earth.li/~sgtatham/putty/latest/w32" putty_filename: "putty.exe" putty_sig_filename: "{{ putty_filename }}.gpg" putty_href: "{{ ssh_mirror_href_base }}/{{ putty_filename }}" # download-and-verify.yml renames files with non-standard extensions putty_sig_href: "{{ ssh_mirror_href_base }}/{{ putty_filename }}.asc" putty_gpg_keyid: "4AE8DA82" putty_download_files: - { "file": "{{ putty_filename }}", "sig": "{{ putty_sig_filename }}" } sshuttle_version: "0.78.0" sshuttle_filename: "sshuttle-{{ sshuttle_version }}.tar.gz" sshuttle_href: "{{ ssh_mirror_href_base }}/{{ sshuttle_filename }}" sshuttle_url: "https://codeload.github.com/sshuttle/sshuttle/tar.gz/v{{ sshuttle_version }}" sshuttle_checksum: "sha256:0742e3e670c8df629ae702a32cfad96c7c4e8f7ab8f66c26d94c55d42b01e4b4" ================================================ FILE: playbooks/roles/sslh/handlers/main.yml ================================================ --- - name: Restart sslh systemd: name: sslh.service state: restarted ================================================ FILE: playbooks/roles/sslh/tasks/main.yml ================================================ --- - name: Install sslh apt: package: sslh install_recommends: false - name: Generate the sslh port multiplexer systemd defaults file template: src: sslh.default.j2 dest: /etc/default/sslh notify: Restart sslh - name: Generate the sslh port multiplexer config file template: src: sslh.cfg.j2 dest: /etc/sslh.cfg notify: Restart sslh - name: Create the sslh systemd drop-in configuration directory file: path: "{{ sslh_systemd_service_path }}" state: directory - name: Generate the sslh systemd drop-in service file template: src: sslh.service.j2 dest: "{{ sslh_systemd_service_path }}/10-restart-failure.conf" mode: 0644 - name: Enable the sslh service systemd: daemon_reload: yes name: sslh.service enabled: yes state: restarted ================================================ FILE: playbooks/roles/sslh/templates/sslh.cfg.j2 ================================================ verbose: false; foreground: true; timeout: 5; user: "sslh"; pidfile: "{{ sslh_pid_file }}"; listen: ( { host: "{{ ansible_default_ipv4.address }}"; port: "443"; } ); protocols: ( { name: "ssh"; host: "127.0.0.1"; port: "{{ ssh_port }}"; probe: "builtin"; fork: true; }, { name: "tls"; host: "127.0.0.1"; port: "{{ nginx_port}}"; probe: "builtin"; }, {% if streisand_openvpn_enabled %} { name: "openvpn"; host: "127.0.0.1"; port: "{{ openvpn_port }}"; probe: "builtin"; }, {% endif %} { name: "anyprot"; host: "127.0.0.1"; port: "{{ nginx_port }}"; probe: "builtin"; } ); on-timeout: "tls"; ================================================ FILE: playbooks/roles/sslh/templates/sslh.default.j2 ================================================ # Default options for sslh initscript # sourced by /etc/init.d/sslh RUN=yes DAEMON=/usr/sbin/sslh DAEMON_OPTS="-F /etc/sslh.cfg" ================================================ FILE: playbooks/roles/sslh/templates/sslh.service.j2 ================================================ [Service] PrivateTmp=true StandardOutput=null RestartSec=5s Restart=on-failure PIDFile={{ sslh_pid_file }} ================================================ FILE: playbooks/roles/sslh/vars/main.yml ================================================ --- sslh_pid_file: "/var/run/sslh/sslh.pid" sslh_systemd_service_path: "/etc/systemd/system/sslh.service.d" ================================================ FILE: playbooks/roles/streisand-gateway/defaults/main.yml ================================================ --- nginx_key_country: "US" nginx_key_province: "California" nginx_key_city: "Malibu" nginx_key_org: "Streisand" nginx_key_ou: "Streisand Effect Department" nginx_port: "443" streisand_gateway_username: "streisand" streisand_gateway_rsa_key_size: "3072" ================================================ FILE: playbooks/roles/streisand-gateway/handlers/main.yml ================================================ --- - name: Restart Nginx for the Gateway vhost service: name: nginx state: restarted ================================================ FILE: playbooks/roles/streisand-gateway/meta/main.yml ================================================ --- dependencies: - { role: nginx } ================================================ FILE: playbooks/roles/streisand-gateway/tasks/docs.yml ================================================ --- - include_role: name: i18n-docs vars: title: "Gateway index" i18n_location: "{{ streisand_gateway_location }}" input_template_name: "index" - name: Ensure the Streisand temporary gateway directory exists file: path: "{{ streisand_temp_gateway_path }}" owner: root group: root mode: 0600 state: directory - include_role: name: i18n-docs vars: title: "Gateway {{ doc.template }}" i18n_location: "{{ streisand_temp_gateway_path }}" input_template_name: "{{ doc.template }}" output_file_name: "{{ doc.output_file }}" with_items: - { template: "instructions", output_file: "{{ streisand_server_name }}" } - { template: "firewall", output_file: "{{ streisand_server_name }}-firewall-information" } loop_control: loop_var: "doc" label: "{{ doc.template }}" ================================================ FILE: playbooks/roles/streisand-gateway/tasks/fetch-and-cleanup.yml ================================================ --- - name: Find the files to fetch from the Streisand temporary gateway directory find: paths: "{{ streisand_temp_gateway_path }}" recurse: no pattern: "*.html" register: gateway_local_files - name: Fetch the local Gateway files fetch: dest: ../{{ streisand_local_directory }}/ src: "{{ file.path }}" flat: yes with_items: "{{ gateway_local_files.files }}" loop_control: loop_var: file label: "{{ file.path }}" - name: Delete the Streisand temporary gateway directory file: path: "{{ streisand_temp_gateway_path }}" state: "absent" ================================================ FILE: playbooks/roles/streisand-gateway/tasks/main.yml ================================================ --- # Use OpenSSL to generate self-signed certificates if we do not have Let's Encrypt. - import_tasks: openssl.yml when: not le_ok # Include Let's Encrypt variables for template rendering - include_vars: ../../lets-encrypt/vars/main.yml - name: Generate a random Gateway password shell: "{{ streisand_word_gen.gateway | trim }} > {{ streisand_gateway_password_file }}" args: creates: "{{ streisand_gateway_password_file }}" - name: Register the Gateway password command: cat {{ streisand_gateway_password_file }} register: streisand_gateway_password changed_when: False - name: Install the required package for the htpasswd command apt: package: apache2-utils - name: Generate the htpasswd file command: htpasswd -b -c {{ streisand_gateway_htpasswd_file }} {{ streisand_gateway_username }} {{ streisand_gateway_password.stdout }} args: creates: "{{ streisand_gateway_htpasswd_file }}" - name: Set permissions on the unhashed Gateway password file file: path: "{{ streisand_gateway_password_file }}" owner: root group: root mode: 0600 - name: Copy the Streisand gateway password file locally fetch: src: "{{ streisand_gateway_password_file }}" dest: "{{ streisand_gateway_password_localpath }}" fail_on_missing: yes flat: yes when: streisand_client_test - name: Generate the virtual host and restart Nginx if it is updated template: src: vhost.j2 dest: /etc/nginx/sites-available/streisand owner: root group: root mode: 0644 notify: Restart Nginx for the Gateway vhost - name: Enable the virtual host file: path: /etc/nginx/sites-enabled/streisand src: /etc/nginx/sites-available/streisand state: link # TODO: # Add to CI testing https://github.com/StreisandEffect/streisand/issues/643 - block: - name: Keep a copy of our diagnostics on the server copy: src: ../../../../streisand-diagnostics.md dest: "{{ streisand_gateway_location }}/streisand-diagnostics.md" # generate the streisand server instructions and documentation - include_tasks: docs.yml # fetch and clean up - include_tasks: fetch-and-cleanup.yml - name: Ensure that all of the files in the Gateway have the proper permissions file: path: "{{ streisand_gateway_location }}" owner: www-data group: www-data mode: 0750 state: directory recurse: yes - meta: flush_handlers - block: - name: Success! pause: prompt: "Server setup is complete. The `{{ streisand_server_name }}.html` instructions file in the generated-docs folder is ready to give to friends, family members, and fellow activists. Press Enter to continue." - name: Attempt to open the instructions on Linux (if applicable). Errors in this task are ignored because the `xdg-open` command is not always available. local_action: command xdg-open ../{{ streisand_local_directory }}/{{ streisand_server_name }}.html ignore_errors: yes when: hostvars['127.0.0.1']['ansible_system'] == "Linux" become: no - name: Open the instructions on macOS (if applicable) local_action: command open ../{{ streisand_local_directory }}/{{ streisand_server_name }}.html when: hostvars['127.0.0.1']['ansible_system'] == "Darwin" become: no when: not streisand_noninteractive when: not streisand_ci ================================================ FILE: playbooks/roles/streisand-gateway/tasks/openssl.yml ================================================ - name: Make the necessary directories for the certificate authority file: path: "{{ item }}" owner: root group: root mode: 0750 state: directory with_items: - "{{ openssl_ca_base }}" - "{{ openssl_ca_private_dir }}" - "{{ openssl_ca_newcerts_dir }}" - name: Generate a local openssl.cnf template: src: openssl-local.cnf.j2 dest: "{{ openssl_ca_base }}/openssl-local.cnf" - name: Create private key and self-signed certificate for Nginx Certificate Authority. command: openssl req -new -nodes -x509 -config "{{ openssl_ca_base }}/openssl-local.cnf" -days {{ nginx_days_valid }} -extensions v3_ca -newkey rsa:{{ streisand_gateway_rsa_key_size }} -keyout {{ openssl_ca_key }} -out {{ openssl_ca_certificate }} -subj "/C={{ nginx_key_country }}/ST={{ nginx_key_province }}/L={{ nginx_key_city }}/O={{ nginx_key_org }}/OU={{ nginx_key_ou }}/CN=Streisand {{ streisand_ipv4_address }} Root CA" args: creates: "{{ openssl_ca_certificate }}" - name: Create a certificate signing request for a leaf certificate issued by the Nginx CA command: openssl req -new -nodes -config "{{ openssl_ca_base }}/openssl-local.cnf" -days {{ nginx_days_valid }} -newkey rsa:{{ streisand_gateway_rsa_key_size }} -keyout {{ nginx_private_key }} -out {{ nginx_self_signed_certificate_request }} -reqexts v3_ca_req -subj "/C={{ nginx_key_country }}/ST={{ nginx_key_province }}/L={{ nginx_key_city }}/O={{ nginx_key_org }}/OU={{ nginx_key_ou }}/CN=Streisand at {{ streisand_ipv4_address }}" args: creates: "{{ nginx_self_signed_certificate_request }}" - name: Set permissions on the SSL private key file: path: "{{ nginx_private_key }}" owner: root group: root mode: 0600 - name: Seed a blank database file that will be used when generating the self-signed certificate file: path: "{{ openssl_ca_base }}/index.txt" state: touch - name: Process certificate signing request with the Nginx CA to create leaf certificate command: openssl ca -batch -create_serial -config "{{ openssl_ca_base }}/openssl-local.cnf" -extensions v3_req -days {{ nginx_days_valid }} -in {{ nginx_self_signed_certificate_request }} -out {{ nginx_self_signed_certificate }} args: creates: "{{ nginx_self_signed_certificate }}" - name: Remove the CA private key. It has signed its first and last certificate. file: path: "{{ openssl_ca_key }}" state: absent - name: Register MITM mitigation fact (leaf certificate serial number) command: openssl x509 -in {{ nginx_self_signed_certificate }} -noout -serial register: ssl_certificate_serial_number - name: Register more MITM mitigation facts (fingerprints) command: openssl x509 -{{ item }} -in {{ nginx_self_signed_certificate }} -noout -fingerprint with_items: - sha256 - sha1 - md5 register: ssl_certificate_fingerprints - name: Convert the CA certificate into the right format for a data uri command: base64 --wrap=0 {{ openssl_ca_certificate }} register: streisand_certificate_data_uri ================================================ FILE: playbooks/roles/streisand-gateway/templates/firewall-fr.md.j2 ================================================ {% include "languages.md.j2" %} Informations à propos du pare-feu --------------------------------- Ces ports sont ouverts par défaut lorsque Streisand crée un nouveau serveur à partir de zéro sur un fournisseur soutenu. Si vous installez Streisand sur un serveur existant ou un fournisseur alternatif, vous devez vous assurer que le pare-feu autorise les ports suivants: --- * Nginx (Passerelle Streisand) * TCP - {{ nginx_port }} {% if streisand_le_enabled %} * HTTP (Let's Encrypt) * TCP - {{ le_port }} {% endif %} {% if streisand_openconnect_enabled %} * OpenConnect / Cisco AnyConnect * TCP - {{ ocserv_port }} * UDP - {{ ocserv_port }} {% endif %} * OpenSSH * TCP - {{ ssh_port }} {% if streisand_openvpn_enabled %} * OpenVPN * UDP - 53 de/à `{{ dnsmasq_openvpn_tcp_ip }}` * UDP - 53 de/à `{{ dnsmasq_openvpn_udp_ip }}` * Dnsmasq écoute le trafic DNS sur ces adresses IP et répond aux requêtes des clients OpenVPN connectés. * TCP - {{ openvpn_port }} * UDP - {{ openvpn_port_udp }} {% if streisand_stunnel_enabled %} * stunnel * TCP - {{ stunnel_remote_port }} {% endif %} {% endif %} {% if streisand_shadowsocks_enabled %} * Shadowsocks * TCP - {{ shadowsocks_server_port }} * UDP - {{ shadowsocks_server_port }} {% endif %} {% if streisand_tor_enabled %} * Tor * TCP - {{ tor_orport }} - Pont * TCP - {{ tor_obfs4_port }} - obfs4 transport enfichable {% endif %} {% if streisand_wireguard_enabled %} * WireGuard * UDP - 53 de/à `{{ dnsmasq_wireguard_ip }}` * Dnsmasq écoute le trafic DNS sur cette IP et répond aux requêtes des clients WireGuard connectés. * UDP - {{ wireguard_port }} {% endif %} ================================================ FILE: playbooks/roles/streisand-gateway/templates/firewall.md.j2 ================================================ {% include "languages.md.j2" %} Firewall Information -------------------- These ports are open by default when Streisand creates a new server from scratch on a supported provider. If you are installing Streisand on an existing server or alternate provider then you need to make sure the firewall allows traffic to the new server on the following ports: --- * Nginx (Streisand Gateway) * TCP - {{ nginx_port }} {% if streisand_le_enabled %} * HTTP (Let's Encrypt) * TCP - {{ le_port }} {% endif %} {% if streisand_openconnect_enabled %} * OpenConnect / Cisco AnyConnect * TCP - {{ ocserv_port }} * UDP - {{ ocserv_port }} {% endif %} * OpenSSH * TCP - {{ ssh_port }} {% if streisand_openvpn_enabled %} * OpenVPN * UDP - 53 to/from `{{ dnsmasq_openvpn_tcp_ip }}` * UDP - 53 to/from `{{ dnsmasq_openvpn_udp_ip }}` * Dnsmasq listens for DNS traffic on these IPs and responds to requests from connected OpenVPN clients. * TCP - {{ openvpn_port }} * UDP - {{ openvpn_port_udp }} {% if streisand_stunnel_enabled %} * stunnel * TCP - {{ stunnel_remote_port }} {% endif %} {% endif %} {% if streisand_shadowsocks_enabled %} * Shadowsocks * TCP - {{ shadowsocks_server_port }} * UDP - {{ shadowsocks_server_port }} {% endif %} {% if streisand_tor_enabled %} * Tor * TCP - {{ tor_orport }} - Bridge * TCP - {{ tor_obfs4_port }} - obfs4 pluggable transport {% endif %} {% if streisand_wireguard_enabled %} * WireGuard * UDP - 53 to/from `{{ dnsmasq_wireguard_ip }}` * Dnsmasq listens for DNS traffic on this IP and responds to requests from connected WireGuard clients. * UDP - {{ wireguard_port }} {% endif %} ================================================ FILE: playbooks/roles/streisand-gateway/templates/index-fr.md.j2 ================================================ {% include "languages.md.j2" %} Bienvenue sur le serveur passerelle **{{streisand_server_name}}** de [Streisand](https://github.com/StreisandEffect/streisand). Vous n'êtes qu'à quelques minutes d'une connexion libre et non-censurée à l'internet. Instructions de connexion ------------------------- --- Il existe de multiples façons de contourner la censure, et Streisand vous propose plusieurs choix et différents protocoles dans le cas où l'un d'entre eux ne marche pas. {% if streisand_openconnect_enabled %} * [OpenConnect/Cisco AnyConnect](/openconnect/index-fr.html) {% endif %} {% if streisand_openvpn_enabled %} * [OpenVPN (direct)](/openvpn/index-fr.html) {% if streisand_stunnel_enabled %} * [OpenVPN (stunnel)](/openvpn/stunnel-fr.html) {% endif %} {% endif %} {% if streisand_shadowsocks_enabled %} * [Shadowsocks](/shadowsocks/index-fr.html) {% endif %} {% if streisand_ssh_forward_enabled %} * [SSH](/ssh/index-fr.html) {% endif %} {% if streisand_tor_enabled %} * [Tor](/tor/index-fr.html) {% endif %} {% if streisand_wireguard_enabled %} * [WireGuard](/wireguard/index-fr.html) {% endif %} {% if streisand_mirrored_clients %} Miroirs des clients ------------------- --- Vous pouvez télécharger le logiciel client nécessaire directement auprès de Streisand au cas où les sites officiels seraient bloqués. Ces versions pourraient être un peu dépassées (selon la configuration de ce serveur), mais elles peuvent être facilement mises à jour une fois que vous avez connecté à l'i'nternet ouvert. {% if streisand_openconnect_enabled %} * [OpenConnect](/mirror/openconnect/index-fr.html) {% endif %} {% if streisand_openvpn_enabled %} * [OpenVPN](/mirror/openvpn/index-fr.html) {% if streisand_stunnel_enabled %} * [stunnel](/mirror/stunnel/index-fr.html) {% endif %} {% endif %} {% if streisand_shadowsocks_enabled %} * [Shadowsocks](/mirror/shadowsocks/index-fr.html) {% endif %} {% if streisand_ssh_forward_enabled %} * [SSH](/mirror/ssh/index-fr.html) {% endif %} {% if streisand_tor_enabled %} * [Tor Browser Bundle](/mirror/tor/index-fr.html) {% endif %} {% endif %} ================================================ FILE: playbooks/roles/streisand-gateway/templates/index.md.j2 ================================================ {% include "languages.md.j2" %} Welcome to the **{{ streisand_server_name }}** [Streisand](https://github.com/StreisandEffect/streisand) Gateway server. You are only moments away from an uncensored connection to the Internet. Connection Instructions ----------------------- --- There are multiple ways to bypass censorship, and Streisand provides several choices and different protocols in the event that any of them are restricted. {% if streisand_openconnect_enabled %} * [OpenConnect / Cisco AnyConnect](/openconnect/) {% endif %} {% if streisand_openvpn_enabled %} * [OpenVPN (direct)](/openvpn/) {% if streisand_stunnel_enabled %} * [OpenVPN (stunnel)](/openvpn/stunnel.html) {% endif %} {% endif %} {% if streisand_shadowsocks_enabled %} * [Shadowsocks](/shadowsocks/) {% endif %} {% if streisand_ssh_forward_enabled %} * [SSH](/ssh/) {% endif %} {% if streisand_tor_enabled %} * [Tor](/tor/) {% endif %} {% if streisand_wireguard_enabled %} * [WireGuard](/wireguard/) {% endif %} {% if streisand_mirrored_clients %} Client Mirrors -------------- --- You can download the necessary client software directly from Streisand in case the official websites are blocked. These versions could be slightly out of date (depending on when this server was set up) but they can be easily updated once you have connected to the open Internet. {% if streisand_openconnect_enabled %} * [OpenConnect](/mirror/openconnect/) {% endif %} {% if streisand_openvpn_enabled %} * [OpenVPN](/mirror/openvpn/) {% if streisand_stunnel_enabled %} * [stunnel](/mirror/stunnel/) {% endif %} {% endif %} {% if streisand_shadowsocks_enabled %} * [Shadowsocks](/mirror/shadowsocks/) {% endif %} {% if streisand_ssh_forward_enabled %} * [SSH](/mirror/ssh/) {% endif %} {% if streisand_tor_enabled %} * [Tor Browser Bundle](/mirror/tor/) {% endif %} {% endif %} ================================================ FILE: playbooks/roles/streisand-gateway/templates/instructions-fr.md.j2 ================================================ {% include "languages.md.j2" %} Votre passerelle Streisand contient des instructions étape par étape pour les services qu'elle fournit, et des miroirs de tous les logiciels clients nécessaires. {% if le_ok %} Ce document vous montrera comment vous connecter à la passerelle afin de décrire comment configurer vos clients pour se connecter aux services fournis par Streisand. {% else %} Ce document couvre les instructions d'installation du certificat SSL et vous montrera comment vous connecter à la passerelle afin de décrire comment configurer vos clients pour se connecter aux services fournis par Streisand. {% endif %} --- {% if not le_ok %} * [Installation du certificat SSL](#ssl) * [Windows](#ssl-windows) * [macOS](#ssl-macos) * [Android](#ssl-android) * [iOS](#ssl-ios) * [Chromium](#ssl-chromium) * [Firefox](#ssl-firefox) * [Vérification du certificat](#ssl-manual) {% endif %} * [Connexion à votre passerelle Streisand](#connecting) * [SSL](#connecting-ssl) {% if streisand_tor_enabled %} * [Service Caché Tor](#connecting-tor) {% endif %} {% if not le_ok %} Installation du certificat SSL ------------------------------ Vous devez installer le certificat SSL de la passerelle afin que votre navigateur puisse vérifier automatiquement l'intégrité de la connexion. Cela empêche quiconque de falsifier votre trafic et également protègera vos identifiants de connexion. Le certificat a été incorporé directement dans ce fichier HTML, et vous pouvez le télécharger içi:

Télécharger le certificat

### Windows ### Ces instructions fonctionnent pour Chrome et Internet Explorer. Firefox utilise son propre gestionnaire de certificats interne, mais est également [facile à configurer](#ssl-firefox). 1. [Téléchargez le certificat SSL](#download) intégré dans ce document. 1. Double-cliquez pour ouvrir le certificat téléchargé. 1. Cliquez sur *Installer le certificat...* 1. L'Assistant d'importation de certificat démarrera. Cliquez sur *Suivant*. 1. Sélectionnez *Placer tous les certificats dans le magasin suivant* puis cliquez *Parcourir*. 1. Sélectionnez *Autorités de certification racine de confiance* puis cliquez *OK*. 1. Cliquez *Suivant* en suite *Finir*. 1. Confirmez que vous souhaitez installer le certificat en choisissant *Oui*. 1. Redémarrez votre navigateur Web. 1. Vous êtes prêt à vous connecter. Voir [Connexion à votre passerelle Streisand](#connecting-ssl). ### macOS ### Ces instructions fonctionnent pour Chrome et Safari. Firefox utilise son propre gestionnaire de certificats interne, mais est également [facile à configurer](#ssl-firefox). 1. Lancez *Trousseaux d'accès*. 1. [Téléchargez le certificat SSL](#download) intégré dans ce document. 1. Faites glisser le certificat téléchargé dans la section de session. 1. Faites un clic-droit et choisissez *Lire les informations*. 1. Cliquez sur le triangle à guache de *Se fier* pour ouvrir le menu contextuelle. 1. Choisissez l'option *Toujours approuver* dans le menu deroulant pour le champ *(SSL) Secure Sockets Layer*. 1. Fermer la fenêtre 1. Une fenêtre apparaîtra qui vous demandera votre mot de passe; saissisez-le, puis cliquez sur *Mettre à jour les réglages* 1. Vous êtes prêt à vous connecter. Voir [Connexion à votre passerelle Streisand](#connecting-ssl). ### Android ### Ces instructions fonctionnent pour Chrome Firefox pour Android utilise son propre gestionnaire de certificats interne mais ne fournit pas d'interface pour l'importation de certificats. Chrome est donc la manière recommandée de se connecter à la passerelle Streisand. #### Moins sécurisé, mais facile #### 1. [Téléchargez le certificat SSL](#download) intégré dans ce document. 1. Envoyez le certificat téléchargé par courrier électronique à un compte auquel vous pouvez accéder à partir de l'appareil Android. 1. Ouvrez l'e-mail, puis appuyez sur la pièce jointe. 1. Une invite apparaîtra, entez `{{ streisand_server_name }}` pour le *Nom du certificat*, et assurez-vous que *VPN et applications* est sélectionné pour la valeur de *Utilisation du certificat*. 1. Tapez *OK*. 1. Vous êtes prêt à vous connecter. Voir [Connexion à votre passerelle Streisand](#connecting-ssl). #### Plus sécurisé #### 1. Connectez l'appareil Android à votre ordinateur. 1. [Téléchargez le certificat SSL](#download) intégré dans ce document. 1. Faites glisser le certificat téléchargé vers la racine du système de fichiers de l'appareil Android. 1. Lancez l'application *Paramètres*. 1. Faites défiler jusqu'à la section *Appareil* et tapez sur *Sécurité*. 1. Faites défiler jusqu'à la section *Stockage des identifiants* et tapez *Installer depuis la carte SD*. 1. Sélectionnez le certificat que vous avez copié sur votre téléphone. 1. Entrez `{{ streisand_server_name }}` pour le *Nom du certificat*, et assurez-vous que *VPN et applications* est sélectionné pour la valeur de *Utilisation du certificat*. 1. Tapez *OK*. 1. Vous êtes prêt à vous connecter. Voir [Connexion à votre passerelle Streisand](#connecting-ssl). ### iOS ### #### Moins sécurisé, mais facile #### 1. [Téléchargez le certificat SSL](#download) intégré dans ce document. 1. Envoyez le certificat téléchargé par courrier électronique à un compte auquel vous pouvez accéder à partir de l'appareil iOS. 1. Ouvrez l'e-mail avec l'app Mail de iOS, en suite appuyez sur la pièce jointe. 1. L'écran *Installer le profil* apparaîtra. Vous pouvez afficher les détails du certificat et vous assurer qu'ils correspondent aux informations de la section Verification SSL . Tapez *Installer*. 1. Saisissez votre code NIP. 1. Tapez *Installer* à nouveau lorsque l'avertissement apparaît. 1. Tapez *OK*. 1. Vous êtes prêt à vous connecter. Voir [Connexion à votre passerelle Streisand](#connecting-ssl). #### Plus sécurisé #### 1. Connectez le périphérique iOS à votre ordinateur macOS. 1. Tapez *Se fier* si l'avertissement *Faire confiance à cet ordinateur* apparaît. 1. Installez l'utilitaire Apple Configurator 2 depuis le Mac App Store. 1. Lancez l'utilitaire Apple Configurator et acceptez la licence. 1. Cliquez *Start Preparing Devices*. 1. Cliquez *Install Profiles...*. 1. Allumez le téléphone et déverouille-le. 1. Cliquez *Next*. 1. Cliquez *New...* and entrez `{{ streisand_server_name }}` dans le champ *Name* dans la section *General*. 1. Faites défiler jusqu'à la section *Certificates*. 1. [Téléchargez le certificat SSL](#download) intégré dans ce document. 1. Cliquez *Configure*, sélectionnez le certificat téléchargé et cliquez sur *Open*. 1. Cliquez *Save*. 1. Cocher l'options à côté du profil `{{ streisand_server_name }}` que vous avez crée. Cliquez *Next*. 1. L'écran *Installer le profile* apparaîtra sur votre appareil iOS. Tapez *Installer*. 1. Tapez *Installer* à nouveau lorsque l'avertissement apparaît. 1. Tapez *OK*. 1. Vous êtes prêt à vous connecter. Voir [Connexion à votre passerelle Streisand](#connecting-ssl). ### Chromium ### 1. Cliquez sur le bouton Menu et accédez à *Paramètres*. 1. Faites défiler jusqu'au bas de la fenêtre et cliquez sur *Afficher les paramètres avancés...* 1. Faites défiler jusqu'au la section HTTPS/SSL et cliquez *Gérer les certificats...* 1. Accedez a l'ognlet *Autorités*. 1. [Téléchargez le certificat SSL](#download) intégré dans ce document. 1. Cliquez *Importer...*, sélectionnez le certificat, en suite cliquez *Ouvrir*. 1. Une invite apparaîtra. Cochez l'option à côté de *Confiez ce certificat pour identifier les sites Web* et cliquez sur *OK*. 1. Cliquez *OK*. 1. Vous êtes prêt à vous connecter. Voir [Connexion à votre passerelle Streisand](#connecting-ssl). ### Firefox ### 1. Lancez Firefox. 1. Ouvrez le panneau *Options*, et sélectionnez *Préférences*. 1. Naviguez vers *Vie privée et sécurité*. 1. Défiler jusqu'à la rubrique *Sécurité > Certificats* 1. Cliquez sur *Afficher les certificats*. 1. Une fenêtre apparaîtra. Cliquez sur l'onglet *Autorités*. 1. [Téléchargez le certificat SSL](#download) intégré dans ce document. 1. Cliquez *Importer...*, sélectionnez le certificat, en suite cliquez *Ouvrir*. 1. Une invite apparaîtra. Cochez l'option à côté de *Confiez ce certificat pour identifier les sites Web* et cliquez sur *OK*. 1. Cliquez *OK* pour fermer le gestionaire des ceritificats, en suite fermer le panneau de *Préférences*. 1. Vous êtes prêt à vous connecter. Voir [Connexion à votre passerelle Streisand](#connecting-ssl). ### Verification SSL manuelle ### *L'option de vérification du certificat manuel est significativement moins sécurisée que l'installation du certificat en utilisant l'une des méthodes ci-dessus. Votre navigateur affichera un message d'avertissement et vous serai plus vulnérable aux [attaques de l'homme du milieu](https://fr.wikipedia.org/wiki/Attaque_de_l%27homme_du_milieu) car les empreintes digitales doivent être vérifiées lors de chaque tentative de connexion. Il devrait être utilisé avec beaucoup de soin, et seulement en dernier recours.* Les détails du certificat doivent correspondre aux informations suivantes: ##### Numéro de série ##### `{{ ssl_certificate_serial_number.stdout }}` ##### Empreintes ##### {% for fingerprint in ssl_certificate_fingerprints.results %} {{ fingerprint.stdout }} {% endfor %} Si tout vérifie, vous êtes prêt à vous connecter. Voir [Connexion à votre passerelle Streisand](#connecting-ssl). {% endif %} Connexion à votre passerelle Streisand -------------------------------------- ### SSL ### {% if le_ok %} [{{ streisand_domain }}](https://{{ streisand_domain }}/index-fr.html) {% else %} [{{ streisand_gateway_url }}]({{ streisand_gateway_url }}/index-fr.html) {% endif %} username: `{{ streisand_gateway_username }}` password: `{{ streisand_gateway_password.stdout }}` {% if streisand_tor_enabled %} ### Service Caché Tor ### *Toutes les connexions aux services cachés Tor sont entièrement chiffrées.* [{{ tor_hidden_service_url }}]({{ tor_hidden_service_url }}/index-fr.html) username: `{{ streisand_gateway_username }}` password: `{{ streisand_gateway_password.stdout }}` {% endif %} ================================================ FILE: playbooks/roles/streisand-gateway/templates/instructions.md.j2 ================================================ {% include "languages.md.j2" %} Your Streisand Gateway contains step-by-step instructions for the services it provides, and mirrors of all necessary client software. {% if le_ok %} This document will show you how to connect to the Gateway to learn how to configure clients to use the Streisand services. {% else %} This document covers the SSL Certificate installation instructions and will show you how to connect to the Gateway to learn how to configure clients to use the Streisand services. {% endif %} --- {% if not le_ok %} * [SSL Certificate Installation](#ssl) * [Windows](#ssl-windows) * [macOS](#ssl-macos) * [Android](#ssl-android) * [iOS](#ssl-ios) * [Chromium](#ssl-chromium) * [Firefox](#ssl-firefox) * [Manual Certificate Verification](#ssl-manual) {% endif %} * [Connecting to your Streisand Gateway](#connecting) * [SSL](#connecting-ssl) {% if streisand_tor_enabled %} * [Tor Hidden Service](#connecting-tor) {% endif %} {% if not le_ok %} SSL Certificate Installation ---------------------------- You should install the Gateway's SSL certificate so your browser can automatically verify the integrity of the connection. This prevents anyone from tampering with your traffic and also protects your login credentials. The certificate has been embedded directly into this HTML file, and you can download it here:

Download Certificate

### Windows ### These instructions work for Chrome and Internet Explorer. Firefox uses its own internal Certificate Manager, but it's [easy to configure](#ssl-firefox) too. 1. [Download the SSL certificate](#download) embedded in this document. 1. Double-click to open the downloaded certificate. 1. Click *Install Certificate...* 1. The Certificate Import Wizard will start. Click *Next*. 1. Select *Place all certificates in the following store* and click *Browse*. 1. Select *Trusted Root Certification Authorities* and click *OK*. 1. Click *Next* and then click *Finish*. 1. Confirm that you would like to install the certificate by choosing *Yes*. 1. Close and re-open your browser. 1. You are ready to connect. See [Connecting to your Streisand Gateway](#connecting-ssl). ### macOS ### These instructions work for Chrome and Safari. Firefox uses its own internal Certificate Manager, but it's [easy to configure](#ssl-firefox) too. 1. Go to *Applications > Utilities* and launch *Keychain Access*. 1. [Download the SSL certificate](#download) embedded in this document. 1. Drag the downloaded certificate into the login keychain section. 1. A window will appear asking if you want to trust the certificate. Click *Always Trust*. 1. Enter your password when you are prompted to do so, and click *Update Settings*. 1. Right-click on the imported certificate and select *Get Info*. 1. Click the arrow next to *Trust* and more options will appear. 1. Choose *Always Trust* next to the *Secure Sockets Layer (SSL)* option. 1. Close the certificate window and enter your password again. 1. You are ready to connect. See [Connecting to your Streisand Gateway](#connecting-ssl). ### Android ### These instructions work for Chrome. Firefox for Android uses its own internal Certificate Manager but does not provide an interface for importing certificates yet. Chrome is therefore the recommended way to connect to the Streisand Gateway. #### Less secure, but easy #### 1. [Download the SSL certificate](#download) embedded in this document. 1. Email the downloaded certificate to an account that can be accessed from the Android device. 1. Open the email and tap the attachment. 1. A popup will appear. Enter `{{ streisand_server_name }}` for the *Certificate name*, and make sure *VPN and apps* is selected for the *Credential use* value. 1. Tap *OK*. 1. You are ready to connect. See [Connecting to your Streisand Gateway](#connecting-ssl). #### More secure #### 1. Connect the Android device to your computer. 1. [Download the SSL certificate](#download) embedded in this document. 1. Drag the downloaded certificate to the root of the Android device's filesystem. 1. Launch the *Settings* application. 1. Scroll to the *Personal* section and tap *Security*. 1. Scroll to the *Credential Storage* section and tap *Install from storage*. 1. Select the certificate that you copied to your phone. 1. Enter `{{ streisand_server_name }}` for the *Certificate name*, and make sure *VPN and apps* is selected for the *Credential use* value. 1. Tap *OK*. 1. You are ready to connect. See [Connecting to your Streisand Gateway](#connecting-ssl). ### iOS ### #### Less secure, but easy #### 1. [Download the SSL certificate](#download) embedded in this document. 1. Email the downloaded certificate to an account that can be accessed from the iOS device. 1. Open the email in the iOS Mail app, and tap the attachment. 1. The *Install Profile* screen will appear. You can view the certificate details and make sure they match the information from the SSL Verification section. Tap *Install*. 1. Tap *Install* again when the warning appears. 1. Tap *Done*. 1. You are ready to connect. See [Connecting to your Streisand Gateway](#connecting-ssl). #### More secure #### 1. Connect the iOS device to your macOS computer. 1. Tap *Trust* if the *Trust This Computer?* popup appears. 1. Install the Apple Configurator 2 utility from the Mac App Store. 1. Launch the Apple Configurator utility and agree to the license. 1. Click *Start Preparing Devices*. 1. Click *Install Profiles...*. 1. Turn the phone on and unlock it. 1. Click *Next*. 1. Click *New...* and enter `{{ streisand_server_name }}` in the *Name* field of the *General* section. 1. Scroll down to the *Certificates* section. 1. [Download the SSL certificate](#download) embedded in this document. 1. Click *Configure*, select the downloaded certificate, and click *Open*. 1. Click *Save*. 1. Check the checkbox next to the `{{ streisand_server_name }}` profile you just created. Click *Next*. 1. The *Install Profile* screen will appear on your iOS device. Tap *Install*. 1. Tap *Install* again when the warning appears. 1. Tap *Done*. 1. You are ready to connect. See [Connecting to your Streisand Gateway](#connecting-ssl). ### Chromium ### 1. Click the Menu button and go to *Settings*. 1. Scroll to the bottom of the window and click *Show advanced settings...* 1. Scroll to the HTTPS/SSL section and click *Manage certificates...* 1. Go the *Authorities* tab. 1. [Download the SSL certificate](#download) embedded in this document. 1. Click *Import...*, select the downloaded certificate, and click *Open*. 1. A popup will appear. Check the box next to *Trust this certificate for identifying websites* and click *OK*. 1. Click *Done*. 1. You are ready to connect. See [Connecting to your Streisand Gateway](#connecting-ssl). ### Firefox ### 1. Launch Firefox. 1. Open the *Options* panel, and select *Preferences*. 1. Go to the *Privacy & Security* tab. 1. Scroll down to the *Security > Certificates* heading. 1. Click *View Certificates*. 1. A window will appear. Click on the *Authorities* tab. 1. [Download the SSL certificate](#download) embedded in this document. 1. Click *Import...*, select the downloaded certificate, and click *Open*. 1. A popup will appear. Check the box next to *Trust this CA to identify websites* and click *OK*. 1. Click *OK* to close the certificate manager, and then close the *Preferences* panel. 1. You are ready to connect. See [Connecting to your Streisand Gateway](#connecting-ssl). ### Manual SSL Verification ### *The manual certificate verification option is significantly less secure than installing the certificate using one of the methods above. Your browser will display a warning message, and you are more vulnerable to man-in-the-middle attacks because the fingerprints must be verified on every connection attempt. It should be used with great care, and only as a last resort.* The certificate details should match the following information: ##### Serial Number ##### `{{ ssl_certificate_serial_number.stdout }}` ##### Fingerprints ##### {% for fingerprint in ssl_certificate_fingerprints.results %} {{ fingerprint.stdout }} {% endfor %} If everything checks out, you are ready to connect. See [Connecting to your Streisand Gateway](#connecting-ssl). {% endif %} Connecting to your Streisand Gateway ------------------------------------ ### SSL ### {% if le_ok %} [{{ streisand_domain }}](https://{{ streisand_domain }}) {% else %} [{{ streisand_gateway_url }}]({{ streisand_gateway_url }}) {% endif %} username: `{{ streisand_gateway_username }}` password: `{{ streisand_gateway_password.stdout }}` {% if streisand_tor_enabled %} ### Tor Hidden Service ### *All connections to Tor hidden services are fully encrypted.* [{{ tor_hidden_service_url }}]({{ tor_hidden_service_url }}) username: `{{ streisand_gateway_username }}` password: `{{ streisand_gateway_password.stdout }}` {% endif %} ================================================ FILE: playbooks/roles/streisand-gateway/templates/openssl-local.cnf.j2 ================================================ # The examples in https://github.com/stanzgy/wiki/blob/master/network/openssl-self-signed-certs-cheatsheet.md # were very useful in the creation of this file. distinguished_name = req_distinguished_name [ ca ] default_ca = CA_default [ CA_default ] dir = {{ openssl_ca_base }} certs = $dir crl_dir = $dir database = $dir/index.txt new_certs_dir = {{ openssl_ca_newcerts_dir }} certificate = {{ openssl_ca_certificate }} serial = $dir/serial crl = $dir/crl.pem private_key = {{ openssl_ca_key }} RANDFILE = $dir/.rand copy_extensions = copy x509_extensions = v3_ca default_days = {{ nginx_days_valid }} default_crl_days= 30 default_md = {{ openssl_ca_default_md }} preserve = no policy = policy_anything_including_no_cn [ policy_anything_including_no_cn ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional # This differs from the certificates role: commonName = optional name = optional emailAddress = optional [ alt_names ] IP.0 = {{ streisand_ipv4_address }} DNS.0 = {{ streisand_ipv4_address }} [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = {{ nginx_key_country }} stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = {{ nginx_key_province }} localityName = Locality Name (eg, city) localityName_default = {{ nginx_key_city }} 0.organizationName = Organization Name (eg, company) 0.organizationName_default = {{ nginx_key_org }} organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = {{ nginx_key_ou }} commonName = Common Name (eg, your name or your server\'s hostname) commonName_default = STREISAND [ v3_ca ] basicConstraints = CA:TRUE # Note: we need an EKU when we do OCSP, but don't open that can of worms yet. # extendedKeyUsage = OCSPSigning [ v3_ca_req ] basicConstraints = CA:FALSE keyUsage = digitalSignature, keyEncipherment, nonRepudiation [ v3_req ] basicConstraints = CA:false subjectAltName = @alt_names keyUsage = digitalSignature, keyEncipherment, nonRepudiation extendedKeyUsage=serverAuth,clientAuth ================================================ FILE: playbooks/roles/streisand-gateway/templates/vhost.j2 ================================================ # Setup an http -> https redirect server { listen 80; {% if streisand_le_enabled %} server_name {{ streisand_domain }}; # Allow ACME challenge requests by the ACME server (letsencrypt) location /.well-known/acme-challenge/ { # This directory is intentionally set to be different # than the Streisand gateway directory. We want to avoid any # chance of leaking gateway credentials/pages over port 80. root {{ nginx_default_html_path }}; allow all; } {% else %} server_name {{ streisand_ipv4_address }}; {% endif %} location / { return 301 https://$server_name$request_uri; } # Disable all logging # Comment these lines out if you want to verify the letsencrypt # ACME challenge/response process is working for debugging purposes. access_log /dev/null; error_log /dev/null crit; } # Catch-all server to prevent requests that either change the HOST header # or request an invalid domain/subdomain. server { listen 80 default_server; server_name _; return 444; # "Connection closed without response" } # SSL Gateway server { listen 127.0.0.1:{{ nginx_port }} default_server ssl http2; {% if le_ok %} ssl_certificate {{ le_certificate }}; ssl_certificate_key {{ le_private_key }}; ssl_trusted_certificate {{ le_chain }}; ssl_stapling on; ssl_stapling_verify on; # resolver resolver {{ upstream_dns_servers | join(' ') }} valid=300s; resolver_timeout 5s; # It'd be great to include HSTS in non-LE installs too, but # HSTS disables "clicking through" the HTTPS error pages. add_header Strict-Transport-Security max-age=63072000; {% else %} ssl_certificate {{ nginx_self_signed_certificate }}; ssl_certificate_key {{ nginx_private_key }}; {% endif %} auth_basic "Authorization Required"; auth_basic_user_file {{ streisand_gateway_htpasswd_file }}; ssl_protocols TLSv1.2; ssl_prefer_server_ciphers on; ssl_ciphers '{{ streisand_tls_ciphers }}'; ssl_ecdh_curve auto; ssl_session_timeout 10m; ssl_session_cache shared:SSL:10m; ssl_session_tickets off; # headers add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; add_header X-Robots-Tag none; add_header Referrer-Policy "no-referrer"; # Disable all logging access_log /dev/null; error_log /dev/null crit; root {{ streisand_gateway_location }}; index index.html index.htm; location / { autoindex off; } } ================================================ FILE: playbooks/roles/streisand-gateway/vars/main.yml ================================================ --- streisand_gateway_url: "https://{{ streisand_ipv4_address }}" streisand_gateway_htpasswd_file: "/etc/nginx/htpasswd" streisand_gateway_password_file: "/etc/nginx/gateway-password.txt" streisand_gateway_password_localpath: "../{{ streisand_local_directory }}/gateway-password.txt" streisand_temp_gateway_path: "/var/local/streisand.gateway" openssl_ca_base: "/root/ca" openssl_ca_private_dir: "{{ openssl_ca_base }}/private" openssl_ca_newcerts_dir: "{{ openssl_ca_base }}/newcerts" openssl_ca_key: "{{ openssl_ca_private_dir }}/cakey.pem" openssl_ca_certificate: "{{ openssl_ca_base }}/cacert.pem" openssl_ca_default_md: "sha256" nginx_days_valid: "1825" nginx_self_signed_certificate: "/etc/ssl/certs/{{ streisand_server_name }}.crt" nginx_self_signed_certificate_request: "/etc/ssl/certs/{{ streisand_server_name }}.csr" nginx_private_key: "/etc/ssl/private/{{ streisand_server_name }}.key" ================================================ FILE: playbooks/roles/streisand-mirror/tasks/main.yml ================================================ --- - name: Determine if there is a need to mirror client software # NOTE(@cpu): If any additional roles start to mirror client software this # conditional fact value should be updated to include the role's enabled var. set_fact: streisand_mirrored_clients: > streisand_openconnect_enabled or streisand_openvpn_enabled or streisand_shadowsocks_enabled or streisand_ssh_forward_enabled or streisand_stunnel_enabled or streisand_tor_enabled - name: Make the directory where mirrored files will be stored file: path: "{{ streisand_mirror_location }}" owner: www-data group: www-data mode: 0755 state: directory - include_role: name: i18n-docs vars: title: "Streisand mirror" i18n_location: "{{ streisand_mirror_location }}" input_template_name: "mirror-index" # Only generate the Markdown mirror page when there is a role enabled that # mirrors client software when: streisand_mirrored_clients ================================================ FILE: playbooks/roles/streisand-mirror/templates/mirror-index-fr.md.j2 ================================================ Miroirs des clients ------------------- Les fichiers reflétés ci-dessous ont tous été cryptographiquement vérifiés. Dans le cas où c'est possible, vérification avec les clés de signature GPG officielles de chaque projet ont été utilisées. Les sommes de contrôle SHA-256 vérifie l'intégrité de tous les autres fichiers. --- * Les clients {% if streisand_openconnect_enabled %} * [OpenConnect](openconnect/index-fr.html) {% endif %} {% if streisand_openvpn_enabled %} * [OpenVPN](openvpn/index-fr.html) {% if streisand_stunnel_enabled %} * [Stunnel](stunnel/index-fr.html) {% endif %} {% endif %} {% if streisand_shadowsocks_enabled %} * [Shadowsocks](shadowsocks/index-fr.html) {% endif %} {% if streisand_ssh_forward_enabled %} * [OpenSSH](ssh/index-fr.html) {% endif %} {% if streisand_tor_enabled %} * [Tor Browser Bundle](tor/index-fr.html) {% endif %} ================================================ FILE: playbooks/roles/streisand-mirror/templates/mirror-index.md.j2 ================================================ Client Mirrors -------------- The files mirrored below have all been cryptographically verified. Where possible, each project's official GPG signing keys were used. SHA-256 checksums verified the integrity of all other files. --- * Clients {% if streisand_openconnect_enabled %} * [OpenConnect](openconnect/) {% endif %} {% if streisand_openvpn_enabled %} * [OpenVPN](openvpn/) {% if streisand_stunnel_enabled %} * [Stunnel](stunnel/) {% endif %} {% endif %} {% if streisand_shadowsocks_enabled %} * [Shadowsocks](shadowsocks/) {% endif %} {% if streisand_ssh_forward_enabled %} * [OpenSSH](ssh/) {% endif %} {% if streisand_tor_enabled %} * [Tor Browser Bundle](tor/) {% endif %} ================================================ FILE: playbooks/roles/streisand-mirror/vars/main.yml ================================================ --- streisand_mirror_location: "{{ streisand_gateway_location }}/mirror" streisand_mirror_warning: "One or more of the VPN clients could not be mirrored. Please file a bug report on GitHub so that the version number, checksum, or download location can be updated. Setup will now continue." streisand_mirror_warning_seconds: "20" ================================================ FILE: playbooks/roles/stunnel/defaults/main.yml ================================================ --- stunnel_cert_country: "GB" stunnel_cert_province: "Berkshire" stunnel_cert_city: "Slough" stunnel_cert_org: "Wernham Hogg Paper Company" stunnel_cert_ou: "Gareth Keenan Investigations" stunnel_key_size: "3072" ================================================ FILE: playbooks/roles/stunnel/handlers/main.yml ================================================ --- - name: Restart stunnel systemd: name: stunnel.service state: restarted ================================================ FILE: playbooks/roles/stunnel/tasks/firewall.yml ================================================ --- - name: Ensure UFW allows stunnel ufw: to_port: "{{ stunnel_remote_port }}" proto: "tcp" rule: "allow" ================================================ FILE: playbooks/roles/stunnel/tasks/main.yml ================================================ --- - name: Install stunnel apt: package: stunnel4 - name: Generate the stunnel private key command: openssl genrsa -out {{ stunnel_key }} {{ stunnel_key_size }} args: creates: "{{ stunnel_key }}" - name: Generate the stunnel certificate. shell: openssl req -new -nodes -x509 -key {{ stunnel_key}} -days {{ stunnel_days_valid }} -subj "{{ stunnel_request_subject }}/CN=stunnel" > {{ stunnel_cert }} args: creates: "{{ stunnel_cert }}" - name: "Export the key and certificate file in PKCS #12 format" command: "openssl pkcs12 -export -in {{ stunnel_cert }} -inkey {{ stunnel_key }} -out {{ stunnel_pkcs12 }} -password pass:" args: creates: "{{ stunnel_pkcs12 }}" - name: Set the proper permissions on the stunnel key file file: path: "{{ stunnel_key }}" owner: root group: root mode: 0600 - name: Generate remote stunnel configuration file (for the server) template: src: stunnel-remote.conf.j2 dest: "{{ stunnel_path }}/stunnel.conf" notify: Restart stunnel - name: Generate local stunnel configuration file (for connecting clients) template: src: stunnel-local.conf.j2 dest: "{{ openvpn_gateway_location }}/stunnel.conf" - name: Stop (init.d's) stunnel4 systemd: name: stunnel4.service state: stopped masked: yes - name: Copy the stunnel system unit file template: src: stunnel.service.j2 dest: /etc/systemd/system/stunnel.service mode: "0644" - name: Enable the stunnel service systemd: daemon_reload: yes name: stunnel.service enabled: yes state: restarted # Set up the stunnel firewall rules - import_tasks: firewall.yml # Mirror the stunnel client - import_tasks: mirror.yml ================================================ FILE: playbooks/roles/stunnel/tasks/mirror.yml ================================================ --- - name: Include the stunnel mirror variables include_vars: mirror.yml - name: Make the directory where the stunnel mirrored files will be stored file: path: "{{ stunnel_mirror_location }}" owner: www-data group: www-data mode: 0755 state: directory - block: - include_role: name: download-and-verify vars: project_name: "stunnel" project_signer: "Michal Trojnara" project_download_baseurl: "{{ stunnel_base_download_url }}" project_download_files: "{{ stunnel_download_files }}" project_download_location: "{{ stunnel_mirror_location }}" project_signer_keyid: "{{ stunnel_gpg_keyid }}" rescue: - name: "{{ streisand_mirror_warning }}" pause: seconds: "{{ streisand_mirror_warning_seconds }}" - name: Get the current stunnel version from the downloaded source file shell: ls *.tar.gz | tail -n 1 | sed -e 's/stunnel-\(.*\)\.tar\.gz/\1/' args: chdir: "{{ stunnel_mirror_location }}" register: stunnel_latest_check changed_when: False - name: Set the target stunnel version set_fact: stunnel_version: "{{ stunnel_latest_check.stdout }}" - include_role: name: i18n-docs vars: title: "stunnel mirror" i18n_location: "{{ stunnel_mirror_location }}" input_template_name: "mirror" ================================================ FILE: playbooks/roles/stunnel/templates/mirror-fr.md.j2 ================================================ {% include "languages.md.j2" %} ### stunnel ### **Source** * [{{ stunnel_source_filename }}]({{ stunnel_source_href }}) ([sig]({{ stunnel_source_sig_href }})) **Windows** * [{{ stunnel_installer_filename }}]({{ stunnel_installer_href }}) ([sig]({{ stunnel_installer_sig_href }})) ================================================ FILE: playbooks/roles/stunnel/templates/mirror.md.j2 ================================================ {% include "languages.md.j2" %} ### stunnel ### **Source** * [{{ stunnel_source_filename }}]({{ stunnel_source_href }}) ([sig]({{ stunnel_source_sig_href }})) **Windows** * [{{ stunnel_installer_filename }}]({{ stunnel_installer_href }}) ([sig]({{ stunnel_installer_sig_href }})) ================================================ FILE: playbooks/roles/stunnel/templates/stunnel-local.conf.j2 ================================================ client = yes [stunnel] accept = 127.0.0.1:{{ stunnel_local_port }} connect = {{ streisand_ipv4_address }}:{{ stunnel_remote_port }} ================================================ FILE: playbooks/roles/stunnel/templates/stunnel-remote.conf.j2 ================================================ cert = {{ stunnel_cert }} key = {{ stunnel_key }} debug = 4 options = NO_SSLv2 options = NO_SSLv3 options = NO_TLSv1 options = NO_TLSv1.1 ciphers = {{ streisand_tls_ciphers }} [stunnel] accept = {{ stunnel_remote_port }} connect = 127.0.0.1:{{ openvpn_port }} ================================================ FILE: playbooks/roles/stunnel/templates/stunnel.service.j2 ================================================ # source https://github.com/liuliang/centos-stunnel-systemd/blob/master/stunnel.service [Unit] Description=SSL tunnel for network daemons After=openvpn@server.target [Install] WantedBy=multi-user.target Alias=stunnel.target [Service] Type=forking ExecStart=/usr/bin/stunnel {{ stunnel_path }}/stunnel.conf ExecStop=/usr/bin/killall -9 stunnel # Give up if ping doesn't get an answer within the timeout TimeoutSec=600 Restart=always PrivateTmp=true ================================================ FILE: playbooks/roles/stunnel/vars/main.yml ================================================ --- stunnel_days_valid: "1825" stunnel_request_subject: "/C={{ stunnel_cert_country }}/ST={{ stunnel_cert_province }}/L={{ stunnel_cert_city }}/O={{ stunnel_cert_org }}/OU={{ stunnel_cert_ou }}" stunnel_path: "/etc/stunnel" stunnel_cert: "{{ stunnel_path }}/stunnel.crt" stunnel_key: "{{ stunnel_path }}/stunnel.key" stunnel_pkcs12: "{{ openvpn_gateway_location }}/stunnel.p12" ================================================ FILE: playbooks/roles/stunnel/vars/mirror.yml ================================================ --- # Stunnel Download variables # -------------------------- stunnel_mirror_location: "{{ streisand_mirror_location }}/stunnel" stunnel_mirror_href_base: "/mirror/stunnel" stunnel_base_download_url: "https://www.stunnel.org" stunnel_installer_url: "stunnel-latest-installer.exe" stunnel_installer_sig_url: "stunnel-latest-installer.exe.asc" stunnel_installer_filename: "{{ stunnel_installer_url }}" stunnel_installer_sig_filename: "{{ stunnel_installer_filename }}.asc" stunnel_installer_href: "{{ stunnel_mirror_href_base }}/{{ stunnel_installer_filename }}" stunnel_installer_sig_href: "{{ stunnel_mirror_href_base }}/{{ stunnel_installer_sig_filename }}" stunnel_source_filename: "stunnel-{{ stunnel_version }}.tar.gz" stunnel_source_sig_filename: "{{ stunnel_source_filename }}.asc" stunnel_source_href: "{{ stunnel_mirror_href_base }}/{{ stunnel_source_filename }}" stunnel_source_sig_href: "{{ stunnel_mirror_href_base }}/{{ stunnel_source_sig_filename }}" stunnel_source_url: "stunnel-latest.tar.gz" stunnel_source_sig_url: "stunnel-latest.tar.gz.asc" stunnel_gpg_keyid: "D416E014" stunnel_download_files: - { "file": "{{ stunnel_installer_url }}", "sig": "{{ stunnel_installer_sig_url }}" } - { "file": "{{ stunnel_source_url }}", "sig": "{{ stunnel_source_sig_url }}" } ================================================ FILE: playbooks/roles/sysctl/tasks/main.yml ================================================ --- - block: - name: Apply custom sysctl values sysctl: name: "{{ item.key }}" value: "{{ item.value }}" ignoreerrors: yes sysctl_set: yes reload: yes state: present with_items: "{{ sysctl_values }}" when: ansible_virtualization_type != 'lxc' ================================================ FILE: playbooks/roles/sysctl/vars/main.yml ================================================ --- sysctl_values: - { key: kernel.sysrq, value: 0 } - { key: kernel.core_uses_pid, value: 1 } - { key: net.ipv4.tcp_syncookies, value: 1 } - { key: kernel.msgmnb, value: 65536 } - { key: kernel.msgmax, value: 65536 } - { key: kernel.shmmax, value: 68719476736 } - { key: kernel.shmall, value: 4294967296 } - { key: net.ipv4.conf.all.accept_source_route, value: 0 } - { key: net.ipv4.conf.default.accept_source_route, value: 0 } - { key: net.ipv4.conf.all.log_martians, value: 1 } - { key: net.ipv4.conf.default.log_martians, value: 1 } - { key: net.ipv4.conf.all.accept_redirects, value: 0 } - { key: net.ipv4.conf.default.accept_redirects, value: 0 } - { key: net.ipv4.conf.all.send_redirects, value: 0 } - { key: net.ipv4.conf.default.send_redirects, value: 0 } - { key: net.ipv4.conf.all.rp_filter, value: 0 } - { key: net.ipv4.conf.default.rp_filter, value: 0 } - { key: net.ipv4.icmp_echo_ignore_broadcasts, value: 1 } - { key: net.ipv4.icmp_ignore_bogus_error_responses, value: 1 } - { key: net.ipv4.conf.all.secure_redirects, value: 0 } - { key: net.ipv4.conf.default.secure_redirects, value: 0 } - { key: kernel.randomize_va_space, value: 1 } - { key: net.core.wmem_max, value: 12582912 } - { key: net.core.rmem_max, value: 12582912 } - { key: fs.suid_dumpable, value: 0 } - { key: fs.protected_hardlinks, value: 1 } - { key: fs.protected_symlinks, value: 1 } ================================================ FILE: playbooks/roles/test-client/files/openvpn_signing.key ================================================ -----BEGIN PGP PUBLIC KEY BLOCK----- mQENBE45PsIBCAC2K2LRZPQIUmJlCDKcncfR6vok2wowDpGpHZffvEEoUj/DoocR LLpPHR5RB1zMWIs2IjF8vOtXMCBguDgtEvQTh6p6DM3D1fTnYp3pPlQyyzAuC81v CQo44h09R4Nh2e38oMRVztmAnacC4g5aiSEamrZ4PbWdAdPc4uZdCPOGmUDJw8+q aAYvL/8pM7YqEu05FqE+aNcG02K+mDhA2bqRLLKoLEFpeMSO6vV8BrE7Vw1Rs1PM VLDJt9HdXmC6vP+WWqDuj7/qfRb2wwlSIp5+aFyRHOUNyFKnWZYIObeV3+Y6oG6h gmBtU1673mHDqVy26TwfjpJeudMKHVCrKXVXABEBAAG0QVNhbXVsaSBTZXBww6Ru ZW4gKE9wZW5WUE4gVGVjaG5vbG9naWVzLCBJbmMpIDxzYW11bGlAb3BlbnZwbi5u ZXQ+iQFVBBMBAgA/AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgBYhBDDr9Oc8 zmPu4STdJ45tqLThWMVpBQJZeJ2tBQkQ4vlrAAoJEI5tqLThWMVpPDUH/RdLsdG/ 4kmal/rfbso3YVxZXGp2fHKrptvCVrUWluYs6H/XBV4x6aMe8Q6K7Qa7BSLA9jZ8 v+UN/4aA+urBcs6Ted/XbP3mKU47tOotW24nA1LRjd4gUSEXCaEOBbCSyw3uw6Vz U1wr1gEmkC7kvBziL+Pcbt5tKTRhUfgbcjYNNdp/nAwn3Pm3OFRaBt/qDU2aYAOH +k191x/ovDRO/UiU2CVvrdfv/VMZfo/rwxe8IiirxQ4k5DR2Vyu0DMNzlNTqRk8l rUH0FBdl0rOiefH0m6ubKstpYCaOUYsh/FaW53O6qqrTlZqPtAav1cRog8zb8mhT sFFAarhnZcQ/DG+5AQ0ETjk+wgEIAOg+Bjk8Wnb7fbbwBDDUalLsIEgFUhsrSLD5 VVYB8tOq7djshckp/3LwfkSsmUzEtXMXxIbDUON1vbCQXZlQDe7E7uY5KFNWyi4+ UJwLMrs+oqfeduUzDxQ+voq/6NGl+2olqd6vT/c/uPb/RPZpOdgoEkqFEOTMRVz0 DZwAyzyYEWBrwDECNbEtqefMLPIaUGUzZvUc80I+MYL6AzRe/utIWcBnZ2nydZ0S vWKRJ0lOs69e6KoFVeE5QXzmTXkjzSbR9eN3ADm2j0EjLnpt/zR4hF8s4l4HLdRd Sn47tAdvahsNfgWmOfiQD8btnu8DiMiJMd8IpVsZX/zCJbSUChcAEQEAAYkBPAQY AQIAJgIbDBYhBDDr9Oc8zmPu4STdJ45tqLThWMVpBQJZeJ2DBQkQ4vlBAAoJEI5t qLThWMVpCCIH+QFqEY+Xk5gJc10lbJUZEhJIknS/3GEd+3WBHgBtBaQCeK7+bFQP ZagTN4SJLiwYcQDV04mZTpFOJV1k9AYaz7ENEjHe51mGhPM9sm5Ix7KwMNo0lHJ+ ryZ0zyie28IbGz+rYa7OdkhE2EmcQkezYNWC03G8yR9yGk3QZ3CtPPO/xYP2tBGc OocqWUkVuR7KpitT9QnOZ4af26b83Vr/+qJ1FdSfW6/VAbyboVWya4oEnKSUusBm 0WCQzaLH15EpzgcdB/x8KVOTS1dAA5GNyRyhbRfP6yBXgBruCkPa4/np78/72jjW vbAvOhOEMnfzWmf3VZq+q6hhIJf6Sp+dcoU= =P3ax -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: playbooks/roles/test-client/files/shadowsocks-qr-decode.py ================================================ #!/usr/bin/python2 # # shadowsocks-qr-decode.py # # This script accepts a path to a QR code image as an input. If the QR code # contains an encoded `ss://` URL for the Shadowsocks protocol[0] # then it is processed and the constituent parts (method, password, server # address, etc) are output to stdout. # This allows other tools (e.g. a test script) to configure itself based on # a QR code from a Shadowsocks server. # # This script requires `zbar-tools` be installed from apt. You might ask # yourself "Why not use the `qrtools` Python package?" The answer is that # getting a 3rd party Python dep requires `pip` be installed and we have no # other reasons to use it. More importantly, it turns out `qrtools` uses the # same lib underneath:`zbar`. # # TODO(@cpu) - update for python3 compatibility. The `urlparse` module was # moved. import urlparse import sys import base64 import re import argparse from subprocess import check_output parser = argparse.ArgumentParser(description="Process a Shadowsocks QR Code") parser.add_argument('file', help="path to a Shadowsocks QR code image") parser.add_argument("--cipher", help="output ciphername", action="store_true") parser.add_argument("--password", help="output password", action="store_true") parser.add_argument("--server", help="output server", action="store_true") parser.add_argument("--port", help="output port", action="store_true") args = parser.parse_args() cmd = ["zbarimg", args.file] try: urlData = check_output(cmd) # Strip the leading "QR-Code:" that `zbarimg` adds if urlData.startswith("QR-Code:"): urlData = urlData.replace("QR-Code:", "") except: print("Error running '{0}'\n".format(" ".join(cmd))) try: parts = urlparse.urlparse(urlData) except: print("Unable to parse decoded image data '{0}' as URL\n".format(urlData)) sys.exit(1) if parts.scheme != "ss": print("URL had scheme {0}, which isn't 'ss' and is unsupported\n".format( parts.scheme)) sys.exit(1) encodedData = parts.netloc try: shadowsocksURL = base64.b64decode(encodedData) except: print("Unable to base64 decode encoded netloc '{0}'\n".format(encodedData)) sys.exit(1) # First capture group: cipher # Second capture group: password # Third capture group: server address # Fourth capture group: server port shadowsocksURLRegexStr = r'^([\w\-]+):([\w+=\/]+)@([\w\-\.]+):([\d]+)$' shadowsocksURLRegex = re.compile(shadowsocksURLRegexStr) match = shadowsocksURLRegex.match(shadowsocksURL) if match: cipher = match.group(1) password = match.group(2) server = match.group(3) port = match.group(4) if args.cipher: print(cipher) elif args.password: print(password) elif args.server: print(server) elif args.port: print(port) else: print("cipher: {0}".format(cipher)) print("password: {0}".format(password)) print("server: {0}".format(server)) print("port: {0}".format(port)) else: print("Decoded Shadowsocks URL '{0}' did not match regex.\n".format( shadowsocksURL)) sys.exit(1) ================================================ FILE: playbooks/roles/test-client/tasks/main.yml ================================================ --- - name: "Include the common vars" include_vars: "../../common/vars/main.yml" - name: "Include the Shadowsocks default vars" include_vars: "../../shadowsocks/defaults/main.yml" - name: "Remove existing state from client if required" file: path: "{{ item }}" state: absent with_items: - "{{ gateway_password_file }}" - "{{ test_client_profiles_file }}" - name: "Copy gateway password file to client" copy: src: "{{ streisand_gateway_password_localpath }}" dest: "{{ gateway_password_file }}" owner: root group: root mode: 0600 - name: "Install the gateway test script" template: src: "streisand-gateway-test.sh.j2" dest: "{{ test_script_dir }}/streisand-gateway-test" owner: root group: root mode: 0755 - name: "Wait for the Streisand gateway" wait_for: host: "{{ streisand_ip }}" port: 443 state: started timeout: 60 - name: "Run the gateway test script" command: "streisand-gateway-test" - name: "Download the client profile list" get_url: url: "{{ gateway_profile_inventory }}" dest: "{{ test_client_profiles_file }}" force_basic_auth: yes url_username: "{{ gateway_test_user }}" url_password: "{{ lookup('file', '{{ streisand_gateway_password_localpath }}') }}" validate_certs: no mode: 0600 - name: "Register the client profile list contents" command: "cat {{ test_client_profiles_file }}" register: test_client_profiles # Run the SSH forwarding tests - import_tasks: ssh-forward.yml # Run the Shadowsocks tests - import_tasks: shadowsocks.yml # Run the WireGuard tests - import_tasks: wireguard.yml # Run the OpenVPN tests - import_tasks: openvpn.yml # Run the OpenConnect tests - import_tasks: openconnect.yml # Run the Tor tests - import_tasks: tor.yml # Run the Stunnel tests - import_tasks: stunnel.yml ... ================================================ FILE: playbooks/roles/test-client/tasks/openconnect-profiletest.yml ================================================ --- - name: "Testing {{ profile_name }} OpenConnect PKCS12 client profile" block: - name: "Read the {{ profile_name }} PKCS12 password from the mobileconfig file" # Use `xmllint` to evaluate an XPATH against the client mobileconfig XML. # The XPATH provided will extract the PKCS12 password for the given profile. # # Short breakdown of the XPATH expression: # `/plist/dict/array/dict` # Gets to the dict's with the config content # `/plist/dict/array/dict/key[text()="Password"] # Finds the "Password" element # `/plist/dict/array/dict/key[text()="Password"]/following-sibling::string[1]` # Finds the "..." element that is the # Password elements first sibling. # `/plist/dict/array/dict/key[text()="Password"]/following-sibling::string[1]/text()` # Extracts the node text value from the ... - this is # the PKCS12 password! Nice! shell: > xmllint --xpath '/plist/dict/array/dict/key[text()="Password"]/following-sibling::string[1]/text()' \ {{ openconnect_profile_dir }}/{{ profile_name }}.mobileconfig register: openconnect_pkcs12_password_contents changed_when: False - name: "Start OpenConnect with PKCS12 auth and the {{ profile_name }} profile" # OpenConnect expects the PKCS12 password to be provided via stdin so we # use `echo -n` to send it to stdin without a newline. # We use `--background` as an argument to make sure the shell returns # immeidately. `--pid-file` helps us kill OpenConnect later. # `--cafile` is used to validate the server's certificate against the # Streisand OpenConnect CA. `--certificate` is used to specify our client # PKCS12 file for client cert auth. shell: > echo -n '{{ openconnect_pkcs12_password_contents.stdout }}' | \ openconnect \ --background --pid-file {{ openconnect_pid_file }} \ --cafile {{ openconnect_ca_file }} \ --certificate {{ openconnect_profile_dir }}/{{ profile_name }}.p12 \ {{ streisand_ip }}:{{ ocserv_port }} - name: "Read the OpenConnect PID file into a var" command: "cat {{ openconnect_pid_file }}" register: openconnect_pid_output changed_when: False - name: "Sleep a short time to allow OpenConnect to connect" pause: seconds: 2 - name: "Register the updated /etc/resolv.conf contents" shell: "cat /etc/resolv.conf" register: openconnect_resolv_conf changed_when: "False" # TODO(@cpu) - We should be sending the dnsmasq server address down to # clients instead of Google DNS. We also shouldn't be sending a bogus # `search` parameter. For now, test against what Streisand does today. - name: "Assert that /etc/resolv.conf was updated with the correct nameservers" assert: that: - "'nameserver 8.8.8.8' in openconnect_resolv_conf.stdout" - "'nameserver 8.8.4.4' in openconnect_resolv_conf.stdout" - "'search example.com' in openconnect_resolv_conf.stdout" - name: "Check {{ external_test_url }} with OpenConnect up" get_url: url: "{{ external_test_url }}" dest: "/dev/null" force: "yes" - name: "Stop OpenConnect" command: "kill -INT {{ openconnect_pid_output.stdout }}" - name: "Remove tun0 from resolvconf" command: "resolvconf -d tun0" ignore_errors: "yes" - name: "Register the updated /etc/resolv.conf contents" shell: "cat /etc/resolv.conf" register: openconnect_resolv_conf changed_when: "False" - name: "Assert that the DNS was restored to pre-OpenConnect state" assert: that: - "'nameserver 8.8.8.8' not in openconnect_resolv_conf.stdout" - "'nameserver 8.8.4.4' not in openconnect_resolv_conf.stdout" - "'search example.com' not in openconnect_resolv_conf.stdout" - name: "Remove the OpenConnect pid file" file: path: "{{ openconnect_pid_file }}" state: absent rescue: - name: "Bring down OpenConnect" command: "killall openconnect" ignore_errors: "yes" - name: "Remove tun0 from resolvconf" command: "resolvconf -d tun0" ignore_errors: "yes" - name: "Delete the tun0 interface" command: "ip link delete tun0" ignore_errors: "yes" ================================================ FILE: playbooks/roles/test-client/tasks/openconnect.yml ================================================ --- # Import the Streisand OpenConnect playbook default vars - include_vars: "../../openconnect/defaults/main.yml" - name: "Remove stale test state if required" file: path: "{{ item }}" state: absent with_items: - "{{ openconnect_profile_dir }}" - "{{ openconnect_ca_file }}" - "{{ openconnect_pid_file }}" - name: "Create OpenConnect profile directory" file: path: "{{ openconnect_profile_dir }}" state: directory - name: "Install OpenConnect & required tools" apt: name: "{{ item }}" with_items: - "openconnect" # libxml2-utils provides xmllint which we use to extract a PKCS12 password # from the mobileconfig XML files for testing - "libxml2-utils" - name: "Download OpenConnect CA certificate from the gateway" get_url: url: "{{ gateway_openconnect_ca_file }}" dest: "{{ openconnect_ca_file }}" force_basic_auth: yes url_username: "{{ gateway_test_user }}" url_password: "{{ lookup('file', '{{ streisand_gateway_password_localpath }}') }}" validate_certs: no mode: 0600 - name: "Remove existing OpenConnect PKCS12 files if they exist" file: path: "{{ openconnect_profile_dir }}/{{ profile_name }}.p12" state: absent with_items: "{{ test_client_profiles.stdout_lines }}" loop_control: loop_var: "profile_name" - name: "Download each of the OpenConnect PKCS12 client profiles" get_url: url: "{{ gateway_test_url }}/openconnect/{{ profile_name }}.p12" dest: "{{ openconnect_profile_dir }}/{{ profile_name }}.p12" force_basic_auth: yes url_username: "{{ gateway_test_user }}" url_password: "{{ lookup('file', '{{ streisand_gateway_password_localpath }}') }}" validate_certs: no with_items: "{{ test_client_profiles.stdout_lines }}" loop_control: loop_var: "profile_name" # NOTE(@cpu) - The Linux OpenConnect client cann not _use_ the mobileconfig # profiles but they are a convenient way to get the PKCS12 password # programmatically so we download them for this purpose. - name: "Download each of the OpenConnect mobileconfig profiles" get_url: url: "{{ gateway_test_url }}/openconnect/{{ profile_name }}.mobileconfig" dest: "{{ openconnect_profile_dir }}/{{ profile_name }}.mobileconfig" force_basic_auth: yes url_username: "{{ gateway_test_user }}" url_password: "{{ lookup('file', '{{ streisand_gateway_password_localpath }}') }}" validate_certs: no with_items: "{{ test_client_profiles.stdout_lines }}" loop_control: loop_var: "profile_name" - name: "Test each Openconnect PKCS12 client profile" include: openconnect-profiletest.yml with_items: "{{ test_client_profiles.stdout_lines }}" loop_control: loop_var: "profile_name" ================================================ FILE: playbooks/roles/test-client/tasks/openvpn-profileget.yml ================================================ --- - name: "Remove existing {{ openvpn_profile_type }} OpenVPN profiles if they exist" file: path: "{{ openvpn_profile_dir}}/{{ profile_name }}-{{ openvpn_profile_type}}*.ovpn" state: absent with_items: "{{ test_client_profiles.stdout_lines }}" loop_control: loop_var: "profile_name" - name: "Download each of the {{ openvpn_profile_type }} OpenVPN profiles" get_url: url: "{{ gateway_test_url }}/openvpn/{{ profile_name }}/{{ streisand_ip }}-{{ openvpn_profile_type }}.ovpn" dest: "{{ openvpn_profile_dir }}/{{ profile_name }}-{{ openvpn_profile_type }}.ovpn" force_basic_auth: yes url_username: "{{ gateway_test_user }}" url_password: "{{ lookup('file', '{{ streisand_gateway_password_localpath }}') }}" validate_certs: no with_items: "{{ test_client_profiles.stdout_lines }}" loop_control: loop_var: "profile_name" # NOTE(cpu): This is a little bit messy but there aren't really any good options # to add a few lines to the Streisand OVPN that we need - mainly the DHCP # up/down settings described in the Streisand docs for Linux clients without # NetworkManager and the PID tracking command we use to kill OpenVPN later - name: "Concatinate the profile customization commands to each OpenVPN {{ openvpn_profile_type }} client profile if required" shell: "cat {{ openvpn_profile_addons }} {{ openvpn_profile_dir }}/{{ profile_name }}-{{ openvpn_profile_type }}.ovpn > {{ openvpn_profile_dir }}/{{ profile_name }}-{{ openvpn_profile_type }}.client-test.ovpn" with_items: "{{ test_client_profiles.stdout_lines }}" loop_control: loop_var: "profile_name" ================================================ FILE: playbooks/roles/test-client/tasks/openvpn-profiletest.yml ================================================ --- - name: "Testing {{ profile_name }} - {{ openvpn_profile_type }}" block: - name: "Start OpenVPN with the {{ profile_name }} {{ openvpn_profile_type }} client profile" shell: "nohup openvpn {{ openvpn_profile_dir }}/{{ profile_name }}-{{ openvpn_profile_type }}.client-test.ovpn&" - name: "Read the OpenVPN PID file into a var" command: "cat {{ openvpn_pid_file }}" register: openvpn_pid_output # TODO(cpu): A nice follow up would be finding a way to add a second "up" # command to the OVPN profile that tells Ansible somehow that the tunnel # is up. It seems like you can only specify one "up" handler and we're using # it to run the DHCP update per the Streisand docs. - name: "Sleep a short time to allow OpenVPN to connect" pause: seconds: 20 - name: "Register the updated /etc/resolv.conf contents" shell: "cat /etc/resolv.conf" register: openvpn_resolv_conf changed_when: "False" - name: "Assert that /etc/resolv.conf was updated for the DNSMasq OpenVPN IP" assert: that: - "'nameserver {{ dnsmasq_openvpn_tcp_ip }}' in openvpn_resolv_conf.stdout" - name: "Check {{ external_test_url }} with OpenVPN up" get_url: url: "{{ external_test_url }}" dest: "/dev/null" force: "yes" - name: "Stop OpenVPN" command: "kill {{ openvpn_pid_output.stdout }}" - name: "Register the updated /etc/resolv.conf contents" shell: "cat /etc/resolv.conf" register: openvpn_resolv_conf changed_when: "False" - name: "Assert that the DNS was restored to pre-OpenVPN state" assert: that: - "'nameserver {{ dnsmasq_openvpn_tcp_ip }}' not in openvpn_resolv_conf.stdout" - name: "Remove the OpenVPN pid file" file: path: "{{ openvpn_pid_file }}" state: absent rescue: - name: "Bring down OpenVPN" command: "killall openvpn" ignore_errors: "yes" ================================================ FILE: playbooks/roles/test-client/tasks/openvpn-test.yml ================================================ --- - name: "Download each {{ openvpn_profile_type }} OpenVPN profile" include: openvpn-profileget.yml - name: "Test each {{ openvpn_profile_type }} OpenVPN profile" include: openvpn-profiletest.yml with_items: "{{ test_client_profiles.stdout_lines }}" loop_control: loop_var: "profile_name" ================================================ FILE: playbooks/roles/test-client/tasks/openvpn.yml ================================================ --- # # Import the Streisand OpenVPN playbook vars - include_vars: "../../openvpn/vars/main.yml" # Install OpenVPN from PPA - import_tasks: "../../openvpn/tasks/install.yml" - name: "Remove stale test state if required" file: path: "{{ item }}" state: absent with_items: - "{{ test_client_profiles_file }}" - "{{ openvpn_profile_addons }}" - "{{ openvpn_pid_file }}" - name: "Generate a file with OpenVPN commands required to customize the profile for the test-client" template: src: "openvpn-profile-addons.j2" dest: "{{ openvpn_profile_addons }}" owner: root group: root mode: 0600 - name: "Download and test each OpenVPN profile type" include_tasks: openvpn-test.yml vars: openvpn_profile_type: "{{ item }}" with_items: - "direct" - "sslh" # TODO(@cpu) - debug direct-udp and combined profiles. They don't work r.n. #- "direct-udp" #- "combined" ================================================ FILE: playbooks/roles/test-client/tasks/shadowsocks.yml ================================================ --- - name: "Download the Linux Shadowsocks client zip to the client machine" get_url: url: "{{ gateway_shadowsocks_client }}" dest: "{{ shadowsocks_client_zip }}" force_basic_auth: yes url_username: "{{ gateway_test_user }}" url_password: "{{ lookup('file', '{{ streisand_gateway_password_localpath }}') }}" validate_certs: no mode: 0600 - name: "Extract the Shadowsocks client on the client machine" # NOTE(@cpu): It's tempting to try and use the `unarchive` module here but with # a brief test it seemed to barf trying to use `unzip` on a `.gz` file. This # isn't a `tar.gz` and is probably too edge-case for `unarchive` so `command` # it is! command: "gunzip -f {{ shadowsocks_client_zip }}" args: chdir: "/root" creates: "{{ shadowsocks_client }}" - name: "Set executable mode on Shadowsocks client" file: path: "{{ shadowsocks_client }}" mode: 0700 - name: "Remove existing Shadowsocks QR code from client if required" file: path: "{{ shadowsocks_qr_file }}" state: absent - name: "Download the Shadowsocks QR code to the client machine" get_url: url: "{{ gateway_shadowsocks_qr }}" dest: "{{ shadowsocks_qr_file }}" force_basic_auth: yes url_username: "{{ gateway_test_user }}" url_password: "{{ lookup('file', '{{ streisand_gateway_password_localpath }}') }}" validate_certs: no mode: 0600 - name: "Install zbar-tools" apt: package: zbar-tools force: yes - name: "Copy Shadowsocks QR code parser tool to client machine" copy: src: shadowsocks-qr-decode.py dest: "{{ test_script_dir }}/shadowsocks-qr-decode" owner: root group: root mode: 0755 - name: "Install Shadowsocks test script" template: src: "streisand-shadowsocks-forward-test.sh.j2" dest: "{{ test_script_dir }}/streisand-shadowsocks-forward-test" owner: root group: root mode: 0755 - name: "Run Shadowsocks test script" command: "streisand-shadowsocks-forward-test" ================================================ FILE: playbooks/roles/test-client/tasks/ssh-forward.yml ================================================ --- - name: "Remove existing state from client if required" file: path: "{{ item }}" state: absent with_items: - "{{ forward_ssh_key }}" - "{{ forward_ssh_config }}" - "{{ forward_ssh_hosts }}" - "{{ ssh_pid_file }}" - name: "Download the forward user SSH key to the client" get_url: url: "{{ gateway_ssh_key }}" dest: "{{ forward_ssh_key }}" force_basic_auth: yes url_username: "{{ gateway_test_user }}" url_password: "{{ lookup('file', '{{ streisand_gateway_password_localpath }}') }}" validate_certs: no mode: 0600 - name: "Download the Streisand server SSH known hosts file" get_url: url: "{{ gateway_ssh_hosts }}" dest: "{{ forward_ssh_hosts }}" force_basic_auth: yes url_username: "{{ gateway_test_user }}" url_password: "{{ lookup('file', '{{ streisand_gateway_password_localpath }}') }}" validate_certs: no mode: 0600 - name: "Install an SSH config file for a SSH SOCKS connection with the forward user" template: src: "ssh-config.j2" dest: "{{ forward_ssh_config }}" owner: root group: root mode: 0600 - name: "Test SSH forward user SOCKS proxy" block: - name: "Start an SSH connection with dynamic port forwarding" shell: > nohup ssh streisand-host -N -T & \ echo $! > {{ ssh_pid_file }} args: creates: "{{ ssh_pid_file }}" - name: "Read the SSH connect PID file into a var" command: "cat {{ ssh_pid_file }}" register: ssh_pid_output changed_when: False - name: "Wait for port {{ forward_socks_port }} to become open" wait_for: port: "{{ forward_socks_port }}" delay: 2 timeout: 60 - name: "Access a test URL through the SSH SOCKS proxy" command: "curl --socks5-hostname localhost:{{ forward_socks_port }} {{ external_test_url }}" # Don't warn about using command to execute curl - the get_url module # doesn't support specifying a socks5 proxy args: warn: no - name: "Stop the SSH connection" command: "kill {{ ssh_pid_output.stdout }}" rescue: - name: "kill any hanging SSH connections" command: "kill -9 {{ ssh_pid_output.stdout }}" ignore_errors: "yes" - name: "Test TinyProxy through a SSH forward user connection" block: - name: "Remove the SSH PID file" file: path: "{{ ssh_pid_file }}" state: absent - name: "Start an SSH connection with TinyProxy's port forwarded" shell: > nohup ssh streisand-host-tinyproxy -N -T & \ echo $! > {{ ssh_pid_file }} args: creates: "{{ ssh_pid_file }}" - name: "Read the SSH connect PID file into a var" command: "cat {{ ssh_pid_file }}" register: ssh_pid_output changed_when: False - name: "Wait for port {{ tinyproxy_local_port }} to become open" wait_for: port: "{{ tinyproxy_local_port }}" delay: 2 timeout: 120 - name: "Access a test URL through the TinyProxy proxy" command: "curl -x localhost:{{ tinyproxy_local_port }} {{ external_test_url }}" # Don't warn about using command to execute curl - the get_url module's # support for HTTP proxies isn't great args: warn: no - name: "Stop the SSH connection" command: "kill {{ ssh_pid_output.stdout }}" rescue: - name: "kill any hanging SSH connections" command: "kill -9 {{ ssh_pid_output.stdout }}" ignore_errors: "yes" ================================================ FILE: playbooks/roles/test-client/tasks/stunnel.yml ================================================ --- # Import the Streisand OpenVPN playbook vars - include_vars: "../../openvpn/vars/main.yml" - name: "Install stunnel and openvpn" apt: package: - "stunnel" - "openvpn" - name: "Ensure the stunnel service is stopped and disabled" # We need to stop the stunnel service & disable it. By default the Ubuntu 16.04 # stunnel package enables & boots the service after being installed. service: name: stunnel4 state: stopped enabled: no - name: "Remove existing stunnel test state from client if required" file: path: "{{ stunnel_dir }}" state: absent with_items: - "{{ openvpn_profile_addons }}" - "{{ openvpn_pid_file }}" - "{{ stunnel_dir }}" - name: "Create stunnel test dir" file: path: "{{ stunnel_dir }}" state: directory - name: "Download stunnel client configuration from the Streisand server gateway" get_url: url: "{{ gateway_stunnel_conf }}" dest: "{{ stunnel_conf }}" force_basic_auth: yes url_username: "{{ gateway_test_user }}" url_password: "{{ lookup('file', '{{ streisand_gateway_password_localpath }}') }}" validate_certs: no mode: 0600 - name: "Add a pidfile option to the client configuration" lineinfile: path: "{{ stunnel_conf }}" line: "pid = {{ stunnel_pid_file }}" state: present insertbefore: BOF - name: "Generate a file with OpenVPN commands required to customize the profile for the test-client" template: src: "openvpn-profile-addons.j2" dest: "{{ openvpn_profile_addons }}" owner: root group: root mode: 0600 - name: "Start the stunnel daemon for testing" block: - name: "Start the stunnel daemon" command: "stunnel4 {{ stunnel_conf }}" - name: "Read the stunnel daemon PID file into a var" command: "cat {{ stunnel_pid_file }}" register: stunnel_pid_output changed_when: False - name: "Download and test each OpenVPN stunnel profile" include_tasks: openvpn-test.yml vars: openvpn_profile_type: "stunnel" - name: "Stop the stunnel daemon" command: "kill -INT {{ stunnel_pid_output.stdout }}" rescue: - name: "kill stunnel instances" command: "killall stunnel4" ignore_errors: "yes" ================================================ FILE: playbooks/roles/test-client/tasks/tor.yml ================================================ --- - name: "Install tor and obs4proxy" apt: package: - "tor" - "obfs4proxy" - name: "Remove stale test state if required" file: path: "{{ item }}" state: absent with_items: - "{{ tor_config }}" - "{{ tor_obfs4_qr }}" - "{{ tor_pid_file }}" - name: "Ensure the tor service is stopped and disabled" # We need to stop the tor service & disable it. By default the Ubuntu 16.04 # tor package enables & boots the service after being installed. service: name: tor state: stopped enabled: no - name: "Download tor obfs4 proxy QR code from the Streisand gateway" get_url: url: "{{ gateway_tor_qr }}" dest: "{{ tor_obfs4_qr}}" force_basic_auth: yes url_username: "{{ gateway_test_user }}" url_password: "{{ lookup('file', '{{ streisand_gateway_password_localpath }}') }}" validate_certs: no mode: 0600 - name: "Download tor hidden service hostname from the Streisand gateway" get_url: url: "{{ gateway_tor_hidden_service_hostname }}" dest: "{{ tor_hidden_service_hostname}}" force_basic_auth: yes url_username: "{{ gateway_test_user }}" url_password: "{{ lookup('file', '{{ streisand_gateway_password_localpath }}') }}" validate_certs: no mode: 0600 - name: "Read the tor hidden service hostname into a var" command: "cat {{ tor_hidden_service_hostname }}" register: tor_hidden_service_hostname_output changed_when: False - name: "Extract the Streisand server obfs4 proxy info from the QR code" # NOTE(@cpu) - zbarimg prefixes the data with "QR-Code" so we strip it with # sed before registering the output shell: > zbarimg -q {{ tor_obfs4_qr }} | \ sed 's/^QR-Code://' register: tor_obfs4_qr_contents - name: "Install the Streiand obfs4 relay client torrc" template: src: obfs4.relay.client.torrc.j2 dest: "{{ tor_config }}" - name: "Test Streisand tor relay with obfs4proxy obfuscation" block: - name: "Start the tor daemon" command: "tor -f {{ tor_config }}" - name: "Read the tor daemon PID file into a var" command: "cat {{ tor_pid_file }}" register: tor_pid_output changed_when: False - name: "Access a test URL through the tor SOCKS proxy" command: "curl --socks5-hostname localhost:{{ tor_socks_port }} {{ external_test_url }}" # Don't warn about using command to execute curl - the get_url module # doesn't support specifying a socks5 proxy args: warn: no register: tor_socks_check # NOTE(@cpu): Tor and hidden services can be a little bit flaky. We give # things a few tries before giving up entirely retries: 3 delay: 3 until: tor_socks_check.rc == 0 - name: "Check the Streisand server hidden service is up & requires auth" shell: > curl -I \ --socks5-hostname localhost:{{ tor_socks_port }} \ --connect-timeout 20 \ http://{{ tor_hidden_service_hostname_output.stdout }} 2> /dev/null | \ grep -q "Authorization Required" register: tor_hs_auth_reject args: warn: no retries: 3 delay: 3 until: tor_hs_auth_reject.rc == 0 - name: "Check the Streisand server hidden service is up & accessible with correct auth" shell: > curl --socks5-hostname localhost:{{ tor_socks_port }} \ --connect-timeout 20 \ -u {{ gateway_test_user }}:{{ lookup('file', '{{ streisand_gateway_password_localpath }}') }} \ http://{{ tor_hidden_service_hostname_output.stdout }} 2> /dev/null | \ grep -q "Streisand" register: tor_hs_auth_accept args: warn: no retries: 3 delay: 3 until: tor_hs_auth_accept.rc == 0 - name: "Stop tor" command: "kill -INT {{ tor_pid_output.stdout }}" rescue: - name: "kill tor instances" command: "killall tor" ignore_errors: "yes" ================================================ FILE: playbooks/roles/test-client/tasks/wireguard-profiletest.yml ================================================ --- - block: - name: "Replace wg0 with the {{ profile_name }} configuration" copy: src: "{{ wireguard_profile_dir }}/{{ profile_name }}.conf" remote_src: yes dest: "/etc/wireguard/wg0.conf" mode: 0600 owner: root group: root force: yes - name: "Bring up the WireGuard interface for the {{ profile_name }} config" shell: "wg-quick up wg0" - name: "Register the updated /etc/resolv.conf contents" shell: "cat /etc/resolv.conf" register: wgclient_resolv_conf changed_when: "False" - name: "Assert that /etc/resolv.conf was updated for the DNSMasq WireGuard IP" assert: that: - "'nameserver {{ dnsmasq_wireguard_ip }}' in wgclient_resolv_conf.stdout" - name: "Check {{ external_test_url }} through the WireGuard route" get_url: url: "{{ external_test_url }}" dest: "/dev/null" force: "yes" - name: "Register the output from `wg` to check Wireguard status" shell: "wg" register: wgclient_wg_output changed_when: "False" - name: "Assert that there has been a successful Wireguard handshake" assert: that: - "'latest handshake' in wgclient_wg_output.stdout" - name: "Bring down the WireGuard interface" command: "wg-quick down wg0" - name: "Register the updated /etc/resolv.conf contents" shell: "cat /etc/resolv.conf" register: wgclient_resolv_conf changed_when: "False" - name: "Assert that the DNS was restored to pre-WireGuard state" assert: that: - "'nameserver {{ dnsmasq_wireguard_ip }}' not in wgclient_resolv_conf.stdout" rescue: - name: "Remove the wg0 interface if present" command: "wg-quick down wg0" ignore_errors: "yes" ================================================ FILE: playbooks/roles/test-client/tasks/wireguard.yml ================================================ --- # Install the WireGuard PPA and packages - import_tasks: "../../wireguard/tasks/install.yml" # Import the Wireguard Streisand playbook vars - include_vars: "../../wireguard/vars/main.yml" - name: "Remove any stale Wireguard profiles" file: path: "{{ wireguard_profile_dir }}" state: absent - name: "Create WireGuard profile directory" file: path: "{{ wireguard_profile_dir }}" state: directory - name: "Download each of the WireGuard client profiles" get_url: url: "{{ gateway_test_url }}/wireguard/{{ profile_name }}.conf" dest: "{{ wireguard_profile_dir }}/{{ profile_name }}.conf" force_basic_auth: yes url_username: "{{ gateway_test_user }}" url_password: "{{ lookup('file', '{{ streisand_gateway_password_localpath }}') }}" validate_certs: no with_items: "{{ test_client_profiles.stdout_lines }}" loop_control: loop_var: "profile_name" - name: "Test each WireGuard client profile" include: wireguard-profiletest.yml with_items: "{{ test_client_profiles.stdout_lines }}" loop_control: loop_var: "profile_name" ================================================ FILE: playbooks/roles/test-client/templates/obfs4.relay.client.torrc.j2 ================================================ RunAsDaemon 1 SOCKSPort {{ tor_socks_port }} PidFile {{ tor_pid_file }} UseBridges 1 ClientTransportPlugin obfs4 exec /usr/bin/obfs4proxy Bridge {{ tor_obfs4_qr_contents.stdout }} ================================================ FILE: playbooks/roles/test-client/templates/openvpn-profile-addons.j2 ================================================ script-security 2 up /etc/openvpn/update-resolv-conf down /etc/openvpn/update-resolv-conf writepid {{ openvpn_pid_file }} ================================================ FILE: playbooks/roles/test-client/templates/ssh-config.j2 ================================================ # Connection profile for a SOCKS proxy over a SSH connection to the forward user # on a Streisand server instance Host streisand-host HostName {{ streisand_ip }} # Open a SOCKS proxy port equivalent to using `-D {{ forward_socks_port }}` on # the command line DynamicForward {{ forward_socks_port }} # Connection profile for using the TinyProxy HTTP(S) proxy over SSH to the # Streisand server instance Host streisand-host-tinyproxy HostName {{ streisand_ip }} # Forward port {{ tinyproxy_remote_port }} on the streisand_ip host to port # {{ tinyproxy_local_port }} localhost. This makes TinyProxy (bound to # 127.0.0.1 on the Streisand host) accessible LocalForward {{ tinyproxy_local_port }} localhost:{{ tinyproxy_remote_port }} # Default settings for all hosts Host * Port 22 User {{ forward_ssh_user }} # Only use keys specified in this config IdentitiesOnly yes # Never try password authentication PasswordAuthentication no # Use the specified key for the connection IdentityFile {{ forward_ssh_key }} # Use the preconfigured SSH known hosts fingerprints to avoid having to make # a trust-on-first use decision UserKnownHostsFile {{ forward_ssh_hosts }} ================================================ FILE: playbooks/roles/test-client/templates/streisand-gateway-test.sh.j2 ================================================ #!/bin/bash -xe # # NOTE: This test script relies on the -e argument to bash in the shebang above. # # Streisand Gateway Test # - Confirms HTTP basic auth is present # - Confirms HTTP basic auth rejects an incorrect password # - Confirms using the correct username/password allows viewing the index # # TODO: Install the streisand gateway CA & remove the --insecure arguments. # Confirm that not sending a password/username results in a 401 error curl --insecure -I {{ gateway_test_url }} | grep "401 Unauthorized" # Confirm that sending the wrong password/username results in a 401 error curl --insecure -I -u "{{ gateway_test_user }}:badpassword" {{ gateway_test_url }} | grep "401 Unauthorized" # Read the password into a var password=$(cat "{{ gateway_password_file }}") # Confirm that using the correct password/username results in a 200 OK curl --insecure -I -u "{{ gateway_test_user }}:$password" {{ gateway_test_url }} | grep "200 OK" ================================================ FILE: playbooks/roles/test-client/templates/streisand-shadowsocks-forward-test.sh.j2 ================================================ #!/bin/bash -xe # # NOTE: This test script relies on the -e argument to bash in the shebang above. # # Streisand Shadowsocks Client Test # - Confirms the gateway mirror for the Linux Shadowsocks client works # - Confirms the gateway Shadowsocks QR has correct information (password, cipher, etc) # - Confirms that the Linux shadowsocks client can connect & route a HTTP request # # Read the Shadowsocks QR code attributes into vars shadowsocksCipher=$(shadowsocks-qr-decode --cipher "{{ shadowsocks_qr_file }}" 2>/dev/null) shadowsocksPassword=$(shadowsocks-qr-decode --password "{{ shadowsocks_qr_file }}" 2>/dev/null) shadowsocksServer=$(shadowsocks-qr-decode --server "{{ shadowsocks_qr_file }}" 2>/dev/null) shadowsocksPort=$(shadowsocks-qr-decode --port "{{ shadowsocks_qr_file }}" 2>/dev/null) # Read the gateway password into a var gatewayPassword=$(cat "{{ gateway_password_file }}") # Connect the Shadowsocks client to the server {{ shadowsocks_client }} -c "$shadowsocksServer:$shadowsocksPort" -password "$shadowsocksPassword" -socks localhost:{{ shadowsocks_local_port }} -verbose -cipher "$shadowsocksCipher" & CLIENT_PID=$! function cleanup { # Kill the backgrounded Shadowsocks client echo "Killing PID $CLIENT_PID" kill $CLIENT_PID echo "Waiting for clean up to finish..." wait $CLIENT_PID } trap cleanup EXIT # Sleep to give the Shadowsocks client a chance to start sleep 2 # Check that the gateway website can be loaded through the Shadowsocks proxy curl --socks5 localhost:{{ shadowsocks_local_port }} --insecure -I -u "{{ gateway_test_user }}:$gatewayPassword" {{ gateway_test_url }} | grep "200 OK" # Check that an external site can be loaded through the Shadowsocks proxy curl --socks5 localhost:{{ shadowsocks_local_port }} --insecure -I -u "{{ gateway_test_user }}:$gatewayPassword" {{ external_test_url }} | grep "200 OK" ================================================ FILE: playbooks/roles/test-client/vars/main.yml ================================================ --- # Path to the gateway password on the Ansible host streisand_gateway_password_localpath: "../{{ streisand_local_directory }}/gateway-password.txt" # Path to the gateway password file on the Streisand client gateway_password_file: "/root/gateway-password.txt" # URL for accessing the Streisand server's HTTP gateway page gateway_test_url: "https://{{ streisand_ip }}" # User for accessing the Streisand server HTTP gateway page gateway_test_user: "streisand" # The directory to copy test scripts to test_script_dir: "/usr/local/bin/" # An external URL used to test connectivity through Shadowsocks/WireGuard/etc external_test_url: "https://github.com" # The gateway URL for clients to download the forward user's RSA key gateway_ssh_key: "{{ gateway_test_url }}/ssh/streisand_rsa" # The gateway URL for clients to download the SSH known hosts file gateway_ssh_hosts: "{{ gateway_test_url }}/ssh/streisand.known_hosts" # Files created on the Streisand client test for SSH config: forward_ssh_key: "/root/.ssh/streisand_rsa" forward_ssh_config: "/root/.ssh/config" forward_ssh_hosts: "/root/.ssh/streisand.known_hosts" # Forwarding SSH username forward_ssh_user: "forward" # Port on the Streisand client to use for the SOCKS SSH proxy forward_socks_port: 9876 # Port on the Streisand server for TinyProxy tinyproxy_remote_port: 8888 # Port on the Streisand client for the TinyProxy port forward tinyproxy_local_port: 8888 # PID file for open SSH port forward connections ssh_pid_file: "/tmp/ssh.pid" # The gateway URL for clients to download the Shadowsocks QR code image gateway_shadowsocks_qr: "{{ gateway_test_url }}/shadowsocks/shadowsocks-qr-code.png" # Path to the Shadowsocks QR code image on the client machine shadowsocks_qr_file: "/root/shadowsocks-qr-code.png" # The gateway URL for clients to download the Shadowsocks2 Linux source gateway_shadowsocks_client: "{{ gateway_test_url }}/mirror/shadowsocks/shadowsocks2-linux-x64.gz" # Path to the Shadowsocks2 client source on the Streisand client shadowsocks_client: "/root/shadowsocks2-linux-x64" shadowsocks_client_zip: "{{ shadowsocks_client }}.gz" # The gateway URL for clients to download the WireGuard config file gateway_wireguard_config: "{{ gateway_test_url }}/wireguard/wg0.conf" wireguard_profile_dir: "/root/wireguard-profiles" gateway_profile_inventory: "{{ gateway_test_url }}/test-client-inventory" test_client_profiles_file: "/tmp/test-client-inventory" openvpn_profile_dir: "/etc/openvpn" openvpn_profile_addons: "/tmp/openvpn-profile-addons" openvpn_pid_file: "/tmp/openvpn.pid" gateway_openconnect_ca_file: "{{ gateway_test_url }}/openconnect/ca.crt" gateway_openconnect_password_file: "{{ gateway_test_url }}/openconnect/password.txt" openconnect_ca_file: "/root/openconnect.ca.crt" openconnect_pid_file: "/tmp/openconnect.pid" openconnect_profile_dir: "/root/openconnect-profiles" openconnect_password_file: "{{ openconnect_profile_dir }}/openconnect.password.txt" gateway_tor_qr: "{{ gateway_test_url }}/tor/tor-obfs4-qr-code.png" gateway_tor_hidden_service_hostname: "{{ gateway_test_url }}/tor/hidden-service-hostname.txt" tor_pid_file: "/tmp/tor.pid" tor_socks_port: 9050 tor_obfs4_qr: "/root/tor-obs4-qr-code.png" tor_config: "/etc/tor/streisand.obfs4.relay" tor_hidden_service_hostname: "/tmp/hidden-service-hostname.txt" gateway_stunnel_conf: "{{ gateway_test_url }}/openvpn/stunnel.conf" stunnel_dir: "/root/stunnel/" stunnel_conf: "{{ stunnel_dir }}/stunnel.conf" stunnel_pid_file: "/tmp/stunnel.pid" ================================================ FILE: playbooks/roles/tinyproxy/handlers/main.yml ================================================ --- - name: Restart Tinyproxy systemd: name: tinyproxy.service state: restarted ================================================ FILE: playbooks/roles/tinyproxy/tasks/main.yml ================================================ --- - name: Install Tinyproxy apt: package: tinyproxy - name: Stop (init.d's) tinyproxy systemd: name: tinyproxy.service state: stopped - name: Create the tinyproxy config directory file: path: "{{ tinyproxy_conf_dir }}" state: directory owner: nobody group: nogroup mode: 0755 - name: Generate the tinyproxy configuration file template: src: tinyproxy.conf.j2 dest: "{{ tinyproxy_conf_file }}" owner: root group: root mode: 0644 - name: Generate the tinyproxy system unit file template: src: tinyproxy.service.j2 dest: /etc/systemd/system/tinyproxy.service owner: root group: root mode: 0644 - name: Generate the systemd tmpfile for tinyproxy template: src: tinyproxytmp.conf.j2 dest: /etc/tmpfiles.d/tinyproxy.conf owner: root group: root mode: 0644 - name: Clean up the installed-by-default tinyproxy configuration file file: path: /etc/tinyproxy.conf state: absent - name: Enable and restart the tinyproxy service systemd: daemon_reload: yes name: tinyproxy.service enabled: yes state: restarted ================================================ FILE: playbooks/roles/tinyproxy/templates/tinyproxy.conf.j2 ================================================ ## ## tinyproxy.conf -- tinyproxy daemon configuration file ## ## This example tinyproxy.conf file contains example settings ## with explanations in comments. For decriptions of all ## parameters, see the tinproxy.conf(5) manual page. ## # # User/Group: This allows you to set the user and group that will be # used for tinyproxy after the initial binding to the port has been done # as the root user. Either the user or group name or the UID or GID # number may be used. # User nobody Group nogroup # # Port: Specify the port which tinyproxy will listen on. Please note # that should you choose to run on a port lower than 1024 you will need # to start tinyproxy using root. # Port {{ tinyproxy_port }} # # Listen: If you have multiple interfaces this allows you to bind to # only one. If this is commented out, tinyproxy will bind to all # interfaces present. # Listen {{ tinyproxy_listen_address }} # # Timeout: The maximum number of seconds of inactivity a connection is # allowed to have before it is closed by tinyproxy. # Timeout {{ tinyproxy_timeout_seconds }} # # DefaultErrorFile: The HTML file that gets sent if there is no # HTML file defined with an ErrorFile keyword for the HTTP error # that has occured. # DefaultErrorFile "/usr/share/tinyproxy/default.html" # # StatFile: The HTML file that gets sent when a request is made # for the stathost. If this file doesn't exist a basic page is # hardcoded in tinyproxy. # StatFile "/usr/share/tinyproxy/stats.html" # # Logfile: Allows you to specify the location where information should # be logged to. If you would prefer to log to syslog, then disable this # and enable the Syslog directive. These directives are mutually # exclusive. # Logfile "/var/log/tinyproxy/tinyproxy.log" # # LogLevel: # # Set the logging level. Allowed settings are: # Critical (least verbose) # Error # Warning # Notice # Connect (to log connections without Info's noise) # Info (most verbose) # # The LogLevel logs from the set level and above. For example, if the # LogLevel was set to Warning, then all log messages from Warning to # Critical would be output, but Notice and below would be suppressed. # LogLevel {{ tinyproxy_log_level }} # # PidFile: Write the PID of the main tinyproxy thread to this file so it # can be used for signalling purposes. # PidFile "{{ tinyproxy_pid_file }}" # # MaxClients: This is the absolute highest number of threads which will # be created. In other words, only MaxClients number of clients can be # connected at the same time. # MaxClients {{ [vpn_clients * 4,50]|min() }} # # MinSpareServers/MaxSpareServers: These settings set the upper and # lower limit for the number of spare servers which should be available. # # If the number of spare servers falls below MinSpareServers then new # server processes will be spawned. If the number of servers exceeds # MaxSpareServers then the extras will be killed off. # MinSpareServers 5 MaxSpareServers 20 # # StartServers: The number of servers to start initially. # StartServers 10 # # MaxRequestsPerChild: The number of connections a thread will handle # before it is killed. In practise this should be set to 0, which # disables thread reaping. If you do notice problems with memory # leakage, then set this to something like 10000. # MaxRequestsPerChild 0 # # Allow: Customization of authorization controls. If there are any # access control keywords then the default action is to DENY. Otherwise, # the default action is ALLOW. # # The order of the controls are important. All incoming connections are # tested against the controls based on order. # Allow {{ tinyproxy_listen_address }} Allow {{ streisand_ipv4_address }} # # ViaProxyName: The "Via" header is required by the HTTP RFC, but using # the real host name is a security concern. If the following directive # is enabled, the string supplied will be used as the host name in the # Via header; otherwise, the server's host name will be used. # ViaProxyName "tinyproxy" # # ConnectPort: This is a list of ports allowed by tinyproxy when the # CONNECT method is used. To disable the CONNECT method altogether, set # the value to 0. If no ConnectPort line is found, all ports are # allowed (which is not very secure.) # # The following two ports are used by SSL. # ConnectPort 443 ConnectPort 563 ================================================ FILE: playbooks/roles/tinyproxy/templates/tinyproxy.service.j2 ================================================ [Unit] Description=tinyproxy - a light-weight HTTP/HTTPS proxy daemon for POSIX operating systems After=network-online.target sshd.service Documentation=man:tinyproxy(8) Documentation=https://www.banu.com/tinyproxy/ [Service] Type=forking PIDFile={{ tinyproxy_pid_file }} ExecStart=/usr/sbin/tinyproxy -c {{ tinyproxy_conf_file }} ExecStop=/usr/bin/killall -9 tinyproxy ExecReload=/bin/kill -HUP $MAINPID PrivateTmp=true RestartSec=5s Restart=on-failure [Install] WantedBy=multi-user.target ================================================ FILE: playbooks/roles/tinyproxy/templates/tinyproxytmp.conf.j2 ================================================ # Allow systemd to create a directory for # tinyproxy to write its PID file # https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html d {{ tinyproxy_pid_dir }} 0755 nobody nogroup - ================================================ FILE: playbooks/roles/tinyproxy/vars/main.yml ================================================ --- tinyproxy_timeout_seconds: 600 tinyproxy_port: 8888 tinyproxy_listen_address: "127.0.0.1" tinyproxy_log_level: "Critical" tinyproxy_pid_dir: "/var/run/tinyproxy" tinyproxy_pid_file: "{{ tinyproxy_pid_dir }}/tinyproxy.pid" tinyproxy_conf_dir: "/etc/tinyproxy" tinyproxy_conf_file: "{{ tinyproxy_conf_dir }}/tinyproxy.conf" ================================================ FILE: playbooks/roles/tor-bridge/defaults/main.yml ================================================ --- tor_orport: 8443 tor_obfs4_port: 9443 # By default Streisand does *not* publish the Tor relay's service descriptor to # the tor network. Using a value of 0 ensures the relay is private to the # streisand operator and their users. See the Tor documentation[0] for more # information: # [0]: https://www.torproject.org/docs/tor-manual.html.en#PublishServerDescriptor # tor_publish_service_desc: 0 # If you would like to contribute to the overall health of the Tor network by # submitting your bridge relay for use by others comment out the # `tor_publish_service_desc: 0` line above and uncomment the following line: # # tor_publish_service_desc: "bridge" ================================================ FILE: playbooks/roles/tor-bridge/files/apparmor-local-override ================================================ # Site-specific additions and overrides for system_tor. # For more details, please see /etc/apparmor.d/local/README. # Workaround https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=862993 # Tor v0.3.0.9 fails to read /var/lib/tor/hidden_service without this # app armor capability override capability dac_read_search, ================================================ FILE: playbooks/roles/tor-bridge/handlers/main.yml ================================================ --- - name: Restart Nginx for the Tor hidden service vhost service: name: nginx state: restarted ================================================ FILE: playbooks/roles/tor-bridge/meta/main.yml ================================================ --- dependencies: # Tor needs to be added to the firewall - { role: ufw } # Tor needs to ensure Nginx is installed to host the hidden service vhost - { role: nginx } ================================================ FILE: playbooks/roles/tor-bridge/tasks/docs.yml ================================================ --- - name: Create the Tor Gateway directory file: path: "{{ tor_gateway_location }}" owner: www-data group: www-data mode: 0750 state: directory - name: Generate the Tor obfs4 QR code shell: echo -n '{{ tor_obfs4_bridge_line }}' | qrencode -s 6 -o {{ tor_obfs4_qr_code }} - include_role: name: i18n-docs vars: title: "Tor" i18n_location: "{{ tor_gateway_location }}" input_template_name: "instructions" ================================================ FILE: playbooks/roles/tor-bridge/tasks/firewall.yml ================================================ --- - name: Ensure UFW allows Tor bridge ufw: to_port: "{{ tor_orport }}" proto: "tcp" rule: "allow" - name: Ensure UFW allows Tor obfs4 pluggable transport ufw: to_port: "{{ tor_obfs4_port }}" proto: "tcp" rule: "allow" ================================================ FILE: playbooks/roles/tor-bridge/tasks/main.yml ================================================ --- - name: "Add the Tor APT key" apt_key: url: "https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc" state: present - name: Add the Tor repository apt_repository: repo: 'deb https://deb.torproject.org/torproject.org {{ ansible_lsb.codename }} main' register: tor_add_apt_repository until: not tor_add_apt_repository.failed retries: "{{ apt_repository_retries }}" delay: "{{ apt_repository_delay }}" - name: Install the package to keep the Tor signing key current apt: package: deb.torproject.org-keyring - name: Install obfs4 and Tor apt: package: - obfs4proxy - tor # Update the firewall to allow Tor and obfs4proxy # NOTE(@cpu): we do this early in the role because the Tor daemon will check if # the obfs4proxy port is externally accessible during startup and we want to # make sure it is. - import_tasks: firewall.yml - name: Generate a random Nickname for the bridge shell: "{{ streisand_word_gen.tor_nickname | trim }} > {{ tor_bridge_nickname_file }}" args: creates: "{{ tor_bridge_nickname_file }}" - name: Register the bridge's random Nickname command: cat {{ tor_bridge_nickname_file }} register: tor_bridge_nickname changed_when: False - name: Generate the bridge config file template: src: torrc.j2 dest: /etc/tor/torrc owner: root group: root mode: 0644 # TODO(@cpu) - This should be removed once it isn't required, maybe in the next # release after tor 0.3.0.9 - name: Copy a local override for the Tor AppArmor profile in place copy: src: apparmor-local-override dest: /etc/apparmor.d/local/system_tor owner: root group: root mode: 0644 # TODO(@cpu) - In theory it seems like it should be possible to add the # following to the local override from above: # /usr/bin/obfsproxy ix, # /usr/bin/obfs4proxy ix, # but doing so results in an error from the existing `PUx` modifiers: # profile system_tor: has merged rule /usr/bin/obfs4proxy with conflicting x modifiers # in the interest of fixing a regression we work around this by changing the # dist provided `/etc/apparmor.d/abstractions/tor` file. - name: Fix the distro Tor apparmor abstraction replace: path: /etc/apparmor.d/abstractions/tor regexp: '^([\s]*)/usr/bin/(obfs4?proxy) PUx,$' replace: '\1/usr/bin/\2 ix,' backup: yes - name: Reload the system_tor AppArmor profile for the override to take effect command: apparmor_parser -r /etc/apparmor.d/system_tor - name: Restart Tor so the server fingerprint will be available in the state file, and the hidden service for the Gateway will start running service: name: tor state: restarted - name: Wait until obfs4proxy information has shown up in its state file wait_for: path: "{{ tor_obfs_state_directory }}/obfs4_state.json" search_regex: "node-id" - name: Wait until the hidden service is online wait_for: path: "{{ tor_hidden_service_directory }}/hostname" - name: Wait until the server fingerprint file is generated wait_for: path: "{{ tor_state_directory }}/fingerprint" - name: Register the Tor Hidden Service hostname command: cat {{ tor_hidden_service_directory }}/hostname register: tor_hidden_service_hostname_output changed_when: False - name: Register the Tor Hidden Service URL fact set_fact: tor_hidden_service_url: "http://{{ tor_hidden_service_hostname_output.stdout }}" - name: Generate the hidden service virtual host and restart Nginx if it is updated template: src: hidden-service-vhost.j2 dest: /etc/nginx/sites-available/streisand-hidden-service owner: root group: root mode: 0644 notify: Restart Nginx for the Tor hidden service vhost - name: Enable the virtual host file: path: /etc/nginx/sites-enabled/streisand-hidden-service src: /etc/nginx/sites-available/streisand-hidden-service state: link - name: Discover the server fingerprint command: "awk '{ print $2 }' {{ tor_state_directory }}/fingerprint" register: tor_fingerprint - name: Discover the obfs4 certificate details shell: cat "{{ tor_obfs_state_directory }}/obfs4_bridgeline.txt" | grep 'Bridge obfs4' | sed -e 's/^.*cert=\(.*\) .*$/\1/' register: tor_obfs4_certificate changed_when: False # Generate the docs gateway page - import_tasks: docs.yml # This needs to be done after the docs folder structure is generated - name: Copy the hidden service hostname to the webroot when in test client mode copy: src: "{{ tor_hidden_service_directory }}/hostname" remote_src: yes dest: "{{ tor_gateway_location }}/hidden-service-hostname.txt" force: yes # Mirror the Tor browser bundle - import_tasks: mirror.yml ================================================ FILE: playbooks/roles/tor-bridge/tasks/mirror-common.yml ================================================ --- - name: Make the directory where the Tor Project's mirrored files will be stored file: path: "{{ tor_mirror_location }}" owner: www-data group: www-data mode: 0755 state: directory - name: Discover the latest stable version of the Tor Browser Bundle # The Python code below can be cleaned up once the Tor Project has # finished transitioning away from putting the OS suffix in the # RecommendedTBBVersions file. This check should work with both # formats though. # # https://trac.torproject.org/projects/tor/ticket/8940#comment:28 shell: curl -s 'https://www.torproject.org/projects/torbrowser/RecommendedTBBVersions/' | python -c 'import json; import re; import sys; j = json.load(sys.stdin); print [re.sub(r"-.*$", "", tbb) for tbb in j if "a" not in tbb and "b" not in tbb][-1];' args: warn: no register: tor_latest_recommended_check - name: Set the target Tor Browser Bundle version set_fact: tor_browser_bundle_version: "{{ tor_latest_recommended_check.stdout }}" - name: Include the mirror variables for Tor Browser Bundle include_vars: mirror.yml ================================================ FILE: playbooks/roles/tor-bridge/tasks/mirror.yml ================================================ --- - name: Include the Tor common variables include_vars: mirror-common.yml - name: Include the Tor download variables include_vars: mirror-download.yml - block: # Include the mirror-common tasks - import_tasks: mirror-common.yml # Download the Tor browser in each locale, verifying GPG signatures. - include_role: name: download-and-verify vars: project_name: "Tor Browser ({{ locale }})" project_download_baseurl: "{{ tor_base_download_url }}" project_download_files: "{{ tor_download_files }}" project_download_location: "{{ tor_mirror_location }}" project_signer_keyid: "{{ tor_signer_keyid }}" with_items: "{{ streisand_languages.values() | map(attribute='tor_locale') | list }}" when: locale in tor_available_locales loop_control: loop_var: locale rescue: - name: "{{ streisand_mirror_warning }}" pause: seconds: "{{ streisand_mirror_warning_seconds }}" - include_role: name: i18n-docs vars: title: "Tor mirror" i18n_location: "{{ tor_mirror_location }}" input_template_name: "mirror" ================================================ FILE: playbooks/roles/tor-bridge/templates/hidden-service-vhost.j2 ================================================ # Streisand Tor Hidden Service Gateway server { listen {{ tor_internal_hidden_service_address }}; auth_basic "Authorization Required"; auth_basic_user_file {{ streisand_gateway_htpasswd_file }}; # Disable all logging access_log /dev/null; error_log /dev/null crit; root {{ streisand_gateway_location }}; index index.html index.htm; } ================================================ FILE: playbooks/roles/tor-bridge/templates/instructions-fr.md.j2 ================================================ {% include "languages.md.j2" %} Pont Tor -------- --- * Plateformes * [Ordinateurs de bureau](#desktops) * [Android](#android) * [iOS](#ios) ### Ordinateurs de bureau ### 1. Téléchargez le [Tor Browser Bundle](/mirror/tor/index-fr.html) Pour votre plate-forme de choix. Le transport enfichable Obfsproxy obfs4 qu'il inclut vous permettra d'utiliser le réseau Tor, même si votre FAI effectue activement une inspection Deep Packet afin de bloquer votre trafic Tor. 1. Lancez Tor Browser Bundle. 1. Cliquez le bouton *Configurer* sous le texte qui lit *La connexion Internet de cet ordinateur est censurée ou relayée*. 1. Choisissez *Oui* pour la question *Votre fournisseur d'accès à Internet (FAI) bloque-t-il ou censure-t-il vos connexions au réseau Tor?* 1. Choisissez l'option *Saisir les ponts personnalisés*, et coller les informations suivantes: {{ tor_obfs4_bridge_line }} 1. Répondez a la question du serveur mandataire locale. Dans la plupart des cas la réponse sera *Non*. 1. Cliquez *Se Connecter*. Vous êtes prêt à partir! Tor est connecté au serveur de pont *{{ tor_bridge_nickname.stdout }}*, et votre trafic Tor crypté est totalement obscurci par obfsproxy afin qu'il ne ressemble pas au trafic Tor! ### Android ### 1. Téléchargez [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android&hl=fr) et lancez-le. 1. Tapez le bouton *Menu*. 1. Tapdz *QR Codes*. 1. Tapez *Analyser BridgeQR*. 1. Scannez le code QR suivant. Cela configurera automatiquement Orbot pour se connecter à ce serveur en utilisant le transport enfichable obfs4: ![Tor obfs4 QR code](/tor/tor-obfs4-qr-code.png) 1. Vous devez redémarrer Orbot pour que les modifications prennent effet. Tapez sur le bouton *Menu*, sélectionnez *Quitter*, et lancer Orbot à nouveau. 1. Appuyez sur le bouton *DÉMARRER* pour activer Orbot. Génial! Orbot s'est connecté au serveur pont *{{ tor_bridge_nickname.stdout }}*! Vous êtes maintenant prêt à configurer Twitter et Firefox pour acheminer leur trafic via Orbot. #### Configuration de Twitter pour Android pour utiliser Orbot #### 1. Ouvrez Twitter. 1. Accedez au menu de paramètres et confidentialité. 1. Choisissez *Position et proxy*. 1. Tapez *Proxy*. 1. Chocher *Activer le proxy HTTP*. 1. Tapez *Hôte du proxy* et entrez `127.0.0.1`. 1. Tapez *Port du proxy* et entrez `8118`. #### Configuration de Firefox pour Android pour utiliser Orbot #### 1. Ouvrez Firefox. 2. Tapez `about:config` dans la barre d'adresse et appuyez sur le bouton 'Go' de votre clavier. 3. Tapez `proxy` dans la barre de recherche. 4. Définissez la valeur de *network.proxy.socks* à `127.0.0.1`. 5. Définissez la valeur de *network.proxy.socks\_port* à `9050`. 6. Définissez la valeur de *network.proxy.socks\_remote\_dns* à `true`. 7. Définissez la valeur de *network.proxy.type* à `1`. ### iOS ### 1. Téléchargez [Onion Browser](https://itunes.apple.com/fr/app/onion-browser/id519296448?mt=8) et lancez-le. 1. Tapez le bouton menu sur le bas de l'écran et choisissez *Browser Settings*. 1. Faites défiler jusqu'au bas et appuyez sur *Bridges & Netwrok Connection* 1. Sélectionnez *Custom Bridges* 1. TAppuyez sur l'icône de la caméra dans le coin inférieur droit. Vous devrez peut-être avoir besoin d'activer l'accès de la caméra pour Onion Browser dans vos paramètres iOS. 1. Scannez le code QR suivant. Cela configurera automatiquement le navigateur Onion pour se connecter à ce serveur à l'aide du transport enfichable obfs4: ![Tor obfs4 QR code](/tor/tor-obfs4-qr-code.png) 1. Tapez *OK* l'orsque la confirmation est affichée. 1. Tapez *Save*. 1. Tapez *Restart app*. 1. Relancez l'application. Si vous vous connectez avec succès, le pont *{{ tor_bridge_nickname.stdout }}* fonctionne correctement et votre connexion est obscurcie par le transport enfichable obfs4! ================================================ FILE: playbooks/roles/tor-bridge/templates/instructions.md.j2 ================================================ {% include "languages.md.j2" %} Tor Bridge ---------- --- * Platforms * [Desktops](#desktops) * [Android](#android) * [iOS](#ios) ### Desktops ### 1. Download the [Tor Browser Bundle](/mirror/tor/) for your platform of choice. The Obfsproxy obfs4 pluggable transport that it includes will let you use the Tor network even if your ISP is actively performing Deep Packet Inspection in an attempt to block all Tor traffic. 1. Launch the Tor Browser Bundle. 1. Click the *Configure* button underneath the text that says *I need to configure bridge, firewall, or proxy settings*. 1. Answer *Yes* to *Does your Internet Service Provider (ISP) block or otherwise censor connections to the Tor Network?* 1. Choose the *Enter custom bridges* option, and paste the following information into the text box: {{ tor_obfs4_bridge_line }} 1. Answer the Local Proxy Configuration question. In most cases the answer will be *No*. 1. Click *Connect*. You are ready to go! Tor has connected to the *{{ tor_bridge_nickname.stdout }}* bridge server, and your encrypted Tor traffic is completely obscured by obfsproxy so it doesn't look like Tor traffic at all! ### Android ### 1. Download [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) and launch it. 1. Tap the *Menu* button. 1. Tap *QR Codes*. 1. Tap *Scan BridgeQR*. 1. Scan the following QR code. This will automatically configure Orbot to connect to this server using the obfs4 pluggable transport: ![Tor obfs4 QR code](/tor/tor-obfs4-qr-code.png) 1. You must restart Orbot in order for the changes to take effect. Tap the *Menu* button, select *Exit*, and launch Orbot again. 1. Long press on the power button to activate Orbot. Great! Orbot has connected to the *{{ tor_bridge_nickname.stdout }}* bridge server! You are now ready to configure Twitter and Firefox to route their traffic through Orbot. #### Configuring Twitter for Android to use Orbot #### 1. Open Twitter. 1. Tap the three dots in the upper-right of the screen to open the menu. 1. Choose *Settings*. 1. Tap *General*. 1. Tap *Proxy*. 1. Check the *Enable HTTP Proxy* checkbox. 1. Tap *Proxy Host* and enter `127.0.0.1`. 1. Tap *Proxy Port* and enter `8118`. #### Configuring Firefox for Android to use Orbot #### 1. Open Firefox. 1. Type `about:config` into the address bar and tap the *Go* button on your keyboard. 1. Type `proxy` into the search box. 1. Set the value of *network.proxy.socks* to `127.0.0.1`. 1. Set the value of *network.proxy.socks_port* to `9050`. 1. Set the value of *network.proxy.socks\_remote\_dns* to `true`. 1. Set the value of *network.proxy.type* to `1`. ### iOS ### 1. Download [Onion Browser](https://itunes.apple.com/us/app/onion-browser/id519296448?mt=8) and launch it. 1. Tap the menu button on the bottom of the screen and choose *Browser Settings*. 1. Scroll to the bottom and tap *Configure Bridges* 1. Tap *Enter Custom Bridges*. 1. Tap the camera icon in the lower-right corner. You may need to enable camera access for Onion Browser in your iOS Settings. 1. Scan the following QR code. This will automatically configure Onion Browser to connect to this server using the obfs4 pluggable transport: ![Tor obfs4 QR code](/tor/tor-obfs4-qr-code.png) 1. Tap *OK* when the confirmation is displayed. 1. Tap *Save*. 1. Tap *Restart app*. 1. Relaunch the application. If you successfully connect, the *{{ tor_bridge_nickname.stdout }}* bridge is working correctly, and your connection is obscured by the obfs4 pluggable transport so it doesn't look like Tor traffic at all! ================================================ FILE: playbooks/roles/tor-bridge/templates/mirror-fr.md.j2 ================================================ {% include "languages.md.j2" %} ### Tor Browser Bundle (localisé en français) ### **Linux** * [{{ tor_linux32_filename_template | regex_replace('locale', item.value.tor_locale) }}]({{ tor_linux32_href | regex_replace('locale', item.value.tor_locale) }}) ([sig]({{ tor_linux32_sig_href | regex_replace('locale', item.value.tor_locale) }})) * [{{ tor_linux64_filename_template | regex_replace('locale', item.value.tor_locale) }}]({{ tor_linux64_href | regex_replace('locale', item.value.tor_locale) }}) ([sig]({{ tor_linux64_sig_href | regex_replace('locale', item.value.tor_locale) }})) **macOS** * [{{ tor_macos_filename_template | regex_replace('locale', item.value.tor_locale) }}]({{ tor_macos_href | regex_replace('locale', item.value.tor_locale) }}) ([sig]({{ tor_macos_sig_href | regex_replace('locale', item.value.tor_locale) }})) **Windows** * [{{ tor_windows_filename_template | regex_replace('locale', item.value.tor_locale) }}]({{ tor_windows_href | regex_replace('locale', item.value.tor_locale) }}) ([sig]({{ tor_windows_sig_href | regex_replace('locale', item.value.tor_locale) }})) ================================================ FILE: playbooks/roles/tor-bridge/templates/mirror.md.j2 ================================================ {% include "languages.md.j2" %} ### Tor Browser Bundle ### **Linux** * [{{ tor_linux32_filename_template | regex_replace('locale', item.value.tor_locale) }}]({{ tor_linux32_href | regex_replace('locale', item.value.tor_locale) }}) ([sig]({{ tor_linux32_sig_href | regex_replace('locale', item.value.tor_locale) }})) * [{{ tor_linux64_filename_template | regex_replace('locale', item.value.tor_locale) }}]({{ tor_linux64_href | regex_replace('locale', item.value.tor_locale) }}) ([sig]({{ tor_linux64_sig_href | regex_replace('locale', item.value.tor_locale) }})) **macOS** * [{{ tor_macos_filename_template | regex_replace('locale', item.value.tor_locale) }}]({{ tor_macos_href | regex_replace('locale', item.value.tor_locale) }}) ([sig]({{ tor_macos_sig_href | regex_replace('locale', item.value.tor_locale) }})) **Windows** * [{{ tor_windows_filename_template | regex_replace('locale', item.value.tor_locale) }}]({{ tor_windows_href | regex_replace('locale', item.value.tor_locale) }}) ([sig]({{ tor_windows_sig_href | regex_replace('locale', item.value.tor_locale) }})) ================================================ FILE: playbooks/roles/tor-bridge/templates/torrc.j2 ================================================ SocksPort 0 ORPort {{ tor_orport }} ExtORPort auto BridgeRelay 1 PublishServerDescriptor {{ tor_publish_service_desc }} ExitPolicy reject *:* Nickname {{ tor_bridge_nickname.stdout }} ServerTransportPlugin obfs4 exec /usr/bin/obfs4proxy ServerTransportListenAddr obfs4 0.0.0.0:{{ tor_obfs4_port }} HiddenServiceDir {{ tor_hidden_service_directory }} HiddenServicePort 80 {{ tor_internal_hidden_service_address }} ================================================ FILE: playbooks/roles/tor-bridge/vars/main.yml ================================================ --- tor_bridge_nickname_file: "/etc/tor/bridge_nickname" tor_standard_connection_details: "{{ streisand_ipv4_address }}:{{ tor_orport }}" tor_obfs4_bridge_line: "obfs4 {{ streisand_ipv4_address }}:{{ tor_obfs4_port }} {{ tor_fingerprint.stdout }} cert={{ tor_obfs4_certificate.stdout }} iat-mode=0" tor_state_directory: "/var/lib/tor" tor_hidden_service_directory: "{{ tor_state_directory }}/hidden_service/" tor_obfs_state_directory: "{{ tor_state_directory }}/pt_state" tor_gateway_location: "{{ streisand_gateway_location }}/tor" tor_markdown_instructions: "{{ tor_gateway_location }}/index.md" tor_html_instructions: "{{ tor_gateway_location }}/index.html" tor_obfs4_qr_code: "{{ tor_gateway_location }}/tor-obfs4-qr-code.png" tor_internal_hidden_service_address: "127.0.0.1:8181" ================================================ FILE: playbooks/roles/tor-bridge/vars/mirror-common.yml ================================================ --- # Tor common variables # -------------------- tor_project_name: "Tor Browser Bundle" tor_mirror_location: "{{ streisand_mirror_location }}/tor" tor_mirror_href_base: "/mirror/tor" tor_base_download_url: "https://dist.torproject.org/torbrowser/{{ tor_browser_bundle_version }}" tor_available_locales: - ar - de - en-US - es-ES - fa - fr - it - ja - ko - nl - pl - pt-BR - ru - tr - vi - zh-CN ================================================ FILE: playbooks/roles/tor-bridge/vars/mirror-download.yml ================================================ --- # Tor download variables # ---------------------- # Windows tor_browser_bundle_windows_filename: "{{ tor_windows_filename_base }}_{{ locale }}.exe" tor_browser_bundle_windows_sig_filename: "{{ tor_browser_bundle_windows_filename }}.asc" # macOS tor_browser_bundle_macos_filename: "{{ tor_macos_filename_base }}_{{ locale }}.dmg" tor_browser_bundle_macos_sig_filename: "{{ tor_browser_bundle_macos_filename }}.asc" # Linux 32bit tor_browser_bundle_linux32_filename: "{{ tor_linux32_filename_base }}_{{ locale }}.tar.xz" tor_browser_bundle_linux32_sig_filename: "{{ tor_browser_bundle_linux32_filename }}.asc" # Linux 64bit tor_browser_bundle_linux64_filename: "{{ tor_linux64_filename_base }}_{{ locale }}.tar.xz" tor_browser_bundle_linux64_sig_filename: "{{ tor_browser_bundle_linux64_filename }}.asc" tor_signer_keyid: "D9FF06E2" tor_download_files: - { "file": "{{ tor_browser_bundle_windows_filename }}", "sig": "{{ tor_browser_bundle_windows_sig_filename }}" } - { "file": "{{ tor_browser_bundle_macos_filename }}", "sig": "{{ tor_browser_bundle_macos_sig_filename }}" } - { "file": "{{ tor_browser_bundle_linux32_filename }}", "sig": "{{ tor_browser_bundle_linux32_sig_filename }}" } - { "file": "{{ tor_browser_bundle_linux64_filename }}", "sig": "{{ tor_browser_bundle_linux64_sig_filename }}" } ================================================ FILE: playbooks/roles/tor-bridge/vars/mirror.yml ================================================ --- # Tor mirror variables # -------------------- tor_mirror_location: "{{ streisand_mirror_location }}/tor" tor_windows_filename_base: "torbrowser-install-{{ tor_browser_bundle_version }}" tor_windows_filename_template: "{{ tor_windows_filename_base }}_locale.exe" tor_windows_sig_filename_template: "{{ tor_windows_filename_template }}.asc" tor_windows_href: "{{ tor_mirror_href_base }}/{{ tor_windows_filename_template }}" tor_windows_sig_href: "{{ tor_mirror_href_base }}/{{ tor_windows_sig_filename_template }}" tor_macos_filename_base: "TorBrowser-{{ tor_browser_bundle_version }}-osx64" tor_macos_filename_template: "{{ tor_macos_filename_base }}_locale.dmg" tor_macos_sig_filename_template: "{{ tor_macos_filename_template }}.asc" tor_macos_href: "{{ tor_mirror_href_base }}/{{ tor_macos_filename_template }}" tor_macos_sig_href: "{{ tor_mirror_href_base }}/{{ tor_macos_sig_filename_template }}" tor_linux32_filename_base: "tor-browser-linux32-{{ tor_browser_bundle_version }}" tor_linux32_filename_template: "{{ tor_linux32_filename_base }}_locale.tar.xz" tor_linux32_sig_filename_template: "{{ tor_linux32_filename_template }}.asc" tor_linux32_href: "{{ tor_mirror_href_base }}/{{ tor_linux32_filename_template }}" tor_linux32_sig_href: "{{ tor_mirror_href_base }}/{{ tor_linux32_sig_filename_template }}" tor_linux64_filename_base: "tor-browser-linux64-{{ tor_browser_bundle_version }}" tor_linux64_filename_template: "{{ tor_linux64_filename_base }}_locale.tar.xz" tor_linux64_sig_filename_template: "{{ tor_linux64_filename_template }}.asc" tor_linux64_href: "{{ tor_mirror_href_base }}/{{ tor_linux64_filename_template }}" tor_linux64_sig_href: "{{ tor_mirror_href_base }}/{{ tor_linux64_sig_filename_template }}" ================================================ FILE: playbooks/roles/ufw/tasks/main.yml ================================================ --- - name: Install UFW apt: package: ufw - name: Disable UFW logging lineinfile: dest: /etc/ufw/ufw.conf regexp: "^LOGLEVEL" line: "LOGLEVEL=off" - name: Change the default forward policy lineinfile: dest: /etc/default/ufw regexp: "^DEFAULT_FORWARD_POLICY" line: 'DEFAULT_FORWARD_POLICY="ACCEPT"' - name: Ensure UFW allows SSH ufw: to_port: "{{ ssh_port }}" proto: "tcp" rule: "allow" - name: Ensure UFW is enabled and denies by default ufw: state: "enabled" policy: "deny" direction: "incoming" - name: Ensure UFW allows nginx ufw: to_port: "{{ nginx_port }}" proto: "tcp" rule: "allow" ================================================ FILE: playbooks/roles/validation/defaults/main.yml ================================================ streisand_new_server_provisioning: true ================================================ FILE: playbooks/roles/validation/tasks/main.yml ================================================ --- # Check SSH key exists - include: ssh.yml - name: Validate that OpenVPN optional variables are rational fail: msg: "stunnel cannot be enabled if openvpn is disabled" when: not streisand_openvpn_enabled and streisand_stunnel_enabled - name: Validate that Tinyproxy optional variables are rational fail: msg: "tinyproxy cannot be enabled if ssh forward user is disabled" when: not streisand_ssh_forward_enabled and streisand_tinyproxy_enabled - name: Validate that sshutle optional variables are rational fail: msg: "streisand_sshuttle_enabled cannot be enabled if ssh forward user is disabled" when: not streisand_ssh_forward_enabled and streisand_sshuttle_enabled # tl;dr: increase vpn_clients beyond 20 at your own risk. # # We currently *support* up to 20 VPN client credentials. If you'd # like more, raising the limit might even work! 20 is conservative, # 50 is probably OK. If you'd like to help us increase it, success # stories at https://github.com/StreisandEffect/discussions would be # nice. We can raise the limit after sufficient testing with each of # the VPN types. # # Places you'll run into trouble if you use more: # # * Generated documentation will become unwieldy. # # * OpenVPN, OpenConnect, and WireGuard each use /24 networks # internally. Addresses 0, 1, and 255 are unavailable, leaving 253 # remaining. # # o OpenVPN and OpenConnect dynamically allocate addresses, so # that's just a limit on concurrent users. # # o WireGuard permanently associates an IP address per client # credential, so that's a hard limit of 253 generated # certificates. # # * Generated client names are not checked/generated for # uniqueness. (This will be fixed.) Duplicates will cause # mysterious failures. At 20 generated names, this essentially # doesn't happen, but because of the birthday paradox, 200 users # gives odds of 0.5%, I think. # # * There may be other resource exhaustion issues with large numbers # of concurrent users. # # Protocols like SSH and Shadowsocks don't have per-user # credentials, so changing vpn_clients doesn't affect them. However, # the vpn_clients value is our guess for what's safe for them. # # Streisand operates over difficult-to-debug networks. The # possibility of resource exhaustion may make troubleshooting other # problems harder. # # Summary: When reporting problems, please try to reproduce them with # vpn_clients set to 20 or less. 50 is probably fine. 254 will fail to # deploy. - name: Validate that the maximum number of clients is set to a reasonable amount when: (vpn_clients > 20) or (vpn_clients < 1) fail: msg: |- Too many VPN clients specified. vpn_clients must be between 1 and 20. See playbooks/roles/validation/tasks/main.yml. - name: Validate that at least one VPN is specified fail: msg: "At least one Streisand VPN service must be enabled." when: (not streisand_openconnect_enabled) and (not streisand_openvpn_enabled) and (not streisand_shadowsocks_enabled) and (not streisand_ssh_forward_enabled) and (not streisand_tor_enabled) and (not streisand_wireguard_enabled) ================================================ FILE: playbooks/roles/validation/tasks/ssh.yml ================================================ --- - block: - name: "Stat the Streisand SSH private key" stat: path: "{{ streisand_ssh_private_key }}" register: streisand_ssh_private_key_status - name: "Fail if the Streisand SSH private key file doesn't exist" fail: msg: "The Streisand SSH private key \"{{ streisand_ssh_private_key }}\" does not exist." when: streisand_ssh_private_key_status.stat.exists == False - name: "Stat the Streisand SSH public key" stat: path: "{{ streisand_ssh_private_key }}.pub" register: streisand_ssh_key_status when: streisand_new_server_provisioning - name: "Fail if the Streisand SSH public key file doesn't exist" fail: msg: "The Streisand SSH public key \"{{ streisand_ssh_private_key }}.pub\" does not exist." when: - streisand_new_server_provisioning - not streisand_ssh_key_status.stat.exists rescue: - fail: msg: "Ensure you specified an existing SSH private key file. Ensure a corresponding SSH public key file exists if you are setting up a new server.\n Try using `ssh-keygen -f {{ streisand_ssh_private_key }} to generate your key if it does not exist\n" ================================================ FILE: playbooks/roles/wireguard/defaults/main.yml ================================================ --- wireguard_port: "51820" ================================================ FILE: playbooks/roles/wireguard/meta/main.yml ================================================ --- dependencies: - { role: dnsmasq } - { role: ip-forwarding } ================================================ FILE: playbooks/roles/wireguard/tasks/docs.yml ================================================ --- - name: Create the WireGuard Gateway directory file: path: "{{ wireguard_gateway_location }}" owner: www-data group: www-data mode: 0750 state: directory - name: Copy the client configuration files to the WireGuard Gateway directory command: "cp {{ wireguard_client_path }}/{{ client_name.stdout }}/client.conf {{ wireguard_gateway_location }}/{{ client_name.stdout }}.conf" args: creates: "{{ wireguard_gateway_location }}/{{ client_name.stdout }}.conf" with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" - name: Generate the client configuration QR codes in the WireGuard Gateway directory shell: "qrencode -s 6 -o {{ wireguard_gateway_location }}/{{ client_name.stdout }}.png < {{ wireguard_gateway_location }}/{{ client_name.stdout }}.conf" args: creates: "{{ wireguard_gateway_location }}/{{ client_name.stdout }}.png" with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" - name: Copy the client OpenWrt configuration fragments to the WireGuard Gateway directory command: "cp {{ wireguard_client_path }}/{{ client_name.stdout }}/client-openwrt.txt {{ wireguard_gateway_location }}/{{ client_name.stdout }}-openwrt.txt" args: creates: "{{ wireguard_gateway_location }}/{{ client_name.stdout }}-openwrt.txt" with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" - include_role: name: i18n-docs vars: title: "WireGuard" i18n_location: "{{ wireguard_gateway_location }}" input_template_name: "instructions" ================================================ FILE: playbooks/roles/wireguard/tasks/firewall.yml ================================================ --- - name: Ensure UFW allows DNS requests from WireGuard clients ufw: to_port: "53" proto: "udp" rule: "allow" from_ip: "10.192.122.0/24" - name: Ensure UFW allows WireGuard ufw: to_port: "{{ wireguard_port }}" proto: "udp" rule: "allow" - name: Allow WireGuard through the firewall command: "{{ item }}" with_items: "{{ wireguard_firewall_rules }}" - name: "Add WireGuard firewall persistence service to init" template: src: streisand-wireguard-service.sh.j2 dest: /etc/init.d/streisand-wireguard mode: 0755 - name: "Enable the streisand-wireguard init service" service: name: streisand-wireguard enabled: yes ================================================ FILE: playbooks/roles/wireguard/tasks/install.yml ================================================ --- - name: Determine the running kernel release command: uname -r register: kernel_release - name: Add the WireGuard PPA apt_repository: repo: 'ppa:wireguard/wireguard' register: wireguard_add_apt_repository until: not wireguard_add_apt_repository.failed retries: "{{ apt_repository_retries }}" delay: "{{ apt_repository_delay }}" - name: Install the WireGuard packages apt: package: - linux-headers-{{ kernel_release.stdout }} - linux-headers-generic - wireguard-dkms - wireguard-tools ================================================ FILE: playbooks/roles/wireguard/tasks/main.yml ================================================ --- # Set up the PPA and install packages - import_tasks: install.yml - name: Generate private and public key for the server shell: "umask 077; wg genkey | tee {{ wireguard_server_private_key_file }} | wg pubkey > {{ wireguard_server_public_key_file }}" args: creates: "{{ wireguard_server_public_key_file }}" - name: Register the server key file contents command: cat {{ item }} register: wireguard_server_key_files changed_when: False with_items: - "{{ wireguard_server_private_key_file }}" - "{{ wireguard_server_public_key_file }}" - name: Set the server key material facts set_fact: wireguard_server_private_key: "{{ wireguard_server_key_files.results[0].stdout }}" wireguard_server_public_key: "{{ wireguard_server_key_files.results[1].stdout }}" - name: Create client directory file: path: "{{ wireguard_client_path }}" state: directory owner: root group: root mode: 0600 - name: Create a directory for each client file: path: "{{ wireguard_client_path }}/{{ client_name.stdout }}" state: directory owner: root group: root mode: 0600 with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" - name: Generate private and public key for each client shell: "umask 077; wg genkey | tee client.key | wg pubkey > client.pub" args: chdir: "{{ wireguard_client_path }}/{{ client_name.stdout }}" creates: client.key with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" - name: Register client public keys command: cat client.pub args: chdir: "{{ wireguard_client_path }}/{{ client_name.stdout }}" with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" register: wireguard_client_pubkeys changed_when: False - name: Register client private keys command: cat client.key args: chdir: "{{ wireguard_client_path }}/{{ client_name.stdout }}" with_items: "{{ vpn_client_names.results }}" loop_control: loop_var: "client_name" label: "{{ client_name.item }}" register: wireguard_client_privkeys changed_when: False - name: Generate the client configuration profiles template: src: client.conf.j2 dest: "{{ wireguard_client_path }}/{{ item[0].stdout }}/client.conf" with_together: - "{{ vpn_client_names.results }}" - "{{ wireguard_client_privkeys.results }}" loop_control: label: "{{ item[0].item }}" - name: Generate OpenWrt configuration fragments template: src: client-openwrt.txt.j2 dest: "{{ wireguard_client_path }}/{{ item[0].stdout }}/client-openwrt.txt" with_together: - "{{ vpn_client_names.results }}" - "{{ wireguard_client_privkeys.results }}" loop_control: label: "{{ item[0].item }}" - name: Generate the server configuration template: src: "server.conf.j2" dest: "{{ wireguard_path }}/wg0.conf" owner: root group: root mode: 0600 - name: Enable reload-module-on-update to upgrade WireGuard without user confirmation file: path: "{{ wireguard_path }}/.reload-module-on-update" state: touch # Set up the WireGuard firewall rules - import_tasks: firewall.yml - name: Enable the WireGuard service so it starts at boot, and bring up the WireGuard network interface systemd: name: wg-quick@wg0.service enabled: yes state: started # Temporary workaround for issue #500 ignore_errors: yes - name: "Configure DNSMasq to listen on {{ dnsmasq_wireguard_ip }}:53" template: src: wireguard_dnsmasq.conf.j2 dest: /etc/dnsmasq.d/wireguard.conf # NOTE(@cpu): We don't use a `notify` to "Restart dnsmasq" here because it seems # that in some conditions Ansible mistakenly believes the dnsmasq restart can be # skipped. We also don't use "reloaded" instead of "restarted" here because # dnsmasq doesn't seem to reload _new_ config files in that case, just existing # ones. A full restart is required in practice (sigh) - name: "Restart DNSMasq to pick up the new configuration" service: name: dnsmasq state: restarted # Generate Gateway documentation - import_tasks: docs.yml ================================================ FILE: playbooks/roles/wireguard/templates/client-openwrt.txt.j2 ================================================ ### START-WIREGUARD # This OpenWrt (LEDE) /etc/rc.local script sets up the WireGuard profile # "{{ item[0].stdout }}" from the "{{ streisand_server_name }}" Streisand server. # Note that this script will replace the router's Internet access; # only run it when you're connected to the LAN/WiFi connection. # Open the LuCI web admin interface to the "System:Startup" page. # Paste this whole file into the "Local Startup" box. During the next # reboot, the script will run, and erase itself. It will leave a # success/failure message in the "Local Startup" box. # If you're using the web admin UI, you need the "luci-app-wireguard" # and "luci-proto-wireguard" packages installed. # LuCI instructions end here. ############ # If you aren't using LuCI, you need the "kmod-wireguard" and # "wireguard-tools" packages. You may need to reboot after running # this script (just restarting /etc/init.d/network may not be enough.) # Commenting out the next line will stop this script from messing # around with /etc/rc.local. trap self_cleanup 0 # Instructions end here. ################################################################ # Make errors fatal. set -e wireguard_completed="# Wireguard setup did not complete." self_cleanup () { sed -i.bak -e '/^### START-WIREGUARD/,/^### END-WIREGUARD/ d' \ -e "\$ a $wireguard_completed" \ /etc/rc.local } . /lib/functions.sh # This should be rewritten in terms of /lib/functions.sh callbacks. move_to_firewall_zone () { ifname="$1" fwzone="$2" for i in $(seq 0 20); do # No more zones? name=$(uci get "firewall.@zone[$i].name" 2>/dev/null) || return networks=$(uci get "firewall.@zone[$i].network") if list_contains networks "$ifname"; then # Shell programmer wanted. networks=$(echo "$networks" | sed "s/ *\\b${ifname}\\b *//") fi if [ "$name" = "$fwzone" ]; then append networks "$ifname" fi uci set "firewall.@zone[$i].network=$networks" done } ifname="{{ item[0].stdout | replace("-", "_") }}" if ! uci get "network.$ifname" >/dev/null 2>&1; then # Clean out any old peers uci delete "network.@wireguard_$ifname[0]" >/dev/null 2>&1 || true uci batch < ### Android ### 1. [Installez WireGuard](https://play.google.com/store/apps/details?id=com.wireguard.android). 1. Lancez l'application et appuyez sur le bouton bleu pour ajouter un nouveau tunnel. 1. Appuyez *Create from QR code* (Créer à partir du code QR) et autoriser l'application à accéder à la caméra. Un viewfinder (viseur) apparaîtra. 1. Utilisez l'appareil photo pour numériser l'un de ces codes QR de configuration client. **Un seul appareil peut utiliser un profil à la fois**: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/wireguard/{{ client.stdout }}.png) {% endfor %} 1. Saisissez un nom pour le tunnel et appuyez sur *Create Tunnel* (Créer tunnel) pour sauvgarder la configuration. 1. Appuyez sur le bouton situé à côté du nom du tunnel pour activer le VPN. Si c'est la première fois que vous utilisez WireGuard sur votre appareil Android, vous serez invité à accepter la demande de connexion VPN. 1. Vous devriez être prêt à partir! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. ### Linux ### 1. [Installez WireGuard](https://www.wireguard.com/install/) 1. Téléchargez un fichier de configuration client. **Un seul ordinateur peut utiliser un profil à la fois**. Pour cet exemple, nous supposerons que vous avez téléchargé le fichier {{ vpn_client_names.results[0].stdout }}: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/wireguard/{{ client.stdout }}.conf) {% endfor %} 1. Déplacez le fichier de configuration du client dans le répertoire correct: `sudo sh -c 'umask 077; mkdir -p /etc/wireguard; cat > /etc/wireguard/{{ vpn_client_names.results[0].stdout }}.conf' < ~/Downloads/{{ vpn_client_names.results[0].stdout }}.conf` 1. Utilisez l'utilitaire `wg-quick` pour démarrer l'interface WireGuard: `sudo wg-quick up {{ vpn_client_names.results[0].stdout }}` 1. Vous devriez être prêt à partir! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. 1. Pour arrêter de routé votre trafic via WireGuard, simplement arrêtez l'interface: `sudo wg-quick down {{ vpn_client_names.results[0].stdout }}` #### Une note sur la configuration DNS #### Chaque fichier inclure la commande `DNS` qui utilise `resolvconf` afin de diriger le trafic DNS vers le serveur dnsmasq qui est disponible via l'interface cryptée WireGuard à `{{ dnsmasq_wireguard_ip }}`. Bien que `resolvconf` soit un utilitaire commun, vous devrez peut-être remplacer cette ligne avec `PostUp`/`PostDown` pour votre distribution ou votre configuration réseau particulière. ### macOS ### **ATTENTION**: Le client macOS WireGuard en est aux premiers stades de développement et est toujours considéré comme expérimental. 1. Installez [Homebrew](https://brew.sh/), si vous ne l'avez pas. 1. Installez << WireGuard tools >> en utilisant Homebrew: `brew install wireguard-tools` 1. Téléchargez l'un des fichiers de configuration client. **Un seul appareil peut utiliser un profil à la fois**. Pour cet exemple, nous supposerons que vous avez téléchargé {{ vpn_client_names.results[0].stdout }}: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/wireguard/{{ client.stdout }}.conf) {% endfor %} 1. Déplacez le fichier de configuration du client dans le répertoire de configuration WireGuard. La commande suivante suppose que vous avez téléchargé le fichier de configuration dans votre dossier `Téléchargements` (ou `Downloads`). (Si ce n'est pas le cas, modifiez-le en conséquence): `sudo sh -c 'umask 077 && mkdir -p /etc/wireguard/ && cat ~/Downloads/{{ vpn_client_names.results[0].stdout }}.conf > /etc/wireguard/{{ vpn_client_names.results[0].stdout }}.conf'` 1. Utilisez l'utilitaire `wg-quick` pour lancer l'interface WireGuard: `sudo wg-quick up {{ vpn_client_names.results[0].stdout }}` 1. Vous devriez être prêt à partir! Vous pouvez vérifier que votre trafic est correctement routé par [recherche de votre adresse IP sur DuckDuckGo]({{ streisand_my_ip_url }}). Il devrait dire *Votre adresse IP publique est {{streisand_ipv4_address}}*. 1. Vous pouvez vérifier le statut du VPN à tout moment en utilisant `wg`: `sudo wg show` 1. Pour arrêter le routage de votre trafic via WireGuard, déconnectez l'interface avec: `sudo wg-quick down {{ vpn_client_names.results[0].stdout }}` --- ### EXPÉRIMENTAL: OpenWrt (LEDE) ### En tant qu'expérience **non prise en charge**, ces profils sont disponibles pour les routeurs exécutant [OpenWrt](https://openwrt.org/). Le support nécessite OpenWrt/LEDE 17.01.4 ou ultérieur. Les périphériques OpenWrt exécutent Linux, mais ils sont gérés via un système de configuration centralisé appelé [UCI](https://openwrt.org/docs/guide-user/base-system/uci). La plupart des appareils OpenWrt ont une interface d'administration Web appelée LuCI, qui cache la complexité de UCI. Ces profils WireGuard peuvent être installés via un shell ou via l'interface Web LuCI. Ces profils remplaceront la connexion Internet existante. Par conséquence, vous ne devez les installer que lorsque vous êtes connecté à l'interface réseau WiFi ou le LAN du routeur. Si vous êtes connecté à distance, vous risquez de vous enfermé de votre réseau. #### Installation du logiciel WireGuard #### Assurez-vous que ces progiciels sont installés: `luci-app-wireguard` et `luci-proto-wireguard`. Pour installer ces packages à partir de l'interface utilisateur Web: 1. Naviguer vers la page LuCI *System:Software*, et cliquez sur le bouton *Update lists* (Mettre à jour les listes). 2. Saisissez `wireguard` dans la boîte *Find package* (Rechercher un package). 3. Cliquez sur l'onglet *Available packages (wireguard)* (progiciels disponibles). 4. Cliquez sur *Install* à côté de `luci-app-wireguard`; Revenez à l'étape 2 pour installer `luci-proto-wireguard`. (Si vous gérez votre routeur sans l’interface utilisateur LuCI, vous pouvez utiliser à la place `opkg update; opkg install kmod-wireguard wireguard-tools` .) #### Installation du profil #### Les utilisateurs expérimentés peuvent installer ces profils à partir de la ligne de commande SSH du routeur. Placez-en un dans un fichier et exécutez-le en tant que un script shell. Un moyen simple existe pour simplifier l'installation à partir de l’interface Web. La page web LuCI *System:Startup* contient une zone de texte *Local Startup*. C'est un script shell qui est exécuté à chaque démarrage du routeur. Ces profils WireGuard sont conçus pour être collés dans la boîte *Local Startup*, remplaçant le contenu existant. (Assurez-vous de supprimer toutes les lignes `exit 0`.) Au prochain redémarrage du routeur, le script sera exécuté. Le script se supprimera automatiquement. Cochez la case *Local Startup* pour le résultat du statut. #### Modifications apportées par le profil #### * Une nouvelle interface nommée comme `poem_walk` sera créée * L'interface est ajoutée à la zone réseau *WAN* * La route par défaut vers Internet est définie sur l'interface. (Cela brisera la connectivité WAN, alors assurez-vous de ne l'installer qu'à partir du WiFi/LAN.) * Le keepalive WireGuard est réglé pour 25 secondes * DNS à l'échelle du système est obligé de pointer sur le serveur Streisand Si vous n'aimez pas le DNS configuré par défaut, vous pouvez modifier le comportement DNS sur la page LuCI: *Network: DHCP and DNS*. Mettez l'adresse du serveur DNS dans *Transfer DNS*. Sur l'onglet *Resolv and Hosts Files*, laissez *Ignore resolve file* coché, sauf si vous souhaitez utiliser votre DNS existant (non recommandé). Si vous savez que vous n'êtes pas derrière un périphérique NAT, modifiez l'interface WireGuard pour définir le paramètre keepalive sur 0. #### Profils OpenWrt #### {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/wireguard/{{ client.stdout }}-openwrt.txt) {% endfor %} ================================================ FILE: playbooks/roles/wireguard/templates/instructions.md.j2 ================================================ {% include "languages.md.j2" %} WireGuard --------- [WireGuard](https://www.wireguard.com/) is almost certainly the best connection option that Streisand provides. This is due to its [incredible performance](https://www.wireguard.com/performance/), [class-leading cryptography](https://www.wireguard.com/protocol/), and many, many other benefits. WireGuard is available on Linux, and [cross-platform](https://www.wireguard.com/xplatform/) and portable userspace implementations can be downloaded for Android and macOS. An [experimental configuration for OpenWrt/LEDE](#openwrt) 17.01.4 (or later) is also available. --- * Platforms * [Windows](#windows) * [Android](#android) * [iOS](#ios) * [Linux](#linux) * [macOS](#macos) * [OpenWrt](#openwrt) ### Windows ### 1. [Install WireGuard for Windows](https://www.wireguard.com/install/) 1. Download one of the client configuration files. **Only one computer can use a profile at a time**. For this example we'll assume you downloaded {{ vpn_client_names.results[0].stdout }}: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/wireguard/{{ client.stdout }}.conf) {% endfor %} 1. Launch the app, and choose *Add Tunnel → Import tunnel(s) from file…*, select the downloaded file, then click *Open*. 1. Select the imported tunnel and click *Activate* to connect. 1. You should be good to go! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. --- ### Android ### 1. [Install WireGuard](https://play.google.com/store/apps/details?id=com.wireguard.android). 1. Launch the app and tap the blue button to add a new tunnel. 1. Tap *Create from QR code* and grant the app permission to access the camera. A viewfinder will appear. 1. Use the camera to scan one of these client configuration QR codes. **Only one device can use a profile at a time**: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/wireguard/{{ client.stdout }}.png) {% endfor %} 1. Enter a name for the tunnel and tap *Create Tunnel* to save the configuration. 1. Tap the switch next to the tunnel's name to enable the VPN. If this is your first time using WireGuard on your Android device, you will be prompted to accept the VPN connection request. 1. You should be good to go! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. --- ### iOS ### 1. Install WireGuard (by WireGuard Development Team) from the App Store. 1. Launch the app and tap the blue button to add a new tunnel. 1. Tap *Create from QR code* and grant the app permission to access the camera. A viewfinder will appear. 1. Use the camera to scan one of these client configuration QR codes. **Only one device can use a profile at a time**: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/wireguard/{{ client.stdout }}.png) {% endfor %} 1. Enter a name for the tunnel and tap *Create Tunnel* to save the configuration. 1. Tap the switch next to the tunnel's name to enable the VPN. If this is your first time using WireGuard on your iOS device, you will be prompted to accept the VPN connection request. 1. You should be good to go! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. --- ### Linux ### 1. [Install WireGuard](https://www.wireguard.com/install/). 1. Download one of the client configuration files. **Only one computer can use a profile at a time**. For this example we'll assume you downloaded {{ vpn_client_names.results[0].stdout }}: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/wireguard/{{ client.stdout }}.conf) {% endfor %} 1. Move the client configuration file into the correct directory: `sudo sh -c 'umask 077; mkdir -p /etc/wireguard; cat > /etc/wireguard/{{ vpn_client_names.results[0].stdout }}.conf' < ~/Downloads/{{ vpn_client_names.results[0].stdout }}.conf` 1. (Ubuntu/Debian) For Ubuntu and Debian users you will need to install the `openresolv` package: `sudo apt-get install openresolv` 1. Use the `wg-quick` utility to bring up the WireGuard interface: `sudo wg-quick up {{ vpn_client_names.results[0].stdout }}` 1. For Linux systems using systemd you can also enable Wireguard at boot: `sudo systemctl enable wg-quick@{{ vpn_client_names.results[0].stdout }}.service` 1. You should be good to go! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. 1. To stop routing your traffic through WireGuard, simply bring the interface back down: `sudo wg-quick down {{ vpn_client_names.results[0].stdout }}` #### A note on DNS configuration #### Each client configuration profile includes a `DNS` command that uses resolvconf to direct DNS traffic to the dnsmasq server that is available via the WireGuard encrypted interface at `{{ dnsmasq_wireguard_ip }}`. Although resolvconf is a common utility, you may need to use `PostUp`/`PostDown` with a different command for your distribution or particular network configuration. --- ### macOS ### **WARNING**: The macOS WireGuard client is in early stages of development and still considered experimental. It may be unstable or buggy. 1. [Install the macOS WireGuard App](https://itunes.apple.com/us/app/wireguard/id1451685025) from the Mac App Store 1. Download one of the client configuration files. **Only one computer can use a profile at a time**. For this example we'll assume you downloaded {{ vpn_client_names.results[0].stdout }}: {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/wireguard/{{ client.stdout }}.conf) {% endfor %} 1. Launch the WireGuard app, click *Import tunnel(s) from file* and choose the file downloaded in the previous step. If this is your first time using WireGuard on your macOS device, you will be prompted allow 'WireGuard' to add VPN Configurations. 1. Click *Activate* to enable the VPN. 1. You should be good to go! You can verify that your traffic is being routed properly by [looking up your IP address on DuckDuckGo]({{ streisand_my_ip_url }}). It should say *Your public IP address is {{ streisand_ipv4_address }}*. --- ### EXPERIMENTAL: OpenWrt (LEDE) ### As an **unsupported** experiment, these profiles are available for routers running [OpenWrt](https://openwrt.org/). Support requires OpenWrt/LEDE 17.01.4 or later; OpenWrt Chaos Calmer is too old. OpenWrt devices run Linux, but they're managed through a centralized configuration system called [UCI](https://openwrt.org/docs/guide-user/base-system/uci). Most OpenWrt devices have a web admin interface called LuCI, which hides the complexity of UCI. These WireGuard profiles can be installed through a shell, or through the LuCI web interface. These profiles will replace the existing Internet connection. As a result, you should only install them when you're connected to the router's WiFi or LAN network interface. If you're logged in remotely, you may be locked out. #### Installing WireGuard software #### Make sure these software packages are installed: `luci-app-wireguard` and `luci-proto-wireguard`. To install those packages from the web UI: 1. Go to the LuCI *System:Software* page, and click the *Update lists* button. 2. Type `wireguard` into the *Find package* box. 3. Click on the *Available packages (wireguard)* tab. 4. Click *Install* next to `luci-app-wireguard`; go back to step 2 to install `luci-proto-wireguard` as well. (If you're managing your router without the LuCI user interface, you can instead `opkg update; opkg install kmod-wireguard wireguard-tools` .) #### Installing the profile #### Experienced users can install these profiles from the router's SSH command line; place one in a file, and run it as a shell script. There's also a simple way to install via the web interface. The LuCI *System:Startup* web page contains a *Local Startup* text box. It's a shell script which is run each time the router boots up. These WireGuard profiles are designed to be pasted into the *Local Startup* box, replacing the existing contents. (Make sure to delete any `exit 0` lines.) The next time the router reboots, the script will be run. It removes itself automatically. Check the *Local Startup* box for the status result. #### Changes made by the profile #### * A new interface named like `poem_walk` will be created * The interface is added to the *WAN* network zone * The default route to the Internet is set to the interface. (This will break WAN connectivity, so be sure to install only from WiFi/LAN.) * The WireGuard keepalive is set to 25 seconds * System-wide DNS is forced to point at the Streisand server If you don't like the DNS default, you can change DNS behavior on the *Network:DHCP and DNS* LuCI page. Put the DNS server address in *DNS forwardings*. On the *Resolv and Hosts Files* tab, leave *Ignore resolve file* checked, unless you want to use your upstream DNS. (You probably don't.) If you know you aren't behind a NAT device, edit the WireGuard interface to set the keepalive to 0. #### OpenWrt Profiles #### {% for client in vpn_client_names.results %} * [{{ client.stdout }}](/wireguard/{{ client.stdout }}-openwrt.txt) {% endfor %} ================================================ FILE: playbooks/roles/wireguard/templates/server.conf.j2 ================================================ [Interface] Address = 10.192.122.1/24 SaveConfig = true ListenPort = {{ wireguard_port }} PrivateKey = {{ wireguard_server_private_key }} {% for client in vpn_client_names.results %} # "{{ client.stdout }}" Client Peer [Peer] PublicKey = {{ wireguard_client_pubkeys.results[(client.item|int)-1].stdout }} AllowedIPs = 10.192.122.{{ (client.item|int)+1 }}/32 {% endfor %} ================================================ FILE: playbooks/roles/wireguard/templates/streisand-wireguard-service.sh.j2 ================================================ #!/bin/sh ### BEGIN INIT INFO # Provides: streisand-wireguard # Required-Start: $network $remote_fs $local_fs # Required-Stop: $network $remote_fs $local_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Persist WireGuard firewall rules for Streisand ### END INIT INFO {% for rule in wireguard_firewall_rules %} {{ rule }} {% endfor %} exit 0 ================================================ FILE: playbooks/roles/wireguard/templates/wireguard_dnsmasq.conf.j2 ================================================ # Listen on the WireGuard address listen-address={{ dnsmasq_wireguard_ip }} ================================================ FILE: playbooks/roles/wireguard/vars/main.yml ================================================ --- wireguard_path: "/etc/wireguard" wireguard_client_path: "{{ wireguard_path }}/clients" wireguard_server_private_key_file: "{{ wireguard_path }}/server.key" wireguard_server_public_key_file: "{{ wireguard_path }}/server.pub" dnsmasq_wireguard_ip: "10.192.122.1" wireguard_firewall_rules: - "iptables --wait {{ streisand_iptables_wait }} -A FORWARD -s 10.192.122.0/24 -j ACCEPT" - "iptables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -s 10.192.122.0/24 -o {{ ansible_default_ipv4.interface }} -j MASQUERADE" wireguard_gateway_location: "{{ streisand_gateway_location }}/wireguard" ================================================ FILE: playbooks/ssh-setup.yml ================================================ --- - name: Configure Ansible SSH hosts: streisand-host gather_facts: no tasks: - set_fact: ansible_ssh_private_key_file: "{{ streisand_ssh_private_key }}" ================================================ FILE: playbooks/streisand.yml ================================================ --- - import_playbook: python.yml - import_playbook: lets-encrypt.yml - name: Collect diagnostics in case of error hosts: localhost gather_facts: no roles: - role: diagnostics when: not streisand_ci - name: Configure the Server and install required software # ======================================================== hosts: streisand-host remote_user: "root" become: true roles: - common - gpg - ssh - service-net - role: openconnect when: streisand_openconnect_enabled - role: openvpn when: streisand_openvpn_enabled - role: shadowsocks when: streisand_shadowsocks_enabled - role: ssh-forward when: streisand_ssh_forward_enabled - role: tinyproxy when: streisand_tinyproxy_enabled # tor-bridge is skipped in our full Ansible run on Travis CI due to # connectivity issues. - role: tor-bridge when: not streisand_ci and streisand_tor_enabled - sslh - ufw - role: wireguard when: streisand_wireguard_enabled - role: cloudflared when: not streisand_ci and streisand_cloudflared_enabled # streisand_le_enabled is set in lets-encrypt.yml based on user input. # lets-encrypt roles sets le_ok, which is used by streisand-gateway. - role: lets-encrypt when: streisand_le_enabled - role: ad-blocking when: streisand_ad_blocking_enabled - streisand-mirror - streisand-gateway ... ================================================ FILE: playbooks/test-client.yml ================================================ --- - name: Configure the client server for Ansible # ========================================= hosts: streisand-client gather_facts: no remote_user: "root" become: true tasks: - name: Install Python using a raw SSH command to enable the execution of Ansible modules raw: apt update && apt install python -y args: executable: /bin/bash - name: Configure the client server as a Streisand test client hosts: streisand-client remote_user: "root" become: true roles: - test-client ... ================================================ FILE: playbooks/vagrant.yml ================================================ --- - import_playbook: python.yml - name: Prepare the vagrant VM for Ansible # ========================================= hosts: streisand-host remote_user: "root" become: true # Use pre_tasks to run these before the roles, since the role expects keys to # exist. pre_tasks: # Ansible uses `ip -4 route get 8.8.8.8` to set the `ansible_default_ipv4` # fact with an interface's details. Without the below route being added this # results in enp0s3 being used when we want enp0s8 to be used. We work # around this by setting a route for 8.8.8.8 through enp0s8. - name: Workaround Ansible default ipv4 interface detection raw: route add -net 8.8.8.8 netmask 255.255.255.255 enp0s8 args: executable: /bin/bash when: ansible_default_ipv4.alias != "enp0s8" # We need a throwaway key pair created in the location expected by # {{ streisand_ssh_private_key }}. - name: Create throwaway SSH key pair directory. file: path: "{{ streisand_ssh_private_key | expanduser | dirname }}" state: directory mode: 0700 recurse: yes - name: Create throwaway SSH key pair. shell: "ssh-keygen -h -t rsa -f {{ streisand_ssh_private_key }} -N ''" args: creates: "{{ streisand_ssh_private_key }}" roles: - validation - import_playbook: ssh-setup.yml - import_playbook: streisand.yml ... ================================================ FILE: playbooks/validate.yml ================================================ --- - name: Perform global variables validation # ========================================= hosts: localhost connection: local gather_facts: no vars_files: - ../global_vars/globals.yml roles: - validation ... ================================================ FILE: requirements.txt ================================================ # Install Streisand requirements with: # # pip install -r ./requirements.txt # # If you have problems running this, try the util/venv-builder.sh script. # Core with Azure dependencies # ansible[azure]==2.8.4 SecretStorage # AWS boto boto3 # Google Compute Engine requests google-auth apache-libcloud # Linode linode-api4 # Rackspace pyrax ================================================ FILE: streisand ================================================ #!/usr/bin/env bash # Streisand provisioning script # Set errexit option to exit immediately on any non-zero status return set -e echo -e "\n\033[38;5;255m\033[48;5;234m\033[1m S T R E I S A N D \033[0m\n" # This is where Ansible looks for local modules. Disable it, in the # interests of consistency between installs. export ANSIBLE_LIBRARY= SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" DEFAULT_SITE_VARS="$SCRIPT_DIR/global_vars/default-site.yml" GLOBAL_VARS="$SCRIPT_DIR/global_vars/globals.yml" HOME_DIR="$HOME/.streisand" SITE_VARS="$HOME_DIR/site.yml" # Include the check_ansible function from ansible_check.sh source util/ansible_check.sh function init_homedir() { if [ ! -d "$HOME_DIR" ]; then mkdir "$HOME_DIR" echo "Created new Streisand home directory: $HOME_DIR" fi if [ ! -f "$SITE_VARS" ]; then cp "$DEFAULT_SITE_VARS" "$SITE_VARS" echo "Created new Streisand site vars file: $SITE_VARS" fi } # check_python checks whether the 'python' interpreter is Python 2 or Python 3. # If it is Python 2 then the inventory file is updated to set the # ansible_interpretter host var explicitly # function check_python() { # return 0 # local PYTHON_VERSION # PYTHON_VERSION="$(python --version 2>/dev/null)" # # if [[ -n $PYTHON_VERSION && ! $PYTHON_VERSION =~ ^Python\ 3\..* ]]; then # local INVENTORY_DIR="$SCRIPT_DIR/inventories/" # for INV_FILE in "$INVENTORY_DIR"/*; do # sed "s|=python\$|=$(type python)|" "$INV_FILE" > "$INV_FILE.new" # mv "$INV_FILE.new" "$INV_FILE" # git -C "$INVENTORY_DIR" update-index --assume-unchanged "$INV_FILE" 2>/dev/null || true # done # fi # } # validate runs the validation role to check the consistency of the Streisand # service vars (e.g. that at least one service is enabled after customization of # $SITE_VARS). function validate() { local NEW_SERVER_PROVISIONING=$1 if [ -z "${NEW_SERVER_PROVISIONING}" ]; then NEW_SERVER_PROVISIONING=true fi echo; echo; ansible-playbook \ --extra-vars="@$GLOBAL_VARS" \ --extra-vars="@$DEFAULT_SITE_VARS" \ --extra-vars="@$SITE_VARS" \ --extra-vars="streisand_new_server_provisioning=$NEW_SERVER_PROVISIONING" \ playbooks/validate.yml } # customize prompts the user to decide if they want to customize the Streisand # installation. If the user wishes, the playbooks/customize.yml role is used to # change the base installation by rewriting the $SITE_VARS file. function customize() { read -r -p " Do you wish to customize which services Streisand will install? By saying 'no' Streisand will use the settings configured in $SITE_VARS Press enter to customize your installation: " confirm case "$confirm" in no) echo; echo "Installing Streisand services specified in $SITE_VARS";; *) echo; echo "Confirmed. Customizing Streisand services."; # NOTE(@cpu): We don't pass the other `--extra-vars` here because the # customize `vars_prompt` will only happen if the vars aren't already # set. If you pass the site/defaults in no prompting will happen. echo; echo; ansible-playbook \ --extra-vars="@$GLOBAL_VARS" \ playbooks/customize.yml;; esac } # run_genesis invokes the genesis playbook file specified by the first argument # to the function, or `streisand.yml` if none is provided. It uses the second # argument to the function as the inventory or `inventories/inventory` if none # is provided. function run_genesis() { local GENESIS_PLAYBOOK=$1 local ASK_BECOME="" if [ -z "${GENESIS_PLAYBOOK}" ]; then GENESIS_PLAYBOOK=streisand.yml fi local GENESIS_INVENTORY=$2 if [ -z "${GENESIS_INVENTORY}" ]; then GENESIS_INVENTORY=inventories/inventory fi # Customize the Streisand services that will be installed customize # Ensure that the customization hasn't resulted in something inconsistent # NEW_SERVER_PROVISIONING can be False or True (0 or 1), tests if we need # to check existence of SSH public key if [ "$GENESIS_PLAYBOOK" != "existing-server.yml" ] && [ "$GENESIS_PLAYBOOK" != "localhost.yml" ]; then validate true else validate false fi if [ -n "${SSH_USER}" ] && [ "$SSH_USER" != "root" ]; then ASK_BECOME="--ask-become-pass" fi # Run the specified genesis playbook with the specified Ansible inventory echo; echo; ansible-playbook \ -i $GENESIS_INVENTORY \ --extra-vars="@$GLOBAL_VARS" \ --extra-vars="@$DEFAULT_SITE_VARS" \ --extra-vars="@$SITE_VARS" \ $ASK_BECOME \ "playbooks/$GENESIS_PLAYBOOK" } # be_careful_friend asks you to pay attention because you're about to do # something that might be impossible to undo! function be_careful_friend() { read -r -p "$1" confirm case "$confirm" in streisand) echo; echo "Confirmed. Continuing";; *) echo; echo "Cancelling & exiting."; exit 1;; esac } # local_provision handles provisioning the same machine as is running the # Streisand script/Ansible. It performs an additional "ARE YOU SURE" step before # invoking ansible-playbook. It uses the `inventory-local-provision` inventory # file from the inventories directory as the Ansible inventory. function local_provision() { be_careful_friend " LOCAL PROVISIONING WILL OVERWRITE CONFIGURATION ON **THIS** MACHINE. THE MACHINE YOU ARE CURRENTLY EXECUTING THIS SHELL SCRIPT ON. ARE YOU 100% SURE THAT YOU WISH TO CONTINUE? Please enter the word 'streisand' to continue: " run_genesis localhost.yml inventories/inventory-local-provision } function existing_server() { read -r -p "What is the IP of the existing server: " SERVER_IP be_careful_friend " THIS WILL OVERWRITE CONFIGURATION ON THE EXISTING SERVER. STREISAND ASSUMES $SERVER_IP IS A BRAND NEW UBUNTU INSTANCE AND WILL NOT PRESERVE EXISTING CONFIGURATION OR DATA. ARE YOU 100% SURE THAT YOU WISH TO CONTINUE? Please enter the word 'streisand' to continue: " # If ANSIBLE_SSH_USER is empty, default to root if [ -z "${ANSIBLE_SSH_USER}" ]; then SSH_USER='root' # Otherwise, use whatever ANSIBLE_SSH_USER is set to as the SSH_USER else SSH_USER=${ANSIBLE_SSH_USER} fi # Create an inventory file string on the fly read -r -d '' TEMPL << EOF || true [localhost] localhost ansible_connection=local ansible_python_interpreter=python3 [streisand-host] $SERVER_IP ansible_user=$SSH_USER EOF # Create the inventory file echo "$TEMPL" > inventories/inventory-existing # Invoke the Streisand playbook on the existing server inventory run_genesis existing-server.yml inventories/inventory-existing } # Make sure the system is ready for the Streisand playbooks init_homedir # check_python check_ansible # Figure out which genesis role to invoke read -r -p "Which provider are you using? 1. Amazon 2. Azure 3. DigitalOcean 4. Google 5. Linode 6. Rackspace 7. localhost (Advanced) 8. Existing Server (Advanced) : " reply case "$reply" in 1) run_genesis amazon.yml;; 2) run_genesis azure.yml;; 3) run_genesis digitalocean.yml;; 4) run_genesis google.yml;; 5) run_genesis linode.yml;; 6) run_genesis rackspace.yml;; 7) local_provision;; 8) existing_server;; *) echo; echo "Invalid provider selected."; exit 1;; esac ================================================ FILE: tests/README.md ================================================ # Streisand-CI Testing Streisand CI testing uses Github Actions to test Ansible syntax and kick off a full playbook run. The Full playbook run is executed against github runners ## Testing Limitations There were some connectivity issues with the `tor-bridge` playbook in the CI testing environment. Due to this, the role is currently skipped. Streisand doc generation is also skipped until some mock variables and tasks are added. ## Local Testing You can use this testing framework to test locally as well. Note that when testing locally, the `tor-bridge` role will be ran. ### Host Environment Expectations: - **Fresh** Trusty or Xenial Ubuntu install (Vagrant or DigitalOcean server, etc.) Because LXC loads the host's kernal modules into the container. Libreswan needs to be compiled and installed on the host. This allows the ipsec role to complete successfully inside the LXC container. #### Running the tests - Clone the Streisand repository - Run test script - `./tests/test.sh full` - The `full` argument will run `development-setup.yml`, `syntax-check.yml`, and `run.yml`. #### Syntax only - `./tests/test.sh syntax` # TODO - Vagrant file which can automate setting up a local test environment for use with development-setup.yml, and run.yml - Figure out a way to test the Tor role - Add some mock variables for the document generation task - Add some automated client testing (Example: Automatically test an openvpn client) - Check if the tor-bridge is still an issue on github actions ================================================ FILE: tests/ansible.cfg ================================================ [defaults] nocows = 1 roles_path = playbooks/roles:../playbooks/roles host_key_checking = False remote_tmp = $HOME/.ansible/tmp local_tmp = $HOME/.ansible/tmp timeout = 100 forks = 15 internal_poll_interval=0.001 [ssh_connection] pipelining = True ================================================ FILE: tests/development-setup.yml ================================================ --- # NOTE(@craun): Removed the "install python" steps for local dev setup. In order to even run this playbook ansible # already needs to be installed, which, implies that python is also installed. - hosts: localhost gather_facts: yes remote_user: root become: yes pre_tasks: # NOTE(@alimakki): Due to key rotation, we pre-emptivley # add the Google linux apt signing key required by some # packages - name: Add an Apt signing key, uses whichever key is at the URL apt_key: url: https://dl-ssl.google.com/linux/linux_signing_key.pub state: present tasks: - name: Ensure consistent & clean apt state apt: update_cache: yes autoclean: yes autoremove: yes purge: yes - name: Remove old LXD/LXC from distro apt: name: - lxd* - lxc* state: absent autoremove: yes purge: yes - name: Install snapd apt: name: snapd - name: Install LXD snap snap: name: lxd - name: Connect LXD plug to slots command: "{{ item }}" with_items: - "snap connect lxd:lxd-support" - "snap connect lxd:network" - name: Start lxd command: "snap start lxd" - name: Wait for the LXD socket wait_for: path: "/var/snap/lxd/common/lxd/unix.socket" state: present sleep: 5 - name: lxd init config command: lxd init --auto --storage-backend dir args: creates: /var/snap/lxd/common/lxd/storage-pools/default - name: lxd create network command: lxc network create testbr0 args: creates: /var/snap/lxd/common/lxd/networks/testbr0 - name: Retrieve the Ubuntu Xenial AMD64 LXC image fingerprint uri: url: https://images.linuxcontainers.org/1.0/images/aliases/ubuntu/xenial/amd64 return_content: yes register: xenial_fingerprint - name: Launch streisand container (this will take a while) lxd_container: name: streisand state: started url: "unix:/var/snap/lxd/common/lxd/unix.socket" source: type: image mode: pull server: https://images.linuxcontainers.org protocol: lxd # Use the retrieved alias to fetch the image alias: "{{ xenial_fingerprint['json']['metadata']['target'] }}" profiles: ["default"] config: security.privileged: "true" wait_for_ipv4_addresses: true timeout: 300 # TODO(@cpu): Why this is required is a mystery to me. Everything in # development-setup.yml is able to access the unix socket without error. # Without this gross chmod the LXD connection used to run the Streisand # playbooks on the container fails with an access error. It would be nice to # reduce these permissions. It's likely dangerous in a non-CI environment - name: Open the permissions on the LXD socket command: chmod 777 /var/snap/lxd/common/lxd/unix.socket - name: Install python in container delegate_to: streisand raw: apt-get update && apt-get install -y python3 python3-apt python3-pip - name: Set python3 as the default raw: update-alternatives --install /usr/bin/python python /usr/bin/python3 1 - name: Set python3-pip as the default raw: update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1 ================================================ FILE: tests/group_vars/all/all ================================================ --- streisand_ci: yes streisand_noninteractive: no server_name: streisand ================================================ FILE: tests/inventory ================================================ [streisand] streisand ansible_connection=lxd ansible_become=yes [localhost] localhost ansible_connection=local ================================================ FILE: tests/randomize_sitevars.sh ================================================ #!/bin/bash -e # randomize_sitevar.sh takes a Streisand site vars yml file and randomly enables # some # of services. It guarantees that at least one service is enabled. It # operates *destructively* - it will disable all services in the yml file before # enabling a random selection. The site var yml contents are changed in-place. # Keep track of how many services we've enabled so we can check there was at # least one enabled. ENABLED_SERVICES=0 # randomize_services mutates a site vars file provided as the first argument function randomize_services { # Reset the state of the file to all disabled sed -i 's/yes/no/' "$1" # NOTE(@cpu): You might be tempted to pipe the `grep` into `read` for the # `while` condition. This will make the loop body excute in a subshell that # can not increment `ENABLED_SERVICES`. To work around this we use process # substitution. See http://mywiki.wooledge.org/BashFAQ/024 for more while read -r LINE do # Generate a random int between 0 and 100 FLIP=$((RANDOM%100)) # If the random int is >= 50, enable the service if [ "$FLIP" -gt 50 ] then SERVICE=$(echo "$LINE" | cut -d: -f1) case $SERVICE in "streisand_stunnel_enabled") # stunnel depends on openvpn echo "Setting 'streisand_openvpn_enabled: yes' to support stunnel" sed -i "s/\(streisand_openvpn_enabled\): no/\1: yes/" "$1" ;; "streisand_tinyproxy_enabled"|"streisand_sshuttle_enabled") # tinyproxy and sshuttle depend on the ssh-forward user echo "Setting 'streisand_ssh_forward_enabled' to support tinyproxy" sed -i "s/\(streisand_ssh_forward_enabled\): no/\1: yes/" "$1" ;; esac ENABLED_SERVICES=$((ENABLED_SERVICES+1)) echo "Setting '$SERVICE: yes'" sed -i "s/\($SERVICE\): no/\1: yes/" "$1" fi done < <(grep "no" "$1") } # Check that the provided site vars file exists if [ ! -e "$1" ] then echo "site vars file \"$1\" does not exist" exit 1 fi # Until we've enabled at least one service continue to randomize the input file while [ "$ENABLED_SERVICES" -eq "0" ] do randomize_services "$1" done echo "Enabled $ENABLED_SERVICES random services" ================================================ FILE: tests/remote_test.sh ================================================ #!/usr/bin/env bash # # Utility script for running the Vagrantfile.remotetest against a Streisand host # # Set errexit option to exit immediately on any non-zero status return set -e echo -e "\n\033[38;5;255m\033[48;5;234m\033[1m S T R E I S A N D R E M O T E T E S T\033[0m\n" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" VAGRANT_FILENAME="Vagrantfile.remotetest" GENERATED_DOCS_DIR="${SCRIPT_DIR}/../generated-docs" mkdir -p "${GENERATED_DOCS_DIR}" VAGRANTFILE="${SCRIPT_DIR}/../${VAGRANT_FILENAME}" function backup_vagrantfile() { cp "${VAGRANTFILE}" "${VAGRANTFILE}.dist" } function restore_vagrantfile() { cp "${VAGRANTFILE}.dist" "${VAGRANTFILE}" rm "${VAGRANTFILE}.dist" } trap restore_vagrantfile EXIT # set_remote_ip replaces the "REMOTE_IP_HERE" token from the # Vagrantfile.remotetest with the response from a user prompt function set_remote_ip() { read -r -p "What is the Streisand server IP? " SERVER_IP sed "s/\"REMOTE_IP_HERE\",/\"${SERVER_IP}\",/" "${VAGRANTFILE}" > "${VAGRANTFILE}.new" mv "${VAGRANTFILE}.new" "${VAGRANTFILE}" } function set_gateway_pass() { local GATEWAY_PASS_FILE="${GENERATED_DOCS_DIR}/gateway-password.txt" read -r -p "What is the Streisand gateway password? " GATEWAY_PASS echo "${GATEWAY_PASS}" > "${GATEWAY_PASS_FILE}" } backup_vagrantfile set_remote_ip set_gateway_pass pushd "${SCRIPT_DIR}/.." VAGRANT_VAGRANTFILE=${VAGRANT_FILENAME} vagrant up --provision popd ================================================ FILE: tests/run.yml ================================================ --- - hosts: streisand gather_facts: yes remote_user: root become: yes tasks: - set_fact: ansible_ssh_private_key_file: "{{ streisand_ssh_private_key }}" - block: - name: Generate SSH Key's for CI run shell: "ssh-keygen -b 2048 -t rsa -f /tmp/id_rsa_insecure -q -N ''" args: creates: "/tmp/id_rsa_insecure" - name: Get the default SSH key command: "cat /tmp/id_rsa_insecure.pub" register: ssh_key changed_when: False - name: Ensure permissions on insecure key are correct file: path: "/tmp/id_rsa_insecure" mode: "0666" when: streisand_ci - name: Check Streisand configuration is valid include_role: name: validation delegate_to: localhost - name: Ensure openssh is installed apt: package: openssh-server - name: Add authorized key authorized_key: user: root state: present key: "{{ ssh_key.stdout }}" - name: Create the in-memory inventory group add_host: name: "{{ ansible_default_ipv4.address }}" groups: streisand-host ansible_ssh_private_key_file: "/tmp/id_rsa_insecure" - name: Set the streisand_ipv4_address variable set_fact: streisand_ipv4_address: "{{ ansible_default_ipv4.address }}" - name: Set the streisand_server_name variable set_fact: streisand_server_name: "{{ server_name | regex_replace('\\s', '_') }}" - name: Include streisand.yml import_playbook: ../playbooks/streisand.yml ================================================ FILE: tests/shellcheck.sh ================================================ #!/bin/bash # # Streisand shellcheck wrapper # # This test script finds all of the *.sh shell files in the project tree and # runs shellcheck against them. # # Fail on errors set -e # Ensure shellcheck is present if ! command -v shellcheck > /dev/null 2>&1; then echo "The 'shellcheck' comand was not found in your PATH. Please install it" exit 1 fi # NOTE(@cpu): We use -x to follow `source` directives across files SHELLCHECK_ARGS=(-x) # Determine the absolute path of this script file's directory SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" # The Streisand project directory is one up from this script's directory, tests/ PROJECT_DIR="$SCRIPT_DIR/.." pushd "$PROJECT_DIR" # Run shellcheck against all of the `.sh` script files in the Streisand # project directory. Ignore any `venv` directory. # # NOTE(@cpu): While tempting to -exec shellcheck directly from find this will # eat-up any non-zero exit codes :-( Instead we find the files first and then # xargs shellcheck on the found files. find . -path "./venv" -prune -or -name '*.sh' -print0 | xargs -0 -n1 shellcheck "${SHELLCHECK_ARGS[@]}" # Also explicitly run `shellcheck` against the streisand wrapper script since # it doesn't end in .sh shellcheck "${SHELLCHECK_ARGS[@]}" "$PROJECT_DIR/streisand" popd ================================================ FILE: tests/site_vars/cloudflared.yml ================================================ --- # This site config enables openvpn, wireguard, and cloudflared (DNS-over-HTTPS) vpn_clients: 5 streisand_ad_blocking_enabled: yes streisand_cloudflared_enabled: yes streisand_openconnect_enabled: no streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: no streisand_shadowsocks_v2ray_enabled: no streisand_ssh_forward_enabled: no streisand_sshuttle_enabled: no streisand_stunnel_enabled: no streisand_tinyproxy_enabled: no streisand_tor_enabled: no streisand_wireguard_enabled: yes ================================================ FILE: tests/site_vars/openconnect.yml ================================================ --- # This site config only enables OpenConnect vpn_clients: 5 streisand_ad_blocking_enabled: no streisand_cloudflared_enabled: no streisand_openconnect_enabled: yes streisand_openvpn_enabled: no streisand_shadowsocks_enabled: no streisand_shadowsocks_v2ray_enabled: no streisand_ssh_forward_enabled: no streisand_sshuttle_enabled: no streisand_stunnel_enabled: no streisand_tinyproxy_enabled: no streisand_tor_enabled: no streisand_wireguard_enabled: no ================================================ FILE: tests/site_vars/openvpn.yml ================================================ --- # This site config only enables openvpn vpn_clients: 5 streisand_ad_blocking_enabled: no streisand_cloudflared_enabled: no streisand_openconnect_enabled: no streisand_openvpn_enabled: yes streisand_shadowsocks_enabled: no streisand_shadowsocks_v2ray_enabled: no streisand_ssh_forward_enabled: no streisand_sshuttle_enabled: no streisand_stunnel_enabled: no streisand_tinyproxy_enabled: no streisand_tor_enabled: no streisand_wireguard_enabled: no ================================================ FILE: tests/site_vars/random.yml ================================================ --- vpn_clients: 1 # Streisand CI's task randomizes these "_enabled" vars at build-time streisand_ad_blocking_enabled: no streisand_cloudflared_enabled: no streisand_openconnect_enabled: no streisand_openvpn_enabled: no streisand_shadowsocks_enabled: no streisand_shadowsocks_v2ray_enabled: no streisand_ssh_forward_enabled: no streisand_sshuttle_enabled: no streisand_stunnel_enabled: no streisand_tinyproxy_enabled: no streisand_tor_enabled: no streisand_wireguard_enabled: no ================================================ FILE: tests/site_vars/shadowsocks.yml ================================================ --- # This site config only enables Shadowsocks vpn_clients: 5 streisand_ad_blocking_enabled: no streisand_cloudflared_enabled: no streisand_openconnect_enabled: no streisand_openvpn_enabled: no streisand_shadowsocks_enabled: yes streisand_shadowsocks_v2ray_enabled: yes streisand_ssh_forward_enabled: no streisand_sshuttle_enabled: no streisand_stunnel_enabled: no streisand_tinyproxy_enabled: no streisand_tor_enabled: no streisand_wireguard_enabled: no ================================================ FILE: tests/site_vars/ssh.yml ================================================ --- # This site config only enables SSH forwarding and sshutle vpn_clients: 5 streisand_ad_blocking_enabled: no streisand_cloudflared_enabled: no streisand_openconnect_enabled: no streisand_openvpn_enabled: no streisand_shadowsocks_enabled: no streisand_shadowsocks_v2ray_enabled: no streisand_ssh_forward_enabled: yes streisand_sshuttle_enabled: yes streisand_stunnel_enabled: no streisand_tinyproxy_enabled: no streisand_tor_enabled: no streisand_wireguard_enabled: no ================================================ FILE: tests/syntax-check.yml ================================================ --- # Include each playbook in order to check syntax of each playbook - name: Include amazon.yml import_playbook: ../playbooks/amazon.yml - name: Include azure.yml import_playbook: ../playbooks/azure.yml - name: Include cloud-status.yml import_playbook: ../playbooks/cloud-status.yml - name: Include digitalocean.yml import_playbook: ../playbooks/digitalocean.yml - name: Include existing-server.yml import_playbook: ../playbooks/existing-server.yml - name: Include google.yml import_playbook: ../playbooks/google.yml - name: Include lets-encrypt.yml import_playbook: ../playbooks/lets-encrypt.yml - name: Include linode.yml import_playbook: ../playbooks/linode.yml - name: Include localhost.yml import_playbook: ../playbooks/localhost.yml - name: Include provider-detect.yml import_playbook: ../playbooks/provider-detect.yml - name: Include python.yml import_playbook: ../playbooks/python.yml - name: Include rackspace.yml import_playbook: ../playbooks/rackspace.yml - name: Include ssh-setup.yml import_playbook: ../playbooks/ssh-setup.yml - name: Include streisand.yml import_playbook: ../playbooks/streisand.yml - name: Include test-client.yml import_playbook: ../playbooks/test-client.yml - name: Include vagrant.yml import_playbook: ../playbooks/vagrant.yml - name: Include run.yml import_playbook: run.yml # Explicity include each role to ensure all roles are tested - hosts: localhost remote_user: root roles: - azure-security-group - certificates - common - diagnostics - dnsmasq - download-and-verify - ec2-security-group - gce-network - genesis-amazon - genesis-azure - genesis-digitalocean - genesis-google - genesis-linode - genesis-rackspace - i18n-docs - ip-forwarding - lets-encrypt - nginx - openconnect - openvpn - service-net - shadowsocks - ssh - ssh-forward - sslh - streisand-gateway - streisand-mirror - stunnel - sysctl - test-client - tinyproxy - tor-bridge - ufw - validation - wireguard ================================================ FILE: tests/tests.sh ================================================ #!/usr/bin/env bash # Streisand CI test script. # Usage: # ./tests.sh [setup|syntax|run|ci|full] # Set errexit option to exit immediately on any non-zero status return set -e echo -e "\n\033[38;5;255m\033[48;5;234m\033[1m S T R E I S A N D \033[0m\n" # Compute an absolute path to the test ansible.cfg file DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" export ANSIBLE_CONFIG=$DIR/ansible.cfg # Include the check_ansible function from ansible_check.sh source util/ansible_check.sh # Use packages installed by snap first PATH=$PATH:/snap/bin:/var/lib/snapd/snap/bin export PATH function run_playbook { PLAYBOOK="$1" # shellcheck disable=SC2206 EXTRA_FLAGS=(${@:2}) # Special case: If $SITE is "random" then we mix things up if [[ "$SITE" = "random" ]]; then SITE="tests/site_vars/random.yml" "$DIR/randomize_sitevars.sh" "$SITE" fi SITE_DECL="" if [ -n "$SITE" ]; then SITE_DECL="--extra-vars=@${SITE}" fi ansible-playbook \ -i "$DIR/inventory" \ --extra-vars=@global_vars/globals.yml \ $SITE_DECL \ "$PLAYBOOK" "${EXTRA_FLAGS[@]}" } # syntax_check runs `ansible-playbook` with `--syntax-check` to vet Ansible # playbooks for syntax errors function syntax_check { run_playbook "$DIR/syntax-check.yml" --syntax-check -vv } function dev_setup { run_playbook "$DIR/development-setup.yml" } function run_tests { run_playbook "$DIR/run.yml" --extra-vars=@"$DIR/vars_ci.yml" "$1" } function ci_tests { dev_setup && run_tests } function ci_tests_verbose { dev_setup && run_tests -vv } # Make sure the system is ready for the Streisand playbooks check_ansible # Allow overriding the RUN env var by providing an arg to the script if [ -n "$1" ]; then RUN="$1" fi # Setup prepares the local environment for running a Streisand LXC if [[ "$RUN" =~ "setup" ]] ; then dev_setup fi # Syntax checks for Ansible syntax errors if [[ "$RUN" =~ "syntax" ]] ; then syntax_check fi # Shellcheck checks for bash/sh errors/pitfalls if [[ "$RUN" =~ "shellcheck" ]] ; then ./tests/shellcheck.sh fi # Yamlcheck checks for general YAML best practices if [[ "$RUN" =~ "yamlcheck" ]] ; then ./tests/yamlcheck.sh fi # Run will run CI tests assuming the local environment is already prepared if [[ "$RUN" =~ "run" ]] ; then run_tests fi # CI will setup the local environment and then run tests if [[ "$RUN" =~ "ci" ]] ; then ci_tests fi # Full will do the same as "ci" but with verbose output if [[ "$RUN" =~ "full" ]] ; then ci_tests_verbose fi ================================================ FILE: tests/vars_ci.yml ================================================ streisand_ci: yes streisand_ssh_private_key: "/tmp/id_rsa_insecure" # Answer questions in playbooks/lets-encrypt.yml streisand_domain_var: "" streisand_domain: "" streisand_admin_email_var: "" streisand_admin_email: "" le_ok: no ================================================ FILE: tests/yamlcheck.sh ================================================ #!/bin/bash # # Streisand yamllint wrapper # # This test script finds all of the *.yml files in the project tree and # runs yamllint against them. Ignore any `venv` directory. # # Fail on errors set -e # Ensure yamllint is present if ! command -v yamllint > /dev/null 2>&1; then echo "The 'yamllint' command was not found in your PATH." echo "Please run 'pip install yamllint' to install." exit 1 fi # Determine the absolute path of this script file's directory SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" # The Streisand project directory is one up from this script's directory, tests/ PROJECT_DIR="$SCRIPT_DIR/.." # Streisand specifies a custom yamllint config to adjust what rules are applied YAMLLINT_ARGS=(-c "$SCRIPT_DIR/yamllint-config.yml") pushd "$PROJECT_DIR" # Run yamllint against all of the `.yml` files in the Streisand # project directory. # # NOTE(@cpu): While tempting to -exec shellcheck directly from find this will # eat-up any non-zero exit codes :-( Instead we find the files first and then # xargs yamllint on the found files. find . -path "./venv" -prune -or -name '*.yml' -print0 | xargs -0 -n1 yamllint "${YAMLLINT_ARGS[@]}" popd ================================================ FILE: tests/yamllint-config.yml ================================================ extends: default rules: # Presently there are a large number of files that break the line-length # limit.This changes the limit to a warning until we are ready to try and # tackle these legacy files. line-length: max: 280 level: warning # We allow a few rules for now to accept the "pretty" layout existing files # use. It may be worth revisiting these in the future. # Accept: # * "too many spaces after colon" # * "too many spaces inside braces" # * "truthy value should be one of [false, true] but we use [no, yes]" colons: disable braces: disable commas: disable truthy: disable ================================================ FILE: util/ansible_check.sh ================================================ # shellcheck shell=bash # This is included by the main streisand script. # check_ansible checks that Ansible is installed on the local system # and that it is a supported version. function check_ansible() { local REQUIRED_ANSIBLE_VERSION="2.8.4" if ! command -v ansible > /dev/null 2>&1; then echo " Streisand requires Ansible and it is not installed. Please see the README Installation section on Prerequisites" exit 1 fi ansible_version="$(ansible --version | head -1 | grep -oe '2[.0-9]*')" if ! ./util/version_at_least.py "$REQUIRED_ANSIBLE_VERSION" "$ansible_version" ; then echo " Streisand requires Ansible version $REQUIRED_ANSIBLE_VERSION or higher. This system has Ansible $ansible_version. " exit 1 fi } ================================================ FILE: util/dependencies.txt ================================================ build-essential python3-pip python3-openssl python3-dev python3-setuptools python3-venv python-cffi libffi-dev libssl-dev libcurl4-openssl-dev ================================================ FILE: util/print-aws-regions.py ================================================ #!/usr/bin/env python # -*- coding: utf-8 -*- # Generate code fragments for amazon.yml names = ( ("us-east-1", "US East", "N. Virginia"), ("us-east-2", "US East", "Ohio"), ("us-west-1", "US West", "N. California"), ("us-west-2", "US West", "Oregon"), ("ca-central-1", "Canada", "Central"), ("eu-central-1", "EU", "Frankfurt"), ("eu-west-1", "EU", "Ireland"), ("eu-west-2", "EU", "London"), ("eu-west-3", "EU", "Paris"), ("ap-northeast-1", "Asia Pacific", "Tokyo"), ("ap-northeast-2", "Asia Pacific", "Seoul"), ("ap-northeast-3", "Asia Pacific", "Osaka-Local"), ("ap-southeast-1", "Asia Pacific", "Singapore"), ("ap-southeast-2", "Asia Pacific", "Sydney"), ("ap-south-1", "Asia Pacific", "Mumbai"), ("ap-east-1", "Asia Pacific", "Hong Kong"), ("eu-north-1", "EU", "Stockholm"), ("sa-east-1", "South America", "São Paulo"), ) sorted_names = sorted(names) print("") print (""" regions:""") for i in range(len(sorted_names)): j = i + 1 o = sorted_names[i] print(' "{j}": "{symname}"'.format(j=j, symname=o[0])) print ("----------------------") print ("") print (""" In what region should the server be located?""") for i in range(len(sorted_names)): j = i + 1 o = sorted_names[i] print(" {j:>2}. {symname:<15} {region:<14} ({nickname})".format( j=j, symname=o[0], region=o[1], nickname=o[2])) ================================================ FILE: util/source_check_and_default_site_vars.sh ================================================ #!/bin/bash # # This is intended to be sourced into a deploy script. It exists to DRY up the # codebase. It expects the following variables set: # # DEFAULT_SITE_VARS /path/to/default-site.yml # PROJECT_DIR /path/to/streisand # SITE_VARS /path/to/site.yml # set -o errexit # If no site vars file is provided, then use one of the two default options. if [ -z "${SITE_VARS}" ]; then SITE_VARS="${HOME}/.streisand/site.yml" if [ ! -f "${SITE_VARS}" ]; then SITE_VARS="${DEFAULT_SITE_VARS}" fi echo "Using default config file: ${SITE_VARS}" fi # Make sure the alleged configuration file exists. if [ ! -f "${SITE_VARS}" ]; then echo "No such config file: ${SITE_VARS}" exit 1 fi ================================================ FILE: util/source_validate_and_deploy.sh ================================================ #!/bin/bash # # This is intended to be sourced into a deploy script. It exists to DRY up the # codebase. It expects the following variables set: # # DEFAULT_SITE_VARS /path/to/default-site.yml # GLOBAL_VARS /path/to/globals.yml # INVENTORY /path/to/inventory # PROJECT_DIR /path/to/streisand # SITE_VARS /path/to/site.yml # PLAYBOOK /path/to/playbook.yml. # set -o errexit ansible-playbook \ --extra-vars="@${GLOBAL_VARS}" \ --extra-vars="@${DEFAULT_SITE_VARS}" \ --extra-vars="@${SITE_VARS}" \ "${PROJECT_DIR}/playbooks/validate.yml" # Update the server. ansible-playbook \ -i "${INVENTORY}" \ --extra-vars="@${GLOBAL_VARS}" \ --extra-vars="@${DEFAULT_SITE_VARS}" \ --extra-vars="@${SITE_VARS}" \ "${PLAYBOOK}" ================================================ FILE: util/ubuntu-dependencies.sh ================================================ #!/bin/bash # Abort on any error. set -e # This script installs Streisand builder dependencies on an Ubuntu # 16.04 system; Debian jessie has been lightly tested. It installs # system-wide packages. As a result, this is most useful on a fresh # machine, where conflicting Python packages won't be installed. If # you're not on a fresh regular machine, consider using # venv-dependencies.sh instead. # # It's safe to run this script multiple times. For most cloud # providers, it should work as a boot/cloud-init script. # # The defaults are fine for an interactive install. If you are using # this as a cloud-init script, change the first two settings: # # * quiet must be "--yes" # * The "DEBIAN_FRONTEND" line must be uncommented. # We default to a regular, interactive upgrade. quiet= # quiet='--yes' # If you *really* want no interaction (for example, overwriting local # config files without prompting), then uncomment this: # export DEBIAN_FRONTEND=noninteractive # If you'd prefer to install dependencies in ~/.local/bin, uncomment # this function and comment the next. #function our_pip_install () { # pip3 install --user --upgrade "$@" #} function our_pip_install () { $sudo_for_pip_install pip3 install --upgrade "$@" } ### No options below this line. ### sudo_command="sudo" sudo_for_pip_install="sudo -H" # If we're root, get rid of sudo--it may not be there... if [ "$(id -u)" == "0" ]; then sudo_command="" sudo_for_pip_install="" fi $sudo_command apt-get update # shellcheck disable=SC2086 $sudo_command apt-get $quiet upgrade # Prefer binaries distributed by upstream OS over those in the pip # repository. # We explicitly want word splitting. # shellcheck disable=SC2046,SC2086 $sudo_command apt-get $quiet install $(cat ./util/dependencies.txt) # Debian doesn't have python-nacl. We'll have to accept the pip # version. # shellcheck disable=SC2086 if ! $sudo_command apt-get $quiet install python-nacl libssl-dev; then our_pip_install pynacl fi # We only really wanted python-pip for its dependencies. Upgrade it. our_pip_install pip # The pip we want should be in our path now. Make sure we use it. hash -r # We explicitly want word splitting. # shellcheck disable=SC2059,SC2086 our_pip_install -r requirements.txt echo ' Streisand dependencies installed. ' ================================================ FILE: util/venv-dependencies.sh ================================================ #!/bin/bash # Abort on any error. set -e # Usage: util/venv-dependencies.sh NEW-DIRECTORY # # See the usage function below. quiet= # pip makes a lot of noise when installing. Uncomment the following # line to make it a little quieter. # quiet=--quiet #### Options end here. usage () { echo " Usage: $0 ./venv This script creates an isolated Python virtualenv at './venv', and installs Ansible and dependencies into it. The script depends on Python 3.5 or later. If this system is running Debian or Ubuntu, this script will also check for other packages needed to install. Although './venv' is recommended, you can specify another location to create the virtualenv. If the location already exists, it should be an existing virtualenv to overwrite. " } is_root="" if [ "$(id -u)" == "0" ]; then is_root=1 fi python="python3" sudo_command="sudo" sudo_for_pip_install="sudo -H" # If we're root, get rid of sudo--it may not be there... if [ -n "$is_root" ]; then sudo_command="" sudo_for_pip_install="" fi request_python_3 () { echo " On Debian, Ubuntu, and WSL: $sudo_command apt install python3 python3-pip python3-virtualenv On macOS: # If you haven't, install homebrew from https://brew.sh/ brew install python On other systems: please see your OS documentation on how to install Python 3. " exit 1 } ensure_python_3_5 () { python_version=$($python --version 2>&1) if [[ $python_version < "Python 3.5" ]]; then echo " The $python command invokes $python_version. Python 3.5 or later is required. " return 1 fi return 0 } if [ "$#" -ne 1 ]; then usage exit 1 fi if type -p $python >/dev/null; then echo "Found a python3 command...." if ! ensure_python_3_5; then request_python_3 exit 1 fi else echo "The command 'python3' doesn't appear to exist. Trying 'python'..." python='python' if ! type -p $python >/dev/null; then echo " On your system, neither 'python3' or 'python' exist as commands. Please install Python 3.5 or later. " request_python_3 exit 1 fi if ! ensure_python_3_5; then request_python_3 exit 1 fi fi # Whew. We now have a working Python 3.5 or later in $python. hard_detect_dpkg () { dpkg-query --status "$1" 2>/dev/null | grep '^Status:.* installed' >/dev/null } check_deb_dependencies () { critical="$(cat ./util/dependencies.txt)" packages_not_found="" for pkg in $critical; do if ! hard_detect_dpkg "$pkg"; then echo "*** Missing package: $pkg" packages_not_found+=" $pkg" else echo "Found: $pkg" fi done if [ -n "$packages_not_found" ]; then echo "-------" echo "Setup will fail without these packages. To install them:" echo "" echo -n "$sudo_command apt-get install " # explicitly want word-spliting here # shellcheck disable=SC2086 echo $packages_not_found echo exit 1 else echo echo "Found all critical packages." echo fi } if [ -f /etc/debian_version ]; then echo echo "This system appears to be running Ubuntu or Debian. Checking" echo "for critical packages." echo check_deb_dependencies fi die () { echo "$@" exit 1 } if ! $python -m venv -h >/dev/null; then echo " The command 'python -m venv -h' failed. The venv module is a standard part of Python, and this script can't proceeed without it. Please report details of your system to the authors of this package if stuck. The output of 'python -m venv' is: " $python -m venv -h exit 1 fi # We want to run some tests on the parent of the path on the command # line. parent_dirname="$(dirname "$1")" if [ ! -d "$parent_dirname" ]; then die " The parent directory of $1 ($parent_dirname) does not exist. Please specify a parent directory you can write to. './venv' is a good choice. " fi if [ ! -w "$parent_dirname" ]; then die " The parent directory of $1 ($parent_dirname) is not writable. Please specify a parent directory you can write to. './venv' is a good choice. " fi # The virtualenv directory should be created from scratch. But if the # directory already exists *and* it looks like it was a virtualenv, # don't pester the user; just use "virtualenv --clear" to clean it out. # In any case, this script will not run "rm -rf $1", regardless of how # tempting. if [ -e "$1" ]; then if [ ! -e "$1/bin/activate.csh" ]; then die " The directory $1 already exists, and it does not appear to contain a Python virtualenv. Please specify a new directory to be created. './venv' is a good choice if it doesn't exist. " fi fi sudo_pip () { # shellcheck disable=SC2086 # pip complains loudly about directory permissions when sudo without -H. $sudo_for_pip_install pip3 $quiet "$@" } our_pip () { # shellcheck disable=SC2086 pip3 $quiet "$@" } our_pip_install () { our_pip install "$@" } NO_SITE_PACKAGES="" # shellcheck disable=SC2086 if ! $python -m venv --clear $NO_SITE_PACKAGES "$1"; then parent_dirname="$(dirname "$1")" echo " 'python -m venv' failed to create directory '$1' Note that $1 must not exist, but its parent ($parent_dirname) must exist. The first argument, 'new-directory', must be somewhere you can write to. A good place is './venv'. If it already exists, please delete the directory, or use a different name. " exit 1 fi [ -d "$1" ] || die "Missing venv directory $1! Something badly wrong." # This mucks around with our environment variables. We know where it # is at shellcheck time. # shellcheck disable=SC1090 source "$1/bin/activate" # Below this line, we are only installing into the virtualenv at "$1" our_pip_install --upgrade pip # The pip we want should be in our path now. Make sure we use it. hash -r # Now we can install all the Python modules. our_pip_install -r requirements.txt echo " ************* All dependencies installed into $1. To use this environment, run this in your shell: source \"$1/bin/activate\" You need to do this once in every terminal window you plan to run the command './streisand' in. After you've run that, you're ready to run ./streisand. " ================================================ FILE: util/version_at_least.py ================================================ #!/usr/bin/env python import sys from distutils.version import StrictVersion if len(sys.argv) != 3: print("Usage: version_at_least minimum_version version_to_check") sys.exit(1) minimum_version = StrictVersion(sys.argv[1].strip()) version_to_check = StrictVersion(sys.argv[2].strip()) if version_to_check >= minimum_version: sys.exit(0) else: sys.exit(1)