├── .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 ├── audits │ └── 2018.otf.includesec.audit.pdf ├── certificates.md ├── localization_howto.md ├── modular_roles.md ├── screenshots │ └── AWS │ │ ├── IAMPolicy1.png │ │ ├── IAMPolicy2.png │ │ ├── IAMPolicy3.png │ │ ├── IAMPolicy4.png │ │ ├── IAMUser1.png │ │ ├── IAMUser2.png │ │ ├── IAMUser3.png │ │ └── IAMUser4.png └── 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 ├── logo.jpg ├── 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-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 │ ├── ssh │ │ ├── defaults │ │ │ └── main.yml │ │ ├── files │ │ │ └── sshd_config │ │ ├── handlers │ │ │ └── main.yml │ │ ├── tasks │ │ │ └── main.yml │ │ └── vars │ │ │ └── main.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 /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | ### Expected behavior: 9 | 10 | ### Actual Behavior: 11 | 12 | ### Steps to Reproduce: 13 | 1. 14 | 15 | 19 | 20 | [ contents of `streisand-diagnostics.md` here ] 21 | 22 | 26 | ### Additional Details: 27 | #### *Log output from Ansible or other relevant services (link to Gist for longer output):* 28 | ##### *Target Cloud Provider:* 29 | ##### *Operating System of target host:* 30 | ##### *Operating System of client:* 31 | ##### *Version of Ansible, using `ansible --version` :* 32 | ##### *Output from `git rev-parse HEAD` in your Streisand directory :* 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | generated-docs 2 | *.retry 3 | *.pyc 4 | *~ 5 | *.swp 6 | *.tmp 7 | *.temp 8 | .DS_Store 9 | .vagrant/ 10 | ubuntu-xenial-16.04-cloudimg-console.log 11 | 12 | # Ignore changes to the existing server inventory to allow users to modify it 13 | inventories/inventory-existing 14 | 15 | # Ignore the Streisand diagnostics file we generate each run 16 | streisand-diagnostics.md 17 | 18 | # We recommend people use a Python virtualenv, and document this as 19 | # the location. 20 | venv/ 21 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # See documentation/testing.md for instructions on using this Vagrantfile 2 | # 3 | Vagrant.require_version ">= 1.9.0" 4 | 5 | Vagrant.configure(2) do |config| 6 | 7 | config.vm.box = "ubuntu/xenial64" 8 | 9 | config.vm.define "streisand-host", primary: true do |streisand| 10 | streisand.vm.hostname = "streisand-host" 11 | streisand.vm.network :private_network, ip: "10.0.0.10" 12 | 13 | streisand.vm.provision "ansible" do |ansible| 14 | # NOTE: Uncomment the below line for verbose Ansible output 15 | # ansible.verbose = "v" 16 | ansible.playbook = "playbooks/vagrant.yml" 17 | ansible.host_vars = { 18 | "streisand-host" => { 19 | "streisand_ipv4_address" => "10.0.0.10" 20 | } 21 | } 22 | ansible.raw_arguments = [ 23 | "--extra-vars=@global_vars/globals.yml", 24 | "--extra-vars=@global_vars/default-site.yml", 25 | "--extra-vars=@global_vars/integration/test-site.yml" 26 | ] 27 | end 28 | end 29 | 30 | config.vm.define "streisand-client" do |client| 31 | client.vm.hostname = "streisand-client" 32 | client.vm.network :private_network, ip: "10.0.0.11" 33 | 34 | client.vm.provision "ansible" do |ansible| 35 | # NOTE: Uncomment the below line for verbose Ansible output 36 | # ansible.verbose = "v" 37 | ansible.playbook = "playbooks/test-client.yml" 38 | ansible.host_vars = { 39 | "streisand-client" => { 40 | "streisand_ip" => "10.0.0.10", 41 | } 42 | } 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /Vagrantfile.remotetest: -------------------------------------------------------------------------------- 1 | # See documentation/testing.md for instructions on using this Vagrantfile 2 | # 3 | # NOTE: You *MUST* replace the "REMOTE_IP_HERE" value in the `host_vars` section 4 | # with the IP address of the Streisand server you wish to test 5 | # 6 | Vagrant.require_version ">= 1.9.0" 7 | 8 | Vagrant.configure(2) do |config| 9 | 10 | config.vm.box = "ubuntu/xenial64" 11 | 12 | config.vm.define "streisand-client" do |client| 13 | client.vm.hostname = "streisand-client" 14 | client.vm.network :private_network, ip: "10.0.0.11" 15 | 16 | client.vm.provision "ansible" do |ansible| 17 | # NOTE: Uncomment the below line for verbose Ansible output 18 | #ansible.verbose = "v" 19 | ansible.playbook = "playbooks/test-client.yml" 20 | ansible.host_vars = { 21 | "streisand-client" => { 22 | "streisand_ip" => "REMOTE_IP_HERE", 23 | } 24 | } 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | inventory = inventories/inventory 3 | nocows = 1 4 | 5 | # This is a convenient setting for a brand-new Streisand server that 6 | # you are connecting to for the first time. However, this line should be 7 | # commented out if you are connecting to an existing server where a 8 | # known_hosts entry has already been created. Host key checking happens 9 | # by default in Ansible. 10 | host_key_checking = False 11 | 12 | # Workaround for an Ansible issue with temporary paths: 13 | # https://github.com/ansible/ansible/issues/21562 14 | remote_tmp = $HOME/.ansible/tmp 15 | local_tmp = $HOME/.ansible/tmp 16 | 17 | # Some providers take a long time to actually begin accepting 18 | # connections even after the OpenSSH daemon has started. 19 | timeout = 100 20 | 21 | library=library 22 | 23 | [ssh_connection] 24 | # Enables multiplexing (lets ansible reuse opened SSH connections) 25 | ssh_args = -o ControlMaster=auto -o ControlPersist=60s 26 | pipelining = True 27 | -------------------------------------------------------------------------------- /documentation/audits/2018.otf.includesec.audit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StreisandEffect/streisand/af5eb7dac157a2416ea64cba96cf32f7f505d9ff/documentation/audits/2018.otf.includesec.audit.pdf -------------------------------------------------------------------------------- /documentation/screenshots/AWS/IAMPolicy1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StreisandEffect/streisand/af5eb7dac157a2416ea64cba96cf32f7f505d9ff/documentation/screenshots/AWS/IAMPolicy1.png -------------------------------------------------------------------------------- /documentation/screenshots/AWS/IAMPolicy2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StreisandEffect/streisand/af5eb7dac157a2416ea64cba96cf32f7f505d9ff/documentation/screenshots/AWS/IAMPolicy2.png -------------------------------------------------------------------------------- /documentation/screenshots/AWS/IAMPolicy3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StreisandEffect/streisand/af5eb7dac157a2416ea64cba96cf32f7f505d9ff/documentation/screenshots/AWS/IAMPolicy3.png -------------------------------------------------------------------------------- /documentation/screenshots/AWS/IAMPolicy4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StreisandEffect/streisand/af5eb7dac157a2416ea64cba96cf32f7f505d9ff/documentation/screenshots/AWS/IAMPolicy4.png -------------------------------------------------------------------------------- /documentation/screenshots/AWS/IAMUser1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StreisandEffect/streisand/af5eb7dac157a2416ea64cba96cf32f7f505d9ff/documentation/screenshots/AWS/IAMUser1.png -------------------------------------------------------------------------------- /documentation/screenshots/AWS/IAMUser2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StreisandEffect/streisand/af5eb7dac157a2416ea64cba96cf32f7f505d9ff/documentation/screenshots/AWS/IAMUser2.png -------------------------------------------------------------------------------- /documentation/screenshots/AWS/IAMUser3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StreisandEffect/streisand/af5eb7dac157a2416ea64cba96cf32f7f505d9ff/documentation/screenshots/AWS/IAMUser3.png -------------------------------------------------------------------------------- /documentation/screenshots/AWS/IAMUser4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StreisandEffect/streisand/af5eb7dac157a2416ea64cba96cf32f7f505d9ff/documentation/screenshots/AWS/IAMUser4.png -------------------------------------------------------------------------------- /global_vars/default-site.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Site specific Streisand configuration. 3 | # 4 | # This file is mutated by the playbooks/customize.yml tasks when a user chooses 5 | # to customize which Streisand services are installed. 6 | 7 | # The SSH private key that Ansible will use to connect to the Streisand node. 8 | # The associated public key will be used if required when provisioning cloud 9 | # nodes for the authorized_keys file. 10 | streisand_ssh_private_key: "~/.ssh/id_rsa" 11 | 12 | vpn_clients: 10 13 | 14 | streisand_ad_blocking_enabled: no 15 | streisand_openconnect_enabled: yes 16 | streisand_openvpn_enabled: yes 17 | streisand_shadowsocks_enabled: yes 18 | streisand_shadowsocks_v2ray_enabled: no 19 | streisand_ssh_forward_enabled: yes 20 | # By default sshuttle is disabled because it creates a `sshuttle` user that has 21 | # full shell privileges on the Streisand host 22 | streisand_sshuttle_enabled: no 23 | streisand_stunnel_enabled: yes 24 | streisand_tinyproxy_enabled: yes 25 | streisand_tor_enabled: no 26 | streisand_wireguard_enabled: yes 27 | streisand_cloudflared_enabled: no 28 | -------------------------------------------------------------------------------- /global_vars/globals.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # If using regular cleartext DNS then dnsmasq will set these upstream DNS servers 4 | upstream_dns_servers: 5 | - 1.1.1.1 6 | - 1.0.0.1 7 | 8 | # If using DNS-over-HTTPS with cloudflared then the upstream servers and queries can be set in: 9 | # playbooks/roles/cloudflared/defaults/main.yml 10 | 11 | streisand_client_test: no 12 | 13 | streisand_site_vars: "{{ lookup('env','HOME') }}/.streisand/site.yml" 14 | -------------------------------------------------------------------------------- /global_vars/integration/test-site.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Test site configuration for the end-to-end integration tests. 3 | 4 | # Don't ask questions 5 | streisand_noninteractive: true 6 | confirmation: true 7 | streisand_domain_var: "" 8 | streisand_admin_email_var: "" 9 | 10 | # Take a few extra steps during server provisioning to make the client tests work 11 | streisand_client_test: true 12 | 13 | # Only services with corresponding tests are enabled. 14 | streisand_ad_blocking_enabled: yes 15 | streisand_shadowsocks_enabled: yes 16 | streisand_ssh_forward_enabled: yes 17 | streisand_openvpn_enabled: yes 18 | streisand_wireguard_enabled: yes 19 | streisand_openconnect_enabled: yes 20 | streisand_tor_enabled: no 21 | streisand_stunnel_enabled: yes 22 | streisand_tinyproxy_enabled: yes 23 | # TODO(@cpu): The services below need some manner of integration test written 24 | streisand_sshuttle_enabled: no 25 | -------------------------------------------------------------------------------- /global_vars/noninteractive/amazon-site.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Example site specific configuration for a noninteractive AWS deployment. 3 | # 4 | # Copy this and edit it as needed before running streisand-new-cloud-server. 5 | # 6 | 7 | streisand_noninteractive: true 8 | confirmation: true 9 | 10 | # The SSH private key that Ansible will use to connect to the Streisand node. 11 | # 12 | # This will be added to the AWS console and given the name streisand-ssh. 13 | streisand_ssh_private_key: "~/.ssh/id_rsa" 14 | 15 | vpn_clients: 10 16 | 17 | streisand_ad_blocking_enabled: no 18 | streisand_openconnect_enabled: yes 19 | streisand_openvpn_enabled: yes 20 | streisand_shadowsocks_enabled: yes 21 | streisand_ssh_forward_enabled: yes 22 | # By default sshuttle is disabled because it creates a `sshuttle` user that has 23 | # full shell privileges on the Streisand host 24 | streisand_sshuttle_enabled: no 25 | streisand_stunnel_enabled: yes 26 | streisand_tinyproxy_enabled: yes 27 | streisand_tor_enabled: no 28 | streisand_wireguard_enabled: yes 29 | 30 | # The AWS region number. 31 | # 32 | # See ./playbooks/amazon.yml for numbering. 33 | # 34 | # Note: aws_region_var must be a number in quotes, e.g. "3" not 3. 35 | aws_region_var: "16" 36 | 37 | # The VPC and subnet IDs to use. They can be empty strings to indicate that a 38 | # VPC will not be used. 39 | aws_vpc_id_var: "" 40 | aws_vpc_subnet_id_var: "" 41 | 42 | aws_instance_name: streisand 43 | 44 | # The AWS credentials to use. 45 | aws_access_key: "" 46 | aws_secret_key: "" 47 | 48 | # Definitions needed for Let's Encrypt HTTPS (or TLS) certificate setup. 49 | # 50 | # If these are both left as empty strings, Let's Encrypt will not be set up and 51 | # a self-signed certificate will be used instead. 52 | # 53 | # The domain to use for Let's Encrypt certificate. 54 | streisand_domain_var: "" 55 | # The admin email address for Let's Encrypt certificate registration. 56 | streisand_admin_email_var: "" 57 | -------------------------------------------------------------------------------- /global_vars/noninteractive/linode-site.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Example site specific configuration for a noninteractive Linode deployment. 3 | # 4 | # Copy this and edit it as needed before running streisand-new-cloud-server. 5 | # 6 | 7 | streisand_noninteractive: true 8 | confirmation: true 9 | 10 | # The SSH private key that Ansible will use to connect to the Streisand node. 11 | streisand_ssh_private_key: "~/.ssh/id_rsa" 12 | 13 | vpn_clients: 10 14 | 15 | streisand_ad_blocking_enabled: no 16 | streisand_openconnect_enabled: yes 17 | streisand_openvpn_enabled: yes 18 | streisand_shadowsocks_enabled: yes 19 | streisand_ssh_forward_enabled: yes 20 | # By default sshuttle is disabled because it creates a `sshuttle` user that has 21 | # full shell privileges on the Streisand host 22 | streisand_sshuttle_enabled: no 23 | streisand_stunnel_enabled: yes 24 | streisand_tinyproxy_enabled: yes 25 | streisand_tor_enabled: no 26 | streisand_wireguard_enabled: yes 27 | 28 | # Choose the server location. 29 | # 1. Atlanta 30 | # 2. Dallas 31 | # 3. Frankfurt 32 | # 4. Fremont 33 | # 5. London 34 | # 6. Newark 35 | # 7. Singapore 36 | # 8. Tokyo 37 | # 9. Tokyo 2 38 | # 39 | # Note: linode_datacenter must be a number in quotes, e.g. "7" not 7. 40 | linode_datacenter: "7" 41 | 42 | linode_server_name: streisand 43 | 44 | # Obtain the API key from the Linode Manager console. 45 | linode_api_key: "" 46 | 47 | # Definitions needed for Let's Encrypt HTTPS (or TLS) certificate setup. 48 | # 49 | # If these are both left as empty strings, Let's Encrypt will not be set up and 50 | # a self-signed certificate will be used instead. 51 | # 52 | # The domain to use for Let's Encrypt certificate. 53 | streisand_domain_var: "" 54 | # The admin email address for Let's Encrypt certificate registration. 55 | streisand_admin_email_var: "" 56 | -------------------------------------------------------------------------------- /global_vars/noninteractive/local-site.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Example site specific configuration for a noninteractive local machine 3 | # deployment. 4 | # 5 | # Copy this and edit it as needed before running streisand-local. 6 | # 7 | 8 | streisand_noninteractive: true 9 | confirmation: true 10 | 11 | # Change this to the location of a key on the local system. 12 | streisand_ssh_private_key: "~/.ssh/id_rsa" 13 | 14 | vpn_clients: 10 15 | 16 | streisand_ad_blocking_enabled: no 17 | streisand_openconnect_enabled: yes 18 | streisand_openvpn_enabled: yes 19 | streisand_shadowsocks_enabled: yes 20 | streisand_ssh_forward_enabled: yes 21 | # By default sshuttle is disabled because it creates a `sshuttle` user that has 22 | # full shell privileges on the Streisand host 23 | streisand_sshuttle_enabled: no 24 | streisand_stunnel_enabled: yes 25 | streisand_tinyproxy_enabled: yes 26 | streisand_tor_enabled: no 27 | streisand_wireguard_enabled: yes 28 | 29 | # Definitions needed for Let's Encrypt HTTPS (or TLS) certificate setup. 30 | # 31 | # If these are both left as empty strings, Let's Encrypt will not be set up and 32 | # a self-signed certificate will be used instead. 33 | # 34 | # The domain to use for Let's Encrypt certificate. 35 | streisand_domain_var: "" 36 | # The admin email address for Let's Encrypt certificate registration. 37 | streisand_admin_email_var: "" 38 | -------------------------------------------------------------------------------- /global_vars/noninteractive/rackspace-site.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Example site specific configuration for a noninteractive Rackspace deployment. 3 | # 4 | # Copy this and edit it as needed before running streisand-new-cloud-server. 5 | # 6 | 7 | streisand_noninteractive: true 8 | confirmation: true 9 | 10 | # The SSH private key that Ansible will use to connect to the Streisand node. 11 | streisand_ssh_private_key: "~/.ssh/id_rsa" 12 | 13 | vpn_clients: 10 14 | 15 | streisand_ad_blocking_enabled: no 16 | streisand_openconnect_enabled: yes 17 | streisand_openvpn_enabled: yes 18 | streisand_shadowsocks_enabled: yes 19 | streisand_ssh_forward_enabled: yes 20 | # By default sshuttle is disabled because it creates a `sshuttle` user that has 21 | # full shell privileges on the Streisand host 22 | streisand_sshuttle_enabled: no 23 | streisand_stunnel_enabled: yes 24 | streisand_tinyproxy_enabled: yes 25 | streisand_tor_enabled: no 26 | streisand_wireguard_enabled: yes 27 | 28 | # Choose the region to deploy into. 29 | # 30 | # 1. Chicago 31 | # 2. Dallas 32 | # 3. Hong Kong 33 | # 4. Northern Virginia 34 | # 5. Sydney 35 | # 36 | # Note: rackspace_region must be a number in quotes, e.g. "1" not 1. 37 | rackspace_region: "1" 38 | 39 | rackspace_server_name: streisand 40 | 41 | # Obtain these credentials from the Rackspace Cloud Control Panel. 42 | rackspace_username: "" 43 | rackspace_api_key: "" 44 | 45 | # Definitions needed for Let's Encrypt HTTPS (or TLS) certificate setup. 46 | # 47 | # If these are both left as empty strings, Let's Encrypt will not be set up and 48 | # a self-signed certificate will be used instead. 49 | # 50 | # The domain to use for Let's Encrypt certificate. 51 | streisand_domain_var: "" 52 | # The admin email address for Let's Encrypt certificate registration. 53 | streisand_admin_email_var: "" 54 | -------------------------------------------------------------------------------- /inventories/inventory: -------------------------------------------------------------------------------- 1 | [localhost] 2 | localhost ansible_connection=local ansible_python_interpreter=python3 3 | 4 | # Uncomment the following lines and update the IP address if you would 5 | # like to use Streisand to configure a server that is already running 6 | # (e.g. a server at a provider not natively supported by Streisand). 7 | # 8 | # Multiple servers can be configured simultaneously if multiple IP 9 | # addresses are defined. 10 | # 11 | # If you already have SSH fingerprints for the hosts you are configuring 12 | # (e.g. using `ssh-keyscan`) then you should also comment out the 13 | # 'host_key_checking = False' line in the ansible.cfg file. That setting 14 | # is only sensible and convenient when connecting to a brand-new host. 15 | # 16 | # [streisand-host] 17 | # 255.255.255.255 18 | # 19 | # If the SSH user to be used to login is not "root", specify it here with 20 | # the ansible_user directive. Streisand will sudo automatically. If sudo 21 | # requires a password, use the --ask-become-pass command line option. 22 | # 23 | # [streisand-host] 24 | # 255.255.255.255 ansible_user=ubuntu 25 | -------------------------------------------------------------------------------- /inventories/inventory-local-provision: -------------------------------------------------------------------------------- 1 | # inventory-local-provision is a pre-built inventory file useful for doing an 2 | # advanced local install of Streisand where the server running Ansible is the 3 | # server that will be configured. 4 | 5 | # Settings for the provisioning process 6 | [localhost] 7 | 8 | # This must specify the Python interpreter when provisioning, because 9 | # "python" may be in, for example, a virtualenv without python-apt. 10 | localhost ansible_connection=local ansible_python_interpreter=/usr/bin/python 11 | 12 | # Settings for the Streisand host that will be provisioned 13 | [streisand-host] 14 | # If you need to override the name of the server (e.g. because the system 15 | # hostname is not the desired server name for documentation/etc) then add a host 16 | # var named "streisand_server_name" with the desired value. 17 | localhost ansible_connection=local streisand_noninteractive=true 18 | -------------------------------------------------------------------------------- /logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StreisandEffect/streisand/af5eb7dac157a2416ea64cba96cf32f7f505d9ff/logo.jpg -------------------------------------------------------------------------------- /playbooks/cloud-status.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Checking instance status 3 | # ========================================= 4 | hosts: streisand-host 5 | gather_facts: no 6 | 7 | remote_user: "root" 8 | become: true 9 | 10 | tasks: 11 | - name: Wait for cloud-init to complete 12 | 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;" 13 | register: result 14 | changed_when: False 15 | failed_when: result.rc != 0 16 | ... 17 | -------------------------------------------------------------------------------- /playbooks/ec2-metadata-instance.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Drop packets to the Amazon EC2 metadata instance 3 | hosts: streisand-host 4 | gather_facts: no 5 | 6 | remote_user: "root" 7 | become: true 8 | 9 | tasks: 10 | 11 | - name: Copy the EC2 metadata instance oneshot unit file 12 | copy: 13 | src: "./roles/genesis-amazon/files/aws-metadata-instance.service" 14 | dest: "/etc/systemd/system/aws-metadata-instance.service" 15 | owner: root 16 | group: root 17 | mode: 0644 18 | 19 | - name: Enable and reload the EC2 metadata instance 20 | systemd: 21 | name: aws-metadata-instance.service 22 | daemon_reload: yes 23 | enabled: yes 24 | state: restarted 25 | -------------------------------------------------------------------------------- /playbooks/existing-server.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # existing-server.yml is an advanced provisioning option that doesn't use a genesis 3 | # role to create a new server and instead applies Streisand to an existing 4 | # remote server. 5 | 6 | - name: Register the genesis role in use 7 | hosts: localhost 8 | gather_facts: yes 9 | tasks: 10 | - set_fact: 11 | streisand_genesis_role: "existing-server" 12 | 13 | - include: ssh-setup.yml 14 | 15 | - name: Check SSH access to existing server 16 | hosts: streisand-host 17 | gather_facts: no 18 | remote_user: "{{ lookup('env', 'SSH_USER') }}" 19 | become: true 20 | tasks: 21 | - block: 22 | - raw: whoami 23 | args: 24 | executable: /bin/bash 25 | changed_when: False 26 | rescue: 27 | - fail: 28 | 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" 29 | 30 | # Ensure Python is installed on the system 31 | - import_playbook: python.yml 32 | 33 | # Try and detect the remote server's provider & apply required workarounds 34 | - import_playbook: provider-detect.yml 35 | 36 | - name: Prepare the remote server for Streisand 37 | # ========================================= 38 | hosts: streisand-host 39 | remote_user: "{{ lookup('env', 'SSH_USER') }}" 40 | become: true 41 | 42 | - import_playbook: streisand.yml 43 | ... 44 | -------------------------------------------------------------------------------- /playbooks/group_vars/all: -------------------------------------------------------------------------------- 1 | --- 2 | streisand_ci: no 3 | streisand_noninteractive: no 4 | -------------------------------------------------------------------------------- /playbooks/linode.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Provision the Linode Server 3 | # ================================= 4 | hosts: localhost 5 | connection: local 6 | gather_facts: yes 7 | 8 | vars: 9 | regions: 10 | "1": "ca-central" 11 | "2": "us-central" 12 | "3": "us-west" 13 | "4": "us-southeast" 14 | "5": "us-east" 15 | "6": "eu-west" 16 | "7": "ap-south" 17 | "8": "eu-central" 18 | "9": "ap-northeast" 19 | "10": "ap-west" 20 | "11": "ap-southeast" 21 | 22 | vars_prompt: 23 | - name: "linode_datacenter" 24 | prompt: > 25 | What region should the server be located in? 26 | 1. Toronto 27 | 2. Dallas 28 | 3. Fremont 29 | 4. Atlanta 30 | 5. Newark 31 | 6. London 32 | 7. Singapore 33 | 8. Frankfurt 34 | 9. Tokyo 35 | 10. Mumbai 36 | 11. Sydney 37 | Please choose the number of your region. Press enter for default (#7) region. 38 | default: "7" 39 | private: no 40 | 41 | - name: "linode_server_name" 42 | prompt: "\nWhat should the server be named? Press enter for default (streisand).\n" 43 | default: "streisand" 44 | private: no 45 | 46 | - name: "linode_api_token" 47 | 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" 48 | private: no 49 | 50 | - name: "confirmation" 51 | prompt: "\nStreisand will now set up your server. This process usually takes around ten minutes. Press Enter to begin setup...\n" 52 | 53 | roles: 54 | - genesis-linode 55 | 56 | - import_playbook: ssh-setup.yml 57 | - import_playbook: streisand.yml 58 | ... 59 | -------------------------------------------------------------------------------- /playbooks/localhost.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # localhost.yml is an advanced provisioning option that doesn't use a genesis 3 | # role to create a new server and instead applies Streisand to the localhost. 4 | 5 | # Ensure Python is installed on the system 6 | - import_playbook: python.yml 7 | 8 | # Try and detect localhost's provider & apply required workarounds 9 | - import_playbook: provider-detect.yml 10 | 11 | - name: Prepare the localhost for Streisand 12 | # ========================================= 13 | hosts: streisand-host 14 | remote_user: "root" 15 | become: true 16 | 17 | tasks: 18 | - set_fact: 19 | streisand_genesis_role: "localhost" 20 | 21 | # If there's no streisand_ipv4_address set then we try our best using 22 | # the interface Ansible thinks is the default. 23 | - name: "Set the Streisand IPv4 address to the Ansible default: interface: {{ ansible_default_ipv4.alias }} address: {{ ansible_default_ipv4.address }}" 24 | set_fact: 25 | # The ansible_default_ipv4 address is calculated based on the default 26 | # ipv4 route to 8.8.8.8 for the system, and for local provisioning on 27 | # _most_ providers, seems to work well for finding the external facing IP. 28 | # See `provider-detect.yml` for cases where this approach doesn't work 29 | # (e.g. GCE) and workarounds. 30 | streisand_ipv4_address: "{{ ansible_default_ipv4.address }}" 31 | when: streisand_ipv4_address is not defined 32 | 33 | - import_playbook: streisand.yml 34 | ... 35 | -------------------------------------------------------------------------------- /playbooks/python.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Prepare the new server for Ansible 3 | # ========================================= 4 | hosts: streisand-host 5 | gather_facts: no 6 | 7 | remote_user: "root" 8 | become: true 9 | 10 | tasks: 11 | - name: Install Python using a raw SSH command to enable the execution of Ansible modules 12 | raw: apt update && apt install python -y 13 | args: 14 | executable: /bin/bash 15 | ... 16 | -------------------------------------------------------------------------------- /playbooks/rackspace.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Provision the Rackspace Server 3 | # ==================================== 4 | hosts: localhost 5 | connection: local 6 | gather_facts: yes 7 | 8 | vars: 9 | regions: 10 | "1": "ORD" 11 | "2": "DFW" 12 | "3": "HKG" 13 | "4": "IAD" 14 | "5": "SYD" 15 | 16 | vars_prompt: 17 | - name: "rackspace_region" 18 | prompt: > 19 | What region should the server be located in? 20 | 1. Chicago 21 | 2. Dallas 22 | 3. Hong Kong 23 | 4. Northern Virginia 24 | 5. Sydney 25 | Please choose the number of your region. Press enter for default (#1) region. 26 | default: "1" 27 | private: no 28 | 29 | - name: "rackspace_server_name" 30 | prompt: "\nWhat should the server be named? Press enter for default (streisand).\n" 31 | private: no 32 | default: "streisand" 33 | 34 | - name: "rackspace_username" 35 | prompt: "\nWhat is your Rackspace username?\n" 36 | private: no 37 | 38 | - name: "rackspace_api_key" 39 | 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" 40 | private: no 41 | 42 | - name: "confirmation" 43 | prompt: "\nStreisand will now set up your server. This process usually takes around ten minutes. Press Enter to begin setup...\n" 44 | 45 | roles: 46 | - genesis-rackspace 47 | 48 | - import_playbook: ssh-setup.yml 49 | - import_playbook: cloud-status.yml 50 | - import_playbook: streisand.yml 51 | ... 52 | -------------------------------------------------------------------------------- /playbooks/roles/ad-blocking/files/download-blocklists: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Now, if there were only some convenient way of storing etags from 5 | # those fetches. 6 | 7 | download_if_newer () { 8 | filename="$1" 9 | url="$2" 10 | if [ -e "$filename" ]; then 11 | curl --time-cond "$filename" -o "$filename" "$url" 12 | else 13 | curl -o "$filename" "$url" 14 | fi 15 | } 16 | 17 | 18 | mkdir -p /var/lib/blocklists 19 | 20 | download_if_newer /var/lib/blocklists/block-hostnames.txt 'https://raw.githubusercontent.com/notracking/hosts-blocklists/master/hostnames.txt' 21 | 22 | download_if_newer /var/lib/blocklists/block-domains.txt 'https://raw.githubusercontent.com/notracking/hosts-blocklists/master/domains.txt' 23 | 24 | transform-domain-list /etc/dnsmasq.d/block-domains.conf 25 | transform-host-list /etc/dnsmasq-block-hosts 26 | 27 | echo "addn-hosts=/etc/dnsmasq-block-hosts" >/etc/dnsmasq.d/block-hosts.conf 28 | 29 | # Sadly, "reload" doesn't work" 30 | systemctl restart dnsmasq.service 31 | -------------------------------------------------------------------------------- /playbooks/roles/ad-blocking/files/download-blocklists.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Download blocklists 3 | 4 | [Service] 5 | Type=oneshot 6 | ExecStart=/usr/local/bin/download-blocklists 7 | 8 | -------------------------------------------------------------------------------- /playbooks/roles/ad-blocking/files/download-blocklists.timer: -------------------------------------------------------------------------------- 1 | [Timer] 2 | OnActiveSec=0 3 | OnBootSec=60 4 | OnUnitActiveSec=1d 5 | RandomizedDelaySec=1h 6 | 7 | [Install] 8 | WantedBy=network.target 9 | -------------------------------------------------------------------------------- /playbooks/roles/ad-blocking/files/transform-domain-list: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | awk ' 4 | BEGIN { FS="/"; print "# post-processed by transform-domain-list" } 5 | 6 | /^#/ { print $0; next; } 7 | 8 | /^address=\/.*\/0\.0\.0\.0$/ { next; } 9 | 10 | /^address=\/.*\/::$/ { 11 | if ($2 !~ /\./) { 12 | print "### no dot found in domain, skipping: " $0; 13 | next; 14 | } 15 | print "address=/" $2 "/0.0.0.0\naddress=/" $2 "/::"; 16 | next; 17 | } 18 | 19 | { print "### ERROR unprocessed line: " $0; } 20 | ' 21 | -------------------------------------------------------------------------------- /playbooks/roles/ad-blocking/files/transform-host-list: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | awk ' 4 | BEGIN { print "# post-processed by transform-host-list"; } 5 | 6 | /^#/ { print $0; next; } 7 | 8 | /^0\.0\.0\.0 / { next; } 9 | 10 | /^:: / { 11 | if ($2 !~ /\./) { 12 | print "### no dot found in hostname, skipping: " $0; 13 | next; 14 | } 15 | print "0.0.0.0 " $2 "\n:: " $2; 16 | next; 17 | } 18 | 19 | { print "### ERROR unprocessed line: " $0; } 20 | ' 21 | -------------------------------------------------------------------------------- /playbooks/roles/ad-blocking/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: "Install blocklist tools" 2 | copy: 3 | src: "{{ item }}" 4 | dest: /usr/local/bin/ 5 | mode: '0755' 6 | loop: 7 | - download-blocklists 8 | - transform-domain-list 9 | - transform-host-list 10 | 11 | - name: "Install blocklist systemd goo" 12 | copy: 13 | src: "{{ item }}" 14 | dest: /etc/systemd/system/ 15 | mode: '0644' 16 | loop: 17 | - download-blocklists.service 18 | - download-blocklists.timer 19 | 20 | - name: "Enable the blocklist download service" 21 | systemd: 22 | name: download-blocklists.service 23 | daemon_reload: true 24 | enabled: true 25 | state: started 26 | 27 | - name: "Enable and start the blocklist re-download timer" 28 | systemd: 29 | name: download-blocklists.timer 30 | enabled: true 31 | state: started 32 | -------------------------------------------------------------------------------- /playbooks/roles/azure-security-group/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | allow_duplicates: yes 3 | -------------------------------------------------------------------------------- /playbooks/roles/azure-security-group/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | azure_resource_group_name: "streisand-{{ azure_instance_name }}" 3 | -------------------------------------------------------------------------------- /playbooks/roles/certificates/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | tls_key_country: "US" 3 | tls_key_province: "California" 4 | tls_key_city: "Beverly Hills" 5 | tls_key_org: "ACME CORPORATION" 6 | tls_key_ou: "Anvil Department" 7 | tls_days_valid: "1825" 8 | tls_default_md: "sha256" 9 | tls_key_size: "4096" 10 | 11 | # What type of certificates to be generated 12 | # must be explicitly set by playbooks that 13 | # include the certificates role 14 | generate_ca_server: no 15 | generate_client: no 16 | generate_pkcs: no 17 | -------------------------------------------------------------------------------- /playbooks/roles/certificates/tasks/client.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create directories for clients 3 | file: 4 | path: "{{ tls_client_path }}/{{ client_name.stdout }}" 5 | state: directory 6 | with_items: "{{ vpn_client_names.results }}" 7 | loop_control: 8 | loop_var: "client_name" 9 | label: "{{ client_name.item }}" 10 | 11 | - name: Generate the private keys for the client certificates 12 | command: openssl genrsa -out client.key {{ tls_key_size }} 13 | args: 14 | chdir: "{{ tls_client_path }}/{{ client_name.stdout }}" 15 | creates: client.key 16 | with_items: "{{ vpn_client_names.results }}" 17 | loop_control: 18 | loop_var: "client_name" 19 | label: "{{ client_name.item }}" 20 | 21 | - name: Set the proper permissions on all private client keys 22 | file: 23 | path: "{{ ca_path }}" 24 | recurse: yes 25 | state: directory 26 | owner: root 27 | group: root 28 | mode: 0600 29 | 30 | - name: Generate CSRs for the clients 31 | 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 32 | args: 33 | chdir: "{{ tls_client_path }}/{{ client_name.stdout }}" 34 | creates: client.csr 35 | with_items: "{{ vpn_client_names.results }}" 36 | loop_control: 37 | loop_var: "client_name" 38 | label: "{{ client_name.item }}" 39 | 40 | - name: Generate certificates for the clients 41 | 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 42 | args: 43 | chdir: "{{ tls_client_path }}/{{ client_name.stdout }}" 44 | creates: client.crt 45 | with_items: "{{ vpn_client_names.results }}" 46 | loop_control: 47 | loop_var: "client_name" 48 | label: "{{ client_name.item }}" 49 | 50 | - name: Authorize certificates via /etc/allowed_vpn_certs 51 | template: 52 | src: allowed_vpn_certs.j2 53 | dest: /etc/allowed_vpn_certs 54 | owner: root 55 | group: root 56 | mode: 0644 57 | -------------------------------------------------------------------------------- /playbooks/roles/certificates/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - import_tasks: ca-server.yml 3 | when: generate_ca_server 4 | 5 | - import_tasks: client.yml 6 | when: generate_client 7 | 8 | - import_tasks: pkcs.yml 9 | when: generate_pkcs 10 | -------------------------------------------------------------------------------- /playbooks/roles/certificates/templates/allowed_vpn_certs.j2: -------------------------------------------------------------------------------- 1 | # This file lists all the enabled VPN certificate names. Note that 2 | # it does not affect WireGuard. 3 | 4 | {% for client in vpn_client_names.results -%} 5 | {{ client.stdout }} 6 | {% endfor %} 7 | -------------------------------------------------------------------------------- /playbooks/roles/certificates/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | tls_request_subject: "/C={{ tls_key_country }}/ST={{ tls_key_province }}/L={{ tls_key_city }}/O={{ tls_key_org }}/OU={{ tls_key_ou }}" 3 | -------------------------------------------------------------------------------- /playbooks/roles/cloudflared/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | cloudflared_base_url: "https://bin.equinox.io/c/VdrWdbjqyF/" 3 | 4 | cloudflared_amd64_apt: "cloudflared-stable-linux-amd64.deb" 5 | cloudflared_amd64_yum: "cloudflared-stable-linux-amd64.rpm" 6 | cloudflared_amd64_binary: "cloudflared-stable-linux-amd64.tgz" 7 | cloudflared_arm_apt: "cloudflared-stable-linux-arm.deb" 8 | cloudflared_arm_yum: "cloudflared-stable-linux-arm.rpm" 9 | cloudflared_arm_binary: "cloudflared-stable-linux-arm.tgz" 10 | 11 | cloudflared_allow_firewall: false 12 | cloudflared_enable_service: true 13 | cloudflared_upstream1: "https://1.1.1.1/dns-query" 14 | cloudflared_upstream2: "https://1.0.0.1/dns-query" 15 | cloudflared_port: 5053 16 | 17 | cloudflared_options: "proxy-dns --port {{ cloudflared_port }} --upstream {{ cloudflared_upstream1 }} --upstream {{ cloudflared_upstream2 }}" 18 | 19 | cloudflared_bin_location: /usr/local/bin 20 | -------------------------------------------------------------------------------- /playbooks/roles/cloudflared/files/cloudflared.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=cloudflared service 3 | After=syslog.target network-online.target 4 | 5 | [Service] 6 | Type=simple 7 | User=cloudflared 8 | EnvironmentFile=/etc/default/cloudflared 9 | ExecStart=/usr/local/bin/cloudflared $CLOUDFLARED_OPTS 10 | Restart=on-failure 11 | RestartSec=10 12 | KillMode=process 13 | 14 | [Install] 15 | WantedBy=multi-user.target 16 | -------------------------------------------------------------------------------- /playbooks/roles/cloudflared/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart cloudflared service 3 | systemd: 4 | name: cloudflared.service 5 | state: restarted 6 | -------------------------------------------------------------------------------- /playbooks/roles/cloudflared/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - { role: dnsmasq } 4 | - { role: ip-forwarding } 5 | -------------------------------------------------------------------------------- /playbooks/roles/cloudflared/tasks/install_binary.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: build filename of file to be downloaded 3 | set_fact: 4 | cloudflared_file: "{{ vars['cloudflared_'+device_arch+'_binary'] }}" 5 | 6 | - name: download correct file for device 7 | get_url: 8 | url: "{{ cloudflared_base_url }}{{ cloudflared_file }}" 9 | dest: "/tmp/{{ cloudflared_file }}" 10 | #checksum: "{{ cloudflared_file_checksum }}" 11 | 12 | - name: extract cloudflared into /usr/local/bin 13 | unarchive: 14 | src: "/tmp/{{ cloudflared_file }}" 15 | dest: "{{ cloudflared_bin_location }}" 16 | remote_src: yes 17 | -------------------------------------------------------------------------------- /playbooks/roles/cloudflared/tasks/install_package.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: build filename of file to be downloaded 3 | set_fact: 4 | cloudflared_file: "{{ vars['cloudflared_'+device_arch+'_'+ansible_pkg_mgr] }}" 5 | 6 | - name: download correct file for device 7 | get_url: 8 | url: "{{ cloudflared_base_url }}{{ cloudflared_file }}" 9 | dest: "/tmp/{{ cloudflared_file }}" 10 | #checksum: "{{ cloudflared_file_checksum }}" 11 | 12 | - name: Install a .deb package 13 | apt: 14 | deb: "/tmp/{{ cloudflared_file }}" 15 | state: present 16 | register: pkg_mgr_output 17 | ignore_errors: true 18 | when: ansible_pkg_mgr == 'apt' 19 | 20 | - name: Install a .rpm package 21 | yum: 22 | name: "/tmp/{{ cloudflared_file }}" 23 | state: present 24 | register: pkg_mgr_output 25 | ignore_errors: true 26 | when: ansible_pkg_mgr == 'yum' 27 | -------------------------------------------------------------------------------- /playbooks/roles/cloudflared/templates/cloudflared.j2: -------------------------------------------------------------------------------- 1 | # Commandline args for cloudflared 2 | CLOUDFLARED_OPTS={{ cloudflared_options }} 3 | -------------------------------------------------------------------------------- /playbooks/roles/cloudflared/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Cloudflared variables 3 | -------------------------------------------------------------------------------- /playbooks/roles/common/files/footer.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /playbooks/roles/common/templates/20auto-upgrades.j2: -------------------------------------------------------------------------------- 1 | APT::Periodic::Update-Package-Lists "1"; 2 | APT::Periodic::Unattended-Upgrade "1"; 3 | -------------------------------------------------------------------------------- /playbooks/roles/common/templates/50unattended-upgrades.j2: -------------------------------------------------------------------------------- 1 | // Automatically upgrade packages from these (origin, archive) pairs 2 | Unattended-Upgrade::Allowed-Origins { 3 | // ${distro_id} and ${distro_codename} will be automatically expanded 4 | "${distro_id} stable"; 5 | "${distro_id} ${distro_codename}-security"; 6 | 7 | // Autoupdate Nginx 8 | "nginx:${distro_codename}"; 9 | 10 | {% if streisand_openvpn_enabled %} 11 | // Autoupdate OpenVPN 12 | "Freight:${distro_codename}"; 13 | {% endif %} 14 | 15 | {% if streisand_shadowsocks_enabled %} 16 | // Autoupdate shadowsocks-libev 17 | "LP-PPA-max-c-lv-shadowsocks-libev:${distro_codename}"; 18 | {% endif %} 19 | 20 | {% if streisand_tor_enabled %} 21 | // Autoupdate Tor 22 | "TorProject:${distro_codename}"; 23 | {% endif %} 24 | 25 | {% if streisand_wireguard_enabled %} 26 | // Autoupdate WireGuard 27 | "LP-PPA-wireguard-wireguard:${distro_codename}"; 28 | {% endif %} 29 | }; 30 | 31 | // List of packages to not update 32 | Unattended-Upgrade::Package-Blacklist { 33 | }; 34 | 35 | // Do automatic removal of new unused dependencies after the upgrade 36 | // (equivalent to apt-get autoremove) 37 | Unattended-Upgrade::Remove-Unused-Dependencies "true"; 38 | 39 | // Automatically reboot *WITHOUT CONFIRMATION* if a 40 | // the file /var/run/reboot-required is found after the upgrade 41 | Unattended-Upgrade::Automatic-Reboot "true"; 42 | 43 | // If automatic reboot is enabled and needed, reboot at the specific 44 | // time instead of immediately 45 | // Default: "now" 46 | Unattended-Upgrade::Automatic-Reboot-Time "00:00"; 47 | 48 | // Avoid conffile dpkg prompt by *always* leaving the modified configuration in 49 | // place and putting the new package configuration in a .dpkg-dist file 50 | Dpkg::Options { 51 | "--force-confdef"; 52 | "--force-confold"; 53 | }; 54 | -------------------------------------------------------------------------------- /playbooks/roles/common/templates/test-client-inventory.j2: -------------------------------------------------------------------------------- 1 | {% for client_name in vpn_client_names.results %} 2 | {{ client_name.stdout }} 3 | {% endfor %} 4 | -------------------------------------------------------------------------------- /playbooks/roles/diagnostics/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Determine the git revision of the current Streisand clone" 3 | command: git rev-parse HEAD 4 | register: streisand_diagnostics_git_rev 5 | changed_when: False 6 | 7 | # Credit to https://stackoverflow.com/a/3879077 for this approach 8 | - name: "Determine if there are untracked changes in the Streisand clone" 9 | shell: git diff-index --quiet HEAD -- && echo "no" || echo "yes"; 10 | register: streisand_diagnostics_git_untracked 11 | changed_when: False 12 | 13 | - name: "Produce the diagnostics markdown file to share if there is an error" 14 | template: 15 | src: streisand-diagnostics.md.j2 16 | dest: ../streisand-diagnostics.md 17 | -------------------------------------------------------------------------------- /playbooks/roles/dnsmasq/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart dnsmasq 3 | systemd: 4 | name: dnsmasq.service 5 | state: restarted 6 | -------------------------------------------------------------------------------- /playbooks/roles/dnsmasq/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure that BIND is not installed in order to avoid conflicts with dnsmasq 3 | apt: 4 | package: bind9 5 | state: absent 6 | 7 | - name: Install dnsmasq 8 | apt: 9 | package: dnsmasq 10 | 11 | - name: Generate the dnsmasq configuration file 12 | template: 13 | src: dnsmasq.conf.j2 14 | dest: /etc/dnsmasq.conf 15 | notify: Restart dnsmasq 16 | 17 | - name: Create the dnsmasq systemd drop-in configuration directory 18 | file: 19 | path: "{{ dnsmasq_systemd_service_path }}" 20 | state: directory 21 | 22 | - name: Generate the dnsmasq systemd drop-in service file 23 | template: 24 | src: dnsmasq.service.j2 25 | dest: "{{ dnsmasq_systemd_service_path }}/10-restart-failure.conf" 26 | mode: 0644 27 | 28 | - name: Enable the dnsmasq service 29 | systemd: 30 | daemon_reload: yes 31 | name: dnsmasq.service 32 | enabled: yes 33 | state: restarted 34 | -------------------------------------------------------------------------------- /playbooks/roles/dnsmasq/templates/dnsmasq.conf.j2: -------------------------------------------------------------------------------- 1 | # Explicitly listen on localhost. This is required since config fragments in 2 | # `/etc/dnsmasq.d` may add additional `listen-address` values and the man page 3 | # says: 4 | # Note that if no --interface option is given, but --listen-address is, 5 | # dnsmasq will not automatically listen on the loopback interface. To achieve 6 | # this, its IP address, 127.0.0.1, must be explicitly given as 7 | # a --listen-address option. 8 | listen-address=127.0.0.1 9 | 10 | # Never forward plain names (without a dot or domain part) 11 | domain-needed 12 | 13 | # Never forward addresses in the non-routed address spaces. 14 | bogus-priv 15 | 16 | # If you don't want dnsmasq to read /etc/resolv.conf or any other 17 | # file, getting its servers from this file instead (see below), then 18 | # uncomment this. 19 | no-resolv 20 | 21 | {% for item in upstream_dns_servers %} 22 | server={{ item }} 23 | {% endfor %} 24 | -------------------------------------------------------------------------------- /playbooks/roles/dnsmasq/templates/dnsmasq.service.j2: -------------------------------------------------------------------------------- 1 | [Service] 2 | PrivateTmp=true 3 | RestartSec=5s 4 | Restart=on-failure 5 | -------------------------------------------------------------------------------- /playbooks/roles/dnsmasq/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dnsmasq_systemd_service_path: "/etc/systemd/system/dnsmasq.service.d" 3 | -------------------------------------------------------------------------------- /playbooks/roles/download-and-verify/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | signature_extension: "asc" 3 | -------------------------------------------------------------------------------- /playbooks/roles/ec2-security-group/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | aws_security_group: "streisand-{{ aws_instance_name }}" 3 | -------------------------------------------------------------------------------- /playbooks/roles/gce-network/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | gce_network: "{{ gce_server_name }}-network" 3 | gce_ipv4_range: "10.240.16.0/24" 4 | -------------------------------------------------------------------------------- /playbooks/roles/genesis-amazon/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | aws_instance_type: "t2.micro" 3 | # Search AMIs owned by this owner. This is the Amazon owner ID. 4 | aws_ami_owner: "099720109477" 5 | # Find AMIs matching this name 6 | aws_ami_name: "ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-*" 7 | -------------------------------------------------------------------------------- /playbooks/roles/genesis-amazon/files/aws-metadata-instance.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Drop packets to the EC2 metadata instance 3 | After=network.target 4 | 5 | [Service] 6 | Type=oneshot 7 | RemainAfterExit=true 8 | ExecStart=/sbin/route add -host 169.254.169.254 reject 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /playbooks/roles/genesis-amazon/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - ec2-security-group 4 | -------------------------------------------------------------------------------- /playbooks/roles/genesis-azure/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | azure_instance_type: "Standard_B1s" 3 | 4 | azure_image_publisher: "Canonical" 5 | azure_image_offer: "UbuntuServer" 6 | azure_image_sku: "16.04-LTS" 7 | azure_image_version: "latest" 8 | -------------------------------------------------------------------------------- /playbooks/roles/genesis-azure/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - azure-security-group 4 | -------------------------------------------------------------------------------- /playbooks/roles/genesis-azure/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - set_fact: 3 | streisand_genesis_role: "genesis-azure" 4 | 5 | - name: "Get the {{ streisand_ssh_private_key }}.pub contents" 6 | command: "cat {{ streisand_ssh_private_key }}.pub" 7 | register: ssh_key 8 | changed_when: False 9 | 10 | - name: Create the Azure instance 11 | local_action: 12 | module: azure_rm_virtualmachine 13 | resource_group: "{{ azure_resource_group_name }}" 14 | network_interfaces: "{{ azure_resource_group_name }}" 15 | name: "{{ azure_instance_name }}" 16 | vm_size: "{{ azure_instance_type }}" 17 | location: "{{ azure_region }}" 18 | image: 19 | offer: "{{ azure_image_offer }}" 20 | publisher: "{{ azure_image_publisher }}" 21 | sku: "{{ azure_image_sku }}" 22 | version: "{{ azure_image_version }}" 23 | admin_username: ubuntu 24 | ssh_password_enabled: false 25 | ssh_public_keys: 26 | - path: /home/ubuntu/.ssh/authorized_keys 27 | key_data: "{{ ssh_key.stdout }}" 28 | register: streisand_server 29 | 30 | - name: Set the streisand_ipv4_address variable 31 | set_fact: 32 | streisand_ipv4_address: "{{ streisand_server.ansible_facts.azure_vm.properties.networkProfile.networkInterfaces[0].properties.ipConfigurations[0].properties.publicIPAddress.properties.ipAddress }}" 33 | 34 | - name: Wait until the server has finished booting and OpenSSH is accepting connections 35 | wait_for: 36 | host: "{{ streisand_ipv4_address }}" 37 | port: 22 38 | search_regex: OpenSSH 39 | timeout: 600 40 | 41 | - name: Create the in-memory inventory group 42 | add_host: 43 | name: "{{ streisand_ipv4_address }}" 44 | groups: streisand-host 45 | ansible_user: ubuntu 46 | ansible_become: yes 47 | 48 | - name: Set the streisand_server_name variable 49 | set_fact: 50 | streisand_server_name: "{{ azure_instance_name }}" 51 | -------------------------------------------------------------------------------- /playbooks/roles/genesis-digitalocean/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | do_ubuntu_x64_image_id: "ubuntu-16-04-x64" 3 | do_small_droplet_size_id: "s-1vcpu-1gb" 4 | -------------------------------------------------------------------------------- /playbooks/roles/genesis-google/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | gce_machine_type: "f1-micro" 3 | gce_image: "ubuntu-1604" 4 | -------------------------------------------------------------------------------- /playbooks/roles/genesis-google/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - gce-network 4 | -------------------------------------------------------------------------------- /playbooks/roles/genesis-google/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - set_fact: 3 | streisand_genesis_role: "genesis-google" 4 | 5 | - name: "Get the {{ streisand_ssh_private_key }}.pub contents" 6 | command: "cat {{ streisand_ssh_private_key }}.pub" 7 | register: ssh_key 8 | changed_when: False 9 | 10 | - name: Create the GCE instance 11 | gce: 12 | name: "{{ gce_server_name }}" 13 | network: "{{ gce_network }}" 14 | zone: "{{ gce_zone }}" 15 | machine_type: "{{ gce_machine_type }}" 16 | credentials_file: "{{ gce_json_file_location }}" 17 | project_id: "{{ gce_project_id }}" 18 | service_account_email: "{{ gce_service_account_email }}" 19 | image: "{{ gce_image }}" 20 | tags: "{{ gce_server_name }}" 21 | metadata: '{"ssh-keys":"ubuntu:{{ ssh_key.stdout }}"}' 22 | register: streisand_server 23 | 24 | - name: Wait until the server has finished booting and OpenSSH is accepting connections 25 | wait_for: 26 | host: "{{ streisand_server.instance_data[0].public_ip }}" 27 | port: 22 28 | search_regex: OpenSSH 29 | timeout: 600 30 | 31 | - name: Create the in-memory inventory group 32 | add_host: 33 | name: "{{ streisand_server.instance_data[0].public_ip }}" 34 | groups: streisand-host 35 | ansible_user: ubuntu 36 | ansible_become: yes 37 | 38 | - name: Set the streisand_ipv4_address variable 39 | set_fact: 40 | streisand_ipv4_address: "{{ streisand_server.instance_data[0].public_ip }}" 41 | 42 | - name: Set the streisand_server_name variable 43 | set_fact: 44 | streisand_server_name: "{{ gce_server_name | regex_replace('\\s', '_') }}" 45 | -------------------------------------------------------------------------------- /playbooks/roles/genesis-linode/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Setting to most minimal linode plan size. 3 | # For a most recent list of types: curl https://api.linode.com/v4/linode/types 4 | linode_plan_id: "g6-nanode-1" 5 | linode_distribution_id: "linode/ubuntu16.04lts" 6 | 7 | ### Preserving these varsfor when we can set these with the ansible linode apiv4 module: 8 | # linode_kernel_id: 210 # GRUB2 to utilize the distribution's kernel for compatibility 9 | 10 | # Threshold for receiving CPU usage alerts. Each CPU core adds 100% to total. 11 | # Since by default Streisand provisions a Linode 1024 with one core a value of 12 | # 90% seems ~reasonable 13 | # linode_alert_cpu_threshold: 90 14 | 15 | # Other values left as the defaults from the Linode module. See 16 | # https://github.com/StreisandEffect/streisand/issues/626 for more detail. 17 | # linode_alert_diskio_threshold: 10000 18 | # linode_alert_bwin_threshold: 10 19 | # linode_alert_bwout_threshold: 10 20 | # linode_alert_bwquota_threshold: 80 21 | -------------------------------------------------------------------------------- /playbooks/roles/genesis-linode/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - set_fact: 3 | streisand_genesis_role: "genesis-linode" 4 | 5 | - name: "Get the {{ streisand_ssh_private_key }}.pub contents" 6 | command: "cat {{ streisand_ssh_private_key }}.pub" 7 | register: ssh_key 8 | changed_when: False 9 | 10 | - name: "Create the server" 11 | linode_v4: 12 | access_token: "{{ linode_api_token }}" 13 | label: "{{ linode_server_name }}" 14 | type: "{{ linode_plan_id }}" 15 | region: "{{ regions[linode_datacenter] }}" 16 | image: "{{ linode_distribution_id }}" 17 | authorized_keys: "{{ ssh_key.stdout }}" 18 | state: present 19 | register: streisand_server 20 | 21 | - name: "Wait until the server has finished booting and OpenSSH is accepting connections" 22 | wait_for: 23 | host: "{{ streisand_server.instance.ipv4[0] }}" 24 | port: 22 25 | search_regex: OpenSSH 26 | timeout: 600 27 | 28 | - name: "Create the in-memory inventory group" 29 | add_host: 30 | name: "{{ streisand_server.instance.ipv4[0] }}" 31 | groups: streisand-host 32 | 33 | - name: "Set the streisand_ipv4_address variable" 34 | set_fact: 35 | streisand_ipv4_address: "{{ streisand_server.instance.ipv4[0] }}" 36 | 37 | - name: "Set the streisand_server_name variable" 38 | set_fact: 39 | streisand_server_name: "{{ linode_server_name | regex_replace('\\s', '_') }}" 40 | -------------------------------------------------------------------------------- /playbooks/roles/genesis-rackspace/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | rackspace_flavor: 2 3 | rackspace_image: "Ubuntu 16.04 LTS (Xenial Xerus) (PVHVM)" 4 | -------------------------------------------------------------------------------- /playbooks/roles/genesis-rackspace/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - set_fact: 3 | streisand_genesis_role: "genesis-rackspace" 4 | 5 | - name: "Get the {{ streisand_ssh_private_key }}.pub contents" 6 | command: "cat {{ streisand_ssh_private_key }}.pub" 7 | register: ssh_key 8 | changed_when: False 9 | 10 | - name: Create the server 11 | rax: 12 | api_key: "{{ rackspace_api_key }}" 13 | username: "{{ rackspace_username }}" 14 | name: "{{ rackspace_server_name }}" 15 | flavor: "{{ rackspace_flavor }}" 16 | image: "{{ rackspace_image }}" 17 | region: "{{ regions[rackspace_region] }}" 18 | files: 19 | /root/.ssh/authorized_keys: "{{ streisand_ssh_private_key }}.pub" 20 | wait: yes 21 | register: streisand_server 22 | 23 | - name: Wait until the server has finished booting and OpenSSH is accepting connections 24 | wait_for: 25 | host: "{{ streisand_server.instances[0].rax_accessipv4 }}" 26 | port: 22 27 | search_regex: OpenSSH 28 | timeout: 600 29 | 30 | - name: Create the in-memory inventory group 31 | add_host: 32 | name: "{{ streisand_server.instances[0].rax_accessipv4 }}" 33 | groups: streisand-host 34 | 35 | - name: Set the streisand_ipv4_address variable 36 | set_fact: 37 | streisand_ipv4_address: "{{ streisand_server.instances[0].rax_accessipv4 }}" 38 | 39 | - name: Set the streisand_server_name variable 40 | set_fact: 41 | streisand_server_name: "{{ rackspace_server_name | regex_replace('\\s', '_') }}" 42 | -------------------------------------------------------------------------------- /playbooks/roles/gpg/files/AF16234E.alimakki@gmail.com.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mG8EWrT8mhMFK4EEACIDAwTdpZNOKCX/kAf9MAU1PM7N1BKcpPdBZFJsG/xVeJRv 4 | OAtFw6oqKYlZbR/GF1N+FL5kHJ98rKippyzgbGlrJ7ybgL4IzpMW1e1DdpgnG6If 5 | ZlfbGntuLVRzTp01+Smz5U20HkFsaSBNYWtraSA8YWxpbWFra2lAZ21haWwuY29t 6 | Poi2BBMTCQA+FiEEpFXaOWgX6MbiyBm/rxYjTsk9zfsFAlq0/JoCGyMFCQtJ/+YF 7 | CwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQrxYjTsk9zfschwF/eUEa+z+2wqhe 8 | 6VAEZgaqchxmQpjMGP1H8l4MheWZVBQc1qtfdXl9s+QDCyYfzNDXAYCw1GIm8DUT 9 | GT3loxmuMYuyzUWohMLY7yEgXn6U8k2NB/CRAcfsz5AT3LAndNuf8Zu4cwRatPya 10 | EgUrgQQAIgMDBCH9Rs++/u1zpHuFKZ/9nj21hHC3bFfOfqcVdru92ES+tr8PCwZT 11 | yXercMUb3179Ej6VEZbJMoZ+7lKwVj77UzlMHvYBNRQX0DS7wztI+yEvbcckxySO 12 | Pxvk2X9px/awmAMBCQmIngQYEwkAJhYhBKRV2jloF+jG4sgZv68WI07JPc37BQJa 13 | tPyaAhsMBQkLSf/mAAoJEK8WI07JPc37JVcBfRLZW0ESnzcSpkfnt2w9hucXT2xo 14 | B/xvKiSl6NBmeO8xmV6giTt+bbiGnGsdMQ5ebgGAqcBpBSXIjNB1onV/wq6k1jr6 15 | 05KnSv5lIRwKQvdDpU3mr70j8Rw5L1tem0NPjkyO 16 | =CFDD 17 | -----END PGP PUBLIC KEY BLOCK----- 18 | -------------------------------------------------------------------------------- /playbooks/roles/gpg/files/CDF6583E.josh@joshlund.com.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQENBFGauS0BCAC+3c+kVPOsMmWQ1IWobTn2WDzbMpgzH9YBVNCs7Rihv1cBufjC 4 | qCetEp+GFfoWdNWl/GFFhdousBSOOOPervnYUiLdIW+BXc/FTbysrXy7qJfCofUf 5 | JB7LOJw261eUYPglHES/IM6F6rmtkbfcnD+NPfJ3t/cEFMWDYid1BZcJAGdQ6pES 6 | ydIG7RW4cbQHJPgzjubwEa/JsnSiGORsaqAjXwcD9ii4q/UkahMpfnmk/iLBh/Sl 7 | Y03Cob9xGx2nW1agzjFbRFUFd5iqKN4RpUwutPsvQDy3gmiEX5abpwZ8nWHlb3Ed 8 | Gu8acwrbMDebwJ/HsC5PA2+0bpNEmE9LqsF9ABEBAAG0H0pvc2h1YSBMdW5kIDxq 9 | b3NoQGpvc2hsdW5kLmNvbT6JATgEEwECACIFAlGauS0CGwMGCwkIBwMCBhUIAgkK 10 | CwQWAgMBAh4BAheAAAoJEM32WD6no1bWnZQH/1EHoD/bbnQfgY/ceUi1pFQF0vls 11 | 00Ish1Wyva/VVoBXj7x72WW2GNPA+FLKRolFtnNo+BUNWFwDQV6mN3sjHuonOCyB 12 | 4GpoX/W30+PKo2VCnml3mheFlAoD6i5aikQcqO/VetFljLl7enfmpd67nUJ0Tw7L 13 | PI6g21zy45RDCXip32XJF1bNDGONN35U5j8fga17w3K2vWV//Rl3SbCO+3erbBhw 14 | O8oAuc93MNtXmOrNoHiMUjdJ18jGMpJ0WUGdjzUuyNXhHYtkrf9KE/W7gk2+Wp85 15 | zumWGiZFU/OKs3U98ujDNS9vk1urWpkrucIwdSbGB+ad3AK7L2NtqQwuGZK5AQ0E 16 | UZq5LQEIALh3+2B3AlWBT+H8BmolIGSz/AHF+CwJkDPqf/BeKrcQxVMjGMHI0LBN 17 | RwcXIVqPnNx5tO80Eu99kIXqUmcpgnq3sS/dbHx5pBPWLwC3PJHpsKO+zlIVkj/q 18 | pN3MpuABy3LfBI97DoC4AwLe5HwNcBjLGY7d7viNnnzk1sA1NJF6kZQ0Y+9wcxKL 19 | U1EYMiFJH2MPurQzS8BHJV3aVp7zCM59YrBUhF0aL0jPs0b/zm+LTvqSn9+72iIq 20 | 1GYV0voBX8ogxBPqf/kSKFuxdox2EN9e3MKwRMcSck6Y4e1rGn5I9QlpCGBCoCBj 21 | gWtlylurM1vgTPhUwOMtgrxbEXoo9y0AEQEAAYkBHwQYAQIACQUCUZq5LQIbDAAK 22 | CRDN9lg+p6NW1pzpB/4lIKZh2vH5p1vKPNUNBL7remReaUJjWS9U5SdQPSB6DWFh 23 | MfEwK3dfcHF6bAPyiWf6JSPGcJSgmcOv4FW1wBuiK51+N8x3fAblp0DrlewFox95 24 | ClWkr94rvHqIPhuKv636Wgo0a0/4A3jPGBqKpkQcxpZ/vW43dxrsfP6e+LZyfASo 25 | 3ft7So0908f0Ij5lKIZO5DgjQJsNaqEK93Onu1++CxEwSMwbFxmDSrwXujaOtMgu 26 | 7LVm/WuZFtfpvzr9bG8n0oyW0cQXJW5jmBsGx6ulrMdvtnEwOEXq+OmhSqW0EMWA 27 | /MJViGcd0lsmd03uuugIp92I2ZorHGFU2H3IELKi 28 | =2maT 29 | -----END PGP PUBLIC KEY BLOCK----- 30 | -------------------------------------------------------------------------------- /playbooks/roles/gpg/templates/dirmngr.conf.j2: -------------------------------------------------------------------------------- 1 | keyserver {{ streisand_gpg_keyserver_address }} 2 | hkp-cacert /etc/ssl/certs/{{ streisand_gpg_keyserver_root_ca }} 3 | -------------------------------------------------------------------------------- /playbooks/roles/gpg/templates/streisand-gpg-refresh.j2: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Refresh the Streisand GPG keyring 4 | /usr/bin/gpg2 {{ streisand_default_gpg_flags }} {{ streisand_default_key_import_flags }} --refresh >/dev/null 2>&1 5 | -------------------------------------------------------------------------------- /playbooks/roles/i18n-docs/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # The jina2 template filename (without extensions) 3 | input_template_name: "instructions" 4 | # The output file name (without extensions) 5 | output_file_name: "index" 6 | -------------------------------------------------------------------------------- /playbooks/roles/i18n-docs/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Generate the {{ title }} Markdown page" 3 | template: 4 | src: "{{ input_template_name }}{{ item.value.file_suffix }}.md.j2" 5 | dest: "{{ i18n_location }}/{{ output_file_name }}{{ item.value.file_suffix }}.md" 6 | with_dict: "{{ streisand_languages }}" 7 | loop_control: 8 | label: "{{ item.value.language_name }}" 9 | 10 | - name: "Convert the {{ title }} Markdown page into HTML" 11 | shell: > 12 | markdown {{ output_file_name }}{{ item.value.file_suffix }}.md | \ 13 | cat {{ streisand_header_template }} - {{ streisand_footer_template }} > {{ output_file_name }}{{ item.value.file_suffix }}.html 14 | args: 15 | chdir: "{{ i18n_location }}" 16 | with_dict: "{{ streisand_languages }}" 17 | loop_control: 18 | label: "{{ item.value.language_name }}" 19 | -------------------------------------------------------------------------------- /playbooks/roles/i18n-docs/templates/languages.md.j2: -------------------------------------------------------------------------------- 1 | - - - 2 | {% for key, value in streisand_languages.items() %} 3 | [{{ value.language_name }}]({{ output_file_name }}{{ value.file_suffix }}.html)  4 | {% endfor %} 5 | - - - 6 | -------------------------------------------------------------------------------- /playbooks/roles/ip-forwarding/files/streisand-ipforward.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: streisand-ipforward 4 | # Required-Start: $network $remote_fs $local_fs 5 | # Required-Stop: $network $remote_fs $local_fs 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: Persist IP forwarding settings for Streisand 9 | ### END INIT INFO 10 | 11 | echo 1 > /proc/sys/net/ipv4/ip_forward 12 | 13 | echo 0 | tee /proc/sys/net/ipv4/conf/*/*_redirects 14 | 15 | exit 0 16 | -------------------------------------------------------------------------------- /playbooks/roles/ip-forwarding/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Enable IPv4 traffic forwarding" 3 | sysctl: 4 | name: net.ipv4.ip_forward 5 | value: 1 6 | when: ansible_virtualization_type != 'lxc' 7 | 8 | - name: "Add IPv4 traffic forwarding persistence service to init" 9 | copy: 10 | src: streisand-ipforward.sh 11 | dest: /etc/init.d/streisand-ipforward 12 | mode: 0755 13 | 14 | - name: "Enable the streisand-ipforward init service" 15 | service: 16 | name: streisand-ipforward 17 | enabled: yes 18 | -------------------------------------------------------------------------------- /playbooks/roles/lets-encrypt/files/01-reload-nginx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Deploy hook that runs after a successful certbot renewal (not each attempt) 4 | 5 | # Reload nginx to gracefully shut down old worker processes and serve the new cert. 6 | systemctl reload nginx 7 | -------------------------------------------------------------------------------- /playbooks/roles/lets-encrypt/tasks/firewall.yml: -------------------------------------------------------------------------------- 1 | - name: Ensure incoming HTTP traffic is allowed 2 | ufw: 3 | to_port: "{{ le_port }}" 4 | proto: tcp 5 | rule: allow 6 | -------------------------------------------------------------------------------- /playbooks/roles/lets-encrypt/tasks/install.yml: -------------------------------------------------------------------------------- 1 | - name: Enable the Universe repository 2 | apt_repository: 3 | repo: "deb http://archive.ubuntu.com/ubuntu {{ ansible_distribution_release }} universe" 4 | state: present 5 | register: le_add_apt_repository 6 | until: not le_add_apt_repository.failed 7 | retries: "{{ apt_repository_retries }}" 8 | delay: "{{ apt_repository_delay }}" 9 | 10 | - name: Add the certbot PPA 11 | apt_repository: 12 | repo: "ppa:certbot/certbot" 13 | register: le_add_certbot_ppa 14 | until: not le_add_certbot_ppa.failed 15 | retries: "{{ apt_repository_retries }}" 16 | delay: "{{ apt_repository_delay }}" 17 | 18 | - name: Install certbot 19 | apt: 20 | package: certbot 21 | -------------------------------------------------------------------------------- /playbooks/roles/lets-encrypt/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | le_base: /etc/letsencrypt 3 | le_port: 80 4 | # RSA key size to request for SSL certificate 5 | le_rsa_key_size: 4096 6 | le_api_endpoint: "https://acme-v02.api.letsencrypt.org/directory" 7 | le_certificate: "{{ le_base }}/live/{{ streisand_domain }}/fullchain.pem" 8 | le_private_key: "{{ le_base }}/live/{{ streisand_domain }}/privkey.pem" 9 | le_chain: "{{ le_base }}/live/{{ streisand_domain }}/chain.pem" 10 | -------------------------------------------------------------------------------- /playbooks/roles/nginx/files/nginx.conf: -------------------------------------------------------------------------------- 1 | user www-data; 2 | worker_processes 4; 3 | 4 | events { 5 | worker_connections 1024; 6 | } 7 | 8 | http { 9 | include /etc/nginx/mime.types; 10 | default_type application/octet-stream; 11 | 12 | sendfile on; 13 | tcp_nopush on; 14 | tcp_nodelay on; 15 | 16 | server_names_hash_bucket_size 128; 17 | types_hash_max_size 4096; 18 | client_max_body_size 10M; 19 | server_tokens off; 20 | keepalive_timeout 65; 21 | 22 | gzip on; 23 | gzip_types text/css text/xml text/plain application/x-javascript application/atom+xml application/rss+xml; 24 | 25 | autoindex on; 26 | index index.html index.htm; 27 | 28 | include /etc/nginx/sites-enabled/*; 29 | } 30 | -------------------------------------------------------------------------------- /playbooks/roles/nginx/files/nginx_signing.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Version: GnuPG v2.0.22 (GNU/Linux) 3 | 4 | mQENBE5OMmIBCAD+FPYKGriGGf7NqwKfWC83cBV01gabgVWQmZbMcFzeW+hMsgxH 5 | W6iimD0RsfZ9oEbfJCPG0CRSZ7ppq5pKamYs2+EJ8Q2ysOFHHwpGrA2C8zyNAs4I 6 | QxnZZIbETgcSwFtDun0XiqPwPZgyuXVm9PAbLZRbfBzm8wR/3SWygqZBBLdQk5TE 7 | fDR+Eny/M1RVR4xClECONF9UBB2ejFdI1LD45APbP2hsN/piFByU1t7yK2gpFyRt 8 | 97WzGHn9MV5/TL7AmRPM4pcr3JacmtCnxXeCZ8nLqedoSuHFuhwyDnlAbu8I16O5 9 | XRrfzhrHRJFM1JnIiGmzZi6zBvH0ItfyX6ttABEBAAG0KW5naW54IHNpZ25pbmcg 10 | a2V5IDxzaWduaW5nLWtleUBuZ2lueC5jb20+iQE+BBMBAgAoAhsDBgsJCAcDAgYV 11 | CAIJCgsEFgIDAQIeAQIXgAUCV2K1+AUJGB4fQQAKCRCr9b2Ce9m/YloaB/9XGrol 12 | kocm7l/tsVjaBQCteXKuwsm4XhCuAQ6YAwA1L1UheGOG/aa2xJvrXE8X32tgcTjr 13 | KoYoXWcdxaFjlXGTt6jV85qRguUzvMOxxSEM2Dn115etN9piPl0Zz+4rkx8+2vJG 14 | F+eMlruPXg/zd88NvyLq5gGHEsFRBMVufYmHtNfcp4okC1klWiRIRSdp4QY1wdrN 15 | 1O+/oCTl8Bzy6hcHjLIq3aoumcLxMjtBoclc/5OTioLDwSDfVx7rWyfRhcBzVbwD 16 | oe/PD08AoAA6fxXvWjSxy+dGhEaXoTHjkCbz/l6NxrK3JFyauDgU4K4MytsZ1HDi 17 | MgMW8hZXxszoICTTiQEcBBABAgAGBQJOTkelAAoJEKZP1bF62zmo79oH/1XDb29S 18 | YtWp+MTJTPFEwlWRiyRuDXy3wBd/BpwBRIWfWzMs1gnCjNjk0EVBVGa2grvy9Jtx 19 | JKMd6l/PWXVucSt+U/+GO8rBkw14SdhqxaS2l14v6gyMeUrSbY3XfToGfwHC4sa/ 20 | Thn8X4jFaQ2XN5dAIzJGU1s5JA0tjEzUwCnmrKmyMlXZaoQVrmORGjCuH0I0aAFk 21 | RS0UtnB9HPpxhGVbs24xXZQnZDNbUQeulFxS4uP3OLDBAeCHl+v4t/uotIad8v6J 22 | SO93vc1evIje6lguE81HHmJn9noxPItvOvSMb2yPsE8mH4cJHRTFNSEhPW6ghmlf 23 | Wa9ZwiVX5igxcvaIRgQQEQIABgUCTk5b0gAKCRDs8OkLLBcgg1G+AKCnacLb/+W6 24 | cflirUIExgZdUJqoogCeNPVwXiHEIVqithAM1pdY/gcaQZmIRgQQEQIABgUCTk5f 25 | YQAKCRCpN2E5pSTFPnNWAJ9gUozyiS+9jf2rJvqmJSeWuCgVRwCcCUFhXRCpQO2Y 26 | Va3l3WuB+rgKjsQ= 27 | =EWWI 28 | -----END PGP PUBLIC KEY BLOCK----- 29 | -------------------------------------------------------------------------------- /playbooks/roles/nginx/templates/nginx.service.j2: -------------------------------------------------------------------------------- 1 | [Service] 2 | PrivateTmp=true 3 | RestartSec=5s 4 | Restart=on-failure 5 | -------------------------------------------------------------------------------- /playbooks/roles/nginx/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apache_packages_to_remove: 3 | - apache2 4 | - apache2.2-bin 5 | - apache2.2-common 6 | - apache2-mpm-event 7 | - apache2-mpm-itk 8 | - apache2-mpm-prefork 9 | - apache2-mpm-worker 10 | 11 | nginx_systemd_service_path: /etc/systemd/system/nginx.service.d 12 | nginx_default_html_path: /usr/share/nginx/html 13 | -------------------------------------------------------------------------------- /playbooks/roles/openconnect/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ocserv_ca_cn: "Streisand" 3 | ocserv_ca_organization: "Streisand Effect Automated Signing, Inc." 4 | ocserv_port: "4443" 5 | ocserv_server_organization: "Streisand Effect Servers, Inc." 6 | ocserv_key_ou: "nogroup" 7 | -------------------------------------------------------------------------------- /playbooks/roles/openconnect/files/ocserv-pam: -------------------------------------------------------------------------------- 1 | account required pam_listfile.so \ 2 | sense=allow item=user file=/etc/allowed_vpn_certs 3 | 4 | auth required pam_listfile.so \ 5 | sense=allow item=user file=/etc/allowed_vpn_certs 6 | 7 | password required pam_deny.so 8 | session required pam_deny.so 9 | 10 | other required pam_deny.so 11 | -------------------------------------------------------------------------------- /playbooks/roles/openconnect/files/openconnect.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Filter out all OpenConnect entries to prevent IP address logging. 3 | # 4 | :msg, contains, "ocserv" then stop 5 | -------------------------------------------------------------------------------- /playbooks/roles/openconnect/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart ocserv 3 | service: 4 | name: ocserv 5 | state: restarted 6 | 7 | - name: Restart rsyslog for OpenConnect 8 | service: 9 | name: rsyslog 10 | state: restarted 11 | -------------------------------------------------------------------------------- /playbooks/roles/openconnect/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - { role: ufw } 4 | - { role: ip-forwarding } 5 | -------------------------------------------------------------------------------- /playbooks/roles/openconnect/tasks/docs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create the OpenConnect/AnyConnect Gateway directory 3 | file: 4 | path: "{{ ocserv_gateway_location }}" 5 | owner: www-data 6 | group: www-data 7 | mode: 0750 8 | state: directory 9 | 10 | - name: Copy the CA certificate file to the OpenConnect Gateway directory 11 | command: cp {{ ocserv_ca_certificate_file }} {{ ocserv_gateway_location }} 12 | args: 13 | creates: "{{ ocserv_gateway_location }}/ca-cert.pem" 14 | 15 | - name: "Copy the client PKCS #12 files to the OpenConnect Gateway directory" 16 | command: cp {{ ocserv_path }}/{{ ocserv_client_password.client_name.stdout }}/{{ ocserv_client_password.client_name.stdout }}.p12 {{ ocserv_gateway_location }} 17 | args: 18 | creates: "{{ ocserv_gateway_location }}/{{ ocserv_client_password.client_name.stdout }}.p12" 19 | with_items: "{{ vpn_client_pkcs12_password_list.results }}" 20 | loop_control: 21 | loop_var: "ocserv_client_password" 22 | label: "{{ ocserv_client_password.client_name.item }}" 23 | 24 | - name: "Copy the client .mobileconfig files to the OpenConnect Gateway directory" 25 | command: cp {{ ocserv_path }}/{{ ocserv_client_password.client_name.stdout }}/{{ ocserv_client_password.client_name.stdout }}.mobileconfig {{ ocserv_gateway_location }} 26 | args: 27 | creates: "{{ ocserv_gateway_location }}/{{ ocserv_client_password.client_name.stdout }}.mobileconfig" 28 | with_items: "{{ vpn_client_pkcs12_password_list.results }}" 29 | loop_control: 30 | loop_var: "ocserv_client_password" 31 | label: "{{ ocserv_client_password.client_name.item }}" 32 | 33 | - include_role: 34 | name: i18n-docs 35 | vars: 36 | title: "OpenConnect/AnyConnect" 37 | input_template_name: "instructions" 38 | i18n_location: "{{ ocserv_gateway_location }}" 39 | -------------------------------------------------------------------------------- /playbooks/roles/openconnect/tasks/firewall.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure UFW allows DNS requests from OpenConnect clients 3 | ufw: 4 | to_port: "53" 5 | proto: "udp" 6 | rule: "allow" 7 | from_ip: "192.168.1.0/24" 8 | 9 | - name: Ensure UFW allows OpenConnect (ocserv) 10 | ufw: 11 | to_port: "{{ ocserv_port }}" 12 | proto: "any" 13 | rule: "allow" 14 | 15 | - name: Install the ocserv iptables service file 16 | template: 17 | src: ocserv-iptables.service.j2 18 | dest: /etc/systemd/system/ocserv-iptables.service 19 | mode: 0644 20 | 21 | - name: Enable the ocserv-iptables service 22 | systemd: 23 | daemon_reload: yes 24 | name: ocserv-iptables.service 25 | enabled: yes 26 | state: started 27 | -------------------------------------------------------------------------------- /playbooks/roles/openconnect/tasks/install.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # It *shouldn't* be necessary to run this particular apt_repository 4 | # call in a "retry" loop; enabling Universe doesn't reach out to the 5 | # network, so this shouldn't have transient failures. For the sake of 6 | # consistency with the other apt_repository calls, it does retry. 7 | 8 | - name: Enable the Universe repository 9 | apt_repository: 10 | repo: "deb http://archive.ubuntu.com/ubuntu {{ ansible_distribution_release }} universe" 11 | state: present 12 | register: openconnect_add_apt_repository 13 | until: not openconnect_add_apt_repository.failed 14 | retries: "{{ apt_repository_retries }}" 15 | delay: "{{ apt_repository_delay }}" 16 | 17 | - name: Install ocserv 18 | apt: 19 | package: ocserv 20 | 21 | - name: Create the OpenConnect rsyslog configuration directory 22 | file: 23 | path: /etc/rsyslog.d/openconnect.d/ 24 | owner: root 25 | group: root 26 | mode: 0644 27 | state: directory 28 | 29 | - name: Copy the modified rsyslog configuration into place that prevents OpenConnect traffic from being logged 30 | copy: 31 | src: openconnect.conf 32 | dest: /etc/rsyslog.d/openconnect.d/openconnect.conf 33 | owner: root 34 | group: root 35 | mode: 0644 36 | notify: Restart rsyslog for OpenConnect 37 | -------------------------------------------------------------------------------- /playbooks/roles/openconnect/tasks/mirror.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Include the OpenConnect mirror variables 3 | include_vars: mirror.yml 4 | 5 | - name: Make the directory where the OpenConnect mirrored files will be stored 6 | file: 7 | path: "{{ openconnect_mirror_location }}" 8 | owner: www-data 9 | group: www-data 10 | mode: 0755 11 | state: directory 12 | 13 | - block: 14 | - name: Mirror the OpenConnect clients 15 | get_url: 16 | url: "{{ item.url }}" 17 | dest: "{{ openconnect_mirror_location }}" 18 | checksum: "{{ item.checksum }}" 19 | owner: www-data 20 | group: www-data 21 | mode: 0644 22 | with_items: "{{ openconnect_download_urls }}" 23 | rescue: 24 | - name: "{{ streisand_mirror_warning }}" 25 | pause: 26 | seconds: "{{ streisand_mirror_warning_seconds }}" 27 | 28 | - include_role: 29 | name: i18n-docs 30 | vars: 31 | title: "OpenConnect mirror" 32 | i18n_location: "{{ openconnect_mirror_location }}" 33 | input_template_name: "mirror" 34 | -------------------------------------------------------------------------------- /playbooks/roles/openconnect/templates/mirror-fr.md.j2: -------------------------------------------------------------------------------- 1 | {% include "languages.md.j2" %} 2 | 3 | 4 | ### OpenConnect ### 5 | 6 | **Source** 7 | 8 | * [{{ openconnect_source_filename }}]({{ openconnect_source_href }}) 9 | * Somme de contrôle: *{{ openconnect_source_checksum }}* 10 | 11 | **Windows** 12 | 13 | * [{{ openconnect_windows_filename }}]({{ openconnect_windows_href }}) 14 | * Somme de contrôle: *{{ openconnect_windows_checksum }}* 15 | 16 | **macOS** 17 | 18 | * [{{ openconnect_macos_filename }}]({{ openconnect_macos_href }}) 19 | * Somme de contrôle: *{{ openconnect_macos_checksum }}* 20 | -------------------------------------------------------------------------------- /playbooks/roles/openconnect/templates/mirror.md.j2: -------------------------------------------------------------------------------- 1 | {% include "languages.md.j2" %} 2 | 3 | 4 | ### OpenConnect ### 5 | 6 | **Source** 7 | 8 | * [{{ openconnect_source_filename }}]({{ openconnect_source_href }}) 9 | * Checksum: *{{ openconnect_source_checksum }}* 10 | 11 | **Windows** 12 | 13 | * [{{ openconnect_windows_filename }}]({{ openconnect_windows_href }}) 14 | * Checksum: *{{ openconnect_windows_checksum }}* 15 | 16 | **macOS** 17 | 18 | * [{{ openconnect_macos_filename }}]({{ openconnect_macos_href }}) 19 | * Checksum: *{{ openconnect_macos_checksum }}* 20 | -------------------------------------------------------------------------------- /playbooks/roles/openconnect/templates/ocserv-iptables.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Set the firewall rules required for ocserv 3 | After=network.target 4 | Before=ocserv.service 5 | 6 | [Service] 7 | Type=oneshot 8 | RemainAfterExit=true 9 | ExecStart=/sbin/{{ ocserv_firewall_rule }} 10 | 11 | [Install] 12 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /playbooks/roles/openconnect/templates/ocserv.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=OpenConnect SSL VPN server 3 | Documentation=man:ocserv(8) 4 | After=network-online.target 5 | After=dbus.service 6 | 7 | [Service] 8 | PrivateTmp=true 9 | PIDFile={{ ocserv_pid_file }} 10 | ExecStart=/usr/sbin/ocserv --foreground --pid-file {{ ocserv_pid_file }} --config {{ ocserv_config_file }} 11 | ExecReload=/bin/kill -HUP $MAINPID 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /playbooks/roles/openconnect/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ocserv_path: "/etc/ocserv" 3 | ocserv_ca: "{{ ocserv_path }}/ca" 4 | ocserv_config_file: "{{ ocserv_path }}/ocserv.conf" 5 | ocserv_firewall_rule: "iptables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -j MASQUERADE" 6 | 7 | ocserv_days_valid: "1825" 8 | ocserv_pid_file: "/var/run/ocserv.pid" 9 | ocserv_socket_file: "/var/run/ocserv-socket" 10 | ocserv_ca_certificate_file: "{{ ocserv_path }}/ca.crt" 11 | ocserv_ca_key_file: "{{ ocserv_path }}/ca.key" 12 | 13 | ocserv_server_certificate_file: "{{ ocserv_path }}/server.crt" 14 | ocserv_server_key_file: "{{ ocserv_path }}/server.key" 15 | 16 | ocserv_hashed_password_file: "{{ ocserv_path }}/ocpasswd" 17 | ocserv_password_file: "{{ ocserv_path }}/ocserv-password" 18 | ocserv_server_common_name_file: "{{ ocserv_path }}/ocserv_server_common_name" 19 | 20 | ocserv_client_name: "streisand-openconnect-{{ streisand_ipv4_address }}" 21 | 22 | ocserv_gateway_location: "{{ streisand_gateway_location }}/openconnect" 23 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | openvpn_key_country: "US" 3 | openvpn_key_province: "California" 4 | openvpn_key_city: "Beverly Hills" 5 | openvpn_key_org: "ACME CORPORATION" 6 | openvpn_key_ou: "Anvil Department" 7 | 8 | openvpn_port: "636" 9 | openvpn_port_udp: "8757" 10 | openvpn_port_sslh: "443" 11 | openvpn_key_size: "4096" 12 | openvpn_cipher: "AES-256-CBC" 13 | openvpn_ncp_ciphers: "AES-256-GCM:AES-128-GCM" 14 | openvpn_auth_digest: "SHA256" 15 | openvpn_connect_timeout: 10 #seconds 16 | 17 | # If stunnel is enabled generating OpenVPN stunnel profiles requires these 18 | # variables to be defined 19 | stunnel_local_port: "41194" 20 | stunnel_remote_port: "993" 21 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/files/openvpn_signing.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQENBE45PsIBCAC2K2LRZPQIUmJlCDKcncfR6vok2wowDpGpHZffvEEoUj/DoocR 4 | LLpPHR5RB1zMWIs2IjF8vOtXMCBguDgtEvQTh6p6DM3D1fTnYp3pPlQyyzAuC81v 5 | CQo44h09R4Nh2e38oMRVztmAnacC4g5aiSEamrZ4PbWdAdPc4uZdCPOGmUDJw8+q 6 | aAYvL/8pM7YqEu05FqE+aNcG02K+mDhA2bqRLLKoLEFpeMSO6vV8BrE7Vw1Rs1PM 7 | VLDJt9HdXmC6vP+WWqDuj7/qfRb2wwlSIp5+aFyRHOUNyFKnWZYIObeV3+Y6oG6h 8 | gmBtU1673mHDqVy26TwfjpJeudMKHVCrKXVXABEBAAG0QVNhbXVsaSBTZXBww6Ru 9 | ZW4gKE9wZW5WUE4gVGVjaG5vbG9naWVzLCBJbmMpIDxzYW11bGlAb3BlbnZwbi5u 10 | ZXQ+iQFVBBMBAgA/AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgBYhBDDr9Oc8 11 | zmPu4STdJ45tqLThWMVpBQJZeJ2tBQkQ4vlrAAoJEI5tqLThWMVpPDUH/RdLsdG/ 12 | 4kmal/rfbso3YVxZXGp2fHKrptvCVrUWluYs6H/XBV4x6aMe8Q6K7Qa7BSLA9jZ8 13 | v+UN/4aA+urBcs6Ted/XbP3mKU47tOotW24nA1LRjd4gUSEXCaEOBbCSyw3uw6Vz 14 | U1wr1gEmkC7kvBziL+Pcbt5tKTRhUfgbcjYNNdp/nAwn3Pm3OFRaBt/qDU2aYAOH 15 | +k191x/ovDRO/UiU2CVvrdfv/VMZfo/rwxe8IiirxQ4k5DR2Vyu0DMNzlNTqRk8l 16 | rUH0FBdl0rOiefH0m6ubKstpYCaOUYsh/FaW53O6qqrTlZqPtAav1cRog8zb8mhT 17 | sFFAarhnZcQ/DG+5AQ0ETjk+wgEIAOg+Bjk8Wnb7fbbwBDDUalLsIEgFUhsrSLD5 18 | VVYB8tOq7djshckp/3LwfkSsmUzEtXMXxIbDUON1vbCQXZlQDe7E7uY5KFNWyi4+ 19 | UJwLMrs+oqfeduUzDxQ+voq/6NGl+2olqd6vT/c/uPb/RPZpOdgoEkqFEOTMRVz0 20 | DZwAyzyYEWBrwDECNbEtqefMLPIaUGUzZvUc80I+MYL6AzRe/utIWcBnZ2nydZ0S 21 | vWKRJ0lOs69e6KoFVeE5QXzmTXkjzSbR9eN3ADm2j0EjLnpt/zR4hF8s4l4HLdRd 22 | Sn47tAdvahsNfgWmOfiQD8btnu8DiMiJMd8IpVsZX/zCJbSUChcAEQEAAYkBPAQY 23 | AQIAJgIbDBYhBDDr9Oc8zmPu4STdJ45tqLThWMVpBQJZeJ2DBQkQ4vlBAAoJEI5t 24 | qLThWMVpCCIH+QFqEY+Xk5gJc10lbJUZEhJIknS/3GEd+3WBHgBtBaQCeK7+bFQP 25 | ZagTN4SJLiwYcQDV04mZTpFOJV1k9AYaz7ENEjHe51mGhPM9sm5Ix7KwMNo0lHJ+ 26 | ryZ0zyie28IbGz+rYa7OdkhE2EmcQkezYNWC03G8yR9yGk3QZ3CtPPO/xYP2tBGc 27 | OocqWUkVuR7KpitT9QnOZ4af26b83Vr/+qJ1FdSfW6/VAbyboVWya4oEnKSUusBm 28 | 0WCQzaLH15EpzgcdB/x8KVOTS1dAA5GNyRyhbRfP6yBXgBruCkPa4/np78/72jjW 29 | vbAvOhOEMnfzWmf3VZq+q6hhIJf6Sp+dcoU= 30 | =P3ax 31 | -----END PGP PUBLIC KEY BLOCK----- 32 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart OpenVPN 3 | systemd: 4 | name: "{{ item }}" 5 | state: restarted 6 | with_items: 7 | - "{{ openvpn_service_names }}" 8 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | # OpenVPN needs to be added to the firewall 4 | - { role: ufw } 5 | - { role: dnsmasq } 6 | - { role: ip-forwarding } 7 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/tasks/docs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create the OpenVPN Gateway directory 3 | file: 4 | path: "{{ openvpn_gateway_location }}" 5 | owner: www-data 6 | group: www-data 7 | mode: 0750 8 | state: directory 9 | 10 | - name: Copy the client files to the OpenVPN Gateway directory 11 | command: cp --recursive {{ openvpn_path }}/{{ client_name.stdout }} {{ openvpn_gateway_location }} 12 | args: 13 | creates: "{{ openvpn_gateway_location }}/{{ client_name.stdout }}/ca.crt" 14 | with_items: "{{ vpn_client_names.results }}" 15 | loop_control: 16 | loop_var: "client_name" 17 | label: "{{ client_name.item }}" 18 | 19 | - include_role: 20 | name: i18n-docs 21 | vars: 22 | title: "OpenVPN" 23 | input_template_name: "instructions" 24 | i18n_location: "{{ openvpn_gateway_location }}" 25 | 26 | - include_role: 27 | name: i18n-docs 28 | vars: 29 | title: "OpenVPN stunnel" 30 | input_template_name: "stunnel-instructions" 31 | output_file_name: "stunnel" 32 | i18n_location: "{{ openvpn_gateway_location }}" 33 | when: streisand_stunnel_enabled 34 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/tasks/firewall.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Allow OpenVPN through the firewall 3 | command: "{{ item }}" 4 | with_items: "{{ openvpn_firewall_rules }}" 5 | 6 | - name: Ensure UFW allows DNS requests from OpenVPN clients 7 | ufw: 8 | to_port: "53" 9 | proto: "udp" 10 | rule: "allow" 11 | from_ip: "10.8.0.0/24" 12 | 13 | - name: Ensure UFW allows DNS requests from OpenVPN UDP clients 14 | ufw: 15 | to_port: "53" 16 | proto: "udp" 17 | rule: "allow" 18 | from_ip: "10.9.0.0/24" 19 | 20 | - name: Ensure UFW allows OpenVPN 21 | ufw: 22 | to_port: "{{ openvpn_port }}" 23 | proto: "tcp" 24 | rule: "allow" 25 | 26 | - name: Ensure UFW allows OpenVPN over UDP 27 | ufw: 28 | to_port: "{{ openvpn_port_udp }}" 29 | proto: "udp" 30 | rule: "allow" 31 | 32 | - name: Install the OpenVPN iptables service file 33 | template: 34 | src: openvpn-iptables.service.j2 35 | dest: /etc/systemd/system/openvpn-iptables.service 36 | mode: 0644 37 | 38 | - name: Enable the openvpn-iptables service 39 | systemd: 40 | daemon_reload: yes 41 | name: openvpn-iptables.service 42 | enabled: yes 43 | state: started 44 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/tasks/install.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Add the official OpenVPN APT key; hiding 25 lines of log..." 3 | apt_key: 4 | id: E158C569 5 | data: "{{ item }}" 6 | with_file: openvpn_signing.key 7 | no_log: True 8 | 9 | - name: Add the official OpenVPN repository 10 | apt_repository: 11 | repo: 'deb https://build.openvpn.net/debian/openvpn/stable {{ ansible_lsb.codename }} main' 12 | state: present 13 | register: openvpn_add_apt_repository 14 | until: not openvpn_add_apt_repository.failed 15 | retries: "{{ apt_repository_retries }}" 16 | delay: "{{ apt_repository_delay }}" 17 | 18 | - name: Install OpenVPN and its dependencies from APT 19 | apt: 20 | package: 21 | - openvpn 22 | - udev 23 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/tasks/mirror.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Include the OpenVPN mirror variables 3 | include_vars: mirror.yml 4 | 5 | - name: Make the directory where OpenVPN's mirrored files will be stored 6 | file: 7 | path: "{{ openvpn_mirror_location }}" 8 | owner: www-data 9 | group: www-data 10 | mode: 0755 11 | state: directory 12 | 13 | - block: 14 | - name: Mirror Tunnelblick for macOS 15 | get_url: 16 | url: "{{ tunnelblick_url }}" 17 | dest: "{{ openvpn_mirror_location }}" 18 | checksum: "{{ tunnelblick_checksum }}" 19 | owner: www-data 20 | group: www-data 21 | mode: 0644 22 | 23 | - include_role: 24 | name: download-and-verify 25 | vars: 26 | project_name: "OpenVPN Community" 27 | project_download_baseurl: "{{ openvpn_base_download_url }}" 28 | project_download_files: "{{ openvpn_download_files }}" 29 | project_download_location: "{{ openvpn_mirror_location }}" 30 | project_signer_keyid: "{{ openvpn_gpg_keyid }}" 31 | rescue: 32 | - name: "{{ streisand_mirror_warning }}" 33 | pause: 34 | seconds: "{{ streisand_mirror_warning_seconds }}" 35 | 36 | - include_role: 37 | name: i18n-docs 38 | vars: 39 | title: "OpenVPN mirror" 40 | i18n_location: "{{ openvpn_mirror_location }}" 41 | input_template_name: "mirror" 42 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/templates/client-combined.ovpn.j2: -------------------------------------------------------------------------------- 1 | client 2 | 3 | 4 | remote {{ openvpn_server }} {{ openvpn_port_udp }} udp 5 | 6 | 7 | remote {{ openvpn_server }} {{ openvpn_port }} tcp 8 | connect-timeout {{ openvpn_connect_timeout }} 9 | 10 | 11 | remote {{ openvpn_server }} {{ openvpn_port_sslh }} tcp 12 | connect-timeout {{ openvpn_connect_timeout }} 13 | 14 | 15 | {% include "client-common.j2" %} 16 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/templates/client-common.j2: -------------------------------------------------------------------------------- 1 | dev tun 2 | cipher {{ openvpn_cipher }} 3 | auth {{ openvpn_auth_digest }} 4 | resolv-retry infinite 5 | nobind 6 | persist-key 7 | persist-tun 8 | remote-cert-tls server 9 | verify-x509-name {{ openvpn_server_common_name.stdout }} name 10 | tls-version-min 1.2 11 | compress 12 | verb 3 13 | route {{ streisand_ipv4_address }} 255.255.255.255 net_gateway 14 | 15 | 16 | {{ openvpn_ca_contents.stdout }} 17 | 18 | 19 | 20 | {{ item[1].stdout }} 21 | 22 | 23 | 24 | {{ item[2].stdout }} 25 | 26 | 27 | 28 | {{ openvpn_hmac_firewall_contents.stdout }} 29 | 30 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/templates/client-direct-udp.ovpn.j2: -------------------------------------------------------------------------------- 1 | client 2 | remote {{ openvpn_server }} {{ openvpn_port_udp }} 3 | proto udp 4 | {% include "client-common.j2" %} 5 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/templates/client-direct.ovpn.j2: -------------------------------------------------------------------------------- 1 | client 2 | remote {{ openvpn_server }} {{ openvpn_port }} 3 | proto tcp 4 | {% include "client-common.j2" %} 5 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/templates/client-sslh.ovpn.j2: -------------------------------------------------------------------------------- 1 | client 2 | remote {{ openvpn_server }} {{ openvpn_port_sslh }} 3 | proto tcp 4 | {% include "client-common.j2" %} 5 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/templates/client-stunnel.ovpn.j2: -------------------------------------------------------------------------------- 1 | client 2 | remote 127.0.0.1 {{ stunnel_local_port }} 3 | proto tcp 4 | {% include "client-common.j2" %} 5 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/templates/etc_openvpn_server.conf.j2: -------------------------------------------------------------------------------- 1 | server 10.8.0.0 255.255.255.0 2 | push "dhcp-option DNS {{ dnsmasq_openvpn_tcp_ip }}" 3 | proto tcp 4 | port {{ openvpn_port }} 5 | {% include "etc_openvpn_server_common.j2" %} 6 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/templates/etc_openvpn_server_common.j2: -------------------------------------------------------------------------------- 1 | dev tun 2 | ca ca.crt 3 | cert server.crt 4 | key server.key # This file should be kept secret 5 | dh none 6 | ifconfig-pool-persist ipp.txt 7 | push "redirect-gateway def1" 8 | 9 | # Fix for the Windows 10 DNS leak described here: 10 | # https://community.openvpn.net/openvpn/ticket/605 11 | push block-outside-dns 12 | 13 | client-to-client 14 | remote-cert-tls client 15 | 16 | # Allow multiple clients with the same common name to concurrently connect. 17 | # In the absence of this option, OpenVPN will disconnect a client instance 18 | # upon connection of a new client having the same common name. 19 | # duplicate-cn 20 | 21 | keepalive 1800 3600 22 | tls-crypt ta.key # This file is secret 23 | cipher {{ openvpn_cipher }} 24 | ncp-ciphers {{ openvpn_ncp_ciphers }} 25 | 26 | # limit the tls ciphers 27 | tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384 28 | 29 | auth {{ openvpn_auth_digest }} 30 | tls-version-min 1.2 31 | compress 32 | user nobody 33 | group nogroup 34 | persist-key 35 | persist-tun 36 | verb 0 37 | 38 | script-security 2 39 | tls-verify "/usr/share/openvpn/verify-cn /etc/allowed_vpn_certs" 40 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/templates/etc_openvpn_server_udp.conf.j2: -------------------------------------------------------------------------------- 1 | server 10.9.0.0 255.255.255.0 2 | push "dhcp-option DNS {{ dnsmasq_openvpn_udp_ip }}" 3 | proto udp 4 | port {{ openvpn_port_udp }} 5 | {% include "etc_openvpn_server_common.j2" %} 6 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/templates/mirror-fr.md.j2: -------------------------------------------------------------------------------- 1 | {% include "languages.md.j2" %} 2 | 3 | 4 | ### OpenVPN ### 5 | 6 | **Source** 7 | 8 | * [{{ openvpn_source_filename }}]({{ openvpn_source_href }}) ([sig]({{ openvpn_source_sig_href }})) 9 | 10 | **macOS** 11 | 12 | * [Tunnelblick\_{{ tunnelblick_version }}.dmg]({{ tunnelblick_href }}) 13 | * Somme de contrôle: *{{ tunnelblick_checksum }}* 14 | 15 | **Windows** 16 | 17 | * [{{ openvpn_windows_installer_filename }}]({{ openvpn_windows_installer_href }}) ([sig]({{ openvpn_windows_installer_sig_href }})) 18 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/templates/mirror.md.j2: -------------------------------------------------------------------------------- 1 | {% include "languages.md.j2" %} 2 | 3 | 4 | ### OpenVPN ### 5 | 6 | **Source** 7 | 8 | * [{{ openvpn_source_filename }}]({{ openvpn_source_href }}) ([sig]({{ openvpn_source_sig_href }})) 9 | 10 | **macOS** 11 | 12 | * [Tunnelblick\_{{ tunnelblick_version }}.dmg]({{ tunnelblick_href }}) 13 | * Checksum: *{{ tunnelblick_checksum }}* 14 | 15 | **Windows** 16 | 17 | * [{{ openvpn_windows_installer_filename }}]({{ openvpn_windows_installer_href }}) ([sig]({{ openvpn_windows_installer_sig_href }})) 18 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/templates/openvpn-iptables.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Firewall rules required for OpenVPN 3 | After=network.target 4 | Before={{ openvpn_service_names | join(' ') }} 5 | 6 | [Service] 7 | Type=oneshot 8 | RemainAfterExit=true 9 | {% for rule in openvpn_firewall_rules %} 10 | ExecStart=/sbin/{{ rule }} 11 | {% endfor %} 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/templates/openvpn.service.j2: -------------------------------------------------------------------------------- 1 | # source: https://github.com/OpenVPN/openvpn/blob/master/distro/systemd/openvpn-server%40.service.in 2 | [Unit] 3 | Description=OpenVPN service for %I 4 | After=syslog.target network-online.target 5 | Wants=network-online.target 6 | Documentation=man:openvpn(8) 7 | Documentation=https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage 8 | Documentation=https://community.openvpn.net/openvpn/wiki/HOWTO 9 | 10 | [Service] 11 | Type=notify 12 | PrivateTmp=true 13 | WorkingDirectory={{ openvpn_path }} 14 | ExecStart=/usr/sbin/openvpn --config %i.conf 15 | CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_OVERRIDE 16 | LimitNPROC=30 17 | DeviceAllow=/dev/null rw 18 | DeviceAllow=/dev/net/tun rw 19 | ProtectSystem=true 20 | ProtectHome=true 21 | KillMode=process 22 | RestartSec=5s 23 | Restart=on-failure 24 | 25 | [Install] 26 | Alias=openvpn@%i.target 27 | WantedBy=multi-user.target 28 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/templates/openvpn_dnsmasq.conf.j2: -------------------------------------------------------------------------------- 1 | # Listen on the OpenVPN TCP and UDP addresses 2 | listen-address={{ dnsmasq_openvpn_tcp_ip }},{{ dnsmasq_openvpn_udp_ip }} 3 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | openvpn_days_valid: "1825" 3 | openvpn_request_subject: "/C={{ openvpn_key_country }}/ST={{ openvpn_key_province }}/L={{ openvpn_key_city }}/O={{ openvpn_key_org }}/OU={{ openvpn_key_ou }}" 4 | openvpn_path: "/etc/openvpn" 5 | openvpn_ca: "{{ openvpn_path }}/ca" 6 | openvpn_hmac_firewall: "{{ openvpn_path }}/ta.key" 7 | openvpn_server: "{{ streisand_ipv4_address }}" 8 | openvpn_server_common_name_file: "{{ openvpn_path }}/openvpn_server_common_name" 9 | 10 | openvpn_direct_profile_filename: "{{ openvpn_server }}-direct.ovpn" 11 | openvpn_direct_udp_profile_filename: "{{ openvpn_server }}-direct-udp.ovpn" 12 | openvpn_sslh_profile_filename: "{{ openvpn_server }}-sslh.ovpn" 13 | openvpn_stunnel_profile_filename: "{{ openvpn_server }}-stunnel.ovpn" 14 | openvpn_combined_profile_filename: "{{ openvpn_server }}-combined.ovpn" 15 | 16 | dnsmasq_openvpn_tcp_ip: "10.8.0.1" 17 | dnsmasq_openvpn_udp_ip: "10.9.0.1" 18 | 19 | openvpn_firewall_rules: 20 | - "iptables --wait {{ streisand_iptables_wait }} -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT" 21 | - "iptables --wait {{ streisand_iptables_wait }} -A FORWARD -s 10.8.0.0/24 -j ACCEPT" 22 | - "iptables --wait {{ streisand_iptables_wait }} -A FORWARD -s 10.9.0.0/24 -j ACCEPT" 23 | - "iptables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -s 10.8.0.0/24 -o {{ ansible_default_ipv4.interface }} -j MASQUERADE" 24 | - "iptables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -s 10.9.0.0/24 -o {{ ansible_default_ipv4.interface }} -j MASQUERADE" 25 | 26 | openvpn_gateway_location: "{{ streisand_gateway_location }}/openvpn" 27 | 28 | openvpn_service_names: 29 | - "openvpn@server.service" 30 | - "openvpn@server-udp.service" 31 | -------------------------------------------------------------------------------- /playbooks/roles/openvpn/vars/mirror.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # OpenVPN Download variables 3 | # -------------------------- 4 | openvpn_mirror_location: "{{ streisand_mirror_location }}/openvpn" 5 | openvpn_mirror_href_base: "/mirror/openvpn" 6 | 7 | openvpn_base_download_url: "https://build.openvpn.net/downloads/releases/latest" 8 | 9 | openvpn_source_filename: "openvpn-latest-stable.tar.gz" 10 | openvpn_source_sig_filename: "{{ openvpn_source_filename }}.asc" 11 | openvpn_source_href: "{{ openvpn_mirror_href_base }}/{{ openvpn_source_filename }}" 12 | openvpn_source_sig_href: "{{ openvpn_mirror_href_base }}/{{ openvpn_source_sig_filename }}" 13 | 14 | openvpn_windows_installer_filename: "openvpn-install-latest-stable-win10.exe" 15 | openvpn_windows_installer_sig_filename: "{{ openvpn_windows_installer_filename }}.asc" 16 | openvpn_windows_installer_href: "{{ openvpn_mirror_href_base }}/{{ openvpn_windows_installer_filename }}" 17 | openvpn_windows_installer_sig_href: "{{ openvpn_mirror_href_base }}/{{ openvpn_windows_installer_sig_filename }}" 18 | 19 | openvpn_gpg_keyid: "5ACFEAC6" 20 | openvpn_download_files: 21 | - { "file": "{{ openvpn_source_filename }}", "sig": "{{ openvpn_source_sig_filename }}" } 22 | - { "file": "{{ openvpn_windows_installer_filename }}", "sig": "{{ openvpn_windows_installer_sig_filename }}" } 23 | 24 | # macOS 25 | tunnelblick_version: "3.8.1" 26 | tunnelblick_build: "5400" 27 | tunnelblick_filename: "Tunnelblick_{{ tunnelblick_version }}_build_{{ tunnelblick_build }}.dmg" 28 | tunnelblick_href: "{{ openvpn_mirror_href_base }}/{{ tunnelblick_filename }}" 29 | tunnelblick_url: "https://tunnelblick.net/release/{{ tunnelblick_filename }}" 30 | tunnelblick_checksum: "sha256:a619a1c01a33a8618fc2489a43241e95c828dcdb7f7c56cfc883dcbb22644693" 31 | -------------------------------------------------------------------------------- /playbooks/roles/service-net/files/10-service0.netdev: -------------------------------------------------------------------------------- 1 | [NetDev] 2 | Name=service0 3 | Kind=dummy 4 | -------------------------------------------------------------------------------- /playbooks/roles/service-net/files/10-service0.network: -------------------------------------------------------------------------------- 1 | [Match] 2 | Name=service0 3 | 4 | [Link] 5 | RequiredForOnline=yes 6 | 7 | [Network] 8 | Address=10.10.10.10/24 9 | -------------------------------------------------------------------------------- /playbooks/roles/service-net/files/service-net.conf: -------------------------------------------------------------------------------- 1 | # Listen on the service network's IP 2 | listen-address=10.10.10.10 3 | -------------------------------------------------------------------------------- /playbooks/roles/service-net/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: dnsmasq 4 | -------------------------------------------------------------------------------- /playbooks/roles/service-net/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install service0 network configuration 3 | copy: 4 | src: "{{ item }}" 5 | dest: /etc/systemd/network/ 6 | owner: root 7 | group: root 8 | mode: 0644 9 | with_items: 10 | - 10-service0.netdev 11 | - 10-service0.network 12 | 13 | - name: Enable and start systemd networking 14 | systemd: 15 | daemon_reload: yes 16 | name: systemd-networkd.service 17 | enabled: yes 18 | state: restarted 19 | 20 | - name: Install dnsmasq for service0 network 21 | copy: 22 | src: service-net.conf 23 | dest: /etc/dnsmasq.d/ 24 | owner: root 25 | group: root 26 | mode: 0644 27 | 28 | # NOTE(@nop): From wireguard/tasks/main.yml: 29 | # NOTE(@cpu): We don't use a `notify` to "Restart dnsmasq" here because it seems 30 | # that in some conditions Ansible mistakenly believes the dnsmasq restart can be 31 | # skipped. We also don't use "reloaded" instead of "restarted" here because 32 | # dnsmasq doesn't seem to reload _new_ config files in that case, just existing 33 | # ones. A full restart is required in practice (sigh) 34 | - name: "Restart DNSMasq to pick up the new configuration" 35 | service: 36 | name: dnsmasq 37 | state: restarted 38 | -------------------------------------------------------------------------------- /playbooks/roles/shadowsocks/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | shadowsocks_server_port: "8530" 3 | shadowsocks_local_port: "1080" 4 | shadowsocks_timeout: "600" 5 | shadowsocks_encryption_method: "chacha20-ietf-poly1305" 6 | shadowsocks_tcp_fast_open: "true" 7 | shadowsocks_v2ray_plugin: "v2ray-plugin" 8 | shadowsocks_v2ray_cover_domain: "github.com" 9 | shadowsocks_v2ray_plugin_options: "host={{ shadowsocks_v2ray_cover_domain }}" 10 | shadowsocks_v2ray_plugin_protocol: "http" 11 | -------------------------------------------------------------------------------- /playbooks/roles/shadowsocks/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart shadowsocks-libev 3 | systemd: 4 | name: shadowsocks-libev.service 5 | state: restarted 6 | -------------------------------------------------------------------------------- /playbooks/roles/shadowsocks/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | # Shadowsocks needs to be added to the firewall 4 | - { role: ufw } 5 | -------------------------------------------------------------------------------- /playbooks/roles/shadowsocks/tasks/docs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create the Shadowsocks Gateway directory 3 | file: 4 | path: "{{ shadowsocks_gateway_location }}" 5 | owner: www-data 6 | group: www-data 7 | mode: 0750 8 | state: directory 9 | 10 | - include_role: 11 | name: i18n-docs 12 | vars: 13 | title: "Shadowsocks" 14 | i18n_location: "{{ shadowsocks_gateway_location }}" 15 | input_template_name: "instructions" 16 | 17 | - name: Generate the Shadowsocks QR code 18 | # The ss:// URI format is documented here: 19 | # http://shadowsocks.org/en/config/quick-guide.html 20 | 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 }} 21 | -------------------------------------------------------------------------------- /playbooks/roles/shadowsocks/tasks/firewall.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure UFW allows Shadowsocks 3 | ufw: 4 | to_port: "{{ shadowsocks_server_port }}" 5 | proto: "any" 6 | rule: "allow" 7 | -------------------------------------------------------------------------------- /playbooks/roles/shadowsocks/tasks/mirror.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Include the Shadowsocks mirror variables 3 | include_vars: mirror.yml 4 | 5 | - name: Make the directory where the Shadowsocks mirrored files will be stored 6 | file: 7 | path: "{{ shadowsocks_mirror_location }}" 8 | owner: www-data 9 | group: www-data 10 | mode: 0755 11 | state: directory 12 | 13 | - block: 14 | - name: Mirror the Shadowsocks clients 15 | get_url: 16 | url: "{{ item.url }}" 17 | dest: "{{ shadowsocks_mirror_location }}" 18 | checksum: "{{ item.checksum }}" 19 | owner: www-data 20 | group: www-data 21 | mode: 0644 22 | with_items: "{{ shadowsocks_download_urls }}" 23 | rescue: 24 | - name: "{{ streisand_mirror_warning }}" 25 | pause: 26 | seconds: "{{ streisand_mirror_warning_seconds }}" 27 | 28 | - include_role: 29 | name: i18n-docs 30 | vars: 31 | title: "Shadowsocks mirror" 32 | i18n_location: "{{ shadowsocks_mirror_location }}" 33 | input_template_name: "mirror" 34 | -------------------------------------------------------------------------------- /playbooks/roles/shadowsocks/tasks/simple-obfs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Clone the simple-obfs source code at tag {{ simple_obfs_version }} 3 | git: 4 | repo: "https://github.com/shadowsocks/simple-obfs" 5 | dest: "{{ simple_obfs_compilation_directory }}" 6 | version: "v{{simple_obfs_version}}" 7 | 8 | - name: Update the simple-obfs source code submodules 9 | command: git submodule update --init --recursive 10 | args: 11 | chdir: "{{ simple_obfs_compilation_directory }}" 12 | # if one of the submodules (libcork) has been populated we assume all of 13 | # the submodules were updated. 14 | creates: "{{ simple_obfs_compilation_directory }}/libcork/Makefile.am" 15 | 16 | - name: Autogen simple-obfs {{ simple_obfs_version }} source 17 | command: ./autogen.sh 18 | args: 19 | chdir: "{{ simple_obfs_compilation_directory }}" 20 | creates: "{{ simple_obfs_compilation_directory }}/configure" 21 | 22 | - name: Configure simple-obfs {{ simple_obfs_version }} source 23 | command: ./configure {{ simple_obfs_configure_flags }} 24 | args: 25 | chdir: "{{ simple_obfs_compilation_directory }}" 26 | creates: "{{ simple_obfs_compilation_directory }}/Makefile" 27 | 28 | - name: Compile simple-obfs {{ simple_obfs_version }} source 29 | command: make -j{{ ansible_processor_cores }} 30 | args: 31 | chdir: "{{ simple_obfs_compilation_directory }}" 32 | creates: "{{ simple_obfs_compilation_directory }}/src/obfs-server" 33 | 34 | - name: Install simple-obfs {{ simple_obfs_version }} binaries 35 | command: make install 36 | args: 37 | chdir: "{{ simple_obfs_compilation_directory }}" 38 | creates: "/usr/local/bin/obfs-server" 39 | ... 40 | -------------------------------------------------------------------------------- /playbooks/roles/shadowsocks/tasks/v2ray.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add the repo for getting the latest version of Go 3 | apt_repository: 4 | repo: 'ppa:longsleep/golang-backports' 5 | register: golang_add_apt_repository 6 | until: not golang_add_apt_repository.failed 7 | retries: "{{ apt_repository_retries }}" 8 | delay: "{{ apt_repository_delay }}" 9 | 10 | - name: Install golang-go 11 | apt: 12 | package: "golang-go" 13 | 14 | # Temporary workaround for insecure v2ray.com redirection 15 | # see https://github.com/StreisandEffect/streisand/issues/1642 16 | # for more info. We clone the v2ray-core repository to avoid 17 | # the "go get" command failing with an "insecure redirect" error. 18 | - file: 19 | path: "{{ go_path }}/src/v2ray.com" 20 | state: directory 21 | 22 | - name: "[Temporary] Clone v2ray-core repository manually to GOPATH" 23 | git: 24 | repo: "{{ v2ray_core_github }}" 25 | dest: "{{ go_path }}/src/v2ray.com/core" 26 | 27 | - name: Get V2Ray-plugin 28 | shell: "go get {{ v2ray_github }}" 29 | environment: 30 | GOPATH: "{{ go_path }}" 31 | 32 | - name: Copying v2ray-plugin to shadowsocks-libev directory 33 | shell: "cp -rf {{ v2ray_location }}/v2ray-plugin {{ shadowsocks_location }}" 34 | ... 35 | -------------------------------------------------------------------------------- /playbooks/roles/shadowsocks/templates/config.json.j2: -------------------------------------------------------------------------------- 1 | { 2 | "server":"{{ ansible_default_ipv4.address }}", 3 | "server_port":{{ shadowsocks_server_port }}, 4 | "local_port":{{ shadowsocks_local_port }}, 5 | "password":"{{ shadowsocks_password.stdout }}", 6 | "timeout":{{ shadowsocks_timeout }}, 7 | "method":"{{ shadowsocks_encryption_method }}", 8 | "fast_open":{{ shadowsocks_tcp_fast_open }} 9 | {%- if streisand_shadowsocks_v2ray_enabled -%} 10 | , 11 | "plugin":"{{ shadowsocks_location }}/v2ray-plugin", 12 | "plugin_opts":"{{ v2ray_options }}" 13 | {% endif %} 14 | } 15 | -------------------------------------------------------------------------------- /playbooks/roles/shadowsocks/templates/mirror-fr.md.j2: -------------------------------------------------------------------------------- 1 | {% include "languages.md.j2" %} 2 | 3 | 4 | ### Shadowsocks ### 5 | 6 | **Linux x64** 7 | 8 | * [{{ shadowsocks_go_filename }}]({{ shadowsocks_go_href }}) 9 | * Somme de contrôle: *{{ shadowsocks_go_checksum }}* 10 | 11 | **Android** 12 | 13 | * [{{ shadowsocks_android_filename }}]({{ shadowsocks_android_href }}) 14 | * Somme de contrôle: *{{ shadowsocks_android_checksum }}* 15 | 16 | **macOS** 17 | 18 | * [{{ shadowsocks_x_ng_filename }}]({{ shadowsocks_x_ng_href }}) 19 | * Somme de contrôle: *{{ shadowsocks_x_ng_checksum }}* 20 | 21 | **Windows** 22 | 23 | * [{{ shadowsocks_gui_filename }}]({{ shadowsocks_gui_href }}) 24 | * Somme de contrôle: *{{ shadowsocks_gui_checksum }}* 25 | 26 | -------------------------------------------------------------------------------- /playbooks/roles/shadowsocks/templates/mirror.md.j2: -------------------------------------------------------------------------------- 1 | {% include "languages.md.j2" %} 2 | 3 | 4 | ### Shadowsocks ### 5 | 6 | **Linux x64** 7 | 8 | * [{{ shadowsocks_go_filename }}]({{ shadowsocks_go_href }}) 9 | * Checksum: *{{ shadowsocks_go_checksum }}* 10 | 11 | **Android** 12 | 13 | * [{{ shadowsocks_android_filename }}]({{ shadowsocks_android_href }}) 14 | * Checksum: *{{ shadowsocks_android_checksum }}* 15 | 16 | **macOS** 17 | 18 | * [{{ shadowsocks_x_ng_filename }}]({{ shadowsocks_x_ng_href }}) 19 | * Checksum: *{{ shadowsocks_x_ng_checksum }}* 20 | 21 | **Windows** 22 | 23 | * [{{ shadowsocks_gui_filename }}]({{ shadowsocks_gui_href }}) 24 | * Checksum: *{{ shadowsocks_gui_checksum }}* 25 | -------------------------------------------------------------------------------- /playbooks/roles/shadowsocks/templates/shadowsocks-libev.default.j2: -------------------------------------------------------------------------------- 1 | # Defaults for shadowsocks systemd service 2 | # See /lib/systemd/system/shadowsocks-libev.service 3 | # installed by Streisand from template 4 | 5 | # Configuration file 6 | CONFFILE="{{ shadowsocks_location }}/config.json" 7 | 8 | # Extra command line arguments 9 | DAEMON_ARGS="{{ shadowsocks_daemon_args }}" 10 | -------------------------------------------------------------------------------- /playbooks/roles/shadowsocks/templates/shadowsocks-libev.service.j2: -------------------------------------------------------------------------------- 1 | [Service] 2 | PrivateTmp=true 3 | Restart=on-failure 4 | RestartSec=5s 5 | -------------------------------------------------------------------------------- /playbooks/roles/shadowsocks/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | shadowsocks_location: "/etc/shadowsocks-libev" 3 | shadowsocks_password_file: "{{ shadowsocks_location }}/shadowsocks-password.txt" 4 | 5 | # V2ray-plugin 6 | go_path: "{{ ansible_env.HOME }}/go" 7 | v2ray_github: "github.com/shadowsocks/v2ray-plugin" 8 | v2ray_core_github: "https://github.com/v2ray/v2ray-core" 9 | v2ray_location: "{{ go_path }}/bin" 10 | v2ray_options: "server;host={{ shadowsocks_v2ray_cover_domain }}" 11 | 12 | # Add -v for verbose mode to help with debugging 13 | shadowsocks_daemon_args: "-u" 14 | 15 | shadowsocks_gateway_location: "{{ streisand_gateway_location }}/shadowsocks" 16 | 17 | shadowsocks_qr_code: "{{ shadowsocks_gateway_location }}/shadowsocks-qr-code.png" 18 | 19 | shadowsocks_systemd_service_path: "/etc/systemd/system/shadowsocks-libev.service.d" 20 | -------------------------------------------------------------------------------- /playbooks/roles/ssh-forward/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh_default_socks_port: 8080 3 | -------------------------------------------------------------------------------- /playbooks/roles/ssh-forward/tasks/docs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create the SSH Gateway directory 3 | file: 4 | path: "{{ ssh_gateway_location }}" 5 | owner: www-data 6 | group: www-data 7 | mode: 0750 8 | state: directory 9 | 10 | - include_role: 11 | name: i18n-docs 12 | vars: 13 | title: "SSH instructions" 14 | i18n_location: "{{ ssh_gateway_location }}" 15 | input_template_name: "instructions" 16 | 17 | - name: Copy the SSH private key that can be used to connect as the 'forward' user to the SSH Gateway directory 18 | command: cp {{ forward_location }}/.ssh/id_rsa {{ ssh_rsa_key }} 19 | args: 20 | creates: "{{ ssh_rsa_key }}" 21 | 22 | - name: Install the putty-tools package to facilitate converting the standard OpenSSH key into PuTTY's unique .ppk format 23 | apt: 24 | package: putty-tools 25 | 26 | - name: Convert the OpenSSH key into a PuTTY .ppk 27 | command: puttygen {{ forward_location }}/.ssh/id_rsa -o {{ ssh_putty_rsa_key }} 28 | args: 29 | creates: "{{ ssh_putty_rsa_key }}" 30 | 31 | - name: Generate a SSH known hosts file 32 | shell: 'ssh-keyscan {{ streisand_server_name }} {{ streisand_ipv4_address }} > {{ ssh_known_hosts }}' 33 | args: 34 | creates: '{{ ssh_known_hosts }}' 35 | -------------------------------------------------------------------------------- /playbooks/roles/ssh-forward/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add the SSH forwarding user and generate a key 3 | user: 4 | name: forward 5 | generate_ssh_key: yes 6 | ssh_key_bits: "{{ ssh_rsa_host_key_size }}" 7 | ssh_key_comment: streisand 8 | home: "{{ forward_location }}" 9 | shell: /bin/bash 10 | comment: "SSH Forwarding User" 11 | 12 | - name: Register the forwarding user's public SSH key 13 | command: cat {{ forward_location }}/.ssh/id_rsa.pub 14 | register: ssh_forward_key 15 | changed_when: False 16 | 17 | - name: Authorize the forward users's key for accessing the forward user 18 | authorized_key: 19 | user: forward 20 | key: 'command="{{ ssh_force_command }}" {{ ssh_forward_key.stdout }}' 21 | 22 | - name: Add the sshuttle user and generate a key 23 | user: 24 | name: sshuttle 25 | generate_ssh_key: yes 26 | ssh_key_bits: "{{ ssh_rsa_host_key_size }}" 27 | ssh_key_comment: streisand-sshuttle 28 | home: "{{ sshuttle_location }}" 29 | shell: /bin/bash 30 | comment: "sshuttle User" 31 | when: streisand_sshuttle_enabled 32 | 33 | - name: Register the sshuttle user's public SSH key 34 | command: cat {{ sshuttle_location }}/.ssh/id_rsa.pub 35 | register: sshuttle_forward_key 36 | changed_when: False 37 | when: streisand_sshuttle_enabled 38 | 39 | - name: Authorize the sshuttle users's key for accessing the sshuttle user 40 | authorized_key: 41 | user: sshuttle 42 | # NOTE(@cpu): Unlike the forward user the sshuttle user does not have 43 | # a ssh_force_comamnd in the ssh authorized keys file. This means the 44 | # sshuttle user is a *full* shell user and can SSH in. They don't have root 45 | # but this is a more significant foothold on a server than is offered by the 46 | # ssh fowrarding user. This is why by default Streisand no longer enables 47 | # sshuttle by default. 48 | key: '{{ ssh_forward_key.stdout }}' 49 | when: streisand_sshuttle_enabled 50 | 51 | # Generate the gateway documentation for the SSH forward user 52 | - import_tasks: docs.yml 53 | 54 | # Mirror Putty for SSH forwarding from Windows 55 | - import_tasks: mirror.yml 56 | -------------------------------------------------------------------------------- /playbooks/roles/ssh-forward/tasks/mirror.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Include the SSH mirror variables 3 | include_vars: mirror.yml 4 | 5 | - name: Make the directory where the SSH client mirrored files will be stored 6 | file: 7 | path: "{{ ssh_mirror_location }}" 8 | owner: www-data 9 | group: www-data 10 | mode: 0755 11 | state: directory 12 | 13 | - block: 14 | - name: Mirror shuttle if enabled 15 | get_url: 16 | url: "{{ sshuttle_url }}" 17 | dest: "{{ ssh_mirror_location }}/{{ sshuttle_filename }}" 18 | checksum: "{{ sshuttle_checksum }}" 19 | owner: www-data 20 | group: www-data 21 | mode: 0644 22 | when: streisand_sshuttle_enabled 23 | 24 | - include_role: 25 | name: download-and-verify 26 | vars: 27 | project_name: "PuTTY" 28 | project_download_baseurl: "{{ putty_base_download_url }}" 29 | project_download_files: "{{ putty_download_files }}" 30 | project_download_location: "{{ ssh_mirror_location }}" 31 | project_signer_keyid: "{{ putty_gpg_keyid }}" 32 | rescue: 33 | - name: "{{ streisand_mirror_warning }}" 34 | pause: 35 | seconds: "{{ streisand_mirror_warning_seconds }}" 36 | 37 | - include_role: 38 | name: i18n-docs 39 | vars: 40 | title: "SSH mirror" 41 | i18n_location: "{{ ssh_mirror_location }}" 42 | input_template_name: "mirror" 43 | -------------------------------------------------------------------------------- /playbooks/roles/ssh-forward/templates/mirror-fr.md.j2: -------------------------------------------------------------------------------- 1 | {% include "languages.md.j2" %} 2 | 3 | 4 | ### SSH ### 5 | 6 | **Windows** 7 | 8 | * [{{ putty_filename }}]({{ putty_href }}) ([sig]({{ putty_sig_href }})) 9 | 10 | {% if streisand_sshuttle_enabled %} 11 | **Linux and OS X** 12 | 13 | * [{{ sshuttle_filename }}]({{ sshuttle_href }}) 14 | * Checksum: *{{ sshuttle_checksum }}* 15 | {% endif %} 16 | -------------------------------------------------------------------------------- /playbooks/roles/ssh-forward/templates/mirror.md.j2: -------------------------------------------------------------------------------- 1 | {% include "languages.md.j2" %} 2 | 3 | 4 | ### SSH ### 5 | 6 | **Windows** 7 | 8 | * [{{ putty_filename }}]({{ putty_href }}) ([sig]({{ putty_sig_href }})) 9 | 10 | {% if streisand_sshuttle_enabled %} 11 | **Linux and OS X** 12 | 13 | * [{{ sshuttle_filename }}]({{ sshuttle_href }}) 14 | * Checksum: *{{ sshuttle_checksum }}* 15 | {% endif %} 16 | -------------------------------------------------------------------------------- /playbooks/roles/ssh-forward/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | forward_location: "/home/forward" 3 | ssh_force_command: "echo 'This account is for port forwarding only.'" 4 | 5 | sshuttle_location: "/home/sshuttle" 6 | 7 | ssh_gateway_location: "{{ streisand_gateway_location }}/ssh" 8 | 9 | ssh_rsa_key: "{{ ssh_gateway_location }}/streisand_rsa" 10 | ssh_putty_rsa_key: "{{ ssh_gateway_location }}/streisand_rsa.ppk" 11 | ssh_known_hosts: "{{ ssh_gateway_location }}/streisand.known_hosts" 12 | -------------------------------------------------------------------------------- /playbooks/roles/ssh-forward/vars/mirror.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # SSH Client Download variables 3 | # ----------------------------- 4 | ssh_mirror_location: "{{ streisand_mirror_location }}/ssh" 5 | ssh_mirror_href_base: "/mirror/ssh" 6 | 7 | putty_base_download_url: "https://the.earth.li/~sgtatham/putty/latest/w32" 8 | 9 | putty_filename: "putty.exe" 10 | putty_sig_filename: "{{ putty_filename }}.gpg" 11 | putty_href: "{{ ssh_mirror_href_base }}/{{ putty_filename }}" 12 | # download-and-verify.yml renames files with non-standard extensions 13 | putty_sig_href: "{{ ssh_mirror_href_base }}/{{ putty_filename }}.asc" 14 | 15 | putty_gpg_keyid: "4AE8DA82" 16 | putty_download_files: 17 | - { "file": "{{ putty_filename }}", "sig": "{{ putty_sig_filename }}" } 18 | 19 | sshuttle_version: "0.78.0" 20 | sshuttle_filename: "sshuttle-{{ sshuttle_version }}.tar.gz" 21 | sshuttle_href: "{{ ssh_mirror_href_base }}/{{ sshuttle_filename }}" 22 | sshuttle_url: "https://codeload.github.com/sshuttle/sshuttle/tar.gz/v{{ sshuttle_version }}" 23 | sshuttle_checksum: "sha256:0742e3e670c8df629ae702a32cfad96c7c4e8f7ab8f66c26d94c55d42b01e4b4" 24 | -------------------------------------------------------------------------------- /playbooks/roles/ssh/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh_port: 22 3 | -------------------------------------------------------------------------------- /playbooks/roles/ssh/files/sshd_config: -------------------------------------------------------------------------------- 1 | # Adapted from the "Modern" configuration detailed on the Mozilla Security 2 | # Guidelines wiki (https://wiki.mozilla.org/Security/Guidelines/OpenSSH). 3 | # 4 | # Three notable changes were made from that initial configuration: 5 | # 1) All logs are disabled. 6 | # 2) Root logins are allowed. This is the default way that most users 7 | # connect to a new VPS. Brute-force attacks against the root user 8 | # are mitigated because public-key authentication is required. 9 | # 3) PAM support is enabled. 10 | # 11 | # This Source Code Form is subject to the terms of the Mozilla Public 12 | # License, v. 2.0. If a copy of the MPL was not distributed with this file, 13 | # You can obtain one at http://mozilla.org/MPL/2.0/. 14 | 15 | # Supported HostKey algorithms by order of preference. 16 | HostKey /etc/ssh/ssh_host_ed25519_key 17 | HostKey /etc/ssh/ssh_host_rsa_key 18 | HostKey /etc/ssh/ssh_host_ecdsa_key 19 | 20 | KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256 21 | 22 | Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr 23 | 24 | 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 25 | 26 | # Password based logins are disabled - only public key based logins are allowed. 27 | AuthenticationMethods publickey 28 | PermitRootLogin Yes 29 | 30 | # Disable logging. 31 | LogLevel QUIET 32 | 33 | # Ensure that logging is disabled for SFTP connections as well. 34 | Subsystem sftp /usr/lib/openssh/sftp-server -f AUTHPRIV -l QUIET 35 | 36 | # Use kernel sandbox mechanisms where possible in unprivileged processes 37 | # Systrace on OpenBSD, Seccomp on Linux, seatbelt on macOS/Darwin, rlimit elsewhere. 38 | UsePrivilegeSeparation sandbox 39 | 40 | # Enable PAM. 41 | PasswordAuthentication no 42 | ChallengeResponseAuthentication no 43 | UsePAM yes 44 | -------------------------------------------------------------------------------- /playbooks/roles/ssh/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart SSH 3 | service: 4 | name: ssh 5 | state: restarted 6 | -------------------------------------------------------------------------------- /playbooks/roles/ssh/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Reconfigure OpenSSH with enhanced security settings 3 | copy: 4 | src: sshd_config 5 | dest: /etc/ssh/sshd_config 6 | owner: root 7 | group: root 8 | mode: 0644 9 | notify: Restart SSH 10 | 11 | - name: Generate a stronger RSA host key 12 | 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 }}" 13 | args: 14 | creates: "{{ ssh_rsa_host_key_change_verification }}" 15 | warn: no 16 | notify: Restart SSH 17 | 18 | - name: Ensure missing host keys are generated 19 | command: ssh-keygen -A 20 | 21 | - name: Register the server's SSH fingerprints 22 | command: ssh-keygen -lf /etc/ssh/{{ item }} 23 | register: ssh_server_fingerprints 24 | with_items: 25 | - ssh_host_ecdsa_key.pub 26 | - ssh_host_rsa_key.pub 27 | -------------------------------------------------------------------------------- /playbooks/roles/ssh/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh_rsa_host_key_size: "3072" 3 | ssh_rsa_host_key_change_verification: "/etc/ssh/ssh_host_rsa_key_was_regenerated" 4 | ssh_rsa_host_private_key: "/etc/ssh/ssh_host_rsa_key" 5 | ssh_rsa_host_public_key: "{{ ssh_rsa_host_private_key }}.pub" 6 | -------------------------------------------------------------------------------- /playbooks/roles/sslh/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart sslh 3 | systemd: 4 | name: sslh.service 5 | state: restarted 6 | -------------------------------------------------------------------------------- /playbooks/roles/sslh/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install sslh 3 | apt: 4 | package: sslh 5 | install_recommends: false 6 | 7 | - name: Generate the sslh port multiplexer systemd defaults file 8 | template: 9 | src: sslh.default.j2 10 | dest: /etc/default/sslh 11 | notify: Restart sslh 12 | 13 | - name: Generate the sslh port multiplexer config file 14 | template: 15 | src: sslh.cfg.j2 16 | dest: /etc/sslh.cfg 17 | notify: Restart sslh 18 | 19 | - name: Create the sslh systemd drop-in configuration directory 20 | file: 21 | path: "{{ sslh_systemd_service_path }}" 22 | state: directory 23 | 24 | - name: Generate the sslh systemd drop-in service file 25 | template: 26 | src: sslh.service.j2 27 | dest: "{{ sslh_systemd_service_path }}/10-restart-failure.conf" 28 | mode: 0644 29 | 30 | - name: Enable the sslh service 31 | systemd: 32 | daemon_reload: yes 33 | name: sslh.service 34 | enabled: yes 35 | state: restarted 36 | -------------------------------------------------------------------------------- /playbooks/roles/sslh/templates/sslh.cfg.j2: -------------------------------------------------------------------------------- 1 | verbose: false; 2 | foreground: true; 3 | timeout: 5; 4 | user: "sslh"; 5 | pidfile: "{{ sslh_pid_file }}"; 6 | 7 | listen: 8 | ( 9 | { host: "{{ ansible_default_ipv4.address }}"; port: "443"; } 10 | ); 11 | 12 | protocols: 13 | ( 14 | { name: "ssh"; host: "127.0.0.1"; port: "{{ ssh_port }}"; probe: "builtin"; fork: true; }, 15 | { name: "tls"; host: "127.0.0.1"; port: "{{ nginx_port}}"; probe: "builtin"; }, 16 | {% if streisand_openvpn_enabled %} 17 | { name: "openvpn"; host: "127.0.0.1"; port: "{{ openvpn_port }}"; probe: "builtin"; }, 18 | {% endif %} 19 | { name: "anyprot"; host: "127.0.0.1"; port: "{{ nginx_port }}"; probe: "builtin"; } 20 | ); 21 | 22 | on-timeout: "tls"; 23 | -------------------------------------------------------------------------------- /playbooks/roles/sslh/templates/sslh.default.j2: -------------------------------------------------------------------------------- 1 | # Default options for sslh initscript 2 | # sourced by /etc/init.d/sslh 3 | 4 | RUN=yes 5 | 6 | DAEMON=/usr/sbin/sslh 7 | 8 | DAEMON_OPTS="-F /etc/sslh.cfg" 9 | -------------------------------------------------------------------------------- /playbooks/roles/sslh/templates/sslh.service.j2: -------------------------------------------------------------------------------- 1 | [Service] 2 | PrivateTmp=true 3 | StandardOutput=null 4 | RestartSec=5s 5 | Restart=on-failure 6 | PIDFile={{ sslh_pid_file }} 7 | -------------------------------------------------------------------------------- /playbooks/roles/sslh/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sslh_pid_file: "/var/run/sslh/sslh.pid" 3 | sslh_systemd_service_path: "/etc/systemd/system/sslh.service.d" 4 | -------------------------------------------------------------------------------- /playbooks/roles/streisand-gateway/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | nginx_key_country: "US" 3 | nginx_key_province: "California" 4 | nginx_key_city: "Malibu" 5 | nginx_key_org: "Streisand" 6 | nginx_key_ou: "Streisand Effect Department" 7 | 8 | nginx_port: "443" 9 | 10 | streisand_gateway_username: "streisand" 11 | streisand_gateway_rsa_key_size: "3072" 12 | -------------------------------------------------------------------------------- /playbooks/roles/streisand-gateway/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart Nginx for the Gateway vhost 3 | service: 4 | name: nginx 5 | state: restarted 6 | -------------------------------------------------------------------------------- /playbooks/roles/streisand-gateway/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - { role: nginx } 4 | -------------------------------------------------------------------------------- /playbooks/roles/streisand-gateway/tasks/docs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_role: 3 | name: i18n-docs 4 | vars: 5 | title: "Gateway index" 6 | i18n_location: "{{ streisand_gateway_location }}" 7 | input_template_name: "index" 8 | 9 | - name: Ensure the Streisand temporary gateway directory exists 10 | file: 11 | path: "{{ streisand_temp_gateway_path }}" 12 | owner: root 13 | group: root 14 | mode: 0600 15 | state: directory 16 | 17 | - include_role: 18 | name: i18n-docs 19 | vars: 20 | title: "Gateway {{ doc.template }}" 21 | i18n_location: "{{ streisand_temp_gateway_path }}" 22 | input_template_name: "{{ doc.template }}" 23 | output_file_name: "{{ doc.output_file }}" 24 | with_items: 25 | - { template: "instructions", output_file: "{{ streisand_server_name }}" } 26 | - { template: "firewall", output_file: "{{ streisand_server_name }}-firewall-information" } 27 | loop_control: 28 | loop_var: "doc" 29 | label: "{{ doc.template }}" 30 | -------------------------------------------------------------------------------- /playbooks/roles/streisand-gateway/tasks/fetch-and-cleanup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Find the files to fetch from the Streisand temporary gateway directory 3 | find: 4 | paths: "{{ streisand_temp_gateway_path }}" 5 | recurse: no 6 | pattern: "*.html" 7 | register: gateway_local_files 8 | 9 | - name: Fetch the local Gateway files 10 | fetch: 11 | dest: ../{{ streisand_local_directory }}/ 12 | src: "{{ file.path }}" 13 | flat: yes 14 | with_items: "{{ gateway_local_files.files }}" 15 | loop_control: 16 | loop_var: file 17 | label: "{{ file.path }}" 18 | 19 | - name: Delete the Streisand temporary gateway directory 20 | file: 21 | path: "{{ streisand_temp_gateway_path }}" 22 | state: "absent" 23 | -------------------------------------------------------------------------------- /playbooks/roles/streisand-gateway/templates/firewall-fr.md.j2: -------------------------------------------------------------------------------- 1 | {% include "languages.md.j2" %} 2 | 3 | Informations à propos du pare-feu 4 | --------------------------------- 5 | 6 | Ces ports sont ouverts par défaut lorsque Streisand crée un nouveau serveur à partir de zéro sur un fournisseur soutenu. 7 | 8 | Si vous installez Streisand sur un serveur existant ou un fournisseur alternatif, vous devez vous assurer que le pare-feu autorise les ports suivants: 9 | 10 | --- 11 | * Nginx (Passerelle Streisand) 12 | * TCP - {{ nginx_port }} 13 | {% if streisand_le_enabled %} 14 | * HTTP (Let's Encrypt) 15 | * TCP - {{ le_port }} 16 | {% endif %} 17 | {% if streisand_openconnect_enabled %} 18 | * OpenConnect / Cisco AnyConnect 19 | * TCP - {{ ocserv_port }} 20 | * UDP - {{ ocserv_port }} 21 | {% endif %} 22 | * OpenSSH 23 | * TCP - {{ ssh_port }} 24 | {% if streisand_openvpn_enabled %} 25 | * OpenVPN 26 | * UDP - 53 de/à `{{ dnsmasq_openvpn_tcp_ip }}` 27 | * UDP - 53 de/à `{{ dnsmasq_openvpn_udp_ip }}` 28 | * Dnsmasq écoute le trafic DNS sur ces adresses IP et répond aux requêtes des clients OpenVPN connectés. 29 | * TCP - {{ openvpn_port }} 30 | * UDP - {{ openvpn_port_udp }} 31 | {% if streisand_stunnel_enabled %} 32 | * stunnel 33 | * TCP - {{ stunnel_remote_port }} 34 | {% endif %} 35 | {% endif %} 36 | {% if streisand_shadowsocks_enabled %} 37 | * Shadowsocks 38 | * TCP - {{ shadowsocks_server_port }} 39 | * UDP - {{ shadowsocks_server_port }} 40 | {% endif %} 41 | {% if streisand_tor_enabled %} 42 | * Tor 43 | * TCP - {{ tor_orport }} - Pont 44 | * TCP - {{ tor_obfs4_port }} - obfs4 transport enfichable 45 | {% endif %} 46 | {% if streisand_wireguard_enabled %} 47 | * WireGuard 48 | * UDP - 53 de/à `{{ dnsmasq_wireguard_ip }}` 49 | * Dnsmasq écoute le trafic DNS sur cette IP et répond aux requêtes des clients WireGuard connectés. 50 | * UDP - {{ wireguard_port }} 51 | {% endif %} 52 | -------------------------------------------------------------------------------- /playbooks/roles/streisand-gateway/templates/firewall.md.j2: -------------------------------------------------------------------------------- 1 | {% include "languages.md.j2" %} 2 | 3 | Firewall Information 4 | -------------------- 5 | 6 | These ports are open by default when Streisand creates a new server from scratch on a supported provider. 7 | 8 | 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: 9 | 10 | --- 11 | * Nginx (Streisand Gateway) 12 | * TCP - {{ nginx_port }} 13 | {% if streisand_le_enabled %} 14 | * HTTP (Let's Encrypt) 15 | * TCP - {{ le_port }} 16 | {% endif %} 17 | {% if streisand_openconnect_enabled %} 18 | * OpenConnect / Cisco AnyConnect 19 | * TCP - {{ ocserv_port }} 20 | * UDP - {{ ocserv_port }} 21 | {% endif %} 22 | * OpenSSH 23 | * TCP - {{ ssh_port }} 24 | {% if streisand_openvpn_enabled %} 25 | * OpenVPN 26 | * UDP - 53 to/from `{{ dnsmasq_openvpn_tcp_ip }}` 27 | * UDP - 53 to/from `{{ dnsmasq_openvpn_udp_ip }}` 28 | * Dnsmasq listens for DNS traffic on these IPs and responds to requests from connected OpenVPN clients. 29 | * TCP - {{ openvpn_port }} 30 | * UDP - {{ openvpn_port_udp }} 31 | {% if streisand_stunnel_enabled %} 32 | * stunnel 33 | * TCP - {{ stunnel_remote_port }} 34 | {% endif %} 35 | {% endif %} 36 | {% if streisand_shadowsocks_enabled %} 37 | * Shadowsocks 38 | * TCP - {{ shadowsocks_server_port }} 39 | * UDP - {{ shadowsocks_server_port }} 40 | {% endif %} 41 | {% if streisand_tor_enabled %} 42 | * Tor 43 | * TCP - {{ tor_orport }} - Bridge 44 | * TCP - {{ tor_obfs4_port }} - obfs4 pluggable transport 45 | {% endif %} 46 | {% if streisand_wireguard_enabled %} 47 | * WireGuard 48 | * UDP - 53 to/from `{{ dnsmasq_wireguard_ip }}` 49 | * Dnsmasq listens for DNS traffic on this IP and responds to requests from connected WireGuard clients. 50 | * UDP - {{ wireguard_port }} 51 | {% endif %} 52 | -------------------------------------------------------------------------------- /playbooks/roles/streisand-gateway/templates/index.md.j2: -------------------------------------------------------------------------------- 1 | {% include "languages.md.j2" %} 2 | 3 | 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. 4 | 5 | Connection Instructions 6 | ----------------------- 7 | --- 8 | 9 | There are multiple ways to bypass censorship, and Streisand provides several choices and different protocols in the event that any of them are restricted. 10 | 11 | {% if streisand_openconnect_enabled %} 12 | * [OpenConnect / Cisco AnyConnect](/openconnect/) 13 | {% endif %} 14 | {% if streisand_openvpn_enabled %} 15 | * [OpenVPN (direct)](/openvpn/) 16 | {% if streisand_stunnel_enabled %} 17 | * [OpenVPN (stunnel)](/openvpn/stunnel.html) 18 | {% endif %} 19 | {% endif %} 20 | {% if streisand_shadowsocks_enabled %} 21 | * [Shadowsocks](/shadowsocks/) 22 | {% endif %} 23 | {% if streisand_ssh_forward_enabled %} 24 | * [SSH](/ssh/) 25 | {% endif %} 26 | {% if streisand_tor_enabled %} 27 | * [Tor](/tor/) 28 | {% endif %} 29 | {% if streisand_wireguard_enabled %} 30 | * [WireGuard](/wireguard/) 31 | {% endif %} 32 | 33 | {% if streisand_mirrored_clients %} 34 | Client Mirrors 35 | -------------- 36 | --- 37 | 38 | 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. 39 | 40 | {% if streisand_openconnect_enabled %} 41 | * [OpenConnect](/mirror/openconnect/) 42 | {% endif %} 43 | {% if streisand_openvpn_enabled %} 44 | * [OpenVPN](/mirror/openvpn/) 45 | {% if streisand_stunnel_enabled %} 46 | * [stunnel](/mirror/stunnel/) 47 | {% endif %} 48 | {% endif %} 49 | {% if streisand_shadowsocks_enabled %} 50 | * [Shadowsocks](/mirror/shadowsocks/) 51 | {% endif %} 52 | {% if streisand_ssh_forward_enabled %} 53 | * [SSH](/mirror/ssh/) 54 | {% endif %} 55 | {% if streisand_tor_enabled %} 56 | * [Tor Browser Bundle](/mirror/tor/) 57 | {% endif %} 58 | 59 | {% endif %} 60 | -------------------------------------------------------------------------------- /playbooks/roles/streisand-gateway/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | streisand_gateway_url: "https://{{ streisand_ipv4_address }}" 3 | 4 | streisand_gateway_htpasswd_file: "/etc/nginx/htpasswd" 5 | streisand_gateway_password_file: "/etc/nginx/gateway-password.txt" 6 | streisand_gateway_password_localpath: "../{{ streisand_local_directory }}/gateway-password.txt" 7 | streisand_temp_gateway_path: "/var/local/streisand.gateway" 8 | 9 | openssl_ca_base: "/root/ca" 10 | openssl_ca_private_dir: "{{ openssl_ca_base }}/private" 11 | openssl_ca_newcerts_dir: "{{ openssl_ca_base }}/newcerts" 12 | openssl_ca_key: "{{ openssl_ca_private_dir }}/cakey.pem" 13 | openssl_ca_certificate: "{{ openssl_ca_base }}/cacert.pem" 14 | openssl_ca_default_md: "sha256" 15 | 16 | nginx_days_valid: "1825" 17 | 18 | nginx_self_signed_certificate: "/etc/ssl/certs/{{ streisand_server_name }}.crt" 19 | nginx_self_signed_certificate_request: "/etc/ssl/certs/{{ streisand_server_name }}.csr" 20 | nginx_private_key: "/etc/ssl/private/{{ streisand_server_name }}.key" 21 | -------------------------------------------------------------------------------- /playbooks/roles/streisand-mirror/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Determine if there is a need to mirror client software 3 | # NOTE(@cpu): If any additional roles start to mirror client software this 4 | # conditional fact value should be updated to include the role's enabled var. 5 | set_fact: 6 | streisand_mirrored_clients: > 7 | streisand_openconnect_enabled or 8 | streisand_openvpn_enabled or 9 | streisand_shadowsocks_enabled or 10 | streisand_ssh_forward_enabled or 11 | streisand_stunnel_enabled or 12 | streisand_tor_enabled 13 | 14 | - name: Make the directory where mirrored files will be stored 15 | file: 16 | path: "{{ streisand_mirror_location }}" 17 | owner: www-data 18 | group: www-data 19 | mode: 0755 20 | state: directory 21 | 22 | - include_role: 23 | name: i18n-docs 24 | vars: 25 | title: "Streisand mirror" 26 | i18n_location: "{{ streisand_mirror_location }}" 27 | input_template_name: "mirror-index" 28 | # Only generate the Markdown mirror page when there is a role enabled that 29 | # mirrors client software 30 | when: streisand_mirrored_clients 31 | -------------------------------------------------------------------------------- /playbooks/roles/streisand-mirror/templates/mirror-index-fr.md.j2: -------------------------------------------------------------------------------- 1 | Miroirs des clients 2 | ------------------- 3 | 4 | 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. 5 | 6 | --- 7 | * Les clients 8 | {% if streisand_openconnect_enabled %} 9 | * [OpenConnect](openconnect/index-fr.html) 10 | {% endif %} 11 | {% if streisand_openvpn_enabled %} 12 | * [OpenVPN](openvpn/index-fr.html) 13 | {% if streisand_stunnel_enabled %} 14 | * [Stunnel](stunnel/index-fr.html) 15 | {% endif %} 16 | {% endif %} 17 | {% if streisand_shadowsocks_enabled %} 18 | * [Shadowsocks](shadowsocks/index-fr.html) 19 | {% endif %} 20 | {% if streisand_ssh_forward_enabled %} 21 | * [OpenSSH](ssh/index-fr.html) 22 | {% endif %} 23 | {% if streisand_tor_enabled %} 24 | * [Tor Browser Bundle](tor/index-fr.html) 25 | {% endif %} 26 | -------------------------------------------------------------------------------- /playbooks/roles/streisand-mirror/templates/mirror-index.md.j2: -------------------------------------------------------------------------------- 1 | Client Mirrors 2 | -------------- 3 | 4 | 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. 5 | 6 | --- 7 | * Clients 8 | {% if streisand_openconnect_enabled %} 9 | * [OpenConnect](openconnect/) 10 | {% endif %} 11 | {% if streisand_openvpn_enabled %} 12 | * [OpenVPN](openvpn/) 13 | {% if streisand_stunnel_enabled %} 14 | * [Stunnel](stunnel/) 15 | {% endif %} 16 | {% endif %} 17 | {% if streisand_shadowsocks_enabled %} 18 | * [Shadowsocks](shadowsocks/) 19 | {% endif %} 20 | {% if streisand_ssh_forward_enabled %} 21 | * [OpenSSH](ssh/) 22 | {% endif %} 23 | {% if streisand_tor_enabled %} 24 | * [Tor Browser Bundle](tor/) 25 | {% endif %} 26 | -------------------------------------------------------------------------------- /playbooks/roles/streisand-mirror/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | streisand_mirror_location: "{{ streisand_gateway_location }}/mirror" 3 | 4 | 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." 5 | streisand_mirror_warning_seconds: "20" 6 | -------------------------------------------------------------------------------- /playbooks/roles/stunnel/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | stunnel_cert_country: "GB" 3 | stunnel_cert_province: "Berkshire" 4 | stunnel_cert_city: "Slough" 5 | stunnel_cert_org: "Wernham Hogg Paper Company" 6 | stunnel_cert_ou: "Gareth Keenan Investigations" 7 | 8 | stunnel_key_size: "3072" 9 | -------------------------------------------------------------------------------- /playbooks/roles/stunnel/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart stunnel 3 | systemd: 4 | name: stunnel.service 5 | state: restarted 6 | -------------------------------------------------------------------------------- /playbooks/roles/stunnel/tasks/firewall.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure UFW allows stunnel 3 | ufw: 4 | to_port: "{{ stunnel_remote_port }}" 5 | proto: "tcp" 6 | rule: "allow" 7 | -------------------------------------------------------------------------------- /playbooks/roles/stunnel/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install stunnel 3 | apt: 4 | package: stunnel4 5 | 6 | - name: Generate the stunnel private key 7 | command: openssl genrsa -out {{ stunnel_key }} {{ stunnel_key_size }} 8 | args: 9 | creates: "{{ stunnel_key }}" 10 | 11 | - name: Generate the stunnel certificate. 12 | shell: openssl req -new -nodes -x509 -key {{ stunnel_key}} -days {{ stunnel_days_valid }} -subj "{{ stunnel_request_subject }}/CN=stunnel" > {{ stunnel_cert }} 13 | args: 14 | creates: "{{ stunnel_cert }}" 15 | 16 | - name: "Export the key and certificate file in PKCS #12 format" 17 | command: "openssl pkcs12 -export -in {{ stunnel_cert }} -inkey {{ stunnel_key }} -out {{ stunnel_pkcs12 }} -password pass:" 18 | args: 19 | creates: "{{ stunnel_pkcs12 }}" 20 | 21 | - name: Set the proper permissions on the stunnel key file 22 | file: 23 | path: "{{ stunnel_key }}" 24 | owner: root 25 | group: root 26 | mode: 0600 27 | 28 | - name: Generate remote stunnel configuration file (for the server) 29 | template: 30 | src: stunnel-remote.conf.j2 31 | dest: "{{ stunnel_path }}/stunnel.conf" 32 | notify: Restart stunnel 33 | 34 | - name: Generate local stunnel configuration file (for connecting clients) 35 | template: 36 | src: stunnel-local.conf.j2 37 | dest: "{{ openvpn_gateway_location }}/stunnel.conf" 38 | 39 | - name: Stop (init.d's) stunnel4 40 | systemd: 41 | name: stunnel4.service 42 | state: stopped 43 | masked: yes 44 | 45 | - name: Copy the stunnel system unit file 46 | template: 47 | src: stunnel.service.j2 48 | dest: /etc/systemd/system/stunnel.service 49 | mode: "0644" 50 | 51 | - name: Enable the stunnel service 52 | systemd: 53 | daemon_reload: yes 54 | name: stunnel.service 55 | enabled: yes 56 | state: restarted 57 | 58 | # Set up the stunnel firewall rules 59 | - import_tasks: firewall.yml 60 | 61 | # Mirror the stunnel client 62 | - import_tasks: mirror.yml 63 | -------------------------------------------------------------------------------- /playbooks/roles/stunnel/tasks/mirror.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Include the stunnel mirror variables 3 | include_vars: mirror.yml 4 | 5 | - name: Make the directory where the stunnel mirrored files will be stored 6 | file: 7 | path: "{{ stunnel_mirror_location }}" 8 | owner: www-data 9 | group: www-data 10 | mode: 0755 11 | state: directory 12 | 13 | - block: 14 | - include_role: 15 | name: download-and-verify 16 | vars: 17 | project_name: "stunnel" 18 | project_signer: "Michal Trojnara" 19 | project_download_baseurl: "{{ stunnel_base_download_url }}" 20 | project_download_files: "{{ stunnel_download_files }}" 21 | project_download_location: "{{ stunnel_mirror_location }}" 22 | project_signer_keyid: "{{ stunnel_gpg_keyid }}" 23 | 24 | rescue: 25 | - name: "{{ streisand_mirror_warning }}" 26 | pause: 27 | seconds: "{{ streisand_mirror_warning_seconds }}" 28 | 29 | - name: Get the current stunnel version from the downloaded source file 30 | shell: ls *.tar.gz | tail -n 1 | sed -e 's/stunnel-\(.*\)\.tar\.gz/\1/' 31 | args: 32 | chdir: "{{ stunnel_mirror_location }}" 33 | register: stunnel_latest_check 34 | changed_when: False 35 | 36 | - name: Set the target stunnel version 37 | set_fact: 38 | stunnel_version: "{{ stunnel_latest_check.stdout }}" 39 | 40 | - include_role: 41 | name: i18n-docs 42 | vars: 43 | title: "stunnel mirror" 44 | i18n_location: "{{ stunnel_mirror_location }}" 45 | input_template_name: "mirror" 46 | -------------------------------------------------------------------------------- /playbooks/roles/stunnel/templates/mirror-fr.md.j2: -------------------------------------------------------------------------------- 1 | {% include "languages.md.j2" %} 2 | 3 | 4 | ### stunnel ### 5 | 6 | **Source** 7 | 8 | * [{{ stunnel_source_filename }}]({{ stunnel_source_href }}) ([sig]({{ stunnel_source_sig_href }})) 9 | 10 | **Windows** 11 | 12 | * [{{ stunnel_installer_filename }}]({{ stunnel_installer_href }}) ([sig]({{ stunnel_installer_sig_href }})) 13 | -------------------------------------------------------------------------------- /playbooks/roles/stunnel/templates/mirror.md.j2: -------------------------------------------------------------------------------- 1 | {% include "languages.md.j2" %} 2 | 3 | 4 | ### stunnel ### 5 | 6 | **Source** 7 | 8 | * [{{ stunnel_source_filename }}]({{ stunnel_source_href }}) ([sig]({{ stunnel_source_sig_href }})) 9 | 10 | **Windows** 11 | 12 | * [{{ stunnel_installer_filename }}]({{ stunnel_installer_href }}) ([sig]({{ stunnel_installer_sig_href }})) 13 | -------------------------------------------------------------------------------- /playbooks/roles/stunnel/templates/stunnel-local.conf.j2: -------------------------------------------------------------------------------- 1 | client = yes 2 | 3 | [stunnel] 4 | accept = 127.0.0.1:{{ stunnel_local_port }} 5 | connect = {{ streisand_ipv4_address }}:{{ stunnel_remote_port }} 6 | -------------------------------------------------------------------------------- /playbooks/roles/stunnel/templates/stunnel-remote.conf.j2: -------------------------------------------------------------------------------- 1 | cert = {{ stunnel_cert }} 2 | key = {{ stunnel_key }} 3 | debug = 4 4 | options = NO_SSLv2 5 | options = NO_SSLv3 6 | options = NO_TLSv1 7 | options = NO_TLSv1.1 8 | ciphers = {{ streisand_tls_ciphers }} 9 | 10 | [stunnel] 11 | accept = {{ stunnel_remote_port }} 12 | connect = 127.0.0.1:{{ openvpn_port }} 13 | -------------------------------------------------------------------------------- /playbooks/roles/stunnel/templates/stunnel.service.j2: -------------------------------------------------------------------------------- 1 | # source https://github.com/liuliang/centos-stunnel-systemd/blob/master/stunnel.service 2 | [Unit] 3 | Description=SSL tunnel for network daemons 4 | After=openvpn@server.target 5 | 6 | [Install] 7 | WantedBy=multi-user.target 8 | Alias=stunnel.target 9 | 10 | [Service] 11 | Type=forking 12 | ExecStart=/usr/bin/stunnel {{ stunnel_path }}/stunnel.conf 13 | ExecStop=/usr/bin/killall -9 stunnel 14 | 15 | # Give up if ping doesn't get an answer within the timeout 16 | TimeoutSec=600 17 | 18 | Restart=always 19 | PrivateTmp=true 20 | -------------------------------------------------------------------------------- /playbooks/roles/stunnel/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | stunnel_days_valid: "1825" 3 | stunnel_request_subject: "/C={{ stunnel_cert_country }}/ST={{ stunnel_cert_province }}/L={{ stunnel_cert_city }}/O={{ stunnel_cert_org }}/OU={{ stunnel_cert_ou }}" 4 | stunnel_path: "/etc/stunnel" 5 | stunnel_cert: "{{ stunnel_path }}/stunnel.crt" 6 | stunnel_key: "{{ stunnel_path }}/stunnel.key" 7 | stunnel_pkcs12: "{{ openvpn_gateway_location }}/stunnel.p12" 8 | -------------------------------------------------------------------------------- /playbooks/roles/stunnel/vars/mirror.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Stunnel Download variables 3 | # -------------------------- 4 | stunnel_mirror_location: "{{ streisand_mirror_location }}/stunnel" 5 | stunnel_mirror_href_base: "/mirror/stunnel" 6 | 7 | stunnel_base_download_url: "https://www.stunnel.org" 8 | 9 | stunnel_installer_url: "stunnel-latest-installer.exe" 10 | stunnel_installer_sig_url: "stunnel-latest-installer.exe.asc" 11 | 12 | stunnel_installer_filename: "{{ stunnel_installer_url }}" 13 | stunnel_installer_sig_filename: "{{ stunnel_installer_filename }}.asc" 14 | stunnel_installer_href: "{{ stunnel_mirror_href_base }}/{{ stunnel_installer_filename }}" 15 | stunnel_installer_sig_href: "{{ stunnel_mirror_href_base }}/{{ stunnel_installer_sig_filename }}" 16 | 17 | stunnel_source_filename: "stunnel-{{ stunnel_version }}.tar.gz" 18 | stunnel_source_sig_filename: "{{ stunnel_source_filename }}.asc" 19 | stunnel_source_href: "{{ stunnel_mirror_href_base }}/{{ stunnel_source_filename }}" 20 | stunnel_source_sig_href: "{{ stunnel_mirror_href_base }}/{{ stunnel_source_sig_filename }}" 21 | 22 | stunnel_source_url: "stunnel-latest.tar.gz" 23 | stunnel_source_sig_url: "stunnel-latest.tar.gz.asc" 24 | 25 | stunnel_gpg_keyid: "D416E014" 26 | 27 | stunnel_download_files: 28 | - { "file": "{{ stunnel_installer_url }}", "sig": "{{ stunnel_installer_sig_url }}" } 29 | - { "file": "{{ stunnel_source_url }}", "sig": "{{ stunnel_source_sig_url }}" } 30 | -------------------------------------------------------------------------------- /playbooks/roles/sysctl/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - block: 3 | - name: Apply custom sysctl values 4 | sysctl: 5 | name: "{{ item.key }}" 6 | value: "{{ item.value }}" 7 | ignoreerrors: yes 8 | sysctl_set: yes 9 | reload: yes 10 | state: present 11 | with_items: "{{ sysctl_values }}" 12 | when: ansible_virtualization_type != 'lxc' 13 | -------------------------------------------------------------------------------- /playbooks/roles/sysctl/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sysctl_values: 3 | - { key: kernel.sysrq, value: 0 } 4 | - { key: kernel.core_uses_pid, value: 1 } 5 | - { key: net.ipv4.tcp_syncookies, value: 1 } 6 | - { key: kernel.msgmnb, value: 65536 } 7 | - { key: kernel.msgmax, value: 65536 } 8 | - { key: kernel.shmmax, value: 68719476736 } 9 | - { key: kernel.shmall, value: 4294967296 } 10 | - { key: net.ipv4.conf.all.accept_source_route, value: 0 } 11 | - { key: net.ipv4.conf.default.accept_source_route, value: 0 } 12 | - { key: net.ipv4.conf.all.log_martians, value: 1 } 13 | - { key: net.ipv4.conf.default.log_martians, value: 1 } 14 | - { key: net.ipv4.conf.all.accept_redirects, value: 0 } 15 | - { key: net.ipv4.conf.default.accept_redirects, value: 0 } 16 | - { key: net.ipv4.conf.all.send_redirects, value: 0 } 17 | - { key: net.ipv4.conf.default.send_redirects, value: 0 } 18 | - { key: net.ipv4.conf.all.rp_filter, value: 0 } 19 | - { key: net.ipv4.conf.default.rp_filter, value: 0 } 20 | - { key: net.ipv4.icmp_echo_ignore_broadcasts, value: 1 } 21 | - { key: net.ipv4.icmp_ignore_bogus_error_responses, value: 1 } 22 | - { key: net.ipv4.conf.all.secure_redirects, value: 0 } 23 | - { key: net.ipv4.conf.default.secure_redirects, value: 0 } 24 | - { key: kernel.randomize_va_space, value: 1 } 25 | - { key: net.core.wmem_max, value: 12582912 } 26 | - { key: net.core.rmem_max, value: 12582912 } 27 | - { key: fs.suid_dumpable, value: 0 } 28 | - { key: fs.protected_hardlinks, value: 1 } 29 | - { key: fs.protected_symlinks, value: 1 } 30 | -------------------------------------------------------------------------------- /playbooks/roles/test-client/files/openvpn_signing.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQENBE45PsIBCAC2K2LRZPQIUmJlCDKcncfR6vok2wowDpGpHZffvEEoUj/DoocR 4 | LLpPHR5RB1zMWIs2IjF8vOtXMCBguDgtEvQTh6p6DM3D1fTnYp3pPlQyyzAuC81v 5 | CQo44h09R4Nh2e38oMRVztmAnacC4g5aiSEamrZ4PbWdAdPc4uZdCPOGmUDJw8+q 6 | aAYvL/8pM7YqEu05FqE+aNcG02K+mDhA2bqRLLKoLEFpeMSO6vV8BrE7Vw1Rs1PM 7 | VLDJt9HdXmC6vP+WWqDuj7/qfRb2wwlSIp5+aFyRHOUNyFKnWZYIObeV3+Y6oG6h 8 | gmBtU1673mHDqVy26TwfjpJeudMKHVCrKXVXABEBAAG0QVNhbXVsaSBTZXBww6Ru 9 | ZW4gKE9wZW5WUE4gVGVjaG5vbG9naWVzLCBJbmMpIDxzYW11bGlAb3BlbnZwbi5u 10 | ZXQ+iQFVBBMBAgA/AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgBYhBDDr9Oc8 11 | zmPu4STdJ45tqLThWMVpBQJZeJ2tBQkQ4vlrAAoJEI5tqLThWMVpPDUH/RdLsdG/ 12 | 4kmal/rfbso3YVxZXGp2fHKrptvCVrUWluYs6H/XBV4x6aMe8Q6K7Qa7BSLA9jZ8 13 | v+UN/4aA+urBcs6Ted/XbP3mKU47tOotW24nA1LRjd4gUSEXCaEOBbCSyw3uw6Vz 14 | U1wr1gEmkC7kvBziL+Pcbt5tKTRhUfgbcjYNNdp/nAwn3Pm3OFRaBt/qDU2aYAOH 15 | +k191x/ovDRO/UiU2CVvrdfv/VMZfo/rwxe8IiirxQ4k5DR2Vyu0DMNzlNTqRk8l 16 | rUH0FBdl0rOiefH0m6ubKstpYCaOUYsh/FaW53O6qqrTlZqPtAav1cRog8zb8mhT 17 | sFFAarhnZcQ/DG+5AQ0ETjk+wgEIAOg+Bjk8Wnb7fbbwBDDUalLsIEgFUhsrSLD5 18 | VVYB8tOq7djshckp/3LwfkSsmUzEtXMXxIbDUON1vbCQXZlQDe7E7uY5KFNWyi4+ 19 | UJwLMrs+oqfeduUzDxQ+voq/6NGl+2olqd6vT/c/uPb/RPZpOdgoEkqFEOTMRVz0 20 | DZwAyzyYEWBrwDECNbEtqefMLPIaUGUzZvUc80I+MYL6AzRe/utIWcBnZ2nydZ0S 21 | vWKRJ0lOs69e6KoFVeE5QXzmTXkjzSbR9eN3ADm2j0EjLnpt/zR4hF8s4l4HLdRd 22 | Sn47tAdvahsNfgWmOfiQD8btnu8DiMiJMd8IpVsZX/zCJbSUChcAEQEAAYkBPAQY 23 | AQIAJgIbDBYhBDDr9Oc8zmPu4STdJ45tqLThWMVpBQJZeJ2DBQkQ4vlBAAoJEI5t 24 | qLThWMVpCCIH+QFqEY+Xk5gJc10lbJUZEhJIknS/3GEd+3WBHgBtBaQCeK7+bFQP 25 | ZagTN4SJLiwYcQDV04mZTpFOJV1k9AYaz7ENEjHe51mGhPM9sm5Ix7KwMNo0lHJ+ 26 | ryZ0zyie28IbGz+rYa7OdkhE2EmcQkezYNWC03G8yR9yGk3QZ3CtPPO/xYP2tBGc 27 | OocqWUkVuR7KpitT9QnOZ4af26b83Vr/+qJ1FdSfW6/VAbyboVWya4oEnKSUusBm 28 | 0WCQzaLH15EpzgcdB/x8KVOTS1dAA5GNyRyhbRfP6yBXgBruCkPa4/np78/72jjW 29 | vbAvOhOEMnfzWmf3VZq+q6hhIJf6Sp+dcoU= 30 | =P3ax 31 | -----END PGP PUBLIC KEY BLOCK----- 32 | -------------------------------------------------------------------------------- /playbooks/roles/test-client/tasks/openvpn-profileget.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Remove existing {{ openvpn_profile_type }} OpenVPN profiles if they exist" 3 | file: 4 | path: "{{ openvpn_profile_dir}}/{{ profile_name }}-{{ openvpn_profile_type}}*.ovpn" 5 | state: absent 6 | with_items: "{{ test_client_profiles.stdout_lines }}" 7 | loop_control: 8 | loop_var: "profile_name" 9 | 10 | - name: "Download each of the {{ openvpn_profile_type }} OpenVPN profiles" 11 | get_url: 12 | url: "{{ gateway_test_url }}/openvpn/{{ profile_name }}/{{ streisand_ip }}-{{ openvpn_profile_type }}.ovpn" 13 | dest: "{{ openvpn_profile_dir }}/{{ profile_name }}-{{ openvpn_profile_type }}.ovpn" 14 | force_basic_auth: yes 15 | url_username: "{{ gateway_test_user }}" 16 | url_password: "{{ lookup('file', '{{ streisand_gateway_password_localpath }}') }}" 17 | validate_certs: no 18 | with_items: "{{ test_client_profiles.stdout_lines }}" 19 | loop_control: 20 | loop_var: "profile_name" 21 | 22 | # NOTE(cpu): This is a little bit messy but there aren't really any good options 23 | # to add a few lines to the Streisand OVPN that we need - mainly the DHCP 24 | # up/down settings described in the Streisand docs for Linux clients without 25 | # NetworkManager and the PID tracking command we use to kill OpenVPN later 26 | - name: "Concatinate the profile customization commands to each OpenVPN {{ openvpn_profile_type }} client profile if required" 27 | 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" 28 | with_items: "{{ test_client_profiles.stdout_lines }}" 29 | loop_control: 30 | loop_var: "profile_name" 31 | -------------------------------------------------------------------------------- /playbooks/roles/test-client/tasks/openvpn-test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Download each {{ openvpn_profile_type }} OpenVPN profile" 3 | include: openvpn-profileget.yml 4 | 5 | - name: "Test each {{ openvpn_profile_type }} OpenVPN profile" 6 | include: openvpn-profiletest.yml 7 | with_items: "{{ test_client_profiles.stdout_lines }}" 8 | loop_control: 9 | loop_var: "profile_name" 10 | -------------------------------------------------------------------------------- /playbooks/roles/test-client/tasks/openvpn.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # 3 | # Import the Streisand OpenVPN playbook vars 4 | - include_vars: "../../openvpn/vars/main.yml" 5 | 6 | # Install OpenVPN from PPA 7 | - import_tasks: "../../openvpn/tasks/install.yml" 8 | 9 | - name: "Remove stale test state if required" 10 | file: 11 | path: "{{ item }}" 12 | state: absent 13 | with_items: 14 | - "{{ test_client_profiles_file }}" 15 | - "{{ openvpn_profile_addons }}" 16 | - "{{ openvpn_pid_file }}" 17 | 18 | - name: "Generate a file with OpenVPN commands required to customize the profile for the test-client" 19 | template: 20 | src: "openvpn-profile-addons.j2" 21 | dest: "{{ openvpn_profile_addons }}" 22 | owner: root 23 | group: root 24 | mode: 0600 25 | 26 | - name: "Download and test each OpenVPN profile type" 27 | include_tasks: openvpn-test.yml 28 | vars: 29 | openvpn_profile_type: "{{ item }}" 30 | with_items: 31 | - "direct" 32 | - "sslh" 33 | # TODO(@cpu) - debug direct-udp and combined profiles. They don't work r.n. 34 | #- "direct-udp" 35 | #- "combined" 36 | -------------------------------------------------------------------------------- /playbooks/roles/test-client/tasks/wireguard-profiletest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - block: 3 | - name: "Replace wg0 with the {{ profile_name }} configuration" 4 | copy: 5 | src: "{{ wireguard_profile_dir }}/{{ profile_name }}.conf" 6 | remote_src: yes 7 | dest: "/etc/wireguard/wg0.conf" 8 | mode: 0600 9 | owner: root 10 | group: root 11 | force: yes 12 | 13 | - name: "Bring up the WireGuard interface for the {{ profile_name }} config" 14 | shell: "wg-quick up wg0" 15 | 16 | - name: "Register the updated /etc/resolv.conf contents" 17 | shell: "cat /etc/resolv.conf" 18 | register: wgclient_resolv_conf 19 | changed_when: "False" 20 | 21 | - name: "Assert that /etc/resolv.conf was updated for the DNSMasq WireGuard IP" 22 | assert: 23 | that: 24 | - "'nameserver {{ dnsmasq_wireguard_ip }}' in wgclient_resolv_conf.stdout" 25 | 26 | - name: "Check {{ external_test_url }} through the WireGuard route" 27 | get_url: 28 | url: "{{ external_test_url }}" 29 | dest: "/dev/null" 30 | force: "yes" 31 | 32 | - name: "Register the output from `wg` to check Wireguard status" 33 | shell: "wg" 34 | register: wgclient_wg_output 35 | changed_when: "False" 36 | 37 | - name: "Assert that there has been a successful Wireguard handshake" 38 | assert: 39 | that: 40 | - "'latest handshake' in wgclient_wg_output.stdout" 41 | 42 | - name: "Bring down the WireGuard interface" 43 | command: "wg-quick down wg0" 44 | 45 | - name: "Register the updated /etc/resolv.conf contents" 46 | shell: "cat /etc/resolv.conf" 47 | register: wgclient_resolv_conf 48 | changed_when: "False" 49 | 50 | - name: "Assert that the DNS was restored to pre-WireGuard state" 51 | assert: 52 | that: 53 | - "'nameserver {{ dnsmasq_wireguard_ip }}' not in wgclient_resolv_conf.stdout" 54 | rescue: 55 | - name: "Remove the wg0 interface if present" 56 | command: "wg-quick down wg0" 57 | ignore_errors: "yes" 58 | -------------------------------------------------------------------------------- /playbooks/roles/test-client/tasks/wireguard.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Install the WireGuard PPA and packages 4 | - import_tasks: "../../wireguard/tasks/install.yml" 5 | 6 | # Import the Wireguard Streisand playbook vars 7 | - include_vars: "../../wireguard/vars/main.yml" 8 | 9 | - name: "Remove any stale Wireguard profiles" 10 | file: 11 | path: "{{ wireguard_profile_dir }}" 12 | state: absent 13 | 14 | - name: "Create WireGuard profile directory" 15 | file: 16 | path: "{{ wireguard_profile_dir }}" 17 | state: directory 18 | 19 | - name: "Download each of the WireGuard client profiles" 20 | get_url: 21 | url: "{{ gateway_test_url }}/wireguard/{{ profile_name }}.conf" 22 | dest: "{{ wireguard_profile_dir }}/{{ profile_name }}.conf" 23 | force_basic_auth: yes 24 | url_username: "{{ gateway_test_user }}" 25 | url_password: "{{ lookup('file', '{{ streisand_gateway_password_localpath }}') }}" 26 | validate_certs: no 27 | with_items: "{{ test_client_profiles.stdout_lines }}" 28 | loop_control: 29 | loop_var: "profile_name" 30 | 31 | - name: "Test each WireGuard client profile" 32 | include: wireguard-profiletest.yml 33 | with_items: "{{ test_client_profiles.stdout_lines }}" 34 | loop_control: 35 | loop_var: "profile_name" 36 | -------------------------------------------------------------------------------- /playbooks/roles/test-client/templates/obfs4.relay.client.torrc.j2: -------------------------------------------------------------------------------- 1 | RunAsDaemon 1 2 | SOCKSPort {{ tor_socks_port }} 3 | PidFile {{ tor_pid_file }} 4 | UseBridges 1 5 | ClientTransportPlugin obfs4 exec /usr/bin/obfs4proxy 6 | Bridge {{ tor_obfs4_qr_contents.stdout }} 7 | -------------------------------------------------------------------------------- /playbooks/roles/test-client/templates/openvpn-profile-addons.j2: -------------------------------------------------------------------------------- 1 | script-security 2 2 | up /etc/openvpn/update-resolv-conf 3 | down /etc/openvpn/update-resolv-conf 4 | writepid {{ openvpn_pid_file }} 5 | -------------------------------------------------------------------------------- /playbooks/roles/test-client/templates/ssh-config.j2: -------------------------------------------------------------------------------- 1 | # Connection profile for a SOCKS proxy over a SSH connection to the forward user 2 | # on a Streisand server instance 3 | Host streisand-host 4 | HostName {{ streisand_ip }} 5 | # Open a SOCKS proxy port equivalent to using `-D {{ forward_socks_port }}` on 6 | # the command line 7 | DynamicForward {{ forward_socks_port }} 8 | 9 | # Connection profile for using the TinyProxy HTTP(S) proxy over SSH to the 10 | # Streisand server instance 11 | Host streisand-host-tinyproxy 12 | HostName {{ streisand_ip }} 13 | # Forward port {{ tinyproxy_remote_port }} on the streisand_ip host to port 14 | # {{ tinyproxy_local_port }} localhost. This makes TinyProxy (bound to 15 | # 127.0.0.1 on the Streisand host) accessible 16 | LocalForward {{ tinyproxy_local_port }} localhost:{{ tinyproxy_remote_port }} 17 | 18 | # Default settings for all hosts 19 | Host * 20 | Port 22 21 | User {{ forward_ssh_user }} 22 | # Only use keys specified in this config 23 | IdentitiesOnly yes 24 | # Never try password authentication 25 | PasswordAuthentication no 26 | # Use the specified key for the connection 27 | IdentityFile {{ forward_ssh_key }} 28 | # Use the preconfigured SSH known hosts fingerprints to avoid having to make 29 | # a trust-on-first use decision 30 | UserKnownHostsFile {{ forward_ssh_hosts }} 31 | -------------------------------------------------------------------------------- /playbooks/roles/test-client/templates/streisand-gateway-test.sh.j2: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | # 3 | # NOTE: This test script relies on the -e argument to bash in the shebang above. 4 | # 5 | # Streisand Gateway Test 6 | # - Confirms HTTP basic auth is present 7 | # - Confirms HTTP basic auth rejects an incorrect password 8 | # - Confirms using the correct username/password allows viewing the index 9 | # 10 | # TODO: Install the streisand gateway CA & remove the --insecure arguments. 11 | 12 | # Confirm that not sending a password/username results in a 401 error 13 | curl --insecure -I {{ gateway_test_url }} | grep "401 Unauthorized" 14 | 15 | # Confirm that sending the wrong password/username results in a 401 error 16 | curl --insecure -I -u "{{ gateway_test_user }}:badpassword" {{ gateway_test_url }} | grep "401 Unauthorized" 17 | 18 | # Read the password into a var 19 | password=$(cat "{{ gateway_password_file }}") 20 | 21 | # Confirm that using the correct password/username results in a 200 OK 22 | curl --insecure -I -u "{{ gateway_test_user }}:$password" {{ gateway_test_url }} | grep "200 OK" 23 | -------------------------------------------------------------------------------- /playbooks/roles/test-client/templates/streisand-shadowsocks-forward-test.sh.j2: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | # 3 | # NOTE: This test script relies on the -e argument to bash in the shebang above. 4 | # 5 | # Streisand Shadowsocks Client Test 6 | # - Confirms the gateway mirror for the Linux Shadowsocks client works 7 | # - Confirms the gateway Shadowsocks QR has correct information (password, cipher, etc) 8 | # - Confirms that the Linux shadowsocks client can connect & route a HTTP request 9 | # 10 | 11 | # Read the Shadowsocks QR code attributes into vars 12 | shadowsocksCipher=$(shadowsocks-qr-decode --cipher "{{ shadowsocks_qr_file }}" 2>/dev/null) 13 | shadowsocksPassword=$(shadowsocks-qr-decode --password "{{ shadowsocks_qr_file }}" 2>/dev/null) 14 | shadowsocksServer=$(shadowsocks-qr-decode --server "{{ shadowsocks_qr_file }}" 2>/dev/null) 15 | shadowsocksPort=$(shadowsocks-qr-decode --port "{{ shadowsocks_qr_file }}" 2>/dev/null) 16 | 17 | # Read the gateway password into a var 18 | gatewayPassword=$(cat "{{ gateway_password_file }}") 19 | 20 | # Connect the Shadowsocks client to the server 21 | {{ shadowsocks_client }} -c "$shadowsocksServer:$shadowsocksPort" -password "$shadowsocksPassword" -socks localhost:{{ shadowsocks_local_port }} -verbose -cipher "$shadowsocksCipher" & 22 | CLIENT_PID=$! 23 | 24 | function cleanup { 25 | # Kill the backgrounded Shadowsocks client 26 | echo "Killing PID $CLIENT_PID" 27 | kill $CLIENT_PID 28 | echo "Waiting for clean up to finish..." 29 | wait $CLIENT_PID 30 | } 31 | trap cleanup EXIT 32 | 33 | # Sleep to give the Shadowsocks client a chance to start 34 | sleep 2 35 | 36 | # Check that the gateway website can be loaded through the Shadowsocks proxy 37 | curl --socks5 localhost:{{ shadowsocks_local_port }} --insecure -I -u "{{ gateway_test_user }}:$gatewayPassword" {{ gateway_test_url }} | grep "200 OK" 38 | 39 | # Check that an external site can be loaded through the Shadowsocks proxy 40 | curl --socks5 localhost:{{ shadowsocks_local_port }} --insecure -I -u "{{ gateway_test_user }}:$gatewayPassword" {{ external_test_url }} | grep "200 OK" 41 | -------------------------------------------------------------------------------- /playbooks/roles/tinyproxy/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart Tinyproxy 3 | systemd: 4 | name: tinyproxy.service 5 | state: restarted 6 | -------------------------------------------------------------------------------- /playbooks/roles/tinyproxy/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install Tinyproxy 3 | apt: 4 | package: tinyproxy 5 | 6 | - name: Stop (init.d's) tinyproxy 7 | systemd: 8 | name: tinyproxy.service 9 | state: stopped 10 | 11 | - name: Create the tinyproxy config directory 12 | file: 13 | path: "{{ tinyproxy_conf_dir }}" 14 | state: directory 15 | owner: nobody 16 | group: nogroup 17 | mode: 0755 18 | 19 | - name: Generate the tinyproxy configuration file 20 | template: 21 | src: tinyproxy.conf.j2 22 | dest: "{{ tinyproxy_conf_file }}" 23 | owner: root 24 | group: root 25 | mode: 0644 26 | 27 | - name: Generate the tinyproxy system unit file 28 | template: 29 | src: tinyproxy.service.j2 30 | dest: /etc/systemd/system/tinyproxy.service 31 | owner: root 32 | group: root 33 | mode: 0644 34 | 35 | - name: Generate the systemd tmpfile for tinyproxy 36 | template: 37 | src: tinyproxytmp.conf.j2 38 | dest: /etc/tmpfiles.d/tinyproxy.conf 39 | owner: root 40 | group: root 41 | mode: 0644 42 | 43 | - name: Clean up the installed-by-default tinyproxy configuration file 44 | file: 45 | path: /etc/tinyproxy.conf 46 | state: absent 47 | 48 | - name: Enable and restart the tinyproxy service 49 | systemd: 50 | daemon_reload: yes 51 | name: tinyproxy.service 52 | enabled: yes 53 | state: restarted 54 | -------------------------------------------------------------------------------- /playbooks/roles/tinyproxy/templates/tinyproxy.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=tinyproxy - a light-weight HTTP/HTTPS proxy daemon for POSIX operating systems 3 | After=network-online.target sshd.service 4 | Documentation=man:tinyproxy(8) 5 | Documentation=https://www.banu.com/tinyproxy/ 6 | 7 | [Service] 8 | Type=forking 9 | PIDFile={{ tinyproxy_pid_file }} 10 | ExecStart=/usr/sbin/tinyproxy -c {{ tinyproxy_conf_file }} 11 | ExecStop=/usr/bin/killall -9 tinyproxy 12 | ExecReload=/bin/kill -HUP $MAINPID 13 | PrivateTmp=true 14 | RestartSec=5s 15 | Restart=on-failure 16 | 17 | [Install] 18 | WantedBy=multi-user.target 19 | -------------------------------------------------------------------------------- /playbooks/roles/tinyproxy/templates/tinyproxytmp.conf.j2: -------------------------------------------------------------------------------- 1 | # Allow systemd to create a directory for 2 | # tinyproxy to write its PID file 3 | # https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html 4 | d {{ tinyproxy_pid_dir }} 0755 nobody nogroup - 5 | -------------------------------------------------------------------------------- /playbooks/roles/tinyproxy/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | tinyproxy_timeout_seconds: 600 3 | tinyproxy_port: 8888 4 | 5 | tinyproxy_listen_address: "127.0.0.1" 6 | tinyproxy_log_level: "Critical" 7 | 8 | tinyproxy_pid_dir: "/var/run/tinyproxy" 9 | tinyproxy_pid_file: "{{ tinyproxy_pid_dir }}/tinyproxy.pid" 10 | 11 | tinyproxy_conf_dir: "/etc/tinyproxy" 12 | tinyproxy_conf_file: "{{ tinyproxy_conf_dir }}/tinyproxy.conf" 13 | -------------------------------------------------------------------------------- /playbooks/roles/tor-bridge/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | tor_orport: 8443 3 | tor_obfs4_port: 9443 4 | 5 | # By default Streisand does *not* publish the Tor relay's service descriptor to 6 | # the tor network. Using a value of 0 ensures the relay is private to the 7 | # streisand operator and their users. See the Tor documentation[0] for more 8 | # information: 9 | # [0]: https://www.torproject.org/docs/tor-manual.html.en#PublishServerDescriptor 10 | # 11 | tor_publish_service_desc: 0 12 | 13 | # If you would like to contribute to the overall health of the Tor network by 14 | # submitting your bridge relay for use by others comment out the 15 | # `tor_publish_service_desc: 0` line above and uncomment the following line: 16 | # 17 | # tor_publish_service_desc: "bridge" 18 | -------------------------------------------------------------------------------- /playbooks/roles/tor-bridge/files/apparmor-local-override: -------------------------------------------------------------------------------- 1 | # Site-specific additions and overrides for system_tor. 2 | # For more details, please see /etc/apparmor.d/local/README. 3 | 4 | # Workaround https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=862993 5 | # Tor v0.3.0.9 fails to read /var/lib/tor/hidden_service without this 6 | # app armor capability override 7 | capability dac_read_search, 8 | -------------------------------------------------------------------------------- /playbooks/roles/tor-bridge/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart Nginx for the Tor hidden service vhost 3 | service: 4 | name: nginx 5 | state: restarted 6 | -------------------------------------------------------------------------------- /playbooks/roles/tor-bridge/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | # Tor needs to be added to the firewall 4 | - { role: ufw } 5 | # Tor needs to ensure Nginx is installed to host the hidden service vhost 6 | - { role: nginx } 7 | -------------------------------------------------------------------------------- /playbooks/roles/tor-bridge/tasks/docs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create the Tor Gateway directory 3 | file: 4 | path: "{{ tor_gateway_location }}" 5 | owner: www-data 6 | group: www-data 7 | mode: 0750 8 | state: directory 9 | 10 | - name: Generate the Tor obfs4 QR code 11 | shell: echo -n '{{ tor_obfs4_bridge_line }}' | qrencode -s 6 -o {{ tor_obfs4_qr_code }} 12 | 13 | - include_role: 14 | name: i18n-docs 15 | vars: 16 | title: "Tor" 17 | i18n_location: "{{ tor_gateway_location }}" 18 | input_template_name: "instructions" 19 | -------------------------------------------------------------------------------- /playbooks/roles/tor-bridge/tasks/firewall.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure UFW allows Tor bridge 3 | ufw: 4 | to_port: "{{ tor_orport }}" 5 | proto: "tcp" 6 | rule: "allow" 7 | 8 | - name: Ensure UFW allows Tor obfs4 pluggable transport 9 | ufw: 10 | to_port: "{{ tor_obfs4_port }}" 11 | proto: "tcp" 12 | rule: "allow" 13 | -------------------------------------------------------------------------------- /playbooks/roles/tor-bridge/tasks/mirror-common.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Make the directory where the Tor Project's mirrored files will be stored 3 | file: 4 | path: "{{ tor_mirror_location }}" 5 | owner: www-data 6 | group: www-data 7 | mode: 0755 8 | state: directory 9 | 10 | - name: Discover the latest stable version of the Tor Browser Bundle 11 | # The Python code below can be cleaned up once the Tor Project has 12 | # finished transitioning away from putting the OS suffix in the 13 | # RecommendedTBBVersions file. This check should work with both 14 | # formats though. 15 | # 16 | # https://trac.torproject.org/projects/tor/ticket/8940#comment:28 17 | 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];' 18 | args: 19 | warn: no 20 | register: tor_latest_recommended_check 21 | 22 | - name: Set the target Tor Browser Bundle version 23 | set_fact: 24 | tor_browser_bundle_version: "{{ tor_latest_recommended_check.stdout }}" 25 | 26 | - name: Include the mirror variables for Tor Browser Bundle 27 | include_vars: mirror.yml 28 | -------------------------------------------------------------------------------- /playbooks/roles/tor-bridge/tasks/mirror.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Include the Tor common variables 3 | include_vars: mirror-common.yml 4 | 5 | - name: Include the Tor download variables 6 | include_vars: mirror-download.yml 7 | 8 | - block: 9 | # Include the mirror-common tasks 10 | - import_tasks: mirror-common.yml 11 | 12 | # Download the Tor browser in each locale, verifying GPG signatures. 13 | - include_role: 14 | name: download-and-verify 15 | vars: 16 | project_name: "Tor Browser ({{ locale }})" 17 | project_download_baseurl: "{{ tor_base_download_url }}" 18 | project_download_files: "{{ tor_download_files }}" 19 | project_download_location: "{{ tor_mirror_location }}" 20 | project_signer_keyid: "{{ tor_signer_keyid }}" 21 | with_items: "{{ streisand_languages.values() | map(attribute='tor_locale') | list }}" 22 | when: locale in tor_available_locales 23 | loop_control: 24 | loop_var: locale 25 | rescue: 26 | - name: "{{ streisand_mirror_warning }}" 27 | pause: 28 | seconds: "{{ streisand_mirror_warning_seconds }}" 29 | 30 | - include_role: 31 | name: i18n-docs 32 | vars: 33 | title: "Tor mirror" 34 | i18n_location: "{{ tor_mirror_location }}" 35 | input_template_name: "mirror" 36 | -------------------------------------------------------------------------------- /playbooks/roles/tor-bridge/templates/hidden-service-vhost.j2: -------------------------------------------------------------------------------- 1 | # Streisand Tor Hidden Service Gateway 2 | server { 3 | listen {{ tor_internal_hidden_service_address }}; 4 | 5 | auth_basic "Authorization Required"; 6 | auth_basic_user_file {{ streisand_gateway_htpasswd_file }}; 7 | 8 | # Disable all logging 9 | access_log /dev/null; 10 | error_log /dev/null crit; 11 | 12 | root {{ streisand_gateway_location }}; 13 | index index.html index.htm; 14 | } 15 | -------------------------------------------------------------------------------- /playbooks/roles/tor-bridge/templates/mirror-fr.md.j2: -------------------------------------------------------------------------------- 1 | {% include "languages.md.j2" %} 2 | 3 | 4 | ### Tor Browser Bundle (localisé en français) ### 5 | 6 | **Linux** 7 | 8 | * [{{ 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) }})) 9 | * [{{ 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) }})) 10 | 11 | **macOS** 12 | 13 | * [{{ 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) }})) 14 | 15 | **Windows** 16 | 17 | * [{{ 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) }})) 18 | -------------------------------------------------------------------------------- /playbooks/roles/tor-bridge/templates/mirror.md.j2: -------------------------------------------------------------------------------- 1 | {% include "languages.md.j2" %} 2 | 3 | 4 | ### Tor Browser Bundle ### 5 | 6 | **Linux** 7 | 8 | * [{{ 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) }})) 9 | * [{{ 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) }})) 10 | 11 | **macOS** 12 | 13 | * [{{ 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) }})) 14 | 15 | **Windows** 16 | 17 | * [{{ 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) }})) 18 | -------------------------------------------------------------------------------- /playbooks/roles/tor-bridge/templates/torrc.j2: -------------------------------------------------------------------------------- 1 | SocksPort 0 2 | ORPort {{ tor_orport }} 3 | ExtORPort auto 4 | BridgeRelay 1 5 | PublishServerDescriptor {{ tor_publish_service_desc }} 6 | ExitPolicy reject *:* 7 | 8 | Nickname {{ tor_bridge_nickname.stdout }} 9 | 10 | ServerTransportPlugin obfs4 exec /usr/bin/obfs4proxy 11 | ServerTransportListenAddr obfs4 0.0.0.0:{{ tor_obfs4_port }} 12 | 13 | HiddenServiceDir {{ tor_hidden_service_directory }} 14 | HiddenServicePort 80 {{ tor_internal_hidden_service_address }} 15 | -------------------------------------------------------------------------------- /playbooks/roles/tor-bridge/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | tor_bridge_nickname_file: "/etc/tor/bridge_nickname" 3 | 4 | tor_standard_connection_details: "{{ streisand_ipv4_address }}:{{ tor_orport }}" 5 | 6 | tor_obfs4_bridge_line: "obfs4 {{ streisand_ipv4_address }}:{{ tor_obfs4_port }} {{ tor_fingerprint.stdout }} cert={{ tor_obfs4_certificate.stdout }} iat-mode=0" 7 | 8 | tor_state_directory: "/var/lib/tor" 9 | 10 | tor_hidden_service_directory: "{{ tor_state_directory }}/hidden_service/" 11 | 12 | tor_obfs_state_directory: "{{ tor_state_directory }}/pt_state" 13 | 14 | tor_gateway_location: "{{ streisand_gateway_location }}/tor" 15 | 16 | tor_markdown_instructions: "{{ tor_gateway_location }}/index.md" 17 | tor_html_instructions: "{{ tor_gateway_location }}/index.html" 18 | 19 | tor_obfs4_qr_code: "{{ tor_gateway_location }}/tor-obfs4-qr-code.png" 20 | 21 | tor_internal_hidden_service_address: "127.0.0.1:8181" 22 | -------------------------------------------------------------------------------- /playbooks/roles/tor-bridge/vars/mirror-common.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Tor common variables 3 | # -------------------- 4 | 5 | tor_project_name: "Tor Browser Bundle" 6 | 7 | tor_mirror_location: "{{ streisand_mirror_location }}/tor" 8 | tor_mirror_href_base: "/mirror/tor" 9 | 10 | tor_base_download_url: "https://dist.torproject.org/torbrowser/{{ tor_browser_bundle_version }}" 11 | 12 | tor_available_locales: 13 | - ar 14 | - de 15 | - en-US 16 | - es-ES 17 | - fa 18 | - fr 19 | - it 20 | - ja 21 | - ko 22 | - nl 23 | - pl 24 | - pt-BR 25 | - ru 26 | - tr 27 | - vi 28 | - zh-CN 29 | -------------------------------------------------------------------------------- /playbooks/roles/tor-bridge/vars/mirror-download.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Tor download variables 3 | # ---------------------- 4 | 5 | # Windows 6 | tor_browser_bundle_windows_filename: "{{ tor_windows_filename_base }}_{{ locale }}.exe" 7 | tor_browser_bundle_windows_sig_filename: "{{ tor_browser_bundle_windows_filename }}.asc" 8 | 9 | # macOS 10 | tor_browser_bundle_macos_filename: "{{ tor_macos_filename_base }}_{{ locale }}.dmg" 11 | tor_browser_bundle_macos_sig_filename: "{{ tor_browser_bundle_macos_filename }}.asc" 12 | 13 | # Linux 32bit 14 | tor_browser_bundle_linux32_filename: "{{ tor_linux32_filename_base }}_{{ locale }}.tar.xz" 15 | tor_browser_bundle_linux32_sig_filename: "{{ tor_browser_bundle_linux32_filename }}.asc" 16 | 17 | # Linux 64bit 18 | tor_browser_bundle_linux64_filename: "{{ tor_linux64_filename_base }}_{{ locale }}.tar.xz" 19 | tor_browser_bundle_linux64_sig_filename: "{{ tor_browser_bundle_linux64_filename }}.asc" 20 | 21 | tor_signer_keyid: "D9FF06E2" 22 | 23 | tor_download_files: 24 | - { "file": "{{ tor_browser_bundle_windows_filename }}", "sig": "{{ tor_browser_bundle_windows_sig_filename }}" } 25 | - { "file": "{{ tor_browser_bundle_macos_filename }}", "sig": "{{ tor_browser_bundle_macos_sig_filename }}" } 26 | - { "file": "{{ tor_browser_bundle_linux32_filename }}", "sig": "{{ tor_browser_bundle_linux32_sig_filename }}" } 27 | - { "file": "{{ tor_browser_bundle_linux64_filename }}", "sig": "{{ tor_browser_bundle_linux64_sig_filename }}" } 28 | -------------------------------------------------------------------------------- /playbooks/roles/tor-bridge/vars/mirror.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Tor mirror variables 3 | # -------------------- 4 | tor_mirror_location: "{{ streisand_mirror_location }}/tor" 5 | 6 | tor_windows_filename_base: "torbrowser-install-{{ tor_browser_bundle_version }}" 7 | tor_windows_filename_template: "{{ tor_windows_filename_base }}_locale.exe" 8 | tor_windows_sig_filename_template: "{{ tor_windows_filename_template }}.asc" 9 | tor_windows_href: "{{ tor_mirror_href_base }}/{{ tor_windows_filename_template }}" 10 | tor_windows_sig_href: "{{ tor_mirror_href_base }}/{{ tor_windows_sig_filename_template }}" 11 | 12 | tor_macos_filename_base: "TorBrowser-{{ tor_browser_bundle_version }}-osx64" 13 | tor_macos_filename_template: "{{ tor_macos_filename_base }}_locale.dmg" 14 | tor_macos_sig_filename_template: "{{ tor_macos_filename_template }}.asc" 15 | tor_macos_href: "{{ tor_mirror_href_base }}/{{ tor_macos_filename_template }}" 16 | tor_macos_sig_href: "{{ tor_mirror_href_base }}/{{ tor_macos_sig_filename_template }}" 17 | 18 | tor_linux32_filename_base: "tor-browser-linux32-{{ tor_browser_bundle_version }}" 19 | tor_linux32_filename_template: "{{ tor_linux32_filename_base }}_locale.tar.xz" 20 | tor_linux32_sig_filename_template: "{{ tor_linux32_filename_template }}.asc" 21 | tor_linux32_href: "{{ tor_mirror_href_base }}/{{ tor_linux32_filename_template }}" 22 | tor_linux32_sig_href: "{{ tor_mirror_href_base }}/{{ tor_linux32_sig_filename_template }}" 23 | 24 | tor_linux64_filename_base: "tor-browser-linux64-{{ tor_browser_bundle_version }}" 25 | tor_linux64_filename_template: "{{ tor_linux64_filename_base }}_locale.tar.xz" 26 | tor_linux64_sig_filename_template: "{{ tor_linux64_filename_template }}.asc" 27 | tor_linux64_href: "{{ tor_mirror_href_base }}/{{ tor_linux64_filename_template }}" 28 | tor_linux64_sig_href: "{{ tor_mirror_href_base }}/{{ tor_linux64_sig_filename_template }}" 29 | -------------------------------------------------------------------------------- /playbooks/roles/ufw/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install UFW 3 | apt: 4 | package: ufw 5 | 6 | - name: Disable UFW logging 7 | lineinfile: 8 | dest: /etc/ufw/ufw.conf 9 | regexp: "^LOGLEVEL" 10 | line: "LOGLEVEL=off" 11 | 12 | - name: Change the default forward policy 13 | lineinfile: 14 | dest: /etc/default/ufw 15 | regexp: "^DEFAULT_FORWARD_POLICY" 16 | line: 'DEFAULT_FORWARD_POLICY="ACCEPT"' 17 | 18 | - name: Ensure UFW allows SSH 19 | ufw: 20 | to_port: "{{ ssh_port }}" 21 | proto: "tcp" 22 | rule: "allow" 23 | 24 | - name: Ensure UFW is enabled and denies by default 25 | ufw: 26 | state: "enabled" 27 | policy: "deny" 28 | direction: "incoming" 29 | 30 | - name: Ensure UFW allows nginx 31 | ufw: 32 | to_port: "{{ nginx_port }}" 33 | proto: "tcp" 34 | rule: "allow" 35 | -------------------------------------------------------------------------------- /playbooks/roles/validation/defaults/main.yml: -------------------------------------------------------------------------------- 1 | streisand_new_server_provisioning: true 2 | -------------------------------------------------------------------------------- /playbooks/roles/validation/tasks/ssh.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - block: 3 | - name: "Stat the Streisand SSH private key" 4 | stat: 5 | path: "{{ streisand_ssh_private_key }}" 6 | register: streisand_ssh_private_key_status 7 | - name: "Fail if the Streisand SSH private key file doesn't exist" 8 | fail: 9 | msg: "The Streisand SSH private key \"{{ streisand_ssh_private_key }}\" does not exist." 10 | when: streisand_ssh_private_key_status.stat.exists == False 11 | - name: "Stat the Streisand SSH public key" 12 | stat: 13 | path: "{{ streisand_ssh_private_key }}.pub" 14 | register: streisand_ssh_key_status 15 | when: streisand_new_server_provisioning 16 | - name: "Fail if the Streisand SSH public key file doesn't exist" 17 | fail: 18 | msg: "The Streisand SSH public key \"{{ streisand_ssh_private_key }}.pub\" does not exist." 19 | when: 20 | - streisand_new_server_provisioning 21 | - not streisand_ssh_key_status.stat.exists 22 | rescue: 23 | - fail: 24 | 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" 25 | -------------------------------------------------------------------------------- /playbooks/roles/wireguard/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | wireguard_port: "51820" 3 | -------------------------------------------------------------------------------- /playbooks/roles/wireguard/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - { role: dnsmasq } 4 | - { role: ip-forwarding } 5 | -------------------------------------------------------------------------------- /playbooks/roles/wireguard/tasks/docs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create the WireGuard Gateway directory 3 | file: 4 | path: "{{ wireguard_gateway_location }}" 5 | owner: www-data 6 | group: www-data 7 | mode: 0750 8 | state: directory 9 | 10 | - name: Copy the client configuration files to the WireGuard Gateway directory 11 | command: "cp {{ wireguard_client_path }}/{{ client_name.stdout }}/client.conf {{ wireguard_gateway_location }}/{{ client_name.stdout }}.conf" 12 | args: 13 | creates: "{{ wireguard_gateway_location }}/{{ client_name.stdout }}.conf" 14 | with_items: "{{ vpn_client_names.results }}" 15 | loop_control: 16 | loop_var: "client_name" 17 | label: "{{ client_name.item }}" 18 | 19 | - name: Generate the client configuration QR codes in the WireGuard Gateway directory 20 | shell: "qrencode -s 6 -o {{ wireguard_gateway_location }}/{{ client_name.stdout }}.png < {{ wireguard_gateway_location }}/{{ client_name.stdout }}.conf" 21 | args: 22 | creates: "{{ wireguard_gateway_location }}/{{ client_name.stdout }}.png" 23 | with_items: "{{ vpn_client_names.results }}" 24 | loop_control: 25 | loop_var: "client_name" 26 | label: "{{ client_name.item }}" 27 | 28 | - name: Copy the client OpenWrt configuration fragments to the WireGuard Gateway directory 29 | command: "cp {{ wireguard_client_path }}/{{ client_name.stdout }}/client-openwrt.txt {{ wireguard_gateway_location }}/{{ client_name.stdout }}-openwrt.txt" 30 | args: 31 | creates: "{{ wireguard_gateway_location }}/{{ client_name.stdout }}-openwrt.txt" 32 | with_items: "{{ vpn_client_names.results }}" 33 | loop_control: 34 | loop_var: "client_name" 35 | label: "{{ client_name.item }}" 36 | 37 | - include_role: 38 | name: i18n-docs 39 | vars: 40 | title: "WireGuard" 41 | i18n_location: "{{ wireguard_gateway_location }}" 42 | input_template_name: "instructions" 43 | -------------------------------------------------------------------------------- /playbooks/roles/wireguard/tasks/firewall.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure UFW allows DNS requests from WireGuard clients 3 | ufw: 4 | to_port: "53" 5 | proto: "udp" 6 | rule: "allow" 7 | from_ip: "10.192.122.0/24" 8 | 9 | - name: Ensure UFW allows WireGuard 10 | ufw: 11 | to_port: "{{ wireguard_port }}" 12 | proto: "udp" 13 | rule: "allow" 14 | 15 | - name: Allow WireGuard through the firewall 16 | command: "{{ item }}" 17 | with_items: "{{ wireguard_firewall_rules }}" 18 | 19 | - name: "Add WireGuard firewall persistence service to init" 20 | template: 21 | src: streisand-wireguard-service.sh.j2 22 | dest: /etc/init.d/streisand-wireguard 23 | mode: 0755 24 | 25 | - name: "Enable the streisand-wireguard init service" 26 | service: 27 | name: streisand-wireguard 28 | enabled: yes 29 | -------------------------------------------------------------------------------- /playbooks/roles/wireguard/tasks/install.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Determine the running kernel release 4 | command: uname -r 5 | register: kernel_release 6 | 7 | - name: Add the WireGuard PPA 8 | apt_repository: 9 | repo: 'ppa:wireguard/wireguard' 10 | register: wireguard_add_apt_repository 11 | until: not wireguard_add_apt_repository.failed 12 | retries: "{{ apt_repository_retries }}" 13 | delay: "{{ apt_repository_delay }}" 14 | 15 | - name: Install the WireGuard packages 16 | apt: 17 | package: 18 | - linux-headers-{{ kernel_release.stdout }} 19 | - linux-headers-generic 20 | - wireguard-dkms 21 | - wireguard-tools 22 | -------------------------------------------------------------------------------- /playbooks/roles/wireguard/templates/client.conf.j2: -------------------------------------------------------------------------------- 1 | # "{{ item[0].stdout }}" - Streisand WireGuard Client Profile 2 | [Interface] 3 | Address = 10.192.122.{{ (item[0].item|int) + 1 }}/32 4 | # The use of DNS below effectively expands to: 5 | # PostUp = echo nameserver {{ dnsmasq_wireguard_ip }} | resolvconf -a tun.%i -m 0 -x 6 | # PostDown = resolvconf -d tun.%i 7 | # If the use of resolvconf is not desirable, simply remove the DNS line 8 | # and use a variant of the PostUp/PostDown lines above. 9 | # The IP address of the DNS server that is available via the encrypted 10 | # WireGuard interface is {{ dnsmasq_wireguard_ip }}. 11 | DNS = {{ dnsmasq_wireguard_ip }} 12 | PrivateKey = {{ item[1].stdout }} 13 | 14 | [Peer] 15 | PublicKey = {{ wireguard_server_public_key }} 16 | AllowedIPs = 0.0.0.0/0,::/0 17 | Endpoint = {{ streisand_ipv4_address }}:{{ wireguard_port }} 18 | -------------------------------------------------------------------------------- /playbooks/roles/wireguard/templates/server.conf.j2: -------------------------------------------------------------------------------- 1 | [Interface] 2 | Address = 10.192.122.1/24 3 | SaveConfig = true 4 | ListenPort = {{ wireguard_port }} 5 | PrivateKey = {{ wireguard_server_private_key }} 6 | 7 | {% for client in vpn_client_names.results %} 8 | # "{{ client.stdout }}" Client Peer 9 | [Peer] 10 | PublicKey = {{ wireguard_client_pubkeys.results[(client.item|int)-1].stdout }} 11 | AllowedIPs = 10.192.122.{{ (client.item|int)+1 }}/32 12 | 13 | {% endfor %} 14 | -------------------------------------------------------------------------------- /playbooks/roles/wireguard/templates/streisand-wireguard-service.sh.j2: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: streisand-wireguard 4 | # Required-Start: $network $remote_fs $local_fs 5 | # Required-Stop: $network $remote_fs $local_fs 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: Persist WireGuard firewall rules for Streisand 9 | ### END INIT INFO 10 | 11 | {% for rule in wireguard_firewall_rules %} 12 | {{ rule }} 13 | {% endfor %} 14 | 15 | exit 0 16 | -------------------------------------------------------------------------------- /playbooks/roles/wireguard/templates/wireguard_dnsmasq.conf.j2: -------------------------------------------------------------------------------- 1 | # Listen on the WireGuard address 2 | listen-address={{ dnsmasq_wireguard_ip }} 3 | -------------------------------------------------------------------------------- /playbooks/roles/wireguard/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | wireguard_path: "/etc/wireguard" 3 | wireguard_client_path: "{{ wireguard_path }}/clients" 4 | 5 | wireguard_server_private_key_file: "{{ wireguard_path }}/server.key" 6 | wireguard_server_public_key_file: "{{ wireguard_path }}/server.pub" 7 | 8 | dnsmasq_wireguard_ip: "10.192.122.1" 9 | 10 | wireguard_firewall_rules: 11 | - "iptables --wait {{ streisand_iptables_wait }} -A FORWARD -s 10.192.122.0/24 -j ACCEPT" 12 | - "iptables --wait {{ streisand_iptables_wait }} -t nat -A POSTROUTING -s 10.192.122.0/24 -o {{ ansible_default_ipv4.interface }} -j MASQUERADE" 13 | 14 | wireguard_gateway_location: "{{ streisand_gateway_location }}/wireguard" 15 | -------------------------------------------------------------------------------- /playbooks/ssh-setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Configure Ansible SSH 3 | hosts: streisand-host 4 | gather_facts: no 5 | tasks: 6 | - set_fact: 7 | ansible_ssh_private_key_file: "{{ streisand_ssh_private_key }}" 8 | -------------------------------------------------------------------------------- /playbooks/streisand.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - import_playbook: python.yml 3 | - import_playbook: lets-encrypt.yml 4 | 5 | - name: Collect diagnostics in case of error 6 | hosts: localhost 7 | gather_facts: no 8 | roles: 9 | - role: diagnostics 10 | when: not streisand_ci 11 | 12 | - name: Configure the Server and install required software 13 | # ======================================================== 14 | hosts: streisand-host 15 | 16 | remote_user: "root" 17 | become: true 18 | 19 | roles: 20 | - common 21 | - gpg 22 | - ssh 23 | - service-net 24 | - role: openconnect 25 | when: streisand_openconnect_enabled 26 | - role: openvpn 27 | when: streisand_openvpn_enabled 28 | - role: shadowsocks 29 | when: streisand_shadowsocks_enabled 30 | - role: ssh-forward 31 | when: streisand_ssh_forward_enabled 32 | - role: tinyproxy 33 | when: streisand_tinyproxy_enabled 34 | # tor-bridge is skipped in our full Ansible run on Travis CI due to 35 | # connectivity issues. 36 | - role: tor-bridge 37 | when: not streisand_ci and streisand_tor_enabled 38 | - sslh 39 | - ufw 40 | - role: wireguard 41 | when: streisand_wireguard_enabled 42 | - role: cloudflared 43 | when: not streisand_ci and streisand_cloudflared_enabled 44 | # streisand_le_enabled is set in lets-encrypt.yml based on user input. 45 | # lets-encrypt roles sets le_ok, which is used by streisand-gateway. 46 | - role: lets-encrypt 47 | when: streisand_le_enabled 48 | - role: ad-blocking 49 | when: streisand_ad_blocking_enabled 50 | - streisand-mirror 51 | - streisand-gateway 52 | ... 53 | -------------------------------------------------------------------------------- /playbooks/test-client.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Configure the client server for Ansible 3 | # ========================================= 4 | hosts: streisand-client 5 | gather_facts: no 6 | remote_user: "root" 7 | become: true 8 | 9 | tasks: 10 | - name: Install Python using a raw SSH command to enable the execution of Ansible modules 11 | raw: apt update && apt install python -y 12 | args: 13 | executable: /bin/bash 14 | 15 | - name: Configure the client server as a Streisand test client 16 | hosts: streisand-client 17 | remote_user: "root" 18 | become: true 19 | roles: 20 | - test-client 21 | ... 22 | -------------------------------------------------------------------------------- /playbooks/vagrant.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - import_playbook: python.yml 3 | 4 | - name: Prepare the vagrant VM for Ansible 5 | # ========================================= 6 | hosts: streisand-host 7 | remote_user: "root" 8 | become: true 9 | 10 | # Use pre_tasks to run these before the roles, since the role expects keys to 11 | # exist. 12 | pre_tasks: 13 | # Ansible uses `ip -4 route get 8.8.8.8` to set the `ansible_default_ipv4` 14 | # fact with an interface's details. Without the below route being added this 15 | # results in enp0s3 being used when we want enp0s8 to be used. We work 16 | # around this by setting a route for 8.8.8.8 through enp0s8. 17 | - name: Workaround Ansible default ipv4 interface detection 18 | raw: route add -net 8.8.8.8 netmask 255.255.255.255 enp0s8 19 | args: 20 | executable: /bin/bash 21 | when: ansible_default_ipv4.alias != "enp0s8" 22 | 23 | # We need a throwaway key pair created in the location expected by 24 | # {{ streisand_ssh_private_key }}. 25 | - name: Create throwaway SSH key pair directory. 26 | file: 27 | path: "{{ streisand_ssh_private_key | expanduser | dirname }}" 28 | state: directory 29 | mode: 0700 30 | recurse: yes 31 | 32 | - name: Create throwaway SSH key pair. 33 | shell: "ssh-keygen -h -t rsa -f {{ streisand_ssh_private_key }} -N ''" 34 | args: 35 | creates: "{{ streisand_ssh_private_key }}" 36 | 37 | roles: 38 | - validation 39 | 40 | - import_playbook: ssh-setup.yml 41 | - import_playbook: streisand.yml 42 | ... 43 | -------------------------------------------------------------------------------- /playbooks/validate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Perform global variables validation 3 | # ========================================= 4 | hosts: localhost 5 | connection: local 6 | gather_facts: no 7 | 8 | vars_files: 9 | - ../global_vars/globals.yml 10 | 11 | roles: 12 | - validation 13 | 14 | ... 15 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Install Streisand requirements with: 2 | # 3 | # pip install -r ./requirements.txt 4 | # 5 | # If you have problems running this, try the util/venv-builder.sh script. 6 | 7 | # Core with Azure dependencies 8 | # 9 | ansible[azure]==2.8.4 10 | 11 | SecretStorage 12 | 13 | # AWS 14 | boto 15 | boto3 16 | 17 | # Google Compute Engine 18 | requests 19 | google-auth 20 | apache-libcloud 21 | 22 | # Linode 23 | linode-api4 24 | 25 | # Rackspace 26 | pyrax 27 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Streisand-CI Testing 2 | Streisand CI testing uses Github Actions to test Ansible syntax and kick off a full playbook run. 3 | The Full playbook run is executed against github runners 4 | 5 | ## Testing Limitations 6 | There were some connectivity issues with the `tor-bridge` playbook in the CI testing environment. Due to this, the role is currently skipped. 7 | Streisand doc generation is also skipped until some mock variables and tasks are added. 8 | 9 | ## Local Testing 10 | You can use this testing framework to test locally as well. Note that when testing locally, the `tor-bridge` role will be ran. 11 | 12 | ### Host Environment Expectations: 13 | - **Fresh** Trusty or Xenial Ubuntu install (Vagrant or DigitalOcean server, etc.) 14 | 15 | Because LXC loads the host's kernal modules into the container. Libreswan needs to be compiled and installed on the host. 16 | This allows the ipsec role to complete successfully inside the LXC container. 17 | 18 | #### Running the tests 19 | - Clone the Streisand repository 20 | - Run test script 21 | - `./tests/test.sh full` 22 | - The `full` argument will run `development-setup.yml`, `syntax-check.yml`, and `run.yml`. 23 | 24 | #### Syntax only 25 | - `./tests/test.sh syntax` 26 | 27 | # TODO 28 | - Vagrant file which can automate setting up a local test environment for use with development-setup.yml, and run.yml 29 | - Figure out a way to test the Tor role 30 | - Add some mock variables for the document generation task 31 | - Add some automated client testing (Example: Automatically test an openvpn client) 32 | - Check if the tor-bridge is still an issue on github actions 33 | -------------------------------------------------------------------------------- /tests/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | nocows = 1 3 | roles_path = playbooks/roles:../playbooks/roles 4 | host_key_checking = False 5 | remote_tmp = $HOME/.ansible/tmp 6 | local_tmp = $HOME/.ansible/tmp 7 | timeout = 100 8 | forks = 15 9 | internal_poll_interval=0.001 10 | 11 | [ssh_connection] 12 | pipelining = True 13 | -------------------------------------------------------------------------------- /tests/group_vars/all/all: -------------------------------------------------------------------------------- 1 | --- 2 | streisand_ci: yes 3 | streisand_noninteractive: no 4 | 5 | server_name: streisand 6 | -------------------------------------------------------------------------------- /tests/inventory: -------------------------------------------------------------------------------- 1 | [streisand] 2 | streisand ansible_connection=lxd ansible_become=yes 3 | 4 | [localhost] 5 | localhost ansible_connection=local 6 | -------------------------------------------------------------------------------- /tests/remote_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Utility script for running the Vagrantfile.remotetest against a Streisand host 4 | # 5 | 6 | # Set errexit option to exit immediately on any non-zero status return 7 | set -e 8 | 9 | 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" 10 | 11 | SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" 12 | 13 | VAGRANT_FILENAME="Vagrantfile.remotetest" 14 | GENERATED_DOCS_DIR="${SCRIPT_DIR}/../generated-docs" 15 | mkdir -p "${GENERATED_DOCS_DIR}" 16 | 17 | VAGRANTFILE="${SCRIPT_DIR}/../${VAGRANT_FILENAME}" 18 | 19 | function backup_vagrantfile() { 20 | cp "${VAGRANTFILE}" "${VAGRANTFILE}.dist" 21 | } 22 | 23 | function restore_vagrantfile() { 24 | cp "${VAGRANTFILE}.dist" "${VAGRANTFILE}" 25 | rm "${VAGRANTFILE}.dist" 26 | } 27 | trap restore_vagrantfile EXIT 28 | 29 | # set_remote_ip replaces the "REMOTE_IP_HERE" token from the 30 | # Vagrantfile.remotetest with the response from a user prompt 31 | function set_remote_ip() { 32 | read -r -p "What is the Streisand server IP? " SERVER_IP 33 | sed "s/\"REMOTE_IP_HERE\",/\"${SERVER_IP}\",/" "${VAGRANTFILE}" > "${VAGRANTFILE}.new" 34 | mv "${VAGRANTFILE}.new" "${VAGRANTFILE}" 35 | } 36 | 37 | function set_gateway_pass() { 38 | local GATEWAY_PASS_FILE="${GENERATED_DOCS_DIR}/gateway-password.txt" 39 | 40 | read -r -p "What is the Streisand gateway password? " GATEWAY_PASS 41 | 42 | echo "${GATEWAY_PASS}" > "${GATEWAY_PASS_FILE}" 43 | } 44 | 45 | backup_vagrantfile 46 | set_remote_ip 47 | set_gateway_pass 48 | 49 | pushd "${SCRIPT_DIR}/.." 50 | VAGRANT_VAGRANTFILE=${VAGRANT_FILENAME} vagrant up --provision 51 | popd 52 | -------------------------------------------------------------------------------- /tests/run.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: streisand 3 | gather_facts: yes 4 | remote_user: root 5 | become: yes 6 | tasks: 7 | - set_fact: 8 | ansible_ssh_private_key_file: "{{ streisand_ssh_private_key }}" 9 | - block: 10 | - name: Generate SSH Key's for CI run 11 | shell: "ssh-keygen -b 2048 -t rsa -f /tmp/id_rsa_insecure -q -N ''" 12 | args: 13 | creates: "/tmp/id_rsa_insecure" 14 | 15 | - name: Get the default SSH key 16 | command: "cat /tmp/id_rsa_insecure.pub" 17 | register: ssh_key 18 | changed_when: False 19 | 20 | - name: Ensure permissions on insecure key are correct 21 | file: 22 | path: "/tmp/id_rsa_insecure" 23 | mode: "0666" 24 | when: streisand_ci 25 | 26 | - name: Check Streisand configuration is valid 27 | include_role: 28 | name: validation 29 | delegate_to: localhost 30 | 31 | - name: Ensure openssh is installed 32 | apt: 33 | package: openssh-server 34 | 35 | - name: Add authorized key 36 | authorized_key: 37 | user: root 38 | state: present 39 | key: "{{ ssh_key.stdout }}" 40 | 41 | - name: Create the in-memory inventory group 42 | add_host: 43 | name: "{{ ansible_default_ipv4.address }}" 44 | groups: streisand-host 45 | ansible_ssh_private_key_file: "/tmp/id_rsa_insecure" 46 | 47 | - name: Set the streisand_ipv4_address variable 48 | set_fact: 49 | streisand_ipv4_address: "{{ ansible_default_ipv4.address }}" 50 | 51 | - name: Set the streisand_server_name variable 52 | set_fact: 53 | streisand_server_name: "{{ server_name | regex_replace('\\s', '_') }}" 54 | 55 | - name: Include streisand.yml 56 | import_playbook: ../playbooks/streisand.yml 57 | -------------------------------------------------------------------------------- /tests/shellcheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Streisand shellcheck wrapper 4 | # 5 | # This test script finds all of the *.sh shell files in the project tree and 6 | # runs shellcheck against them. 7 | # 8 | 9 | # Fail on errors 10 | set -e 11 | 12 | # Ensure shellcheck is present 13 | if ! command -v shellcheck > /dev/null 2>&1; then 14 | echo "The 'shellcheck' comand was not found in your PATH. Please install it" 15 | exit 1 16 | fi 17 | 18 | # NOTE(@cpu): We use -x to follow `source` directives across files 19 | SHELLCHECK_ARGS=(-x) 20 | 21 | # Determine the absolute path of this script file's directory 22 | SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" 23 | # The Streisand project directory is one up from this script's directory, tests/ 24 | PROJECT_DIR="$SCRIPT_DIR/.." 25 | 26 | pushd "$PROJECT_DIR" 27 | # Run shellcheck against all of the `.sh` script files in the Streisand 28 | # project directory. Ignore any `venv` directory. 29 | # 30 | # NOTE(@cpu): While tempting to -exec shellcheck directly from find this will 31 | # eat-up any non-zero exit codes :-( Instead we find the files first and then 32 | # xargs shellcheck on the found files. 33 | find . -path "./venv" -prune -or -name '*.sh' -print0 | xargs -0 -n1 shellcheck "${SHELLCHECK_ARGS[@]}" 34 | 35 | # Also explicitly run `shellcheck` against the streisand wrapper script since 36 | # it doesn't end in .sh 37 | shellcheck "${SHELLCHECK_ARGS[@]}" "$PROJECT_DIR/streisand" 38 | popd 39 | -------------------------------------------------------------------------------- /tests/site_vars/cloudflared.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This site config enables openvpn, wireguard, and cloudflared (DNS-over-HTTPS) 3 | vpn_clients: 5 4 | streisand_ad_blocking_enabled: yes 5 | streisand_cloudflared_enabled: yes 6 | streisand_openconnect_enabled: no 7 | streisand_openvpn_enabled: yes 8 | streisand_shadowsocks_enabled: no 9 | streisand_shadowsocks_v2ray_enabled: no 10 | streisand_ssh_forward_enabled: no 11 | streisand_sshuttle_enabled: no 12 | streisand_stunnel_enabled: no 13 | streisand_tinyproxy_enabled: no 14 | streisand_tor_enabled: no 15 | streisand_wireguard_enabled: yes 16 | -------------------------------------------------------------------------------- /tests/site_vars/openconnect.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This site config only enables OpenConnect 3 | vpn_clients: 5 4 | streisand_ad_blocking_enabled: no 5 | streisand_cloudflared_enabled: no 6 | streisand_openconnect_enabled: yes 7 | streisand_openvpn_enabled: no 8 | streisand_shadowsocks_enabled: no 9 | streisand_shadowsocks_v2ray_enabled: no 10 | streisand_ssh_forward_enabled: no 11 | streisand_sshuttle_enabled: no 12 | streisand_stunnel_enabled: no 13 | streisand_tinyproxy_enabled: no 14 | streisand_tor_enabled: no 15 | streisand_wireguard_enabled: no 16 | -------------------------------------------------------------------------------- /tests/site_vars/openvpn.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This site config only enables openvpn 3 | vpn_clients: 5 4 | streisand_ad_blocking_enabled: no 5 | streisand_cloudflared_enabled: no 6 | streisand_openconnect_enabled: no 7 | streisand_openvpn_enabled: yes 8 | streisand_shadowsocks_enabled: no 9 | streisand_shadowsocks_v2ray_enabled: no 10 | streisand_ssh_forward_enabled: no 11 | streisand_sshuttle_enabled: no 12 | streisand_stunnel_enabled: no 13 | streisand_tinyproxy_enabled: no 14 | streisand_tor_enabled: no 15 | streisand_wireguard_enabled: no 16 | -------------------------------------------------------------------------------- /tests/site_vars/random.yml: -------------------------------------------------------------------------------- 1 | --- 2 | vpn_clients: 1 3 | 4 | # Streisand CI's task randomizes these "_enabled" vars at build-time 5 | streisand_ad_blocking_enabled: no 6 | streisand_cloudflared_enabled: no 7 | streisand_openconnect_enabled: no 8 | streisand_openvpn_enabled: no 9 | streisand_shadowsocks_enabled: no 10 | streisand_shadowsocks_v2ray_enabled: no 11 | streisand_ssh_forward_enabled: no 12 | streisand_sshuttle_enabled: no 13 | streisand_stunnel_enabled: no 14 | streisand_tinyproxy_enabled: no 15 | streisand_tor_enabled: no 16 | streisand_wireguard_enabled: no 17 | -------------------------------------------------------------------------------- /tests/site_vars/shadowsocks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This site config only enables Shadowsocks 3 | vpn_clients: 5 4 | streisand_ad_blocking_enabled: no 5 | streisand_cloudflared_enabled: no 6 | streisand_openconnect_enabled: no 7 | streisand_openvpn_enabled: no 8 | streisand_shadowsocks_enabled: yes 9 | streisand_shadowsocks_v2ray_enabled: yes 10 | streisand_ssh_forward_enabled: no 11 | streisand_sshuttle_enabled: no 12 | streisand_stunnel_enabled: no 13 | streisand_tinyproxy_enabled: no 14 | streisand_tor_enabled: no 15 | streisand_wireguard_enabled: no 16 | -------------------------------------------------------------------------------- /tests/site_vars/ssh.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This site config only enables SSH forwarding and sshutle 3 | vpn_clients: 5 4 | streisand_ad_blocking_enabled: no 5 | streisand_cloudflared_enabled: no 6 | streisand_openconnect_enabled: no 7 | streisand_openvpn_enabled: no 8 | streisand_shadowsocks_enabled: no 9 | streisand_shadowsocks_v2ray_enabled: no 10 | streisand_ssh_forward_enabled: yes 11 | streisand_sshuttle_enabled: yes 12 | streisand_stunnel_enabled: no 13 | streisand_tinyproxy_enabled: no 14 | streisand_tor_enabled: no 15 | streisand_wireguard_enabled: no 16 | -------------------------------------------------------------------------------- /tests/vars_ci.yml: -------------------------------------------------------------------------------- 1 | streisand_ci: yes 2 | 3 | streisand_ssh_private_key: "/tmp/id_rsa_insecure" 4 | 5 | # Answer questions in playbooks/lets-encrypt.yml 6 | streisand_domain_var: "" 7 | streisand_domain: "" 8 | streisand_admin_email_var: "" 9 | streisand_admin_email: "" 10 | le_ok: no 11 | -------------------------------------------------------------------------------- /tests/yamlcheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Streisand yamllint wrapper 4 | # 5 | # This test script finds all of the *.yml files in the project tree and 6 | # runs yamllint against them. Ignore any `venv` directory. 7 | # 8 | 9 | # Fail on errors 10 | set -e 11 | 12 | # Ensure yamllint is present 13 | if ! command -v yamllint > /dev/null 2>&1; then 14 | echo "The 'yamllint' command was not found in your PATH." 15 | echo "Please run 'pip install yamllint' to install." 16 | exit 1 17 | fi 18 | 19 | # Determine the absolute path of this script file's directory 20 | SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" 21 | # The Streisand project directory is one up from this script's directory, tests/ 22 | PROJECT_DIR="$SCRIPT_DIR/.." 23 | 24 | # Streisand specifies a custom yamllint config to adjust what rules are applied 25 | YAMLLINT_ARGS=(-c "$SCRIPT_DIR/yamllint-config.yml") 26 | 27 | pushd "$PROJECT_DIR" 28 | # Run yamllint against all of the `.yml` files in the Streisand 29 | # project directory. 30 | # 31 | # NOTE(@cpu): While tempting to -exec shellcheck directly from find this will 32 | # eat-up any non-zero exit codes :-( Instead we find the files first and then 33 | # xargs yamllint on the found files. 34 | find . -path "./venv" -prune -or -name '*.yml' -print0 | xargs -0 -n1 yamllint "${YAMLLINT_ARGS[@]}" 35 | popd 36 | -------------------------------------------------------------------------------- /tests/yamllint-config.yml: -------------------------------------------------------------------------------- 1 | extends: default 2 | 3 | rules: 4 | # Presently there are a large number of files that break the line-length 5 | # limit.This changes the limit to a warning until we are ready to try and 6 | # tackle these legacy files. 7 | line-length: 8 | max: 280 9 | level: warning 10 | # We allow a few rules for now to accept the "pretty" layout existing files 11 | # use. It may be worth revisiting these in the future. 12 | # Accept: 13 | # * "too many spaces after colon" 14 | # * "too many spaces inside braces" 15 | # * "truthy value should be one of [false, true] but we use [no, yes]" 16 | colons: disable 17 | braces: disable 18 | commas: disable 19 | truthy: disable 20 | -------------------------------------------------------------------------------- /util/ansible_check.sh: -------------------------------------------------------------------------------- 1 | # shellcheck shell=bash 2 | 3 | # This is included by the main streisand script. 4 | 5 | # check_ansible checks that Ansible is installed on the local system 6 | # and that it is a supported version. 7 | function check_ansible() { 8 | local REQUIRED_ANSIBLE_VERSION="2.8.4" 9 | 10 | if ! command -v ansible > /dev/null 2>&1; then 11 | echo " 12 | Streisand requires Ansible and it is not installed. 13 | Please see the README Installation section on Prerequisites" 14 | exit 1 15 | fi 16 | 17 | ansible_version="$(ansible --version | head -1 | grep -oe '2[.0-9]*')" 18 | 19 | if ! ./util/version_at_least.py "$REQUIRED_ANSIBLE_VERSION" "$ansible_version" ; then 20 | echo " 21 | Streisand requires Ansible version $REQUIRED_ANSIBLE_VERSION or higher. 22 | This system has Ansible $ansible_version. 23 | " 24 | exit 1 25 | fi 26 | } 27 | -------------------------------------------------------------------------------- /util/dependencies.txt: -------------------------------------------------------------------------------- 1 | build-essential 2 | python3-pip 3 | python3-openssl 4 | python3-dev 5 | python3-setuptools 6 | python3-venv 7 | python-cffi libffi-dev 8 | libssl-dev 9 | libcurl4-openssl-dev 10 | -------------------------------------------------------------------------------- /util/print-aws-regions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Generate code fragments for amazon.yml 5 | 6 | names = ( 7 | ("us-east-1", "US East", "N. Virginia"), 8 | ("us-east-2", "US East", "Ohio"), 9 | ("us-west-1", "US West", "N. California"), 10 | ("us-west-2", "US West", "Oregon"), 11 | ("ca-central-1", "Canada", "Central"), 12 | ("eu-central-1", "EU", "Frankfurt"), 13 | ("eu-west-1", "EU", "Ireland"), 14 | ("eu-west-2", "EU", "London"), 15 | ("eu-west-3", "EU", "Paris"), 16 | ("ap-northeast-1", "Asia Pacific", "Tokyo"), 17 | ("ap-northeast-2", "Asia Pacific", "Seoul"), 18 | ("ap-northeast-3", "Asia Pacific", "Osaka-Local"), 19 | ("ap-southeast-1", "Asia Pacific", "Singapore"), 20 | ("ap-southeast-2", "Asia Pacific", "Sydney"), 21 | ("ap-south-1", "Asia Pacific", "Mumbai"), 22 | ("ap-east-1", "Asia Pacific", "Hong Kong"), 23 | ("eu-north-1", "EU", "Stockholm"), 24 | ("sa-east-1", "South America", "São Paulo"), 25 | ) 26 | 27 | sorted_names = sorted(names) 28 | 29 | print("") 30 | print (""" 31 | regions:""") 32 | for i in range(len(sorted_names)): 33 | j = i + 1 34 | o = sorted_names[i] 35 | print(' "{j}": "{symname}"'.format(j=j, symname=o[0])) 36 | 37 | print ("----------------------") 38 | 39 | print ("") 40 | print (""" 41 | In what region should the server be located?""") 42 | for i in range(len(sorted_names)): 43 | j = i + 1 44 | o = sorted_names[i] 45 | print(" {j:>2}. {symname:<15} {region:<14} ({nickname})".format( 46 | j=j, symname=o[0], region=o[1], nickname=o[2])) 47 | -------------------------------------------------------------------------------- /util/source_check_and_default_site_vars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This is intended to be sourced into a deploy script. It exists to DRY up the 4 | # codebase. It expects the following variables set: 5 | # 6 | # DEFAULT_SITE_VARS /path/to/default-site.yml 7 | # PROJECT_DIR /path/to/streisand 8 | # SITE_VARS /path/to/site.yml 9 | # 10 | 11 | set -o errexit 12 | 13 | # If no site vars file is provided, then use one of the two default options. 14 | if [ -z "${SITE_VARS}" ]; then 15 | 16 | SITE_VARS="${HOME}/.streisand/site.yml" 17 | 18 | if [ ! -f "${SITE_VARS}" ]; then 19 | SITE_VARS="${DEFAULT_SITE_VARS}" 20 | fi 21 | 22 | echo "Using default config file: ${SITE_VARS}" 23 | fi 24 | 25 | # Make sure the alleged configuration file exists. 26 | if [ ! -f "${SITE_VARS}" ]; then 27 | echo "No such config file: ${SITE_VARS}" 28 | exit 1 29 | fi 30 | -------------------------------------------------------------------------------- /util/source_validate_and_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This is intended to be sourced into a deploy script. It exists to DRY up the 4 | # codebase. It expects the following variables set: 5 | # 6 | # DEFAULT_SITE_VARS /path/to/default-site.yml 7 | # GLOBAL_VARS /path/to/globals.yml 8 | # INVENTORY /path/to/inventory 9 | # PROJECT_DIR /path/to/streisand 10 | # SITE_VARS /path/to/site.yml 11 | # PLAYBOOK /path/to/playbook.yml. 12 | # 13 | 14 | set -o errexit 15 | 16 | ansible-playbook \ 17 | --extra-vars="@${GLOBAL_VARS}" \ 18 | --extra-vars="@${DEFAULT_SITE_VARS}" \ 19 | --extra-vars="@${SITE_VARS}" \ 20 | "${PROJECT_DIR}/playbooks/validate.yml" 21 | 22 | # Update the server. 23 | ansible-playbook \ 24 | -i "${INVENTORY}" \ 25 | --extra-vars="@${GLOBAL_VARS}" \ 26 | --extra-vars="@${DEFAULT_SITE_VARS}" \ 27 | --extra-vars="@${SITE_VARS}" \ 28 | "${PLAYBOOK}" 29 | -------------------------------------------------------------------------------- /util/version_at_least.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | from distutils.version import StrictVersion 5 | 6 | if len(sys.argv) != 3: 7 | print("Usage: version_at_least minimum_version version_to_check") 8 | sys.exit(1) 9 | 10 | minimum_version = StrictVersion(sys.argv[1].strip()) 11 | version_to_check = StrictVersion(sys.argv[2].strip()) 12 | 13 | if version_to_check >= minimum_version: 14 | sys.exit(0) 15 | else: 16 | sys.exit(1) 17 | --------------------------------------------------------------------------------