Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions doc/frontend_x509_authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Frontend X.509 authentication

`require_x509` is available in v3.1.x Innovative-tier and v4.x builds. v3.0.x does not recognize or read the key; it does not look up, validate, log, or enforce `require_x509`.

## Configure a frontend account

Set the policy in the existing `mysql_users.attributes` JSON object:

```sql
UPDATE mysql_users
SET attributes='{"require_x509":true}'
WHERE username='application_user';
LOAD MYSQL USERS TO RUNTIME;
SAVE MYSQL USERS TO DISK;
```

`require_x509` accepts only a JSON boolean (`true` or `false`). A malformed value is retained in the runtime record, diagnosed when users are loaded, and denied at authentication until it is corrected. ProxySQL does not coerce malformed strings or numbers into a boolean.

## What the policy proves

The frontend TLS context validates client certificates against ProxySQL's frontend `proxysql-ca.pem`. With `require_x509=true`, the configured password or authentication plugin must succeed **and** the physical frontend TLS connection must have presented a certificate whose verification result is `X509_V_OK`. A trusted client certificate without a URI SAN is sufficient.

`use_ssl` requires an encrypted transport. `require_x509` additionally requires a verified client certificate.

`spiffe_id` binds a username to a URI SAN identity and remains the authoritative identity check after the configured frontend password step. `require_x509` proves membership in the trusted PKI and remains additive to password authentication. If both attributes are present, both policies must pass.

## Connection changes and pass-through authentication

`COM_CHANGE_USER` does not renegotiate TLS. The target account reuses immutable certificate evidence from the original physical connection; absent or invalid evidence rejects a `require_x509` target and requires a fresh connection. Any SPIFFE-authenticated source and every SPIFFE target are rejected, also requiring a fresh connection.

For row-backed pass-through authentication, ProxySQL enforces the per-user certificate policy before the username allowlist and cache lookup, so it applies identically on cold and warm paths. A warm cache hit is verified inline. On a cache miss, the existing global `mysql-passthrough_auth_require_tls` transport gate runs before ProxySQL requests the cleartext password or dispatches a backend probe. Backend verification still supplies the password verdict. SPIFFE rows are excluded from pass-through. Unknown-user pass-through has no row attributes and remains governed by the existing global, miss-only TLS gate. `COM_CHANGE_USER` rejects pass-through targets, while a pass-through-authenticated source may change to an ordinary password-backed row.

The frontend client certificate is never forwarded to a backend. Backend client certificates and keys are configured independently through backend SSL settings.

## Failures

Authentication-policy denials return the generic MySQL error 1045; configuration and certificate details remain in ProxySQL logs. The existing earlier TLS-handshake failure for an untrusted certificate that carries a SPIFFE URI SAN is preserved.
22 changes: 22 additions & 0 deletions doc/internal/passthrough_authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ When pass-through completes for a user not in `mysql_users`, no row is inserted.

Because these are re-evaluated each connect, changing `mysql-passthrough_default_hg` immediately affects routing on the next connect from a cached unknown user — no cache flush required.

### 3.6 Frontend certificate policy (v3.1+/v4 only)

A row-backed frontend account can set `attributes.require_x509=true`. On v3.1+ and v4, that requires the configured password/authentication-plugin step and a trusted certificate on the physical frontend TLS connection. A SPIFFE row is excluded from pass-through because `spiffe_id` is an identity policy, not an empty-password pass-through signal. Unknown-user pass-through has no row or attributes object, so its existing `mysql-passthrough_auth_require_tls` transport gate is unchanged; it is not a per-user X.509 rule.

## 4. Protocol flow

### 4.1 `caching_sha2_password` (the primary case)
Expand Down Expand Up @@ -175,6 +179,8 @@ For Phase 1, `COM_CHANGE_USER` targeting a user that would require pass-through

Implementation: `process_pkt_COM_CHANGE_USER` in `lib/MySQL_Protocol.cpp` returns early with `ret=false` when the target's stored password is empty and the master gate is on. The check runs BEFORE the function's unconditional session-state mutations (`sess->default_hostgroup`, `transaction_persistent`, `user_attributes`) so a rejected attempt has no observable side effects on the already-authenticated session.

The directions are intentionally asymmetric: a pass-through target is rejected even when the original connection carries a valid certificate, while a pass-through-authenticated source may change to an ordinary password-backed target. The SPIFFE source/target prohibition remains separate: an SPIFFE-authenticated source and every SPIFFE target are rejected. `COM_CHANGE_USER` relies on immutable certificate evidence from the original connection and never renegotiates TLS.

May be revisited in a later phase if there's demand.

## 6. Probe details
Expand Down Expand Up @@ -248,6 +254,22 @@ The §8.4 invalidation eviction (a *later* 1045 during real query traffic agains
| Stale cached password after backend rotation | TTL + invalidate-on-backend-rejection during real traffic |
| Unintended exposure of unknown-user code path | `mysql-passthrough_auth_unknown_users` defaults to `false`; `username_pattern` allowlist for further restriction |

For a row-backed authentication attempt, the security ordering is:

```text
row lookup
-> require_x509 / SPIFFE classification
-> username allowlist
-> cache lookup
-> on cache miss, pass-through TLS gate
-> cleartext request
-> backend probe
```

Row-backed `require_x509` is evaluated before cache lookup, so the certificate policy applies identically on cold and warm paths. A warm cache hit is verified inline before the miss-only TLS gate. `mysql-passthrough_auth_require_tls` protects acquisition of the cleartext password and backend-probe dispatch on a cache miss; it does not reject an inline warm-cache hit.

Cold-probe completion sends the frontend OK from `MySQL_Session::handler_again___status_AUTHENTICATING_BACKEND_FOR_CLIENT()`. Certificate policy must therefore be decided before dispatch, rather than relying only on the normal handshake epilogue. The frontend certificate is not sent to the backend. Unknown-user pass-through has no row attributes and remains subject to the same existing global, miss-only TLS gate rather than a per-user X.509 rule.

### 7.2 Rate limiting

Maintain two sliding-window counters:
Expand Down
Loading
Loading