Files
auto-ci 21718042b6
Configuration validation / lint (push) Has been cancelled
Configuration validation / smoke (push) Has been cancelled
Harden release defaults and validation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-03 19:29:00 -04:00

390 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Welcome to darkproxy...
## Production hardening defaults
The stack now uses safer defaults for production:
* Service admin/web passwords are loaded from Docker secrets instead of inline values.
* Admin/monitoring ports are bound to `127.0.0.1` on the host.
* The published SOCKS entrypoint now requires credentials from a Docker secret.
* CoreDNS recursive resolution for the catch-all zone is restricted to trusted/internal networks.
* Pi-hole ARP cache parsing is disabled in Docker (`FTLCONF_database_network_parseARPcache=false`) to prevent recurring netlink `neigh`/ARP errors.
* Fixed `10.5.0.x` service IPs are retained for the stack's internal routing/DNS design, but host/LAN-specific values now default to portable settings and can be overridden with environment variables.
* Optional integrations that need operator-specific credentials, such as Tailscale, are no longer enabled with placeholder defaults in the base stack.
Before starting in production, set these secret files:
```sh
printf 'YOUR_STRONG_PIHOLE_PASSWORD\n' > secrets/pihole_webpassword.txt
printf 'proxyuser:REPLACE_WITH_A_LONG_RANDOM_PASSWORD\n' > secrets/3proxy_users.txt
printf 'REPLACE_WITH_A_LONG_RANDOM_NAMECOIN_RPC_PASSWORD\n' > secrets/namecoin_rpc_password.txt
```
Optional Tailscale note:
* The `tailscale` service is now behind the Compose `tailscale` profile.
* It is not started by default.
* To enable it, export a real auth key and start that profile explicitly:
```sh
export TS_AUTHKEY=tskey-your-real-key
docker compose --profile tailscale up -d tailscale
```
Release default note for `.zil`:
* `zildns` now uses explicit public resolvers inside its container so its Zilliqa RPC lookups do not depend on Docker's embedded DNS path.
* This change is limited to the `zildns` container; the stack's internal fixed `10.5.0.x` service IP design remains unchanged.
SOCKS auth note:
* `dark3proxy` now reads credentials from `secrets/3proxy_users.txt`.
* Each non-comment line in that secret must be `username:password`.
* If you intentionally want an internal-only unauthenticated proxy, set `PROXY_REQUIRE_AUTH=false` for `dark3proxy` and remove the public `2000:1080` port mapping.
* The router now defaults to **no auth bypass CIDRs**. If you want trusted subnets to skip SOCKS auth, set `ROUTER_NO_AUTH_CIDRS` in your shell or `.env`, for example `ROUTER_NO_AUTH_CIDRS=192.168.1.0/24`.
Pi-hole note: disabling ARP parsing avoids noisy `Failed to read ARP cache`
messages in containerized setups where neighbor-table netlink operations are not
supported. This only affects Pi-hole's network-table enrichment, not DNS
blocking/forwarding behavior.
This container runs the following vontainers in the stack.
dark3proxy - SOCKS5/http proxy with hostname-preserving router
- DNS
- pihole - ad blocking and metrics
- PopuraDNS - non-recursive DNS forwards requests to the following:
Alfis - Blockchain based yggdrasil resolver
EmerCoin - Resolves EMC domains
UnBound - Recursive DNS for Clearnet domains
* .onion and .i2p domain names are now resolved via the stack (see DNS section below)
- I2PD over Yggdrasil container (`container_name: darki2p`)
* Yggdrasil IP network stack
* Socks5 server for I2P
- Tor container
## SOCKS ports on host
Public proxy access is exposed on a single host port:
* `<host-ip>:2000` -> `dark3proxy` SOCKS (`dark3proxy:1080`), externally reachable.
- Intended as the single client entrypoint for clearnet, onion, and Ygg/I2P routing rules.
Local admin endpoint:
* `127.0.0.1:2002` -> 3proxy admin (`dark3proxy:8161`), host-local only.
* `127.0.0.1:2003` -> Pi-hole web UI (`darkpihole:80`), host-local by default.
* `127.0.0.1:2004` -> status dashboard (`darkstatus:8080`), host-local by default.
These host-local admin bindings can be overridden with environment variables when
needed:
```sh
export PIHOLE_WEB_BIND_HOST=192.168.1.31
export PIHOLE_WEB_BIND_PORT=2003
export STATUS_DASHBOARD_BIND_HOST=192.168.1.31
export STATUS_DASHBOARD_BIND_PORT=2004
```
Router internals for `:2000`:
* Front-end process: `3proxy/socks_router.py` (SOCKS5), keeps destination hostnames intact.
* Upstream dispatch:
- `*.onion` -> Tor SOCKS (`10.5.0.7:9050`)
- `*.i2p` -> i2pd SOCKS (`10.5.0.2:4447`)
- `*.ygg`, `*.meshname`, `*.meship`, and `200::/7` -> local Ygg-guarded SOCKS (`127.0.0.1:1085`)
- all other targets -> Tor SOCKS (`10.5.0.7:9050`)
* The primary 3proxy instance still enforces ACL/parent rules internally, with SOCKS moved to internal port `1088` to avoid collision with the router front-end.
Additional non-proxy endpoint:
* `127.0.0.1:2006/udp` -> Tor DNSPort (`darktor:9053`).
Quick checks:
```sh
# Onion over the only published proxy port
curl --proxy-user proxyuser:REPLACE_WITH_A_LONG_RANDOM_PASSWORD --socks5-hostname <host-ip>:2000 -I http://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion
# Clearnet over the only published proxy port
curl --proxy-user proxyuser:REPLACE_WITH_A_LONG_RANDOM_PASSWORD --socks5-hostname <host-ip>:2000 -I https://example.com
```
The system offers a DNS that resolves many darknet and clearnet IP's. The
stack now handles:
Local resolution model (important):
* DNS queries are resolved inside this Docker stack on your host (`pihole -> CoreDNS -> local resolver services`).
* The stack does not forward DNS queries to public DNS providers for supported darknet/blockchain TLDs.
* Some namespaces still require blockchain RPC data sources for authoritative records (see limitations below).
* `*.onion` forwarded to Tor's DNSPort running in the `darktor` container at
10.5.0.7:9053.
* `*.i2p` resolved by local `i2pdns` bridge service (`10.5.0.16:53`).
- `i2pdns` synthesizes deterministic private A records and writes IP<->hostname mappings.
- `dark3proxy` consumes that map so synthetic IP requests are translated back to `.i2p` hostnames and routed to i2pd SOCKS (`10.5.0.2:4447`).
You can query these directly from any container on the `darkproxy` network, for
example:
```sh
# Tor .onion domains
docker run --rm --network darkproxy infoblox/dig dig @10.5.0.4 facebookcorewwwi.onion
# I2P .i2p domains
docker run --rm --network darkproxy infoblox/dig dig @10.5.0.4 stats.i2p
# Ethereum Name Service (.eth) via local ensdns service
docker run --rm --network darkproxy infoblox/dig dig @10.5.0.4 vitalik.eth
# ENS address/content hash records (TXT)
docker run --rm --network darkproxy infoblox/dig dig @10.5.0.4 TXT vitalik.eth
# Other supported TLDs: .bit, .alt, .loki, .zil, .web3, .exit, .onion4, .onion6
docker run --rm --network darkproxy infoblox/dig dig @10.5.0.4 example.bit
```
> **Note:** the CoreDNS service at `10.5.0.4` enforces an ACL on the root zone.
> Recursive lookups are limited to loopback, RFC1918/private ranges, and the
> Yggdrasil/internal subnets used by this stack.
### Host-side quick tests (no extra images)
Run DNS queries from your host by executing `dig` in the existing `darkpihole`
container:
```sh
docker exec darkpihole dig @10.5.0.4 TXT vitalik.eth +short
docker exec darkpihole dig @10.5.0.4 A vitalik.eth +short
```
Expected behavior:
* `TXT` includes `address=` and (if present) `contenthash=`.
* `A` returns a CNAME fallback like `<name>.eth.limo.`.
## Supported TLDs and resolvers
The stack now provides DNS resolution for a comprehensive set of alternative TLDs:
| TLD | Resolver | Purpose |
|-----|----------|---------|
| `.onion`, `.exit`, `.onion4`, `.onion6` | Tor DNSPort (10.5.0.7:9053) | Tor hidden services |
| `.i2p` | i2pdns bridge (10.5.0.16:53) + i2pd SOCKS (10.5.0.2:4447) | I2P eepsite names |
| `.eth` | ensdns (10.5.0.11) via CoreDNS | Ethereum Name Service (local resolver) |
| `.bit` | namecoindns (10.5.0.12) via CoreDNS | Namecoin names (local resolver service) |
| `.alt` | local unbound (10.5.0.5) via CoreDNS | Alternative DNS root (local-only fallback mode) |
| `.loki` | local lokinet (10.5.0.14) via CoreDNS | Lokinet privacy network (no Cloudflare/public DNS forwarder dependency) |
| `.zil` | zildns (10.5.0.15) via CoreDNS | Zilliqa blockchain (local resolver service; no Cloudflare DNS dependency) |
| `.web3` | local unbound (10.5.0.5) via CoreDNS | General blockchain namespace (local-only fallback mode) |
| Alfis TLDs | Alfis (10.5.0.3) | `.anon`, `.btn`, `.conf`, `.index`, `.merch`, `.mirror`, `.mob`, `.screen`, `.srv`, `.ygg` |
| EmerCoin TLDs | EmerCoin (10.5.0.9) | `.emc`, `.coin`, `.lib`, `.bazar`, `.enum` |
| OpenNIC TLDs | local unbound (10.5.0.5) via CoreDNS | `.bbs`, `.chan`, `.cyb`, `.dyn`, `.epic`, `.geek`, `.gopher`, `.indy`, `.libre`, `.neo`, `.null`, `.o`, `.oss`, `.oz`, `.parody`, `.pirate`, `.fur`, `.ku`, `.rm`, `.te`, `.ti`, `.uu`, `.ko` (local-only fallback mode) |
| Clearnet | Unbound (10.5.0.5) | All other domains via recursive resolution |
## Local-only DNS mode limitations
Darkproxy now avoids direct public DNS forwarders for `.alt`, `.zil`, `.web3`, OpenNIC TLDs, and Lokinet fallback DNS.
Resolution still happens locally in this stack. The remaining external dependency
surface is blockchain RPC/data access for record lookup, not public DNS
forwarding.
Important limitations still apply for authoritative data:
* `.eth` requires an Ethereum JSON-RPC backend. By default this stack uses `https://ethereum-rpc.publicnode.com` in `ensdns`.
* `.alt`, OpenNIC TLDs, and `.web3` are not in the ICANN root. In local-only fallback mode they are sent to local Unbound, so they usually return `NXDOMAIN` unless you provide your own authoritative/local resolver backend for those namespaces.
* `.zil` is resolved through local `zildns`, which queries ZNS using `https://api.zilliqa.com` by default. This is a Zilliqa RPC lookup path (on-chain resolution), not a Cloudflare DNS forwarder path. You can override the RPC endpoint with `ZILDNS_ZNS_URL` in `docker-compose.yml`.
* `zildns` supports `ZILDNS_CACHE_SECONDS` (record cache duration) and `ZILDNS_PREWARM_DOMAIN` (startup warm-up query) to reduce first-query latency.
* For browser convenience, `A` lookups may return a CNAME to a public IPFS gateway when a `.zil` content hash exists. That gateway alias is separate from DNS resolution itself.
* `.loki` is resolved by local Lokinet, and Lokinet fallback DNS points to local Unbound (no Cloudflare/public DNS forwarder dependency).
## Configuration validation and drift tests
A simple automation is included to help detect accidental changes to the
compose and DNS configuration:
1. **Local check** run `scripts/validate-config.sh` (or its PowerShell equivalent).
- The script requires `docker` to be installed; it will generate a normalized
manifest and compare it against `docker-compose.lock.yml`.
- If the baseline file does not exist it will be created; commit the file after
review.
- CoreDNS syntax is validated with the repo's compiled CoreDNS image so the
custom `meshname` and `meship` plugins are available during the check.
- Unbound syntax is validated with the repo's Unbound image so the chrooted
runtime layout matches the stack.
2. **CI workflow** `.github/workflows/config-validation.yml` is triggered on
pushes or pull requests affecting `docker-compose.yml` or `PopuraDNS/`.
The job executes the same `docker compose config` comparison and runs the
Corefile check inside a container.
3. **Updating the baseline** when intentional changes are made to the
compose file, regenerate the baseline:
```sh
docker compose config > docker-compose.lock.yml
scripts/validate-config.sh
git add docker-compose.lock.yml
```
These steps ensure that configuration drift is caught early and reviewers can
see what has changed.
## Local ENS (.eth) client
If you want to resolve Ethereum ENS names locally from the host, use the
standalone client script:
```sh
python3 -m venv .venv-ens
source .venv-ens/bin/activate
pip install -r scripts/requirements-ens.txt
python3 scripts/resolve_ens.py vitalik.eth --pretty
```
Options:
* `--rpc <url>` to set a custom Ethereum JSON-RPC endpoint.
* `--include-text` to include common ENS text records.
Example:
```sh
python3 scripts/resolve_ens.py ens.eth --include-text --pretty
```
## Local Namecoin (.bit) resolver (true on-chain)
`.bit` lookups are routed through a local blockchain-backed resolver path:
* CoreDNS forwards `bit.:53` to `10.5.0.12:53` (`namecoindns`).
* `namecoindns` queries local `namecoind` JSON-RPC (`10.5.0.13:8336`).
* `namecoind` syncs the Namecoin blockchain and serves on-chain records.
Quick test:
```sh
docker exec darkpihole dig @10.5.0.4 A id.bit
docker exec darkpihole dig @10.5.0.4 TXT id.bit
```
If your environment returns `NXDOMAIN` for `.bit`, configure Namecoin-capable
records in Namecoin itself and wait for local `namecoind` sync completion.
> **Important:** initial blockchain sync can take significant time. During sync,
> `.bit` responses may be empty or incomplete.
## Local Lokinet (.loki) resolver
`.loki` lookups are resolved by a local Lokinet daemon path:
* CoreDNS forwards `loki.:53` to `10.5.0.14:53` (`lokinet`).
* `lokinet` resolves `.loki` names through the Lokinet network.
Quick test:
```sh
LOKI_ADDR="$(docker logs darklokinet 2>&1 | sed -n 's/.*endpoint:\([a-z0-9]\{52\}\.loki\).*/\1/p' | tail -n1)"
docker exec darkpihole dig @10.5.0.4 A "$LOKI_ADDR"
```
> **Note:** Lokinet may need a short bootstrap period after startup before
> `.loki` names resolve. Older sample names such as `oxen.loki` are no longer a
> reliable health check; resolving the container's self-published `.loki`
> address is a better validation of the local Lokinet path.
## Monitoring and stack status
Two lightweight tools are included for visibility:
* `3proxy_exporter` exposes 3proxy counters in Prometheus format for external
scraping.
* `status_dashboard` serves a local-only status page for the whole stack,
including container state, probe results, proxy usernames, and recent logs.
### how it works
* `3proxy` is configured with `monitor -p6800`, which sets up a plain-text
status socket. Samples look like `PROXY CONNS 12` or `SOCKS IN 345`.
* `monitor/exporter.py` polls that socket every few seconds and exports
the counters on HTTP port **9100** in Prometheus format.
* `monitor/status_dashboard.py` talks to the local Docker socket, runs targeted
DNS and SOCKS checks, and serves both HTML and JSON for stack status.
### building & running
The monitoring utilities live in `monitor/`; build the images and start the
stack:
```sh
# build the exporter and status dashboard
docker-compose build 3proxy_exporter status_dashboard
docker-compose up -d
```
By default the status page is available on **http://127.0.0.1:2004/** and the
raw JSON is available on **http://127.0.0.1:2004/api/status**. If you override
the bind host/port via `STATUS_DASHBOARD_BIND_HOST` or
`STATUS_DASHBOARD_BIND_PORT`, use that address instead.
If you want external dashboards or alerts, point a Prometheus-compatible
collector at the exporter and use metrics such as `proxy_conns` and
`proxy_bytes_in`.
### tips
* Alerts can be added in your external Prometheus rules, e.g. fire when
`proxy_conns` exceeds a threshold for several minutes.
* You can still query the monitor port directly with `nc`; nothing in the
proxy depends on the exporter.
* The status dashboard can show logs and configured proxy usernames, so keep it
behind a trusted admin network or add your own access controls before
exposing it broadly.
* The `scripts/validate-config.sh` script warns if a 3proxy config lacks a
`monitor` line.
## Production readiness checklist
Make sure you never commit real secrets. The `secrets/` directory is now
included in `.gitignore`; both timezone and Yggdrasil key files live there and
are mounted into containers at runtime.
Before deploying to a live environment, run the validation script and resolve
any warnings or errors. The script covers:
* YAML syntax and drift against the committed baseline
* CoreDNS/PopuraDNS Corefile syntax
* Unbound configuration syntax
* Presence of required secret files
* Portable host-local defaults for Pi-hole, the status dashboard, and router auth bypass CIDRs
* Simple 3proxy configuration sanity (presence of `socks`/`proxy` rules)
* Detection of mutable `:latest` image tags in deployment compose files
* Verification that critical resolver/backend services keep explicit healthchecks
* Live CoreDNS smoke checks for `.onion`, `.i2p`, `.eth`, and `.zil`
* Verification that Tailscale stays opt-in and does not ship a placeholder auth key
* Verification that restart policies exist
The GitHub Actions workflow also includes a **smoke test** job that spins up
all services in `docker-compose.yml` using a DockerinDocker service and
waits for each containers healthcheck to report `healthy`. This gives an
additional layer of confidence that the stack can be built and started
successfully.
You can also extend the script with additional service-specific checks such as
running `docker run ...` commands to exercise the images, scanning the
`3proxy` config more deeply, or linting other mounted configuration files.
Run the script locally via `./scripts/validate-config.sh` (or
`.scripts/validate-config.ps1` on Windows) and review the output carefully.
CI will execute the same validations automatically on pull requests.