feat(server): add optional mTLS client-certificate authentication
- Dominant language
- Rust
- Stars
- 4.9k
- Forks
- 432
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 173
Description
### Description
Add optional mutual TLS authentication for client-facing Iggy transports.
Iggy supports TLS for TCP, HTTP, WebSocket, and QUIC, but its server TLS configurations do not request or validate client certificates. TLS currently encrypts the connection and authenticates the server only.
This prevents operators from:
- Restricting connections to clients holding certificates from a trusted CA.
- Authenticating users or services through X.509 certificates.
- Using certificate identities for service-to-service access.
- Resolving certificate identities through an external authentication and authorization provider (#3929 ).
### Affected area / component
Wire protocol / API
### Proposed solution
When certificates are optional and a client does not present one, the connection should remain unauthenticated and use the existing login mechanism.
#### Certificate verification
When enabled, the listener should:
- Request a client certificate during the TLS handshake.
- Validate its chain against the configured client CA trust store.
- Validate certificate validity and client-authentication key usage.
- Verify possession of the corresponding private key through the TLS handshake.
- Reject invalid, expired, or untrusted certificates.
- Expose only verified certificate information to the authentication layer.
Certificate fields supplied through untrusted application headers must not be accepted as certificate identity.
#### Local identity mapping
In `iggy_user` mode, Iggy should extract identity from a configurable certificate field:
```toml
[tcp.tls.client_auth.identity]
source = "san_uri"
```
Potential identity sources include:
- URI SAN.
- DNS SAN.
- Subject distinguished name.
- SHA-256 certificate fingerprint.
Example mapping:
```toml
[[tcp.tls.client_auth.identities]]
value = "spiffe://example.org/services/analytics-ingestor"
user_id = 12
```
The flow would be:
TLS handshake
1. verify client certificate
2. extract certificate identity
3. map identity to an existing non-root Iggy user
4. establish an authenticated session
5. enforce the user’s existing permissions
Unknown identities must fail.
For external identity mapping, the listener should:
1. verify client certificate
2. extract verified certificate information
3. call the external authentication provider once
4. receive an Iggy user or session-scoped permissions
5. establish an authenticated session
6. enforce permissions locally
The callout must fail closed on:
- Explicit denial.
- Timeout or provider unavailability.
- Invalid or malformed response.
- Missing authorization context.
- Expired authorization decision.
### Alternatives considered
- mTLS at a reverse proxy: Does not provide native support across all Iggy transports.
- Certificate fingerprint allowlist/mapping: Complicates certificate rotation.
### Contribution
- [x] I'm willing to submit a pull request to implement this feature
### Good first issue
- [ ] I think this could be a good first issue for a new contributor
Contributor guide
Assessment
This issue has not been assessed yet.