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.
For a detailed walk-through explaining the whole stack and how to set it up, check the blog post Deploy a Tor onion service with Onionspray and Ansible - zoug.fr.
Depending on the configuration chosen, the Ansible controller may need to
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
The role also comes with two variables to customize privilege escalation
(onionspray_become_method and onionspray_become_flags).
This potential requirement is further discussed on ticket tpo/onion-services/ansible/onionspray-role#2.
This role can be installed directly through Ansible Galaxy:
ansible-galaxy role install torproject.onionspray
It's also possible to clone it directly from the upstream repository. A GitHub mirror is also provided.
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:
- onionsprayYou 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.orgThe 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: yetkvkuqlr23sdzkf2mynt7aixfjzq6pjys2ffurr3hzpyfxrc7swpqdThis 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.org
# A random Onion Service address is generated by Onionspray
# HTTPS certificates are generated and self-signed by Onionspray
- upstream_address: example.org
# Onion Service mapping to example.com
# HTTPS certificates are generated and self-signed by Onionspray
- onion_address: expeksycd6djb4bvyan7vpl7rqb6rfecz4kkluj66gw6fd6wopc2pxyd.onion
upstream_address: example.com
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 my.example
# HTTPS certificates are uploaded using an external script
- onion_address: exmp3cho5nxislcjefyovvsqd36g23ouofdjtiiypv4cs3ahhpyonxyd.onion
upstream_address: my.example
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.test
# Onion Service mapping to test.example, using Onionbalance
# Certificates are generated and self-signed by Onionspray
- upstream_address: test.example
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: 2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion
upstream_address: torproject.orgThis role uses Onionspray's self-signed HTTPS certificate generation script by default. The script parameters can be customized as documented in the variables section and on the Onionspray documentation.
Additionally, custom HTTPS keys and certificates can be provided, either by:
- Declaring then as variables, as documented in the defaults file.
- 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) and Digicert (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.
Further reading:
- Certificates for Onion Services: a broader and introductory discussion on the topic.
- HTTPS Certificates for Onionspray: specific guide for handling certificates on Onionspray.
This role can be customized in many ways through variables, which are described in length in 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
podmanscenario is a generic one and is well suited for testing both locally and through CI. - The
localscenario 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