Repository navigation
[Add] configurable TLS and certificate verification for the PostgreSQL connection - #410
Merged
Merged
Conversation
…L connection; fixes #406 - New Backtier settings: SslMode, RootCertificate, ClientCertificate, ClientCertificateKey, ClientCertificatePassword, CheckCertificateRevocation. Default stays SslMode=Prefer; PGSSLROOTCERT and the default root.crt are honoured like Npgsql does. - Both connection strings are built with NpgsqlConnectionStringBuilder from one shared method (identical TLS settings, no injection through ';' or '=' in passwords). - Startup validates the certificate files (exist, readable, parseable, key and password match) and stops with a clear message when they are not. - The startup log shows whether the connection is encrypted (TLS version and cipher from pg_stat_ssl) and warns when it is not encrypted or not verified. - A TLS handshake failure stops startup at once; a diagnostic handshake (no credentials sent) explains the cause: untrusted issuer, host name mismatch, expired server or CA certificate, revocation, no common TLS version. - Permanent errors stop startup at once instead of waiting for BacktierWaitTime: wrong password (28P01), pg_hba rejection (28000), no CONNECT privilege (42501), and a missing database (3D000) unless it can still be created through /Data/Exchange (seed or import enabled) (#409). - "ready to accept requests" is logged only after the data store check and migrations, with the addresses actually listened on (#409). - A failed startup exits with code 1; invalid configuration is logged as one line instead of a stack trace. - docker-compose.yml and docker-compose-nginx.yml publish PostgreSQL on 127.0.0.1 only. - CI runs the integration suite against a TLS-only PostgreSQL with SslMode=VerifyFull, checks the TLS state in the API log, and checks that TLS misconfigurations stop the API. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lxatstariongroup
approved these changes
Oct 20, 2026
lxatstariongroup
left a comment
Contributor
There was a problem hiding this comment.
Approved, but please check SQ thingies.
joao4all
approved these changes
Oct 20, 2026
joao4all
left a comment
There was a problem hiding this comment.
LGTM, small comments and suggestions
Comment on lines
+121
to
+123
| this.applicationLifetime.Verify(x => x.StopApplication(), Times.Once); | ||
| this.dataStoreConnectionChecker.Verify(x => x.CheckConnectionAsync(It.IsAny<CancellationToken>()), Times.Never); | ||
| this.cometHasStartedService.Verify(x => x.SetHasStartedAndIsReady(true), Times.Never); |
| It.IsAny<EventId>(), | ||
| It.Is<It.IsAnyType>((state, _) => state.ToString().Contains("ready to accept requests")), | ||
| It.IsAny<Exception>(), | ||
| It.IsAny<Func<It.IsAnyType, Exception, string>>()), |
There was a problem hiding this comment.
Maybe supressing the warning with a tag for the test fixture, like
[SuppressMessage("Performance", "CA1873:Avoid potentially expensive logging", Justification = "Moq expression tree in unit test.")]
Since it will be evaluated only for tests, the impact is negligible.
Comment on lines
+83
to
+85
| /// <returns> | ||
| /// The certificate | ||
| /// </returns> |
There was a problem hiding this comment.
I think it would easier to read if this (and param, etc) doc was just one line, like
/// <returns>The certificate</returns>
6 tasks
…S diagnostics handshake - S4830: the diagnostic handshake no longer accepts any certificate. The validation callback records the certificate and chain but returns the real validation result; a rejected handshake now evaluates the recorded certificate, so the findings stay the same. - CA2012: Moq returns a fresh ValueTask per call in the checker tests. - CA1859, S3267, CA1826, CA1822: small code cleanups. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
3 of 5 tasks
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.



Closes #406. Also fixes two items of #409.
Summary
The connection to PostgreSQL could not be secured. The services had no TLS settings, so Npgsql's default
SslMode=Preferalways applied: plain text against a server without TLS, and no certificate verification against a server with TLS. This PR makes TLS configurable, verifiable and diagnosable.Configuration (
Backtiersection, also as environment variables)SslModePrefer(unchanged behaviour)Disable,Allow,Prefer,Require,VerifyCA,VerifyFull. A misspelled value stops startup and lists the valid valuesRootCertificatePGSSLROOTCERT, then the defaultroot.crt, then the OS trust store, the same order as NpgsqlClientCertificate,ClientCertificateKey,ClientCertificatePasswordCheckCertificateRevocationfalseBehaviour
NpgsqlConnectionStringBuilderfrom one shared method, socdp4serverandcdp4managealways get identical TLS settings. A password containing;or=no longer breaks the connection string.pg_stat_ssl). It warns when the connection is not encrypted, or not verified (anything other thanVerifyFull).BacktierWaitTime(300 s by default). Npgsql reports every rejected certificate with the same message, so a diagnostic TLS handshake (no credentials are sent) explains the actual cause:SslModerequires it, or a server that only accepts TLS withSslModeDisable28P01), rejected bypg_hba.conf(28000) and noCONNECTprivilege (42501). A missing database (3D000) stops at once unless seed or import is enabled, because thenPOST /Data/Exchangecreates it; the waiting log says so. Temporary errors are still retried: connection refused, database starting up (57P03), too many connections.docker-compose.ymlanddocker-compose-nginx.ymlnow publish PostgreSQL on127.0.0.1only. Before, they published it on all interfaces, with default credentials.CI
The integration suite now runs against a TLS-only PostgreSQL with
SslMode=VerifyFull. The tests only use HTTP, so the whole suite runs unchanged.ssl=onand apg_hba.confthat only accepts TLS connections over the network (hostssl).SslMode=Disableeach stop the API with exit code 1 and the expected message.Behaviour changes to note
BacktierWaitTime.restart: on-failurerestarts it.Test plan
dotnet build COMET-WebServices.sln: 0 errors. The warnings are existing NuGet package warnings, now tracked in [Update]: Docker base image aspnet:10.0.7 is six security releases behind (latest 10.0.13) #408.dotnet test COMET-WebServices.sln: 489 passed, 0 failed (432 of them inCometServer.Tests). The certificate tests use certificates generated inside the tests, and the diagnostics are tested with real TLS handshakes against an in-process server.Smoke tested by hand against
stariongroup/cdp4-database-community-edition:4.2.0, with and without TLS:PreferPreferVerifyFull, correct CA (also throughPGSSLROOTCERT)VerifyFullagainst a server without TLSDisableagainst a TLS-only serverSslModeDocker image built from this branch on Alpine: OpenSSL present,
VerifyFullworks (TLS 1.3).CI flow rehearsed locally against
stariongroup/cdp4-test-database-community-edition:4.2.0in TLS-only mode: seed over TLS, "ready", authenticatedGET /SiteDirectoryreturns 200.Integration suite over TLS: runs in this PR's CI.
Not in this PR
🤖 Generated with Claude Code