Skip to content

Configure verified TLS for the PostgreSQL demo - #2700

Open
nellshamrell wants to merge 3 commits into
edgefrom
nellshamrell-postgresql-demo-tls
Open

nellshamrell wants to merge 3 commits into
edgefrom
nellshamrell-postgresql-demo-tls

Conversation

@nellshamrell

@nellshamrell nellshamrell commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Make the PostgreSQL demo work with TLS-enforcing recipes by configuring verified TLS and mounting only the public CA certificate. The existing image and client are unchanged: no TypeScript, dependency, or password-selection changes.

PGSSLMODE=verify-full enables certificate and hostname verification; NODE_EXTRA_CA_CERTS loads the mounted CA. The server's private key is never mounted in the demo.

Reason for change

Companion to the recipe fix, radius-project/resource-types-contrib#419, and tutorial update, radius-project/docs#2054.

This targets edge. Coordinate publication of the recipes, sample, and docs: older plaintext-only recipes are incompatible with this configuration. No new demo image is needed.

How to test

From the repository root:

npm --prefix samples/demo ci
npm --prefix samples/demo/client ci
npm --prefix samples/demo run build

Follow the README's certificate setup in an isolated TLS-capable environment. Deploy samples/demo/app-postgresql.bicep, wait for the database and demo rollouts, then create, list, and delete a todo. Repeat with both recipes under required and optional: the demo should use TLS in both modes. Wrong-CA and hostname-mismatch cases must fail without plaintext fallback.

Validation passed: the local build and actual demo database, HTTP, trust-rejection, and password-precedence checks with both recipes under required/optional policies.

File change summary

File Summary of change
samples/demo/app-postgresql.bicep Configure verified TLS and mount the public CA.
samples/demo/README.md Document certificate setup, compatibility, rotation, and cleanup.

@github-actions

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Nell Shamrell-Harrington <nells@microsoft.com>
@nellshamrell
nellshamrell force-pushed the nellshamrell-postgresql-demo-tls branch from 526fef3 to ebbb759 Compare October 22, 2026 17:40
@nellshamrell

Copy link
Copy Markdown
Contributor Author

Approver review

Reviewed head: ebbb759d13cfa8d4038b4101ef17979ba43c0fff, against merge base 14c75bc19b2aa495c8077f3bcc9ba04e44942cb4.

1. Orientation

This PR makes the PostgreSQL demo connect securely to the TLS-enforcing Kubernetes recipes. The implementation is small and consistent with its stated purpose.

Aspect Assessment
Purpose Keep the demo working when PostgreSQL requires encrypted connections, while verifying the server's identity.
Placement Deployment configuration and operator documentation. A Radius recipe translates a declared resource into infrastructure such as Kubernetes Deployments and Services.
Mechanism Mount a public certificate authority (CA) certificate and configure the existing Node PostgreSQL driver through environment variables.
Failure surface Missing Secrets prevent deployment readiness; incorrect trust or hostname causes database operations to fail.
Review focus Driver configuration precedence, separation of public certificates from private keys, release ordering, and evidence beyond ordinary CI.

2. What the code changes

The diff contains 46 added lines across two files, with no application-code or dependency changes:

  • samples/demo/app-postgresql.bicep:29-42,50-54 adds verified TLS configuration and a public-CA Secret volume.
  • samples/demo/README.md:24-48 documents certificate preparation, namespace and hostname requirements, compatibility, rotation, and cleanup.

The existing image, database name, username, password selection, and modeled connections remain unchanged. This PR configures the client; server-side TLS enforcement belongs to radius-project/resource-types-contrib#419.

3. Data and control flow

The deployment references the operator-created postgresql-<environment>-ca Secret and mounts it at /etc/postgresql-ca. Node reads ca.crt when the process starts (app-postgresql.bicep:33-40,50-54).

The application then:

  1. Selects PostgreSQL from the projected connection variables and prefers the authored password Secret over the legacy password variable (src/db/repository.ts:67-85).
  2. Builds a connection URI without SSL query parameters (repository.ts:84).
  3. Passes that URI to pg.Pool, without an explicit SSL override (src/db/postgres.ts:12-15).
  4. Opens the database connection and creates the items table before returning the repository (postgres.ts:18-26).
  5. Executes the requested todo operation and closes the pool through the existing repository lifecycle.

