Add optional stratum TLS via stunnel sidecar

New 'tls' union config (disabled by default) spins up an
stunnel4 process inside the container that terminates TLS on
a configurable port (3334 by default) and forwards decrypted
stratum traffic to 127.0.0.1:${STRATUM_PORT}.

Cert is self-signed, generated once on first start with a
10-year validity and persisted at /root/.kamado/tls/ so the
fingerprint stays stable across restarts. SHA-256 fingerprint
is printed to container logs on each startup so users can
pin it on their miners. Miners must connect with
verification disabled (no CA trust chain for a private pool).

Runtime image grows by ~3MB for stunnel4 + openssl. The
supervisor loop now waits on three PIDs and tears all of them
down together if any one exits.
This commit is contained in:
satoshi
2026-04-14 11:21:45 +03:00
parent 74387b8c45
commit cb739867cc
4 changed files with 85 additions and 9 deletions
+2 -2
View File
@@ -73,7 +73,7 @@ FROM debian:bookworm-slim AS runtime
ARG ARCH ARG ARCH
ARG YQ_VERSION=v4.44.3 ARG YQ_VERSION=v4.44.3
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
tini ca-certificates curl libzmq5 \ tini ca-certificates curl libzmq5 stunnel4 openssl \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# yq for reading StartOS config.yaml in the entrypoint # yq for reading StartOS config.yaml in the entrypoint
@@ -95,6 +95,6 @@ COPY docker_entrypoint.sh /usr/local/bin/docker_entrypoint.sh
RUN chmod +x /usr/local/bin/docker_entrypoint.sh \ RUN chmod +x /usr/local/bin/docker_entrypoint.sh \
&& mkdir -p /run/ckpool /var/log/ckpool /var/lib/kamado && mkdir -p /run/ckpool /var/log/ckpool /var/lib/kamado
EXPOSE 3333 8080 EXPOSE 3333 3334 8080
WORKDIR /root WORKDIR /root
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/docker_entrypoint.sh"] ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/docker_entrypoint.sh"]
+46 -5
View File
@@ -43,6 +43,8 @@ export MAXDIFF=$(q '.maxdiff // 0')
export DROPIDLE=$(q '.dropidle // 0') export DROPIDLE=$(q '.dropidle // 0')
LOG_LEVEL=$(q '.log-level // "info"') LOG_LEVEL=$(q '.log-level // "info"')
ZMQ_ENABLED=$(q '.zmq-enabled // true') ZMQ_ENABLED=$(q '.zmq-enabled // true')
TLS_ENABLED=$(q '.tls.enabled // "disabled"')
TLS_PORT=$(q '.tls.port // 3334')
export LOGDIR=/var/log/ckpool export LOGDIR=/var/log/ckpool
export SOCKET_DIR=/run/ckpool export SOCKET_DIR=/run/ckpool
@@ -99,17 +101,56 @@ echo "kamado-entrypoint: starting kamado-api"
/usr/local/bin/kamado-api & /usr/local/bin/kamado-api &
API_PID=$! API_PID=$!
# Optional TLS stratum via stunnel sidecar.
STUNNEL_PID=""
if [[ "${TLS_ENABLED}" == "enabled" ]]; then
TLS_DIR=/root/.kamado/tls
CERT="${TLS_DIR}/stratum.pem"
mkdir -p "${TLS_DIR}"
if [[ ! -f "${CERT}" ]]; then
echo "kamado-entrypoint: generating self-signed stratum TLS cert"
openssl req -x509 -newkey rsa:2048 -sha256 -nodes \
-keyout "${TLS_DIR}/stratum.key" \
-out "${TLS_DIR}/stratum.crt" \
-days 3650 \
-subj "/CN=kamado-pool-stratum" >/dev/null 2>&1
cat "${TLS_DIR}/stratum.key" "${TLS_DIR}/stratum.crt" > "${CERT}"
chmod 600 "${TLS_DIR}/stratum.key" "${CERT}"
fi
FINGERPRINT=$(openssl x509 -in "${TLS_DIR}/stratum.crt" -noout -fingerprint -sha256 | cut -d= -f2)
echo "kamado-entrypoint: stratum TLS SHA256 fingerprint: ${FINGERPRINT}"
STUNNEL_CONF=/etc/stunnel/stratum.conf
mkdir -p /etc/stunnel
cat > "${STUNNEL_CONF}" <<EOF
foreground = yes
pid =
output = /dev/stdout
debug = 4
[stratum]
accept = 0.0.0.0:${TLS_PORT}
connect = 127.0.0.1:${STRATUM_PORT}
cert = ${CERT}
EOF
echo "kamado-entrypoint: starting stunnel on :${TLS_PORT} -> :${STRATUM_PORT}"
/usr/bin/stunnel4 "${STUNNEL_CONF}" &
STUNNEL_PID=$!
fi
term() { term() {
echo "kamado-entrypoint: SIGTERM — shutting down" echo "kamado-entrypoint: SIGTERM — shutting down"
kill -TERM "${API_PID}" "${CKPOOL_PID}" 2>/dev/null || true kill -TERM "${API_PID}" "${CKPOOL_PID}" ${STUNNEL_PID:-} 2>/dev/null || true
wait "${API_PID}" "${CKPOOL_PID}" 2>/dev/null || true wait "${API_PID}" "${CKPOOL_PID}" ${STUNNEL_PID:-} 2>/dev/null || true
exit 0 exit 0
} }
trap term TERM INT trap term TERM INT
wait -n "${CKPOOL_PID}" "${API_PID}" # shellcheck disable=SC2086
wait -n ${CKPOOL_PID} ${API_PID} ${STUNNEL_PID:-}
EXIT_CODE=$? EXIT_CODE=$?
echo "kamado-entrypoint: one of ckpool/kamado-api exited (${EXIT_CODE}), stopping the other" echo "kamado-entrypoint: a supervised process exited (${EXIT_CODE}), stopping the rest"
kill -TERM "${API_PID}" "${CKPOOL_PID}" 2>/dev/null || true kill -TERM "${API_PID}" "${CKPOOL_PID}" ${STUNNEL_PID:-} 2>/dev/null || true
wait || true wait || true
exit "${EXIT_CODE}" exit "${EXIT_CODE}"
+5 -1
View File
@@ -11,7 +11,11 @@ Kamado is a solo Bitcoin mining pool built on a patched fork of CKPool-solo, wit
## Connecting miners ## Connecting miners
Kamado's stratum server listens on TCP port **3333** inside the container. **StartOS 0.3.x does not forward raw TCP ports on the LAN interface**, so you have two options to reach stratum from miners on your local network: Kamado's plaintext stratum server listens on TCP port **3333** inside the container (configurable). If you enable **Stratum TLS** in the config, an stunnel sidecar also terminates TLS on port **3334** (configurable) and forwards the decrypted traffic to the plaintext stratum locally. The TLS certificate is generated once on first start and persisted at `/root/.kamado/tls/stratum.crt` — the SHA-256 fingerprint is printed to the container logs so you can pin it on your miners.
Because the cert is self-signed, miners must connect with certificate verification disabled (`stratum+ssl://`, plus whatever skip-verify / insecure flag your firmware exposes).
**StartOS 0.3.x does not forward raw TCP ports on the LAN interface**, so you have two options to reach stratum — plaintext or TLS — from miners on your local network:
- **Router port-forward**: Forward an external port on your router directly to your StartOS server's LAN IP on port 3333 and point miners at that. - **Router port-forward**: Forward an external port on your router directly to your StartOS server's LAN IP on port 3333 and point miners at that.
- **simpleproxy on a second host**: Run `simpleproxy -L 3333 -R <startos-lan-ip>:3333` on any always-on LAN host and point miners at that host. - **simpleproxy on a second host**: Run `simpleproxy -L 3333 -R <startos-lan-ip>:3333` on any always-on LAN host and point miners at that host.
+32 -1
View File
@@ -85,12 +85,43 @@ export const getConfig: T.ExpectedExports.getConfig = compat.getConfig({
"type": "number", "type": "number",
"name": "Stratum Port", "name": "Stratum Port",
"description": "description":
"TCP port the stratum server listens on inside the container. Defaults to 3333. Change this if you are running simpleproxy (or another TCP forwarder) and want Kamado to listen on a different port — useful when another service on the same host already uses 3333.", "TCP port the plaintext stratum server listens on inside the container. Defaults to 3333. Change this if you are running simpleproxy (or another TCP forwarder) and want Kamado to listen on a different port.",
"nullable": false, "nullable": false,
"default": 3333, "default": 3333,
"range": "[1,65535]", "range": "[1,65535]",
"integral": true, "integral": true,
}, },
"tls": {
"type": "union",
"name": "Stratum TLS",
"description":
"Accept stratum connections over TLS via an stunnel sidecar. A self-signed certificate is generated on first start and persisted across restarts — miners must connect with TLS verification disabled (most firmware exposes this as 'stratum+ssl://' with a skip-verify or insecure flag).",
"tag": {
"id": "enabled",
"name": "TLS Mode",
"description": "Disable or enable TLS termination in front of stratum.",
"variant-names": {
"disabled": "Disabled",
"enabled": "Enabled (stunnel sidecar)",
},
},
"default": "disabled",
"variants": {
"disabled": {},
"enabled": {
"port": {
"type": "number",
"name": "TLS Stratum Port",
"description":
"TCP port stunnel listens on for TLS stratum connections. Forwards decrypted traffic to the plaintext stratum port locally.",
"nullable": false,
"default": 3334,
"range": "[1,65535]",
"integral": true,
},
},
},
},
"startdiff": { "startdiff": {
"type": "number", "type": "number",
"name": "Starting Difficulty", "name": "Starting Difficulty",