-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: Add dedicated TLS for admin interfaces #5956
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v3.0
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| # Dedicated TLS for the Admin interfaces | ||
|
|
||
| ProxySQL can use a dedicated TLS context for its MySQL and PostgreSQL Admin | ||
| interfaces. The feature is disabled by default, so existing deployments retain | ||
| their previous behavior. | ||
|
|
||
| When `admin-ssl_enabled` is enabled, all new connections to either Admin | ||
| interface must negotiate TLS. Existing connections are not disconnected. | ||
|
|
||
| ## Configuration | ||
|
|
||
| | Variable | Default | Description | | ||
| |---|---|---| | ||
| | `admin-ssl_enabled` | `false` | Require TLS and activate the dedicated Admin TLS context. | | ||
| | `admin-ssl_key` | empty | PEM private key. Required when enabled. | | ||
| | `admin-ssl_cert` | empty | PEM certificate chain. Required when enabled. | | ||
| | `admin-ssl_ca` | empty | PEM CA bundle used to verify client certificates. | | ||
| | `admin-ssl_capath` | empty | OpenSSL hashed CA directory used to verify client certificates. | | ||
| | `admin-ssl_cipher` | empty | OpenSSL TLS 1.2 cipher list. The OpenSSL default is used when empty. | | ||
| | `admin-tls_version` | `TLSv1.2` | Minimum accepted version: `TLSv1.2` or `TLSv1.3`. | | ||
| | `admin-ssl_curves` | empty | OpenSSL groups/curves list. | | ||
| | `admin-ssl_verify_client` | `DISABLED` | Client certificate mode: `DISABLED`, `OPTIONAL`, or `REQUIRED`. Numeric values `0`, `1`, and `2` are also accepted. | | ||
| | `admin-ssl_crl` | empty | PEM certificate revocation list. | | ||
| | `admin-ssl_crlpath` | empty | OpenSSL hashed CRL directory. | | ||
|
|
||
| Relative paths are resolved against ProxySQL's data directory. Client | ||
| verification requires `admin-ssl_ca` or `admin-ssl_capath`. | ||
|
|
||
| Example using ProxySQL's existing default certificate: | ||
|
|
||
| ```sql | ||
| SELECT Variable_Name, Variable_Value | ||
| FROM stats.stats_proxysql_global | ||
| WHERE Variable_Name IN | ||
| ('TLS_Key_File', 'TLS_Server_Cert_File', 'TLS_CA_Cert_File'); | ||
|
|
||
| SET admin-ssl_key='/var/lib/proxysql/proxysql-key.pem'; | ||
| SET admin-ssl_cert='/var/lib/proxysql/proxysql-cert.pem'; | ||
| SET admin-ssl_ca='/var/lib/proxysql/proxysql-ca.pem'; | ||
| SET admin-ssl_enabled='true'; | ||
| LOAD ADMIN VARIABLES TO RUNTIME; | ||
| SAVE ADMIN VARIABLES TO DISK; | ||
| ``` | ||
|
|
||
| To require a trusted client certificate: | ||
|
|
||
| ```sql | ||
| SET admin-ssl_verify_client='REQUIRED'; | ||
| LOAD ADMIN VARIABLES TO RUNTIME; | ||
| ``` | ||
|
|
||
| ## Reload behavior | ||
|
|
||
| `LOAD ADMIN VARIABLES TO RUNTIME` builds and validates a complete replacement | ||
| context before activating it. If validation fails, the previous runtime | ||
| configuration and context remain active and the command returns an error. | ||
|
|
||
| Certificate files can be re-read without changing variables: | ||
|
|
||
| ```sql | ||
| PROXYSQL RELOAD ADMIN TLS; | ||
| ``` | ||
|
|
||
| The reload is atomic for new connections. Connections that are already using | ||
| TLS continue with the context under which they were established. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6529,6 +6529,35 @@ void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE_ | |
|
|
||
| void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(PtrSize_t *pkt, bool *wrong_pass) { | ||
| bool is_encrypted = client_myds->encrypted; | ||
| const bool admin_tls_required = | ||
| (session_type == PROXYSQL_SESSION_ADMIN || session_type == PROXYSQL_SESSION_STATS) | ||
| && GloVars.is_admin_SSL_enabled(); | ||
|
|
||
| // A plaintext MySQL HandshakeResponse starts with the client capability | ||
| // flags after the four-byte packet header. Reject it before credentials | ||
| // are parsed when the dedicated Admin TLS context is active. | ||
| if ( | ||
| admin_tls_required && is_encrypted == false && client_myds->DSS == STATE_SERVER_HANDSHAKE | ||
| && pkt->size >= 8 | ||
| ) { | ||
| const unsigned char *payload = (const unsigned char *)pkt->ptr + 4; | ||
| const uint32_t capabilities = | ||
| (uint32_t)payload[0] | ((uint32_t)payload[1] << 8) | ||
| | ((uint32_t)payload[2] << 16) | ((uint32_t)payload[3] << 24); | ||
| if ((capabilities & CLIENT_SSL) == 0) { | ||
| l_free(pkt->size, pkt->ptr); | ||
| *wrong_pass = true; | ||
| client_myds->setDSS_STATE_QUERY_SENT_NET(); | ||
| uint8_t packet_id = client_myds->pkt_sid; | ||
| packet_id++; | ||
| client_myds->myprot.generate_pkt_ERR( | ||
| true, NULL, NULL, packet_id, 1045, "28000", | ||
| "ProxySQL Admin interface requires SSL", true | ||
| ); | ||
| return; | ||
| } | ||
|
Comment on lines
+6539
to
+6558
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Where is pkt_sid assigned? Confirm it is only updated during handshake-response parsing.
rg -nP --type=cpp -C3 '\bpkt_sid\s*=' lib/ include/Repository: sysown/proxysql Length of output: 4553 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "MySQL_Session.cpp around lines 6480-6625 and 6700-6845:"
sed -n '6480,6845p' lib/MySQL_Session.cpp | nl -ba -v6480
echo
echo "Relevant MySQL_Protocol generate_pkt_ERR signatures/sequence handling:"
sed -n '220,430p' lib/MySQL_Protocol.cpp | nl -ba -v220
echo
echo "mysql_hdr definition and myprot uses:"
rg -n --type=cpp -C3 '\bstruct\s+mysql_hdr\b|\btypedef\s+\(.*\)mysql_hdr\b|pkt_sid|generate_pkt_ERR\s*\(' lib includeRepository: sysown/proxysql Length of output: 249 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "MySQL_Session.cpp around lines 6480-6625 and 6700-6845:"
sed -n '6480,6845p' lib/MySQL_Session.cpp | awk '{printf "%4d %s\n", NR+6479, $0}'
echo
echo "Relevant MySQL_Protocol generate_pkt_ERR signatures/sequence handling:"
sed -n '220,430p' lib/MySQL_Protocol.cpp | awk '{printf "%4d %s\n", NR+219, $0}'
echo
echo "mysql_hdr definition and myprot uses:"
rg -n --type=cpp -C3 '\bstruct\s+mysql_hdr\b|\btypedef\s+\(.*\)mysql_hdr\b|pkt_sid|generate_pkt_ERR\s*\(' lib includeRepository: sysown/proxysql Length of output: 50371 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "mysql_data_stream.cpp packet reads around pkt_sid:"
sed -n '1220,1390p' lib/mysql_data_stream.cpp | awk '{printf "%4d %s\n", NR+1219, $0}'
echo
echo "MySQL_Data_Stream state transition methods:"
rg -n --type=cpp -C4 'setDSS_STATE_(HANDSHAKE|QUERY_SENT_NET|SSL_INIT|ERROR|WAITING|AUTH)' lib include
echo
echo "process_pkt_handshake_response sequence-id effects:"
rg -n --type=cpp -C8 '\bprocess_pkt_handshake_response\b|process_pkt_(handshake|SSL)' lib includeRepository: sysown/proxysql Length of output: 50371 Use the incoming packet id for this pre-parse SSL ERR.
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| bool handshake_response_return = client_myds->myprot.process_pkt_handshake_response((unsigned char *)pkt->ptr,pkt->size); | ||
| bool handshake_err = true; | ||
|
|
||
|
|
@@ -6548,16 +6577,50 @@ void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE( | |
| client_myds->DSS=STATE_SSL_INIT; | ||
| client_myds->rbio_ssl = BIO_new(BIO_s_mem()); | ||
| client_myds->wbio_ssl = BIO_new(BIO_s_mem()); | ||
| client_myds->ssl = GloVars.get_SSL_new(); | ||
| const bool admin_session = | ||
| session_type == PROXYSQL_SESSION_ADMIN || session_type == PROXYSQL_SESSION_STATS; | ||
| client_myds->ssl = | ||
| admin_session ? GloVars.get_admin_SSL_new() : GloVars.get_SSL_new(); | ||
| if (client_myds->ssl == NULL) { | ||
| proxy_error( | ||
| "Unable to create %s TLS connection: SSL_new() failed\n", | ||
| admin_session ? "MySQL Admin" : "MySQL frontend" | ||
| ); | ||
| BIO_free(client_myds->rbio_ssl); | ||
| BIO_free(client_myds->wbio_ssl); | ||
| client_myds->rbio_ssl = NULL; | ||
| client_myds->wbio_ssl = NULL; | ||
| l_free(pkt->size, pkt->ptr); | ||
| *wrong_pass = true; | ||
| client_myds->setDSS_STATE_QUERY_SENT_NET(); | ||
| return; | ||
| } | ||
| SSL_set_fd(client_myds->ssl, client_myds->fd); | ||
| SSL_set_accept_state(client_myds->ssl); | ||
| SSL_set_bio(client_myds->ssl, client_myds->rbio_ssl, client_myds->wbio_ssl); | ||
| l_free(pkt->size,pkt->ptr); | ||
| proxysql_keylog_attach_callback(GloVars.get_SSL_ctx()); | ||
| proxysql_keylog_attach_callback(SSL_get_SSL_CTX(client_myds->ssl)); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| if ( | ||
| handshake_response_return == true | ||
| && admin_tls_required | ||
| && is_encrypted == false | ||
| ) { | ||
| l_free(pkt->size, pkt->ptr); | ||
| *wrong_pass = true; | ||
| client_myds->setDSS_STATE_QUERY_SENT_NET(); | ||
| uint8_t packet_id = client_myds->pkt_sid; | ||
| packet_id++; | ||
| client_myds->myprot.generate_pkt_ERR( | ||
| true, NULL, NULL, packet_id, 1045, "28000", | ||
| "ProxySQL Admin interface requires SSL", true | ||
| ); | ||
| return; | ||
| } | ||
|
|
||
| if ( | ||
| //(client_myds->myprot.process_pkt_handshake_response((unsigned char *)pkt->ptr,pkt->size)==true) | ||
| (handshake_response_return == true) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.