I checked the tagged pg 8.21.0 implementation: PGSSLMODE=verify-full enables TLS; the driver passes the DNS hostname to Node's TLS connection and does not retry in plaintext if the server rejects TLS. Explicit SSL settings parsed from a connection string would override the environment, which explains the warning in README.md:28.

4. Key implementation decisions

Can deployment configuration replace a client-code change?

Code's answer: Set PGSSLMODE=verify-full and NODE_EXTRA_CA_CERTS, leaving PostgresFactory unchanged (app-postgresql.bicep:29-35; postgres.ts:12-15).

Why it matters: The existing driver already supports the required behavior. A code change would introduce another configuration path and require a new image without adding necessary functionality.

Alternative: Construct an explicit ssl object in TypeScript, loading a dedicated CA file. That could restrict trust to PostgreSQL rather than extending Node's process-wide trust, but adds code and image-release work.

Assessment: The configuration-only choice is sound for this sample. Driver-source inspection and the recorded runtime tests support it.

Does the client receive only public trust material?

Code's answer: Mount a separate postgresql-<environment>-ca Secret, not the server's -tls Secret (app-postgresql.bicep:50-54). The README explicitly separates the public CA from the server certificate and private key (README.md:35-41).

Why it matters: The client needs to authenticate the server, not possess its private key. The combined integration script also checked that the CA Secret contained only ca.crt and that the deployment did not mount the server Secret.

Assessment: The separation is correct. The recipe mounts the whole named Secret, so keeping that Secret public-only remains an operator responsibility.

Should optional server policy make the demo use plaintext?

Code's answer: No. The demo always requests verified TLS (app-postgresql.bicep:30-31; README.md:26).

Why it matters: "Optional" permits other clients to use plaintext; it does not require this client to weaken its connection.

Alternative: Branch on the projected server policy. That adds complexity and could turn an intentional server allowance into an unnecessary client downgrade.

Assessment: Always using verified TLS is the simpler and safer choice.

5. Failure handling and operations

Trust failures remain failures, rather than triggering a fallback. PostgreSQL connection errors propagate from repository creation; they do not cause createFactory() to select the in-memory implementation (repository.ts:67-89; postgres.ts:18-26).

There are two operational distinctions:

  • A missing CA Secret prevents the Kubernetes volume from mounting. A malformed or missing CA file causes Node to emit a warning and ignore the extra certificates; verification still applies using its remaining trust roots. With the private CA used by this sample, database connections then fail.
  • Node loads extra CAs only at startup. Updating the Secret alone does not refresh a running process's trust. The README correctly requires restarting the demo when its CA changes (README.md:45).

The existing application creates a pool per repository request. Its request-handler cleanup begins after factory.create() succeeds, so factory failures do not pass through that cleanup (src/api/todo.ts:5-19). This is an existing lifecycle limitation, not introduced here; this PR adds no retries, timeout policy, or recovery mechanism.

Database restarts deserve particular care: the recipes do not provide durable storage. The explicit backup warning in README.md:45 is necessary, not incidental.

6. Security and compatibility

The implementation verifies both the certificate chain and the hostname. The required DNS subject alternative name, the certificate field identifying valid server names, matches the recipe's returned Service hostname (README.md:30).

NODE_EXTRA_CA_CERTS extends Node's normal trust store; it does not pin this connection exclusively to the mounted CA. It also affects other TLS clients in the same process unless they supply their own CA configuration. That tradeoff is acceptable here, but operators should use an appropriately scoped CA.

The compatibility break is intentional: this template no longer works with plaintext-only PostgreSQL recipes (README.md:43). Reverting only the sample is not a safe rollback while the server still requires TLS. Prefer restoring valid certificates or compatible recipe artifacts rather than disabling verification.

No new permissions, packages, authentication rules, or credential outputs are introduced.

7. Test evidence and gaps

