-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Feature/review pgsql native backend protocol #6112
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: feature/pgsql-native-backend-protocol
Are you sure you want to change the base?
Changes from 75 commits
8e92cec
6b4f255
fd7a093
7191b02
7043540
03dd798
aeb823b
0d26cbb
e56e15b
84db5a6
8b7477c
d3a03ca
c4341c7
d8ef3f4
faf7719
2df9d2f
8fff29c
978b95c
fc99c3d
5f7c9ff
7543079
2c448ab
71a9c95
04bb225
47bcc14
c4537e2
840498b
b5db822
3de165f
5e6c06c
6d404b0
834affd
54acb29
e23c4bd
f2eed5b
899e977
6124281
da61452
b567859
c5d6877
55d6f82
dcf7195
a722b7f
7794ce9
174aa6e
5bc8453
18a8e6a
c909215
407ce0b
0cdfaa1
72d0f28
7771889
75dbcaa
b05d67c
e8de14b
1f944a5
990e0f8
905d4a9
cacbb16
9c7f12f
cf103b0
f166f78
bd73b2c
c3ef56a
2c141ff
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,205 @@ | ||
| --- ../tmp/src/interfaces/libpq/libpq-int.h 2026-06-24 03:51:37.037786331 +0500 | ||
| +++ ./src/interfaces/libpq/libpq-int.h 2026-06-24 03:03:14.441153729 +0500 | ||
| @@ -384,6 +384,9 @@ | ||
| char *pgpassfile; /* path to a file containing password(s) */ | ||
| char *channel_binding; /* channel binding mode | ||
| * (require,prefer,disable) */ | ||
| + char *scram_client_key; /* base64 32-byte ClientKey (ProxySQL SCRAM pass-through) */ | ||
| + char *scram_server_key; /* base64 32-byte ServerKey (ProxySQL SCRAM pass-through) */ | ||
| + char *md5_secret; /* stored "md5"+32hex to reuse for backend md5 auth */ | ||
| char *keepalives; /* use TCP keepalives? */ | ||
| char *keepalives_idle; /* time between TCP keepalives */ | ||
| char *keepalives_interval; /* time between TCP keepalive | ||
| --- ../tmp/src/interfaces/libpq/fe-connect.c 2026-06-24 03:51:37.040811504 +0500 | ||
| +++ ./src/interfaces/libpq/fe-connect.c 2026-06-24 03:04:47.178883449 +0500 | ||
| @@ -361,6 +361,18 @@ | ||
| "Load-Balance-Hosts", "", 8, /* sizeof("disable") = 8 */ | ||
| offsetof(struct pg_conn, load_balance_hosts)}, | ||
|
|
||
| + {"scram_client_key", NULL, NULL, NULL, | ||
| + "SCRAM-Client-Key", "*", 64, | ||
| + offsetof(struct pg_conn, scram_client_key)}, | ||
| + | ||
| + {"scram_server_key", NULL, NULL, NULL, | ||
| + "SCRAM-Server-Key", "*", 64, | ||
| + offsetof(struct pg_conn, scram_server_key)}, | ||
| + | ||
| + {"md5_secret", NULL, NULL, NULL, | ||
| + "MD5-Secret", "*", 64, | ||
| + offsetof(struct pg_conn, md5_secret)}, | ||
| + | ||
| /* Terminating entry --- MUST BE LAST */ | ||
| {NULL, NULL, NULL, NULL, | ||
| NULL, NULL, 0} | ||
| @@ -4436,6 +4436,22 @@ | ||
| } | ||
| free(conn->pgpassfile); | ||
| free(conn->channel_binding); | ||
| + /* ProxySQL SCRAM/md5 verifier pass-through: free + scrub the injected key material. */ | ||
| + if (conn->scram_client_key) | ||
| + { | ||
| + explicit_bzero(conn->scram_client_key, strlen(conn->scram_client_key)); | ||
| + free(conn->scram_client_key); | ||
| + } | ||
| + if (conn->scram_server_key) | ||
| + { | ||
| + explicit_bzero(conn->scram_server_key, strlen(conn->scram_server_key)); | ||
| + free(conn->scram_server_key); | ||
| + } | ||
| + if (conn->md5_secret) | ||
| + { | ||
| + explicit_bzero(conn->md5_secret, strlen(conn->md5_secret)); | ||
| + free(conn->md5_secret); | ||
| + } | ||
| free(conn->keepalives); | ||
| free(conn->keepalives_idle); | ||
| free(conn->keepalives_interval); | ||
| --- ../tmp/src/interfaces/libpq/fe-auth-scram.c 2026-06-24 03:51:37.041568997 +0500 | ||
| +++ ./src/interfaces/libpq/fe-auth-scram.c 2026-06-24 03:08:23.927939654 +0500 | ||
| @@ -120,6 +120,29 @@ | ||
| return NULL; | ||
| } | ||
|
|
||
| + /* | ||
| + * ProxySQL SCRAM pass-through: when a ClientKey is injected the exchange | ||
| + * uses it instead of a password. Require BOTH ClientKey and ServerKey (or | ||
| + * neither), so mutual authentication can never be silently skipped. | ||
| + */ | ||
| + { | ||
| + bool has_ck = (conn->scram_client_key && conn->scram_client_key[0]); | ||
| + bool has_sk = (conn->scram_server_key && conn->scram_server_key[0]); | ||
| + | ||
| + if (has_ck != has_sk) | ||
| + { | ||
| + free(state->sasl_mechanism); | ||
| + free(state); | ||
| + return NULL; | ||
| + } | ||
| + if (has_ck) | ||
| + { | ||
| + /* No password to normalize; keys are injected. */ | ||
| + state->password = NULL; | ||
| + return state; | ||
| + } | ||
| + } | ||
| + | ||
| /* Normalize the password with SASLprep, if possible */ | ||
| rc = pg_saslprep(password, &prep_password); | ||
| if (rc == SASLPREP_OOM) | ||
| @@ -785,14 +808,37 @@ | ||
| * Calculate SaltedPassword, and store it in 'state' so that we can reuse | ||
| * it later in verify_server_signature. | ||
| */ | ||
| - if (scram_SaltedPassword(state->password, state->hash_type, | ||
| - state->key_length, state->salt, state->saltlen, | ||
| - state->iterations, state->SaltedPassword, | ||
| - errstr) < 0 || | ||
| - scram_ClientKey(state->SaltedPassword, state->hash_type, | ||
| - state->key_length, ClientKey, errstr) < 0 || | ||
| - scram_H(ClientKey, state->hash_type, state->key_length, | ||
| - StoredKey, errstr) < 0) | ||
| + if (state->conn->scram_client_key && state->conn->scram_client_key[0]) | ||
| + { | ||
| + /* | ||
| + * ProxySQL SCRAM pass-through: use the injected ClientKey directly and | ||
| + * derive StoredKey = SHA256(ClientKey). Skips SASLprep + PBKDF2. | ||
| + */ | ||
| + int dec = pg_b64_decode(state->conn->scram_client_key, | ||
| + strlen(state->conn->scram_client_key), | ||
| + (char *) ClientKey, state->key_length); | ||
| + | ||
| + if (dec != state->key_length) | ||
| + { | ||
| + *errstr = "invalid scram_client_key"; | ||
| + pg_hmac_free(ctx); | ||
| + return false; | ||
| + } | ||
| + if (scram_H(ClientKey, state->hash_type, state->key_length, | ||
| + StoredKey, errstr) < 0) | ||
| + { | ||
| + pg_hmac_free(ctx); | ||
| + return false; | ||
| + } | ||
| + } | ||
| + else if (scram_SaltedPassword(state->password, state->hash_type, | ||
| + state->key_length, state->salt, state->saltlen, | ||
| + state->iterations, state->SaltedPassword, | ||
| + errstr) < 0 || | ||
| + scram_ClientKey(state->SaltedPassword, state->hash_type, | ||
| + state->key_length, ClientKey, errstr) < 0 || | ||
| + scram_H(ClientKey, state->hash_type, state->key_length, | ||
| + StoredKey, errstr) < 0) | ||
| { | ||
| /* errstr is already filled here */ | ||
| pg_hmac_free(ctx); | ||
| @@ -847,8 +893,22 @@ | ||
| return false; | ||
| } | ||
|
|
||
| - if (scram_ServerKey(state->SaltedPassword, state->hash_type, | ||
| - state->key_length, ServerKey, errstr) < 0) | ||
| + if (state->conn->scram_server_key && state->conn->scram_server_key[0]) | ||
| + { | ||
| + /* ProxySQL SCRAM pass-through: verify with the injected ServerKey. */ | ||
| + int dec = pg_b64_decode(state->conn->scram_server_key, | ||
| + strlen(state->conn->scram_server_key), | ||
| + (char *) ServerKey, state->key_length); | ||
| + | ||
| + if (dec != state->key_length) | ||
| + { | ||
| + *errstr = "invalid scram_server_key"; | ||
| + pg_hmac_free(ctx); | ||
| + return false; | ||
| + } | ||
| + } | ||
| + else if (scram_ServerKey(state->SaltedPassword, state->hash_type, | ||
| + state->key_length, ServerKey, errstr) < 0) | ||
| { | ||
| /* errstr is filled already */ | ||
| pg_hmac_free(ctx); | ||
| --- ../tmp/src/interfaces/libpq/fe-auth.c 2026-06-24 03:51:37.042103163 +0500 | ||
| +++ ./src/interfaces/libpq/fe-auth.c 2026-06-24 03:08:34.430125390 +0500 | ||
| @@ -553,7 +553,8 @@ | ||
| password = conn->connhost[conn->whichhost].password; | ||
| if (password == NULL) | ||
| password = conn->pgpass; | ||
| - if (password == NULL || password[0] == '\0') | ||
| + if ((password == NULL || password[0] == '\0') && | ||
| + !(conn->scram_client_key && conn->scram_client_key[0])) | ||
| { | ||
| appendPQExpBufferStr(&conn->errorMessage, | ||
| PQnoPasswordSupplied); | ||
| @@ -731,9 +732,20 @@ | ||
| } | ||
|
|
||
| crypt_pwd2 = crypt_pwd + MD5_PASSWD_LEN + 1; | ||
| - if (!pg_md5_encrypt(password, conn->pguser, | ||
| - strlen(conn->pguser), crypt_pwd2, | ||
| - &errstr)) | ||
| + if (conn->md5_secret && conn->md5_secret[0]) | ||
| + { | ||
| + /* ProxySQL: reuse the stored md5 secret as the inner hash. */ | ||
| + if (strlen(conn->md5_secret) != MD5_PASSWD_LEN) | ||
| + { | ||
| + libpq_append_conn_error(conn, "invalid md5_secret"); | ||
| + free(crypt_pwd); | ||
| + return STATUS_ERROR; | ||
| + } | ||
| + strcpy(crypt_pwd2, conn->md5_secret); | ||
| + } | ||
| + else if (!pg_md5_encrypt(password, conn->pguser, | ||
| + strlen(conn->pguser), crypt_pwd2, | ||
| + &errstr)) | ||
| { | ||
| libpq_append_conn_error(conn, "could not encrypt password: %s", errstr); | ||
| free(crypt_pwd); | ||
| @@ -1096,7 +1108,8 @@ | ||
| password = conn->connhost[conn->whichhost].password; | ||
| if (password == NULL) | ||
| password = conn->pgpass; | ||
| - if (password == NULL || password[0] == '\0') | ||
| + if ((password == NULL || password[0] == '\0') && | ||
|
Contributor
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. [P1] Restrict the The surrounding upstream switch shares this password gate between The special Please bypass the missing-password error only for |
||
| + !(conn->md5_secret && conn->md5_secret[0])) | ||
| { | ||
| appendPQExpBufferStr(&conn->errorMessage, | ||
| PQnoPasswordSupplied); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: sysown/proxysql
Length of output: 7632
🌐 Web query:
PostgreSQL libpq PQconninfoOption struct fields order keyword envvar compiled val label dispchar dispsize💡 Result:
The PQconninfoOption structure in the PostgreSQL libpq library is defined in libpq-fe.h [1][2]. The fields are arranged in the following order [3][4][5]: 1. char *keyword: The keyword of the option [3][1]. 2. char *envvar: The fallback environment variable name [3][1]. 3. char *compiled: The fallback compiled-in default value [3][1]. 4. char *val: The option's current value, or NULL [3][1]. 5. char *label: The label for the field in a connection dialog [3][1]. 6. char dispchar: An indicator of how to display this field in a connection dialog [3][1]. Supported values include "" (display as is), "" (password field/hide value), and "D" (debug option/don't show by default) [3][4]. 7. int dispsize: The field size in characters for a dialog [3][1]. All fields except val point to static strings that must not be altered [1][2]. The val field is either NULL or a dynamically allocated string, which is managed (along with the array itself) by PQconninfoFree [1][6]. The array of structures is terminated by an entry where the keyword pointer is NULL [3][4].
Citations:
🏁 Script executed:
Repository: sysown/proxysql
Length of output: 14569
🏁 Script executed:
Repository: sysown/proxysql
Length of output: 25437
🏁 Script executed:
Repository: sysown/proxysql
Length of output: 2097
Fix the PostgreSQL archive path before applying this patch.
postgresql-16.10.tar.gzextracts topostgres-REL_16_10, butdeps/Makefilechanges topostgresql/postgresql. The patch and build therefore fail before compilation. Use the extracted directory name or normalize it first.The
PQconninfoOptionentries andexplicit_bzero()usage are valid for PostgreSQL 16.10.🤖 Prompt for AI Agents