Capture TLS fingerprints in report context

This commit is contained in:
fawney19
2026-05-06 15:01:31 +08:00
parent 621eb55ae1
commit bfda9b3e78
7 changed files with 818 additions and 111 deletions

View File

@@ -0,0 +1,94 @@
# TLS Fingerprint Capture
Aether stores per-request TLS capture under `usage.request_metadata.tls_fingerprint`.
```json
{
"tls_fingerprint": {
"incoming": {
"source": "forwarded_header",
"ja3": "...",
"ja3_hash": "...",
"ja4": "...",
"protocol": "TLSv1.3",
"cipher": "TLS_AES_128_GCM_SHA256",
"sni": "api.example.com",
"alpn": "h2"
},
"outgoing": {
"source": "aether_transport_config",
"observed": false,
"transport_path": "direct",
"backend": "reqwest_rustls",
"http_mode": "auto",
"tls_stack": "rustls",
"tls_versions_offered": ["TLS1.3", "TLS1.2"],
"alpn_offered": ["h2", "http/1.1"]
}
}
}
```
`incoming` is the client-to-Aether TLS fingerprint. It can be populated by Aether native TLS capture in direct deployments or by trusted reverse-proxy headers when TLS terminates before Aether.
`outgoing` is the Aether-to-provider TLS transport record. The current gateway records the exact transport configuration it controls. It sets `observed: false` because reqwest/rustls does not expose the emitted ClientHello bytes on the direct path. A future connector-level ClientHello capture or probe result can reuse the same object with `observed: true` plus `ja3`, `ja3_hash`, and `ja4`.
## Nginx TLS Termination
When nginx terminates HTTPS and proxies HTTP to Aether, Aether cannot see the original ClientHello. Configure nginx to forward the TLS fields it can observe:
```nginx
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Aether-TLS-Source nginx;
proxy_set_header X-Aether-TLS-Protocol $ssl_protocol;
proxy_set_header X-Aether-TLS-Cipher $ssl_cipher;
proxy_set_header X-Aether-TLS-SNI $ssl_server_name;
}
}
```
Stock nginx does not provide JA3/JA4 variables. The forwarded record is still useful, but it is not a complete TLS fingerprint. To forward JA3/JA4 through nginx, use an nginx build/module or edge layer that computes them and set:
```nginx
proxy_set_header X-Aether-TLS-JA3 $ja3;
proxy_set_header X-Aether-TLS-JA3-Hash $ja3_hash;
proxy_set_header X-Aether-TLS-JA4 $ja4;
```
Only accept these headers from trusted infrastructure. Do not expose Aether directly to public clients while also trusting client-supplied `X-Aether-TLS-*` headers.
## Nginx TCP Passthrough
If Aether terminates TLS itself, nginx can pass TCP through without decrypting:
```nginx
stream {
map $ssl_preread_server_name $aether_backend {
api.example.com 127.0.0.1:3443;
default 127.0.0.1:3443;
}
server {
listen 443;
proxy_pass $aether_backend;
ssl_preread on;
}
}
```
In this mode nginx cannot inject HTTP headers because it never sees HTTP. Aether native TLS capture is responsible for populating `tls_fingerprint.incoming`.