Add SCRAM-SHA-256 authentication#4
Open
thierrymarianne wants to merge 12 commits into
Open
Conversation
Implements SCRAM-SHA-256 SASL authentication so the library can connect
to default-configured PostgreSQL 10+ instances and managed providers
that no longer accept cleartext password auth.
What changes:
- messages.pl: add auth_method/2 to decode any R(N) AuthenticationRequest
into a tagged term (ok, password, md5/1, sasl/1, sasl_continue/1,
sasl_final/1); add sasl_initial_response_message/3 and
sasl_response_message/2 builders.
- scram.pl (new): SCRAM-SHA-256 client per RFC 5802 + RFC 7677.
PBKDF2-HMAC-SHA-256 is implemented as HMAC iteration on top of
library(crypto)'s crypto_data_hash/3 with the hmac/1 option. No new
external dependencies.
- postgresql.pl: connect/6 now reads the first auth response, decodes
it via auth_method/2, and dispatches:
* ok -> done
* password -> existing cleartext path (unchanged)
* sasl(_) -> SCRAM-SHA-256 if offered, otherwise throws
Drive-by: accept the Host argument as either an atom or chars
(current Scryer Prolog's socket_client_open/3 requires an atom).
Verified end-to-end against postgres:17-alpine configured with
POSTGRES_HOST_AUTH_METHOD=scram-sha-256 (no cleartext fallback). The
existing cleartext flow is preserved; backward compatibility is decided
at runtime by the server's auth-method byte.
Out of scope (deferred): SASLprep (RFC 4013) -- ASCII passwords only;
channel binding (SCRAM-SHA-256-PLUS); TLS; MD5; GSS/SSPI.
Test:
just psql-up
just test-scram
triska
reviewed
May 22, 2026
| ]). | ||
|
|
||
| % Reads bytes from Stream the same way postgresql.pl get_bytes/2 does. | ||
| :- use_module('types', [int32/2]). |
Author
There was a problem hiding this comment.
Thank you, the unnecessary quotes have been removed.
triska
reviewed
May 22, 2026
| append("n=", User, NUser0), | ||
| append(NUser0, ",r=", NUser1), | ||
| append(NUser1, ClientNonce, ClientFirstBare), | ||
| append("n,,", ClientFirstBare, ClientFirst), |
There was a problem hiding this comment.
Is this something like phrase(("n=",seq(User),",r=",seq(ClientNonce),"n,,",seq(ClientFirstBare),...", Bytes)?
Then maybe using phrase/2 and DCGs could make the construction more readable.
a719463 to
b20b14a
Compare
CI now boots two Postgres services side by side:
- postgres (16-alpine, POSTGRES_HOST_AUTH_METHOD=password, port 5432)
used by the existing Logtalk test suite.
- postgres-scram (17-alpine, scram-sha-256, port 5433)
used by the new SCRAM-SHA-256 round-trip smoke test.
Local docker-compose.yml mirrors this split.
logtalk_tester now runs in verbose mode (-o verbose) so subsequent
"broken" failures surface the actual load error in the CI log.
scram_test.pl defaults to DATABASE_PORT=5433 so it targets the scram
container by default.
The existing Logtalk-driven test set fails to load on CI for reasons
that don't reproduce with plain scryer-prolog, and chasing that
diagnostic isn't on the critical path for the SCRAM-SHA-256
contribution. The SCRAM round-trip step alone gives CI a meaningful
green/red signal for this branch's purpose.
The postgres password service is left in services{} for re-enabling
the Logtalk suite once the load issue is understood.
The Logtalk suite needs scryer-prolog pinned to v0.9.4 (last release the Logtalk 3.70.0 scryer adapter compiles against), while the SCRAM round-trip needs scryer master for its newer crypto APIs. Running both off one scryer build forces a single version that satisfies neither; splitting lets each job pin its own. The new workflow caches the built v0.9.4 binary so the ~5 min cargo build only happens on cache miss, and uploads the logtalk_tester_logs/ directory on failure (the previous CI showed only "broken / exit 5" because the per-tester .results file was never surfaced). Also bumps every JS action to a Node 24 LTS runtime and replaces the node16-based outliers: actions-rs/toolchain (archived org) -> dtolnay rust-toolchain (composite, no Node), and logtalk-actions/setup-logtalk (node16, no tagged releases) -> inline install.sh from the upstream tarball.
Signed-off-by: Thierry Marianne <thierry@marianne.io>
Signed-off-by: Thierry Marianne <thierry@marianne.io>
Signed-off-by: Thierry Marianne <thierry@marianne.io>
Signed-off-by: Thierry Marianne <thierry@marianne.io>
Signed-off-by: Thierry Marianne <thierry@marianne.io>
Signed-off-by: Thierry Marianne <thierry@marianne.io>
Signed-off-by: Thierry Marianne <thierry@marianne.io>
types.pl already worked around the bug by switching int32/2 and int16/2 from `<<` to `*`. tests/indexing_regression.pl now reproduces it: it exercises the same clause shape as auth_method_/3 with both formulations and halts non-zero when any probe mismatches its expected method. The script runs in both CI workflows. The SCRAM job (v0.10.0) requires it to pass, asserting the regression is fixed upstream. The Logtalk job (v0.9.4) marks it continue-on-error so the failure stays visible in CI without breaking the build. Two justfile targets cover the same versions for local runs. Signed-off-by: Thierry Marianne <thierry@marianne.io>
cb53df3 to
7580437
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hello @aarroyoc 👋🏼,
I needed to connect to a PostgreSQL instance via SCRAM-SHA-256 authentication.
I thought you might be interested in this implementation.
I've noticed that Logtalk suite didn't work anymore with the most recent version of Scryer Prolog so I've tried to keep the suite separate.
Please let me know if you'd like to see something done differently.
Summary
messages.pl: decodeAuthenticationRequestinto a tagged term (ok/password/md5/1/sasl/1/sasl_continue/1/sasl_final/1); add SASL response builders.scram.pl(new): SCRAM-SHA-256 client with PBKDF2-HMAC-SHA-256 implemented on top oflibrary(crypto). No new external dependencies.connect/6reads the first auth response and dispatches:ok→ done,password→ existing cleartext path (unchanged),sasl(_)→ SCRAM-SHA-256 when offered. The existing cleartext flow is preserved; backward compatibility is decided at runtime by the server's auth-method byte.Test plan
just psql-upboots both postgres:16-alpine (password) and postgres:17-alpine (scram-sha-256);just test-scramruns the round-trip against the SCRAM instance on port 5433.postgres:16-alpinepassword service; a secondpostgres:17-alpinescram service runs the SCRAM smoke test side by side.