feat(deploy): device client-CA + issuance/revocation + frp/nginx mTLS templates (V2/V7)
Committable VPS artifacts for the native reverse-tunnel (no live-ops, no secrets):
- gen-device-ca.sh: EC P-256 self-signed device CA (CA:TRUE pathlen:0, keyCertSign+cRLSign),
openssl ca db + empty CRL, idempotent, --kind frp-client scaffolds the control-channel CA.
- issue-device-cert.sh: P-256 leaf, EKU=clientAuth only, 825d, emits .p12 (-legacy) + .pem/.key/.fullchain,
copy_extensions=none prevents CSR-injected serverAuth/CA:TRUE; CN traversal guard.
- revoke-device.sh: openssl ca revoke -> regenerate crl.pem (revocation enforced via -crl_check).
- frp/{frps.toml.example,frpc.example.toml}: control-channel mTLS+token, disableCustomTLSFirstByte=true.
- nginx/frp-mtls.conf: :8470 TLS-term, ssl_verify_client on + device-CA + ssl_crl, WS-upgrade proxy to :7080.
- nginx/stream-sni-additions.md: the two additive SNI map lines (merge-only, coexistence-safe).
Verified: gen->issue->openssl verify->revoke->CRL chain green in a tmp dir (OpenSSL 3.0.18).
This commit is contained in:
68
deploy/scripts/revoke-device.sh
Executable file
68
deploy/scripts/revoke-device.sh
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# RELAY-PHASE1 · Native mTLS tunnel · V2 — Revoke a device/frp-client leaf, regenerate the CRL.
|
||||
#
|
||||
# STAGING TEMPLATE. Marks a previously-issued leaf revoked in the CA database and regenerates
|
||||
# <CA_DIR>/crl.pem, which nginx :8470 serves via `ssl_crl` (V4) — so the revoked device is rejected at
|
||||
# the TLS handshake fleet-wide the moment nginx reloads. This script does NOT touch the remote VPS: it
|
||||
# only prints the reload hint; run the reload yourself on the box after copying the CRL.
|
||||
#
|
||||
# Requires the `openssl ca` database created by deploy/scripts/gen-device-ca.sh (index.txt/serial/…).
|
||||
#
|
||||
# Usage: revoke-device.sh <LEAF_CERT.pem> # revoke by the issued cert file
|
||||
# or revoke-device.sh --serial <HEX_SERIAL> # revoke by serial (uses <CA_DIR>/newcerts/<serial>.pem)
|
||||
#
|
||||
# Config via ENV or flags (no secrets):
|
||||
# CA_DIR which CA to revoke against (default /etc/relay/device-ca) — or --ca-dir <path>
|
||||
#
|
||||
# Verify (syntax): bash -n deploy/scripts/revoke-device.sh
|
||||
set -euo pipefail
|
||||
|
||||
CA_DIR="${CA_DIR:-/etc/relay/device-ca}"
|
||||
LEAF=""
|
||||
SERIAL=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--ca-dir) CA_DIR="${2:?--ca-dir needs a value}"; shift 2 ;;
|
||||
--serial) SERIAL="${2:?--serial needs a value}"; shift 2 ;;
|
||||
-h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
-*) echo "FATAL: unknown flag '$1' (see --help)" >&2; exit 2 ;;
|
||||
*) if [[ -z "${LEAF}" ]]; then LEAF="$1";
|
||||
else echo "FATAL: too many positional args at '$1'" >&2; exit 2; fi; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
command -v openssl >/dev/null 2>&1 || { echo "FATAL: openssl not found on PATH." >&2; exit 3; }
|
||||
[[ -f "${CA_DIR}/openssl.cnf" ]] || { echo "FATAL: no CA db in ${CA_DIR} (run gen-device-ca.sh first)." >&2; exit 3; }
|
||||
|
||||
# ---- resolve the target leaf (by path or by serial) to an absolute path --------------------------
|
||||
if [[ -n "${SERIAL}" ]]; then
|
||||
# Boundary-validate the serial (used to build a path); openssl stores newcerts/<UPPERCASE-HEX>.pem.
|
||||
[[ "${SERIAL}" =~ ^[0-9A-Fa-f]+$ ]] || { echo "FATAL: --serial must be hex." >&2; exit 2; }
|
||||
LEAF="${CA_DIR}/newcerts/$(echo "${SERIAL}" | tr '[:lower:]' '[:upper:]').pem"
|
||||
fi
|
||||
[[ -n "${LEAF}" ]] || { echo "FATAL: pass a <LEAF_CERT.pem> or --serial <HEX>." >&2; exit 2; }
|
||||
[[ -f "${LEAF}" ]] || { echo "FATAL: leaf cert not found: ${LEAF}" >&2; exit 3; }
|
||||
LEAF="$(cd "$(dirname "${LEAF}")" && pwd)/$(basename "${LEAF}")"
|
||||
|
||||
REVOKED_SERIAL="$(openssl x509 -in "${LEAF}" -noout -serial | cut -d= -f2)"
|
||||
echo "== Revoking serial ${REVOKED_SERIAL} against CA ${CA_DIR} =="
|
||||
|
||||
# ---- revoke + regenerate the CRL (cd in so openssl.cnf's dir=. resolves to CA_DIR) ----------------
|
||||
(
|
||||
cd "${CA_DIR}"
|
||||
openssl ca -config openssl.cnf -revoke "${LEAF}"
|
||||
openssl ca -config openssl.cnf -gencrl -out crl.pem
|
||||
)
|
||||
chmod 644 "${CA_DIR}/crl.pem"
|
||||
|
||||
cat <<SUMMARY
|
||||
|
||||
Revoked serial ${REVOKED_SERIAL}. Updated CRL: ${CA_DIR}/crl.pem
|
||||
Confirm: openssl crl -in ${CA_DIR}/crl.pem -noout -text | grep -A1 'Serial Number'
|
||||
|
||||
RELOAD HINT (run ON THE VPS after copying the new crl.pem into place — NOT executed here):
|
||||
nginx -t && nginx -s reload
|
||||
Until nginx reloads the CRL, the revoked cert is still accepted.
|
||||
SUMMARY
|
||||
Reference in New Issue
Block a user