From 6353a9868ed2274e3ed922b6b59f397b5dbb254f Mon Sep 17 00:00:00 2001 From: Nick Gnazzo Date: Tue, 3 Sep 2019 10:57:14 -0400 Subject: [PATCH 1/5] Run tor HS as separate tor process/user, make HS use local bridge The Tor Project recommends against running a hidden service and a tor bridge on the same machine, or at the very least to run them as separate processes. This way, the HS activity won't be able to block the bridge's activity (this is a side effect of Tor being single-threaded). Also, the bridge will actually report your onion service history in its statistics if they use the same process. See: https://trac.torproject.org/projects/tor/ticket/8742 https://trac.torproject.org/projects/tor/ticket/16585 for more info. Also, since Streisand is making a locally running bridge, it makes sense for the HS to use that same bridge to connect to Tor. --- .../roles/tor-bridge/files/system_tor_hs | 22 ++++++++ playbooks/roles/tor-bridge/tasks/main.yml | 55 +++++++++++++++++++ .../templates/tor-hidden-service.service.j2 | 37 +++++++++++++ .../templates/torrc-hidden-service.j2 | 19 +++++++ playbooks/roles/tor-bridge/templates/torrc.j2 | 3 - playbooks/roles/tor-bridge/vars/main.yml | 9 ++- 6 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 playbooks/roles/tor-bridge/files/system_tor_hs create mode 100644 playbooks/roles/tor-bridge/templates/tor-hidden-service.service.j2 create mode 100644 playbooks/roles/tor-bridge/templates/torrc-hidden-service.j2 diff --git a/playbooks/roles/tor-bridge/files/system_tor_hs b/playbooks/roles/tor-bridge/files/system_tor_hs new file mode 100644 index 000000000..686ca905a --- /dev/null +++ b/playbooks/roles/tor-bridge/files/system_tor_hs @@ -0,0 +1,22 @@ +# vim:syntax=apparmor +#include + +profile system_tor_hs flags=(attach_disconnected) { + #include + + owner /var/lib/tor-hidden-service/** rwk, + owner /var/lib/tor-hidden-service/ r, + owner /var/log/tor-hidden-service/* w, + + # During startup, tor (as root) tries to open various things such as + # directories via check_private_dir(). Let it. + /var/lib/tor-hidden-service/** r, + + /{,var/}run/tor-hidden-service/ r, + /{,var/}run/tor-hidden-service/control w, + /{,var/}run/tor-hidden-service/socks w, + /{,var/}run/tor-hidden-service/tor.pid w, + /{,var/}run/tor-hidden-service/control.authcookie w, + /{,var/}run/tor-hidden-service/control.authcookie.tmp rw, + /{,var/}run/systemd/notify w, +} diff --git a/playbooks/roles/tor-bridge/tasks/main.yml b/playbooks/roles/tor-bridge/tasks/main.yml index 75ef49b80..dea801d58 100644 --- a/playbooks/roles/tor-bridge/tasks/main.yml +++ b/playbooks/roles/tor-bridge/tasks/main.yml @@ -46,6 +46,61 @@ group: root mode: 0644 +- name: Generate the hidden service torrc config file + template: + src: torrc-hidden-service.j2 + dest: /etc/tor/torrc-hidden-service + owner: root + group: root + mode: 0644 + +# We have to setup an apparmor profile for the tor hidden service +# since we're trying to use the non-default tor data directories, +# control socket, PID file, etc. +- name: Copy Tor Hidden Service AppArmor profile + copy: + src: system_tor_hs + dest: /etc/apparmor.d/system_tor_hs + owner: root + group: root + mode: 0644 + +# Permissions are handled by the tor-hidden-service.service file +# (systemd) each time the service is started/restarted. +- name: Create hidden service data directory + file: + path: "{{ tor_hidden_service_state_directory }}" + state: directory + +- name: Generate the hidden service systemd file + template: + src: tor-hidden-service.service.j2 + dest: /etc/systemd/system/tor-hidden-service.service + owner: root + group: root + mode: 0644 + +# Run tor processes as separate users locally. By default, +# tor runs as "debian-tor". Since we're running the hidden service +# and bridge as separate tor processes, it makes sense to run +# these under separate user accounts as well. +- name: Create local hidden service user + user: + name: "{{ tor_hidden_service_user }}" + shell: /bin/false + system: yes + create_home: False + # the "home" folder for debian-tor by default is + # set to /var/lib/tor, since we aren't using that dir + # here we set debian-tor-hs home to the new HS data dir + home: "{{ tor_hidden_service_state_directory }}" + +- name: Start tor hidden service + service: + name: tor-hidden-service + enabled: true + state: started + # TODO(@cpu) - This should be removed once it isn't required, maybe in the next # release after tor 0.3.0.9 - name: Copy a local override for the Tor AppArmor profile in place diff --git a/playbooks/roles/tor-bridge/templates/tor-hidden-service.service.j2 b/playbooks/roles/tor-bridge/templates/tor-hidden-service.service.j2 new file mode 100644 index 000000000..1f3970d69 --- /dev/null +++ b/playbooks/roles/tor-bridge/templates/tor-hidden-service.service.j2 @@ -0,0 +1,37 @@ +[Unit] +Description=Anonymizing overlay network for TCP +After=network.target nss-lookup.target tor@default.service +PartOf=tor-hidden-service.service +ReloadPropagatedFrom=tor-hidden-service.service + +[Service] +Type=notify +NotifyAccess=all +PIDFile={{ tor_hidden_service_run_directory }}/tor.pid +PermissionsStartOnly=yes +ExecStartPre=/usr/bin/install -Z -m 02755 -o {{ tor_hidden_service_user }} -g {{ tor_hidden_service_user }} -d {{ tor_hidden_service_run_directory }} +ExecStartPre=/usr/bin/install -Z -m 02755 -o {{ tor_hidden_service_user }} -g {{ tor_hidden_service_user }} -d {{ tor_hidden_service_state_directory }} +ExecStartPre=/usr/bin/tor --defaults-torrc /usr/share/tor/tor-service-defaults-torrc -f {{ tor_hidden_service_config }} --RunAsDaemon 0 --verify-config +ExecStart=/usr/bin/tor --defaults-torrc /usr/share/tor/tor-service-defaults-torrc -f {{ tor_hidden_service_config }} --RunAsDaemon 0 +ExecReload=/bin/kill -HUP ${MAINPID} +KillSignal=SIGINT +TimeoutStartSec=300 +TimeoutStopSec=60 +Restart=on-failure +LimitNOFILE=65536 + +# Hardening +# system_tor_hs is a hidden service apparmor profile so we can use +# different tor data dir/control socket/PID file. +AppArmorProfile=-system_tor_hs +NoNewPrivileges=yes +PrivateTmp=yes +PrivateDevices=yes +ProtectHome=yes +ProtectSystem=full +ReadOnlyDirectories=/ +ReadWriteDirectories=-/proc +ReadWriteDirectories=-{{ tor_hidden_service_state_directory }} +ReadWriteDirectories=-{{ tor_hidden_service_log_directory }} +ReadWriteDirectories=-/run +CapabilityBoundingSet=CAP_SETUID CAP_SETGID CAP_NET_BIND_SERVICE CAP_DAC_READ_SEARCH diff --git a/playbooks/roles/tor-bridge/templates/torrc-hidden-service.j2 b/playbooks/roles/tor-bridge/templates/torrc-hidden-service.j2 new file mode 100644 index 000000000..768b96fdb --- /dev/null +++ b/playbooks/roles/tor-bridge/templates/torrc-hidden-service.j2 @@ -0,0 +1,19 @@ +# Use the local bridge to connect to the Tor network as a hidden service. +# We also use a separate tor process to host the hidden service, instead of +# hosting both the Streisand bridge and Streisand gateway hidden service +# on the same tor process. Otherwise, we risk leaking a lot of information +# about our hidden service possibly leading to deanonymization. +# see https://trac.torproject.org/projects/tor/ticket/8742 +UseBridges 1 +Bridge 127.0.0.1:{{ tor_orport }} +SocksPort 0 +User {{ tor_hidden_service_user }} + +HiddenServiceDir {{ tor_hidden_service_directory }} +HiddenServicePort 80 {{ tor_internal_hidden_service_address }} + +DataDirectory {{ tor_hidden_service_state_directory }} +PidFile {{ tor_hidden_service_run_directory }}/tor.pid + +ControlSocket {{ tor_hidden_service_run_directory }}/control GroupWritable RelaxDirModeCheck +CookieAuthFile {{ tor_hidden_service_run_directory }}/control.authcookie diff --git a/playbooks/roles/tor-bridge/templates/torrc.j2 b/playbooks/roles/tor-bridge/templates/torrc.j2 index 98d5e5689..f1e4fb4ff 100644 --- a/playbooks/roles/tor-bridge/templates/torrc.j2 +++ b/playbooks/roles/tor-bridge/templates/torrc.j2 @@ -9,6 +9,3 @@ Nickname {{ tor_bridge_nickname.stdout }} ServerTransportPlugin obfs4 exec /usr/bin/obfs4proxy ServerTransportListenAddr obfs4 0.0.0.0:{{ tor_obfs4_port }} - -HiddenServiceDir {{ tor_hidden_service_directory }} -HiddenServicePort 80 {{ tor_internal_hidden_service_address }} diff --git a/playbooks/roles/tor-bridge/vars/main.yml b/playbooks/roles/tor-bridge/vars/main.yml index 9ea1e3dbf..91c767794 100644 --- a/playbooks/roles/tor-bridge/vars/main.yml +++ b/playbooks/roles/tor-bridge/vars/main.yml @@ -6,8 +6,13 @@ tor_standard_connection_details: "{{ streisand_ipv4_address }}:{{ tor_orport }}" tor_obfs4_bridge_line: "obfs4 {{ streisand_ipv4_address }}:{{ tor_obfs4_port }} {{ tor_fingerprint.stdout }} cert={{ tor_obfs4_certificate.stdout }} iat-mode=0" tor_state_directory: "/var/lib/tor" - -tor_hidden_service_directory: "{{ tor_state_directory }}/hidden_service/" +# Hidden service is run as a separate tor process to avoid leaking data/deanonymization +tor_hidden_service_state_directory: "/var/lib/tor-hidden-service" +tor_hidden_service_run_directory: "/run/tor-hidden-service" +tor_hidden_service_directory: "{{ tor_hidden_service_state_directory }}/hidden_service/" +tor_hidden_service_log_directory: "/var/log/tor-hidden-service" +tor_hidden_service_config: "/etc/tor/torrc-hidden-service" +tor_hidden_service_user: "debian-tor-hs" tor_obfs_state_directory: "{{ tor_state_directory }}/pt_state" From fef1165c32c64b54844ab881726aa8d39b6718af Mon Sep 17 00:00:00 2001 From: Nick Gnazzo Date: Thu, 5 Sep 2019 12:49:48 -0400 Subject: [PATCH 2/5] Add vanguards add-on to harden Tor hidden service --- playbooks/roles/tor-bridge/tasks/main.yml | 76 +++++++++++++++- .../tor-vanguards-update-and-verify.sh.j2 | 90 +++++++++++++++++++ .../tor-bridge/templates/vanguards.conf.j2 | 4 + .../tor-bridge/templates/vanguards.service.j2 | 13 +++ playbooks/roles/tor-bridge/vars/main.yml | 7 ++ 5 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 playbooks/roles/tor-bridge/templates/tor-vanguards-update-and-verify.sh.j2 create mode 100644 playbooks/roles/tor-bridge/templates/vanguards.conf.j2 create mode 100644 playbooks/roles/tor-bridge/templates/vanguards.service.j2 diff --git a/playbooks/roles/tor-bridge/tasks/main.yml b/playbooks/roles/tor-bridge/tasks/main.yml index dea801d58..04667dafb 100644 --- a/playbooks/roles/tor-bridge/tasks/main.yml +++ b/playbooks/roles/tor-bridge/tasks/main.yml @@ -16,11 +16,13 @@ apt: package: deb.torproject.org-keyring -- name: Install obfs4 and Tor +# stem is a vanguards add-on dependency (python lib for controlling Tor) +- name: Install obfs4, Tor, and stem apt: package: - obfs4proxy - tor + - python-stem # Update the firewall to allow Tor and obfs4proxy # NOTE(@cpu): we do this early in the role because the Tor daemon will check if @@ -54,6 +56,14 @@ group: root mode: 0644 +- name: Generate the vanguards config file + template: + src: vanguards.conf.j2 + dest: "{{ tor_vanguards_config }}" + owner: root + group: root + mode: 0644 + # We have to setup an apparmor profile for the tor hidden service # since we're trying to use the non-default tor data directories, # control socket, PID file, etc. @@ -80,6 +90,14 @@ group: root mode: 0644 +- name: Generate the vanguards service systemd file + template: + src: vanguards.service.j2 + dest: /etc/systemd/system/vanguards.service + owner: root + group: root + mode: 0644 + # Run tor processes as separate users locally. By default, # tor runs as "debian-tor". Since we're running the hidden service # and bridge as separate tor processes, it makes sense to run @@ -101,6 +119,62 @@ enabled: true state: started +# Clone the vanguards repo for hardening the Tor hidden service +# See https://blog.torproject.org/announcing-vanguards-add-onion-services +# and https://github.com/mikeperry-tor/vanguards +# Installation instructions recommend doing this via git, as deb/rpm packages +# aren't as generally available yet (checking xenial proved fruitless for example, +# it looks like vanguards is only in ubuntu's apt repos for newer releases). +- name: Clone vanguards onion services add-on + git: + repo: "{{ tor_vanguards_repo_url }}" + dest: "{{ tor_vanguards_addon_directory }}" + +- name: Find latest vanguards add-on git tag + command: git describe --abbrev=0 + args: + chdir: "{{ tor_vanguards_addon_directory }}" + register: tor_vanguard_git_tag + +- name: Checkout latest git tag in vanguard repo + git: + repo: "{{ tor_vanguards_repo_url }}" + dest: "{{ tor_vanguards_addon_directory }}" + clone: no + update: no + version: "{{ tor_vanguard_git_tag.stdout }}" + +# Tell git to use gpg2 when verifying tags/commits +- shell: "git config --global gpg.program $(which gpg2)" + args: + chdir: "{{ tor_vanguards_addon_directory }}" + +# Grab the signing key. +- name: Grab Vanguards add-on PGP signing key + command: "gpg2 {{ streisand_default_gpg_flags }} {{ streisand_default_key_import_flags}} --recv-keys {{ tor_vanguards_addon_gpg_key }}" + +# Now, finally, we can run the gpg verify +# We tell git (or really, gpg) where Streisand's default keyring is located by using +# the env variable $GNUPGHOME. This way, when git makes a call to gpg2 it uses the right +# keyring and can find the public key associated with the tag/commit. +- name: Verify vanguard add-on git signature + git: + repo: "{{ tor_vanguards_repo_url }}" + dest: "{{ tor_vanguards_addon_directory }}" + clone: no + update: no + version: "{{ tor_vanguard_git_tag.stdout }}" + verify_commit: yes + environment: + GNUPGHOME: "{{ streisand_gpg_dir }}" + register: tor_vanguard_git_verify_results + +- name: Start the vanguards service + service: + name: vanguards + enabled: true + state: started + # TODO(@cpu) - This should be removed once it isn't required, maybe in the next # release after tor 0.3.0.9 - name: Copy a local override for the Tor AppArmor profile in place diff --git a/playbooks/roles/tor-bridge/templates/tor-vanguards-update-and-verify.sh.j2 b/playbooks/roles/tor-bridge/templates/tor-vanguards-update-and-verify.sh.j2 new file mode 100644 index 000000000..490af809b --- /dev/null +++ b/playbooks/roles/tor-bridge/templates/tor-vanguards-update-and-verify.sh.j2 @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +set -eux + +# Cronjob/utility script to fetch updates to the "vanguards" Tor add-on +# https://github.com/mikeperry-tor/vanguards + +# This script uses the latest git tag of the repo and Mike Perry's PGP key ID +# grabbed from https://2019.www.torproject.org/docs/signing-keys.html.en +# +# N.B. this script should be idempotent and will exit early if git reports no changes + +VANGUARD_REPO_DIR="{{ tor_vanguards_addon_directory }}" +VANGUARD_REPO_URL="{{ tor_vanguards_repo_url }}" +VANGUARD_SIGNING_KEY="{{ tor_vanguards_addon_gpg_key }}" + +cd $VANGUARD_REPO_DIR +# Store currently checked out commit SHA in case anything fails. +VANGUARD_CURRENT_COMMIT=$(git show --format="%H" --no-patch) + +# Failsafe function to ensure we're checked out into the last "known" good git tag/commit +# This way, if a git pull/gpg verify/other command in this script fails, we can leave +# the vanguards git repo in a known "good state" and hopefully not break anything. +function git_checkout_known_tag() { + git checkout $VANGUARD_CURRENT_COMMIT +} +trap git_checkout_known_tag EXIT + +function check_for_git_updates() { + git fetch + +} + +# Fetch the detached signature from git internally by using cat-file +# to view the actual contents of the tag object within git. This is needed +# to verify the PGP commit since Streisand is using a non-default GPG keyring, +# and it's not directly configurable to tell git to use a different GPG keyring +# when verifying tags/commits. Awkward. +# So if you were to just run git verify-tag normally, it would try to find +# the public key in ~/.gnupg/pubring.gpg which doesn't exist on a Streisand install. +# (instead, it exists in ~/.gnupg/streisand/pubring.gpg), but there's no easy +# way to tell git that, so we just extract the detached signature, write it to a file, +# and then verify that signature using the normal gpg2 command with Streisand's pubring +# set via gpg2 option flags. +# +# First, we have to get the latest tag in the repo to verify. +function get_latest_git_tag_ref() { + local latest_git_tag=$(git describe --abbrev=0) + local tag_commit_ref=$(git show-ref --tags $latest_git_tag -s) + echo $? +} + +# Now we extract the detached GPG signature from the git object. +function extract_git_commit_signature($commit) { + # We only want lines between GPG's ascii-armor markers. + git cat-file -p $commit \ + | awk '/-----BEGIN PGP SIGNATURE-----/,/-----END PGP SIGNATURE-----/' + echo $? +} + +# Now, extract the git object *without* the signature, which we use +# as the "input data" for GPG +function extract_git_commit_unsigned($commit) { + git cat-file -p $commit \ + | grep -B 9999 -- '-----BEGIN PGP SIGNATURE-----' \ + | head -n -1 + echo $? +} + +function verify_and_install() { + LATEST_TAGGED_COMMIT=$(get_latest_git_tag_ref) + COMMIT_OBJECT_SIGNATURE=$(extract_git_commit_signature "$LATEST_TAGGED_COMMIT") + + COMMIT_OBJECT_UNSIGNED=$(extract_git_commit_unsigned "$LATEST_TAGGED_COMMIT") + + # Write them to temp files for use with gpg2 --verify + echo "$COMMIT_OBJECT_SIGNATURE" > /tmp/vanguard_commit_signature.asc + echo "$COMMIT_OBJECT_UNSIGNED" > /tmp/vanguard_commit_unsigned + + # So now, we can finally verify! + gpgv2 --keyring {{ streisand_gpg_keyring }} \ + /tmp/vanguard_commit_signature.asc /tmp/vanguard_commit_unsigned + # Since we use gpgv2 that command will exit with 0/1 if the signature is valid/invalid. + + # If we get this far, we can confirm the latest tag is signed and should be OK to use. + # Now we can do a checkout of that tag inside the git repo. + git checkout $LATEST_TAGGED_COMMIT + + # Restart the systemd service to run the updated code + systemctl restart tor-vanguards.service +} diff --git a/playbooks/roles/tor-bridge/templates/vanguards.conf.j2 b/playbooks/roles/tor-bridge/templates/vanguards.conf.j2 new file mode 100644 index 000000000..da06a05de --- /dev/null +++ b/playbooks/roles/tor-bridge/templates/vanguards.conf.j2 @@ -0,0 +1,4 @@ +## Global options, override default control socket/state file +[Global] +control_socket = {{ tor_hidden_service_run_directory }}/control +state_file = {{ tor_hidden_service_state_directory }}/vanguards.state diff --git a/playbooks/roles/tor-bridge/templates/vanguards.service.j2 b/playbooks/roles/tor-bridge/templates/vanguards.service.j2 new file mode 100644 index 000000000..057601e3a --- /dev/null +++ b/playbooks/roles/tor-bridge/templates/vanguards.service.j2 @@ -0,0 +1,13 @@ +[Unit] +Description=Additional protections for Tor onion services +After=network.target tor.service + +[Service] +User={{ tor_hidden_service_user }} +Group={{ tor_hidden_service_user }} +Environment=VANGUARDS_CONFIG={{ tor_vanguards_config }} +ExecStart=/usr/bin/python {{ tor_vanguards_addon_directory }}/src/vanguards.py +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/playbooks/roles/tor-bridge/vars/main.yml b/playbooks/roles/tor-bridge/vars/main.yml index 91c767794..1c01234cf 100644 --- a/playbooks/roles/tor-bridge/vars/main.yml +++ b/playbooks/roles/tor-bridge/vars/main.yml @@ -24,3 +24,10 @@ tor_html_instructions: "{{ tor_gateway_location }}/index.html" tor_obfs4_qr_code: "{{ tor_gateway_location }}/tor-obfs4-qr-code.png" tor_internal_hidden_service_address: "127.0.0.1:8181" +# Vanguards add-on git tags are signed by Tor developer Mike Perry +# see https://2019.www.torproject.org/docs/signing-keys.html.en +# for source of key ID. +tor_vanguards_addon_gpg_key: "0xC963C21D63564E2B10BB335B29846B3C683686CC" +tor_vanguards_addon_directory: "/usr/lib/tor/vanguards" +tor_vanguards_repo_url: "https://github.com/mikeperry-tor/vanguards.git" +tor_vanguards_config: "/etc/tor/vanguards.conf" From b3433287ae57f363c75bf46e2cfa57d3f363e558 Mon Sep 17 00:00:00 2001 From: Nick Gnazzo Date: Fri, 6 Sep 2019 14:04:35 -0400 Subject: [PATCH 3/5] Improve vanguards integration; update/verify script and use venvs --- playbooks/roles/tor-bridge/tasks/main.yml | 38 +++++--- .../tor-vanguards-update-and-verify.sh.j2 | 96 +++++++++---------- .../tor-bridge/templates/vanguards.service.j2 | 4 +- 3 files changed, 71 insertions(+), 67 deletions(-) diff --git a/playbooks/roles/tor-bridge/tasks/main.yml b/playbooks/roles/tor-bridge/tasks/main.yml index 04667dafb..5f66ef392 100644 --- a/playbooks/roles/tor-bridge/tasks/main.yml +++ b/playbooks/roles/tor-bridge/tasks/main.yml @@ -16,13 +16,13 @@ apt: package: deb.torproject.org-keyring -# stem is a vanguards add-on dependency (python lib for controlling Tor) -- name: Install obfs4, Tor, and stem +- name: Install obfs4, Tor, and virtualenv apt: package: - obfs4proxy - tor - - python-stem + # Vanguards dependency + - python-virtualenv # Update the firewall to allow Tor and obfs4proxy # NOTE(@cpu): we do this early in the role because the Tor daemon will check if @@ -129,25 +129,23 @@ git: repo: "{{ tor_vanguards_repo_url }}" dest: "{{ tor_vanguards_addon_directory }}" + # Keep a separate git directory to keep failed verifications/pulls/etc. from messing + # up anything in the working tree. Just suffixed with .git in the same dir. + separate_git_dir: "{{ tor_vanguards_addon_directory }}.git" - name: Find latest vanguards add-on git tag command: git describe --abbrev=0 args: - chdir: "{{ tor_vanguards_addon_directory }}" + chdir: "{{ tor_vanguards_addon_directory }}.git" register: tor_vanguard_git_tag - name: Checkout latest git tag in vanguard repo - git: - repo: "{{ tor_vanguards_repo_url }}" - dest: "{{ tor_vanguards_addon_directory }}" - clone: no - update: no - version: "{{ tor_vanguard_git_tag.stdout }}" + command: "git checkout {{ tor_vanguard_git_tag.stdout }}" + args: + chdir: "{{ tor_vanguards_addon_directory }}" # Tell git to use gpg2 when verifying tags/commits - shell: "git config --global gpg.program $(which gpg2)" - args: - chdir: "{{ tor_vanguards_addon_directory }}" # Grab the signing key. - name: Grab Vanguards add-on PGP signing key @@ -161,14 +159,26 @@ git: repo: "{{ tor_vanguards_repo_url }}" dest: "{{ tor_vanguards_addon_directory }}" - clone: no - update: no version: "{{ tor_vanguard_git_tag.stdout }}" verify_commit: yes environment: GNUPGHOME: "{{ streisand_gpg_dir }}" register: tor_vanguard_git_verify_results +- name: Setup vanguards virtualenv/install + command: "{{ tor_vanguards_addon_directory }}/setup.sh" + args: + chdir: "{{ tor_vanguards_addon_directory }}" + +- name: Install cronjob to auto-update and verify vanguards + template: + src: "tor-vanguards-update-and-verify.sh.j2" + dest: "/etc/cron.daily/tor-vanguards-update-and-verify.sh" + owner: root + group: root + mode: 0755 + when: not streisand_ci + - name: Start the vanguards service service: name: vanguards diff --git a/playbooks/roles/tor-bridge/templates/tor-vanguards-update-and-verify.sh.j2 b/playbooks/roles/tor-bridge/templates/tor-vanguards-update-and-verify.sh.j2 index 490af809b..31a145878 100644 --- a/playbooks/roles/tor-bridge/templates/tor-vanguards-update-and-verify.sh.j2 +++ b/playbooks/roles/tor-bridge/templates/tor-vanguards-update-and-verify.sh.j2 @@ -15,7 +15,10 @@ VANGUARD_SIGNING_KEY="{{ tor_vanguards_addon_gpg_key }}" cd $VANGUARD_REPO_DIR # Store currently checked out commit SHA in case anything fails. +# If we can't verify a new tag, we can checkout the old one which we know has been +# verified by Ansible during Streisand install. VANGUARD_CURRENT_COMMIT=$(git show --format="%H" --no-patch) +VANGUARD_CURRENT_TAG=$(git describe --tags) # Failsafe function to ensure we're checked out into the last "known" good git tag/commit # This way, if a git pull/gpg verify/other command in this script fails, we can leave @@ -23,68 +26,57 @@ VANGUARD_CURRENT_COMMIT=$(git show --format="%H" --no-patch) function git_checkout_known_tag() { git checkout $VANGUARD_CURRENT_COMMIT } -trap git_checkout_known_tag EXIT +trap git_checkout_known_tag ERR SIGINT SIGTERM SIGQUIT SIGKILL function check_for_git_updates() { + # Since individual commits aren't verified in the repo (only tags), + # we compare the number of tags in the local repo vs the remote repo. + # git ls-remote in this case communicates on the fly with the remote repo + # for an up-to-date list of tags in the project. If we don't have any new + # tags to fetch, we can exit early. We can even avoid doing "git fetch" this way. + local local_tag_count=$(git tag -l | wc -l) + # The grep -v is to strip away "tag references" which will show up for each annotated tag. + # otherwise we get a bigger list of tags (e.g. "refs/tags/v0.0.1", "refs/tags/v0.0.1^{}" etc.) + local remote_tag_count=$(git ls-remote --tags origin | grep -v "\^{}" | wc -l) + if [ $local_tag_count == $remote_tag_count ]; then + echo "Local/remote tag count is the same, nothing to do." + exit 0 + fi + # Repo has new tags, so we should do a fetch git fetch - -} - -# Fetch the detached signature from git internally by using cat-file -# to view the actual contents of the tag object within git. This is needed -# to verify the PGP commit since Streisand is using a non-default GPG keyring, -# and it's not directly configurable to tell git to use a different GPG keyring -# when verifying tags/commits. Awkward. -# So if you were to just run git verify-tag normally, it would try to find -# the public key in ~/.gnupg/pubring.gpg which doesn't exist on a Streisand install. -# (instead, it exists in ~/.gnupg/streisand/pubring.gpg), but there's no easy -# way to tell git that, so we just extract the detached signature, write it to a file, -# and then verify that signature using the normal gpg2 command with Streisand's pubring -# set via gpg2 option flags. -# -# First, we have to get the latest tag in the repo to verify. -function get_latest_git_tag_ref() { - local latest_git_tag=$(git describe --abbrev=0) - local tag_commit_ref=$(git show-ref --tags $latest_git_tag -s) - echo $? } -# Now we extract the detached GPG signature from the git object. -function extract_git_commit_signature($commit) { - # We only want lines between GPG's ascii-armor markers. - git cat-file -p $commit \ - | awk '/-----BEGIN PGP SIGNATURE-----/,/-----END PGP SIGNATURE-----/' - echo $? -} - -# Now, extract the git object *without* the signature, which we use -# as the "input data" for GPG -function extract_git_commit_unsigned($commit) { - git cat-file -p $commit \ - | grep -B 9999 -- '-----BEGIN PGP SIGNATURE-----' \ - | head -n -1 - echo $? -} - -function verify_and_install() { - LATEST_TAGGED_COMMIT=$(get_latest_git_tag_ref) - COMMIT_OBJECT_SIGNATURE=$(extract_git_commit_signature "$LATEST_TAGGED_COMMIT") - - COMMIT_OBJECT_UNSIGNED=$(extract_git_commit_unsigned "$LATEST_TAGGED_COMMIT") - - # Write them to temp files for use with gpg2 --verify - echo "$COMMIT_OBJECT_SIGNATURE" > /tmp/vanguard_commit_signature.asc - echo "$COMMIT_OBJECT_UNSIGNED" > /tmp/vanguard_commit_unsigned +function verify_and_update() { + # Find latest available tag by sorting by "taggerdate" (date tag was created) + # We also want to avoid ever checking out an alpha/beta/dev tag and only use "stable" tags + LATEST_TAG=$(git tag -l --sort=-taggerdate | grep -v "alpha\|beta\|dev\|test" | head -n 1) + # If it's the same tag we're currently using we don't need to do anything + # (means we likely only fetched beta/dev tags) + if [ "$LATEST_TAG" == "$VANGUARD_CURRENT_TAG" ]; then + echo "Already on the latest stable tag. Nothing to do." + exit 0 + fi # So now, we can finally verify! - gpgv2 --keyring {{ streisand_gpg_keyring }} \ - /tmp/vanguard_commit_signature.asc /tmp/vanguard_commit_unsigned - # Since we use gpgv2 that command will exit with 0/1 if the signature is valid/invalid. + # This should have been run during install, but just in case, we always configure + # git to use gpg2 instead of default /usr/bin/gpg + git config --global gpg.program $(which gpg2) + # Tell GPG which keyring to use (Streisand installs to a non-default location) + export GNUPGHOME="{{ streisand_gpg_dir }}" + # Make sure the verify-tag command is checking against the right signing key + # --raw redirects output to stderr and gives us full raw output from gpg + # if sig is valid we should find "VALIDSIG [...] KEY_ID_HERE" in the output + git verify-tag --raw "$LATEST_TAG" 2>&1 | grep -q "VALIDSIG.*${VANGUARD_SIGNING_KEY#0x}" # If we get this far, we can confirm the latest tag is signed and should be OK to use. + # (git verify-tag will exit with an error status if it doesn't succeed) # Now we can do a checkout of that tag inside the git repo. - git checkout $LATEST_TAGGED_COMMIT + git checkout $LATEST_TAG # Restart the systemd service to run the updated code - systemctl restart tor-vanguards.service + systemctl restart vanguards.service } + +check_for_git_updates +verify_and_update +exit $? diff --git a/playbooks/roles/tor-bridge/templates/vanguards.service.j2 b/playbooks/roles/tor-bridge/templates/vanguards.service.j2 index 057601e3a..2a5152d1e 100644 --- a/playbooks/roles/tor-bridge/templates/vanguards.service.j2 +++ b/playbooks/roles/tor-bridge/templates/vanguards.service.j2 @@ -5,8 +5,10 @@ After=network.target tor.service [Service] User={{ tor_hidden_service_user }} Group={{ tor_hidden_service_user }} +# Vanguards will check this env variable on startup and use the value as its config file Environment=VANGUARDS_CONFIG={{ tor_vanguards_config }} -ExecStart=/usr/bin/python {{ tor_vanguards_addon_directory }}/src/vanguards.py +# Use virtualenv binary installed by vanguards setup script +ExecStart={{ tor_vanguards_addon_directory }}/vanguardenv/bin/vanguards Restart=always [Install] From 13b8f255d855a6340e33d065dde26870df93fc53 Mon Sep 17 00:00:00 2001 From: Nick Gnazzo Date: Fri, 6 Sep 2019 14:08:10 -0400 Subject: [PATCH 4/5] Make vanguards start after the tor hidden service is running --- playbooks/roles/tor-bridge/templates/vanguards.service.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playbooks/roles/tor-bridge/templates/vanguards.service.j2 b/playbooks/roles/tor-bridge/templates/vanguards.service.j2 index 2a5152d1e..cfceb8011 100644 --- a/playbooks/roles/tor-bridge/templates/vanguards.service.j2 +++ b/playbooks/roles/tor-bridge/templates/vanguards.service.j2 @@ -1,6 +1,6 @@ [Unit] Description=Additional protections for Tor onion services -After=network.target tor.service +After=network.target tor-hidden-service.service [Service] User={{ tor_hidden_service_user }} From 2c963d000746614eb1ab113fefe3183aa8b3e19b Mon Sep 17 00:00:00 2001 From: Nick Gnazzo Date: Fri, 6 Sep 2019 14:31:54 -0400 Subject: [PATCH 5/5] Use better git tag logic for initial vanguards checkout --- playbooks/roles/tor-bridge/tasks/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/playbooks/roles/tor-bridge/tasks/main.yml b/playbooks/roles/tor-bridge/tasks/main.yml index 5f66ef392..8c20aa9f1 100644 --- a/playbooks/roles/tor-bridge/tasks/main.yml +++ b/playbooks/roles/tor-bridge/tasks/main.yml @@ -134,7 +134,9 @@ separate_git_dir: "{{ tor_vanguards_addon_directory }}.git" - name: Find latest vanguards add-on git tag - command: git describe --abbrev=0 + # Find latest available tag by sorting by "taggerdate" (date tag was created) + # We also want to avoid ever checking out an alpha/beta/dev tag and only use "stable" tags + shell: git tag -l --sort=-taggerdate | grep -v "alpha\|beta\|dev\|test" | head -n 1 args: chdir: "{{ tor_vanguards_addon_directory }}.git" register: tor_vanguard_git_tag