A private single-CA mutual-TLS PKI used by all ansible-managed servers and pomelo. The CA cert doubles as the server identity — there are no per-host certificates. See ssl for overview and ssl/create-certs for the commands to regenerate.
The CA is self-signed. Every managed server receives the CA cert/key and uses them directly as its TLS identity. The server's CAfile points to itself, so verify = 2 means: the client must present a certificate signed by this CA. There is one client certificate (pomelo), signed by the CA. No per-host server certs exist — all servers share the same CA identity.
This keeps the PKI simple: rotating the CA cert means updating one file on all servers and on pomelo. The tradeoff is that any server can impersonate any other server — acceptable for a single-operator setup.
| Role | Source on pomelo | Deployed to (remote) | Deployed as |
|---|---|---|---|
| CA cert (= server cert) | ~/ssl/C/CA/cert | all stunnel servers | /etc/stunnel/server.pem |
| CA private key (= server key) | ~/ssl/C/CA/key | all stunnel servers | /etc/stunnel/server.key |
| Client cert | ~/ssl/C/cert | pomelo only (manual) | /etc/stunnel/cert.pem |
| Client private key | ~/ssl/C/key | pomelo only (manual) | /etc/stunnel/key.pem |
| CA cert (client trust store) | ~/ssl/C/CA/cert | pomelo only (manual) | /etc/stunnel/CA.pem |
CA cert: CN=John Critchley, self-signed, valid 2025-09-01 to 2035-09-02.
Client cert: CN=John Critchley, signed by CA, valid 2025-09-02 to 2035-09-03.
Both certs use the same CN — checkHost = John Critchley in the client config verifies the server's CN. This works because the server cert IS the CA cert.
cert = /etc/stunnel/server.pem
key = /etc/stunnel/server.key
CAfile = /etc/stunnel/server.pem ; same file — server verifies clients against itself
verify = 2
[socks5h]
accept = 11080
connect = 127.0.0.1:1080
[notes]
accept = 18021
connect = 127.0.0.1:8020
[notes-mcp]
accept = 18023
connect = 127.0.0.1:8023
verify = 2
CAfile = /etc/stunnel/CA.pem ; ~/ssl/C/CA/cert
cert = /etc/stunnel/cert.pem ; ~/ssl/C/cert
key = /etc/stunnel/key.pem ; ~/ssl/C/key
[socks5h]
client = yes
accept = 1080
connect = gravlax:11080
checkHost = John Critchley
[notes]
client = yes
accept = 8021
connect = gravlax:18021
checkHost = John Critchley
[notes-mcp]
client = yes
accept = 8023
connect = gravlax:18023
checkHost = John Critchley
The server side is fully automated: run ansible-playbook setup_server.yml --tags stunnel -e target=<host>. The playbook copies ~/ssl/C/CA/cert and ~/ssl/C/CA/key from pomelo to the remote host and writes the stunnel.conf. No new certificates are needed.
The client side is manual — not yet in any playbook. On the new controller:
1. Copy ~/ssl/C/CA/cert to /etc/stunnel/CA.pem
2. Copy ~/ssl/C/cert to /etc/stunnel/cert.pem
3. Copy ~/ssl/C/key to /etc/stunnel/key.pem (mode 600)
4. Write /etc/stunnel/stunnel.conf with the client sections (see above), adjusting connect = to the current application server hostname.
5. Enable and start stunnel4.
The CA private key (~/ssl/C/CA/key) must never leave pomelo except to be deployed to managed servers via ansible. It must never appear in notes or git.
The client private key (~/ssl/C/key) must never leave pomelo. Do not deploy it to remote hosts.