390 lines
17 KiB
Markdown
390 lines
17 KiB
Markdown
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 Docker‑in‑Docker service and
|
||
waits for each container’s 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.
|