This Ansible role manages Onionspray, a tool to add Onion Service capability into existing websites.
This role clones the Onionspray repo and builds from source the necessary software (OpenResty with NGINX's http_substitutions_filter module, Onionbalance and Tor).
The role first generates the configuration files needed to serve a website. The
build is then done by the opt/build-DISTRO.sh
script inside the Onionspray
repo, executed by this role, depending on the distribution of your server and
if supported.
Your Ansible controller should implement an unprivileged user workaround
such as installing the setfacl
tool in the remote host (available in Debian-like
systems through the acl package); or using pipelining, which can be configured
through the following addition to ansible.cfg
:
[connection]
pipelining=True
This requirement is further discussed on ticket tpo/onion-services/ansible/onionspray-role#2.
Assuming you have a host named myhost
on which you can run
ansible-playbook
, and you cloned this role in a roles
directory, this is an
example of a basic playbook:
- name: Onionspray Tor proxy
hosts: myhost
roles:
- onionspray
You can configure your project(s), i.e. the website(s) that Onionspray will
handle, using the onionspray_projects
variable, a list of
dictionaries. You may also want to (re)define other values: check the
defaults for a complete list of variables and their usage.
As an example, you could have a minimal config at host_vars/myhost.yml
,
just telling which upstream website you want to have a proxy for:
onionspray_projects:
- name: "exampleorg"
# Onion Service proxying using Onionspray's hardmap config
hardmaps:
# Onion Service mapping to example.org
# A random Onion Service address is generated by Onionspray
# HTTPS certificates are generated and self-signed by Onionspray
- upstream_address: example.org
The quick start example from the previous section showcases the simplest configuration, but won't ensure a permanent .onion address to be used all the time.
In fact, if you don't specify a .onion address, this Ansible Role will make Onionspray to generate a new address every time the configuration changes.
To prevent that, either update the Ansible configuration with the generate address or, even better, use a pre-generate address from the outset, as explained in the next section.
This Ansible role can also deploy pre-generated and customized Onion Service keys.
Begin by generating your keys somehow, like with Onionmine.
Then encode the public key in Base64:
cat hs_ed25519_public_key | base64
Now encode the private key in Base64 and encrypt the result key with Ansible Vault:
cat hs_ed25519_secret_key | base64 | ansible-vault encrypt_string
Finally, add the results in the variable definitions for your host, such as
host_vars/myhost.yml
:
onionspray_projects:
- name: "examplenet"
# Onion Service proxying using Onionspray's hardmap config
hardmaps:
# Onion Service mapping to example.net
# HTTPS certificates are copied from Ansible
- onion_address: yetkvkuqlr23sdzkf2mynt7aixfjzq6pjys2ffurr3hzpyfxrc7swpqd.onion
upstream_address: example.net
public_key_base64: BASE64_ENCODED_ONION_SERVICE_PUBLIC_KEY
secret_key_base64: BASE64_ENCODED_ONION_SERVICE_SECRET_KEY_ENCRYPTED_WITH_ANSIBLE_VAULT
tls_certificate: |
TLS_CERTIFICATE
tls_secret_key: |
TLS_SECRET_KEY_ENCRYPTED_WITH_ANSIBLE_VAULT
onion_address: yetkvkuqlr23sdzkf2mynt7aixfjzq6pjys2ffurr3hzpyfxrc7swpqd
This role has many settings, all documented in the defaults file.
Below is an extended example highlighting some of the role features:
# Stick with a specific Onionspray version
onionspray_repository_version: a0e43045fe135e1b3f5b96e075ed519e4359ab7f
# Uploading keys and certificates from external locations,
# such as a password manager
onionspray_provider: 'myprovider'
onionspray_key_uploader_script : 'roles/onionspray/scripts/upload-keys-to-onionspray-instances'
onionspray_cert_uploader_script: 'roles/onionspray/scripts/upload-certs-to-onionspray-instances'
onionspray_projects:
- name: "example1"
# Onion Service proxying using Onionspray's hardmap config
hardmaps:
# Onion Service mapping to example.null
# A random Onion Service address is generated by Onionspray
# HTTPS certificates are generated and self-signed by Onionspray
- upstream_address: example.null
# Onion Service mapping to example.tld
# HTTPS certificates are generated and self-signed by Onionspray
- onion_address: expeksycd6djb4bvyan7vpl7rqb6rfecz4kkluj66gw6fd6wopc2pxyd.onion
upstream_address: example.tld
public_key_base64: BASE64_ENCODED_ONION_SERVICE_PUBLIC_KEY
secret_key_base64: BASE64_ENCODED_ONION_SERVICE_SECRET_KEY_ENCRYPTED_WITH_ANSIBLE_VAULT
# Onion Service mapping to example.net
# HTTPS certificates are copied from Ansible
- onion_address: yetkvkuqlr23sdzkf2mynt7aixfjzq6pjys2ffurr3hzpyfxrc7swpqd.onion
upstream_address: example.net
public_key_base64: BASE64_ENCODED_ONION_SERVICE_PUBLIC_KEY
secret_key_base64: BASE64_ENCODED_ONION_SERVICE_SECRET_KEY_ENCRYPTED_WITH_ANSIBLE_VAULT
tls_certificate: |
TLS_CERTIFICATE
tls_secret_key: |
TLS_SECRET_KEY_ENCRYPTED_WITH_ANSIBLE_VAULT
# Onion Service mapping to example.io
# HTTPS certificates are uploaded using an external script
- onion_address: exmp3cho5nxislcjefyovvsqd36g23ouofdjtiiypv4cs3ahhpyonxyd.onion
upstream_address: example.io
certificate_upload: true
keys_upload: true
# Log settings
log_separate: '1'
# Proxy settings (NGINX)
x_from_onion_value : '1'
nginx_resolver : '127.0.0.53 ipv6=off'
nginx_cache_seconds : '60'
nginx_cache_size : '64m'
nginx_tmpfile_size : '8m'
tor_export_circuit_id : 'haproxy'
tor_intros_per_daemon : '6'
tor_single_onion : '1'
tor_pow_enabled : '1'
tor_max_streams : '5000'
tor_max_streams_close_circuit: '1'
tor_intro_dos_defense : '1'
tor_intro_dos_burst_per_sec : '20000'
tor_intro_dos_rate_per_sec : '20000'
# Custom settings, passed as-is to the Onionspray project configuration
custom_settings: |
# block access to "forbidden" subdomain
set block_err This subdomain is forbidden.
set block_host_re ^forbidden\.
- name: "example2"
# Onion Service proxying using Onionspray's softmap config
softmaps:
# Onion Service mapping to example.org, using Onionbalance
# A random Onion Service address is generated by Onionspray
# Certificates are generated and self-signed by Onionspray
- upstream_address: example.org
# Onion Service mapping to example.com, using Onionbalance
# Certificates are generated and self-signed by Onionspray
- upstream_address: example.com
onion_address: excomw23fzloy3lekgrayzsiina4lqztjka5bvgqqe35xbfgcfrmjpyd.onion
# Foreignmaps: declaring onion-to-site mappings that exist outside of
# this particular configuration file, eg: for some other sites.
foreingmaps:
- onion_address: exaymhwjhgdopeebkv5p3lmb5vu2mmvcc7krpgwx6ngf4uvob2whkcqd.onion
upstream_address: example.info
This role uses Onionspray's self-signed HTTPS certificate generation script by default. The script parameters can be customized as documented in the variables section.
Additionally, custom HTTPS keys and certificates can be provided, either by:
- Declaring then as encrypted role variables.
- Uploading then directly in the Onionspray instance with an external script.
In Tor, all requests are encrypted by the protocol. The URL itself is the guarantee that you are connecting to the right server. It is hence not strictly necessary to generate a valid HTTPS certificate, more info here.
However, it is still better to use a valid HTTPS certificate, to avoid HTTPS warnings on browsers such as Brave for example. The Tor Browser does not display HTTPS warnings if using a self-signed certificate with an Onion service, though this may change in the future.
Should you want to get a valid HTTPS certificate, both HARICA
(normal certificate, $10/year) and [Digicert][digicert-onion] (expensive, EV
certificate) provide them. Let's Encrypt and other providers using the ACME
protocol (i.e. automation possible through certbot
for example) still do not
support these certificates. Using these valid certificates is hence a manual
operation, as long as ACME for Onions is not implemented.
This role can be customized in many ways through variables, which are described in length at the defaults file.
All contributions are very welcome. Feel free to send your enhancements and patches as merge requests, or open issues.
This role has molecule tests:
- The
podman
scenario is a generic one and is well suited for testing both locally and through CI. - The
local
scenario actually applies the configuration into the running node, so be careful were to run it.
A Makefile exists to help local testing, which relies on AnCIble to be available somewhere. Details in how to use it are given here.
This project is licensed with the Affero GPLv3. Check LICENSE for the full license, or this page for a quick recap. In general, if you use a modified version of this role, you must make the source code public to comply with the AGPL.
Many thanks to Mediapart for which this role has been created, for allowing it to be open sourced. You can visit their website over Tor at https://www.mediapartrvj4bsgolbxixw57ru7fh4jqckparke4vs365guu6ho64yd.onion/.
- The Onionspray documentation: quickstart guide, troubleshooting sections mainly.
- Great blogpost: A Complete Guide to EOTK
- Another great blogpost: ProPublica's experience with EOTK