create onion recipe
continuous-integration/drone/tag Build is passing

This commit is contained in:
2026-01-13 00:44:00 -05:00
parent 27fe37bb40
commit c03384df72
6 changed files with 129 additions and 45 deletions
+5 -5
View File
@@ -6,17 +6,17 @@ steps:
image: git.coopcloud.tech/coop-cloud/stack-ssh-deploy:latest
settings:
host: swarm-test.autonomic.zone
stack: example_com # UPDATE ME
generate_secrets: true
stack: onion
purge: true
deploy_key:
from_secret: drone_ssh_swarm_test
networks:
- proxy
environment:
DOMAIN: example.swarm-test.autonomic.zone # UPDATE ME
STACK_NAME: example_com # UPDATE ME
LETS_ENCRYPT_ENV: staging
DOMAIN: onion.swarm-test.autonomic.zone
STACK_NAME: onion
TRAEFIK_SERVICE: traefik.swarm-test.autonomic.zone
TORRC_CONFIG_VERSION: v1
# Also set any config versions from abra.sh
trigger:
branch:
+16 -5
View File
@@ -1,8 +1,19 @@
TYPE=onion-service
TYPE=onion
DOMAIN=onion-service.example.com
DOMAIN=onion.example.com
## Domain aliases
#EXTRA_DOMAINS=', `www.onion-service.example.com`'
# Disabled by default because this will backup the sensitive onion secret key
ENABLE_BACKUPS=false
LETS_ENCRYPT_ENV=production
# Include the full stack name of your traefik service plus "_app"
# For example, if your traefik is at domain traefik.example.com, set traefik_example_com_app
# TODO Can we automate setting this?
TRAEFIK_SERVICE=traefik_example_com_app
TRAEFIK_PORT=8080 # traefik port with the entrypoint to use
ONION_PORT=80 # port that will be exposed to clients of the onion service
## Enabling these reduces latency but you will NOT be anonymous
## You must enable NON_ANONYMOUS_MODE in order to enable SINGLE_HOP_MODE
## If you enable this, you can NEVER run the same service anonymously
# NON_ANONYMOUS_MODE=1
# SINGLE_HOP_MODE=1
+56 -12
View File
@@ -1,24 +1,68 @@
# onion-service
> One line description of the recipe
Creates a [Tor .onion service](https://community.torproject.org/onion-services) for your Co-op Cloud applications
<!-- metadata -->
* **Category**: Apps
* **Status**: 0
* **Image**: [`onion-service`](https://hub.docker.com/r/onion-service), 4, upstream
* **Healthcheck**: No
* **Backups**: No
* **Email**: No
* **Status**: 3
* **Image**: [`onimages/tor:alpine`](https://onionservices.torproject.org/apps/base/containers), 4, upstream
* **Healthcheck**: Yes
* **Backups**: Yes
* **Email**: N/A
* **Tests**: No
* **SSO**: No
* **SSO**: N/A
<!-- endmetadata -->
## Quick start
## Basic Usage
* `abra app new onion-service --secrets`
* `abra app config <app-name>`
* `abra app deploy <app-name>`
1. Set up Docker Swarm and [`abra`]
2. Deploy [`coop-cloud/traefik`]
3. `abra app new onion`
4. `abra app config <onion-app>` - You must at least set `$TRAEFIK_SERVICE` to the name of your traefik service. For example, if traefik's URL is `traefik.example.com`, this variable should be set to `traefik_example_com_app`
5. `abra app deploy <onion-app>`
6. Retrieve the hostname of the created onion service: `abra app cmd <onion-app> app show_hostname`
7. If your app's recipe supports an onion service, run `abra app config <app-name>` on the app you would like the onion service to point to, and set `ONION_DOMAIN` to the output of the previous command. You can use sub-domains, like `chat.xxx.onion`
For more, see [`docs.coopcloud.tech`](https://docs.coopcloud.tech).
## Backup and restore
Backups are disabled by default for this recipe because any meaningful backup will include the sensitive private key for your onion service. If this is stolen, an attacker could impersonate you to users of your service. To enable backups, set `ENABLE_BACKUPS=true`.
Fortunately, this key will not change unless you generate a new .onion, so regular automated backups are likely unnecessary; it should be perfectly fine to let backup bot take one snapshot to store in a secure location and then disable backups again.
Other useful data included in the backup includes the `authorized_clients` directory, so clients can still access your service after a restore.
After restoring a backup, make sure to run `abra app restart <onion-app> app` so Tor can advertise the restored hostname.
## Enabling client authorization
Onion services allow you to use client authorization to prevent just anyone from accessing your service without your permission. To set this up, you need to first [create a public/private keypair](https://community.torproject.org/onion-services/advanced/client-auth/) and then copy it to the `authorized_clients` folder in your app's HiddenServiceDir (in this case, `/var/lib/tor/traefik/authorized_clients`.
Assuming you followed the instructions from the linked tutorial to create your keypair, you can do this with `echo "descriptor:x25519:$(cat /tmp/k1.pub.key)" > pubkey.auth && abra app cp <onion-app> pubkey.auth /var/lib/tor/traefik/authorized_clients/1.auth`. You can replace `1.auth` with any descriptive name as long as it ends in `.auth`. You may see a couple `Operation not permitted` errors, but this is expected, and the key will have been successfully copied.
After copying the file, run `abra app restart <onion-app> app`
## Add support to another recipe
On a networking level, adding support for an onion service to your recipe is fairly simple. Add a file called `compose.onion.yml` to your recipe with the following lines:
```
services:
app:
labels:
- "traefik.http.routers.${STACK_NAME}-onion.rule=Host(`${ONION_DOMAIN}`)"
- "traefik.http.routers.${STACK_NAME}-onion.entrypoints=onion"
```
And, in `.env.sample`, add the following:
```
## Uncomment and set ONION_DOMAIN to enable onion service access
## Subdomains can also be used for ONION_DOMAIN, for example, chat.< ... >.onion
# COMPOSE_FILE="$COMPOSE_FILE:compose.onion.yml"
# ONION_DOMAIN=<56-char onion address>.onion
```
This creates a new traefik router for your recipe that recognizes the .onion domain requested by the client and routes requests to your application, bypassing forced SSL.
On the application level, there may sometimes be some tinkering required to get an app to truly work with an onion service. Common issues relate to apps themselves forcing SSL, or expecting a specific domain name.
+5
View File
@@ -1,2 +1,7 @@
# Set any config versions here
# Docs: https://docs.coopcloud.tech/maintainers/handbook/#manage-configs
export TORRC_CONFIG_VERSION=v1
show_hostname() {
cat /var/lib/tor/traefik/hostname
}
+31 -23
View File
@@ -1,39 +1,47 @@
---
services:
app:
image: nginx:1.27.5
image: containers.torproject.org/tpo/onion-services/onimages/tor:alpine
volumes:
- "tor:/var/lib/tor"
networks:
- proxy
deploy:
restart_policy:
condition: on-failure
labels:
- "traefik.enable=true"
- "traefik.http.services.${STACK_NAME}.loadbalancer.server.port=80"
- "traefik.http.routers.${STACK_NAME}.rule=Host(`${DOMAIN}`${EXTRA_DOMAINS})"
- "traefik.http.routers.${STACK_NAME}.entrypoints=web-secure"
- "traefik.http.routers.${STACK_NAME}.tls.certresolver=${LETS_ENCRYPT_ENV}"
## Edit the following line if you are using one, but not both, "Redirect" sections below
#- "traefik.http.routers.${STACK_NAME}.middlewares=${STACK_NAME}-redirectscheme,${STACK_NAME}-redirecthostname"
## Redirect from EXTRA_DOMAINS to DOMAIN
# - "traefik.http.middlewares.${STACK_NAME}-redirecthostname.redirectregex.regex=^http[s]?://([^/]*)/(.*)"
# - "traefik.http.middlewares.${STACK_NAME}-redirecthostname.redirectregex.replacement=https://${DOMAIN}/$${2}"
# - "traefik.http.middlewares.${STACK_NAME}-redirecthostname.redirectregex.permanent=true"
## Redirect HTTP to HTTPS
# - "traefik.http.middlewares.${STACK_NAME}-redirectscheme.redirectscheme.scheme=https"
# - "traefik.http.middlewares.${STACK_NAME}-redirectscheme.redirectscheme.permanent=true"
## When you're ready for release, run "abra recipe sync <name>" to set this
- "coop-cloud.${STACK_NAME}.version="
## Enable backups: https://docs.coopcloud.tech/maintainers/handbook/#how-do-i-configure-backuprestore
# - "backupbot.backup=true"
# - "backupbot.backup.path=/some/path"
- "traefik.enable=false"
- "coop-cloud.${STACK_NAME}.version=1.0.0+alpine"
- "backupbot.backup=${ENABLE_BACKUPS}"
- "backupbot.backup.volumes.tor.path=traefik"
configs:
- source: torrc_config
target: /etc/tor/torrc
mode: 0644
command:
- "-f"
- "/etc/tor/torrc"
# Connects to Tor conrol port using cookie authentication and verifies bootstrapping completed
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
test: test -n "$$(
printf '%s\n%s\n%s\n'
"AUTHENTICATE $$(xxd -p -c 32 /var/lib/tor/.tor/control_auth_cookie | tr -d "\n")"
"GETINFO status/bootstrap-phase"
"QUIT" |
nc 127.0.0.1 9051 |
grep "PROGRESS=100")"
interval: 30s
timeout: 10s
retries: 10
start_period: 1m
configs:
torrc_config:
name: ${STACK_NAME}_torrc_config_${TORRC_CONFIG_VERSION}
file: torrc.tmpl
template_driver: golang
networks:
proxy:
external: true
volumes:
tor:
+16
View File
@@ -0,0 +1,16 @@
RunAsDaemon 0
# If there's a use case to enable client functionality we can put this behind a config
SocksPort 0
ControlPort 127.0.0.1:9051
CookieAuthentication 1
HiddenServiceDir /var/lib/tor/traefik
HiddenServicePort {{ env "ONION_PORT" }} {{ env "TRAEFIK_SERVICE" }}:{{ env "TRAEFIK_PORT" }}
{{ if eq (env "NON_ANONYMOUS_MODE") "1" -}}
HiddenServiceNonAnonymousMode 1
{{ end -}}
{{ if eq (env "SINGLE_HOP_MODE") "1" -}}
HiddenServiceSingleHopMode 1
{{ end -}}