mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
95 lines
3.3 KiB
Markdown
95 lines
3.3 KiB
Markdown
|
|
# 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`.
|