The evidence exercises the changed behavior, not just compilation. I inspected the preserved combined-run report, probe scripts, demo logs, and build log for this exact samples commit. The combined-run summary is also recorded in radius-project/resource-types-contrib#419.

Evidence What it proves
Current CI at review time Bicep validation, image builds, existing sample tests, dependency review, and DCO pass.
Recorded server/client build The frozen samples source builds successfully.
Four local recipe/policy combinations The unchanged published demo image works with both Bicep and Terraform recipes under default-required and optional policies.
Repository and HTTP probes Real database operations succeed; HTTP create/list/delete and post-delete 404 behave correctly.
Transport and negative probes pg_stat_ssl reports encryption; wrong CA and hostname produce specific certificate errors; required plaintext is rejected and optional plaintext is accepted.
Password and mount assertions Authored-password precedence remains intact, and the client mounts public CA material rather than the server Secret.

The Bicep cases used the docs-embedded template; the report records that it was byte-identical to the samples template.

The principal gap is durable automation. Standard demo CI deploys samples/demo/app.bicep, not the PostgreSQL variant (.github/workflows/test.yaml:51-56). The TLS-specific tests are preserved local integration evidence, not a regression gate in this repository.

The recorded run used local PR recipe artifacts. It does not prove that subsequently published public artifacts contain the same implementation. No new cluster deployment was run during this review.

8. Risk register

Risk Likelihood / impact Detection and recovery
Sample reaches users before compatible recipes Medium / high availability impact Database connections fail visibly. Publish and verify recipes first; README documents the incompatibility.
CA rotation leaves running clients with old trust Medium / medium Certificate errors identify the failure. Update trust and restart clients; server restarts require data protection.
Future image or driver change breaks environment-based TLS Low currently / medium Local tests cover today's image; ordinary CI does not cover this path. A retained PostgreSQL regression test would close the gap.
Operator puts private material in the CA Secret Low with documented procedure / high confidentiality impact The template mounts the whole Secret. Separate names, explicit instructions, and the integration assertion mitigate this; production enforcement is absent.

9. Findings

Blocking findings: None identified in the changed code.

Non-blocking concern: Preserve the PostgreSQL verification matrix as repeatable CI coverage. The contract introduced at samples/demo/app-postgresql.bicep:29-54 currently depends on manual integration tests. At minimum, retain successful database access, wrong-CA rejection, hostname rejection, and optional-policy verified TLS.

Strengths: The PR uses existing driver behavior, keeps private keys off the client, preserves credential selection, and documents the release and storage hazards.

Residual risks: Public artifact publication remains unverified, latest is mutable, and certificate lifecycle remains operator-managed.

10. Approval recommendation

Approve with follow-up. The configuration is correct for the inspected driver and is supported by substantive integration evidence against the exact samples head. Require coordinated recipe publication before rollout, and track a durable PostgreSQL TLS regression test; a failed smoke test against the published artifacts would change this recommendation.

11. Coverage and confidence

Both changed files were reviewed completely, along with the database factory, request handlers, image configuration, relevant workflow behavior, external volume/hostname contracts, and tagged driver implementation. I ran merge-base diff validation and refreshed CI/review status; I inspected rather than reran the recorded integration tests.

Architecture, trust, failure propagation, lifecycle, compatibility, rollout, and rollback were covered. Schema migrations, new concurrency mechanisms, and performance benchmarking were not applicable to this configuration-only change. The outstanding release question is whether the public recipe artifacts match the tested local artifacts.

Confidence: high in the implementation; medium in release readiness until published artifacts are checked. This assessment is a comment, not a formal GitHub approval.

@nellshamrell
nellshamrell marked this pull request as ready for review October 22, 2026 20:24
@nellshamrell
nellshamrell requested review from a team as code owners October 22, 2026 20:24
Comment thread samples/demo/README.md Resolved
Comment thread samples/demo/README.md Resolved
nellshamrell and others added 2 commits October 23, 2026 13:03
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Nell Shamrell-Harrington <nells@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Nell Shamrell-Harrington <nells@microsoft.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants