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:
31
deploy/frp/frpc.example.toml
Normal file
31
deploy/frp/frpc.example.toml
Normal file
@@ -0,0 +1,31 @@
|
||||
# frpc.toml — per-host client (Native mTLS tunnel · V7 / M1 / S1).
|
||||
#
|
||||
# Copy to the LOCAL host that runs the base app, fill the <PLACEHOLDERS>, chmod 600.
|
||||
# TEMPLATE ONLY — NO REAL SECRETS committed. frpc dials OUT to the VPS :443; nothing is opened
|
||||
# inbound on the local host.
|
||||
#
|
||||
# frps ≥ v0.52 required (`disableCustomTLSFirstByte` exists ≥ v0.50) — it makes frpc send a real TLS
|
||||
# ClientHello first byte (0x16) so the VPS nginx `ssl_preread` stream can SNI-route it (PLAN M1 #6).
|
||||
|
||||
serverAddr = "8.138.1.192"
|
||||
serverPort = 443
|
||||
|
||||
auth.method = "token"
|
||||
auth.token = "<FRP_TOKEN>" # same token as frps.toml (/etc/relay/secrets.env)
|
||||
|
||||
# --- control-channel TLS: present THIS host's frp-client cert to frps; verify the frps server cert ---
|
||||
transport.tls.enable = true
|
||||
transport.tls.serverName = "frp.terminal.yaojia.wang" # SNI the stream routes to frps :7000
|
||||
transport.tls.disableCustomTLSFirstByte = true # CRITICAL for ssl_preread (real ClientHello first)
|
||||
transport.tls.certFile = "<host-frp-client.cert.pem>" # issued by gen-device-ca.sh --kind frp-client + issue-device-cert.sh
|
||||
transport.tls.keyFile = "<host-frp-client.key.pem>" # (0600)
|
||||
transport.tls.trustedCaFile = "<frps-ctrl-ca.cert.pem>" # CA that signed frps-ctrl.cert.pem (V1)
|
||||
|
||||
loginFailExit = false # keep retrying if the VPS/frps is briefly down
|
||||
|
||||
[[proxies]]
|
||||
name = "<name>" # unique per host; frps rejects duplicate subdomains
|
||||
type = "http"
|
||||
localIP = "127.0.0.1" # base app must bind loopback (BIND_HOST=127.0.0.1)
|
||||
localPort = 3000
|
||||
subdomain = "<name>" # reachable at https://<name>.terminal.yaojia.wang
|
||||
Reference in New Issue
Block a user