Skip to content

docs: configure verified PostgreSQL TLS in the getting-started tutorial - #2054

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

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

Conversation

@nellshamrell

@nellshamrell nellshamrell commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Update the PostgreSQL getting-started tutorial for TLS-enforcing Kubernetes Recipes. Add development certificate commands, a server TLS Secret, public-only client CA trust, and verified TLS. Restore sample highlighting, make cleanup work in a new terminal, and verify the connection with todo operations.

The embedded sample matches the samples companion. Leave the generated schema reference to the existing bot pipeline.

Reason for change

Related to radius-project/resource-types-contrib#337.

Merge after radius-project/resource-types-contrib#419 and radius-project/samples#2700. Confirm compatible recipe artifacts and default-pack references are available before publishing the tutorial.

Sample-sync remains blocked until the samples companion lands on edge. Older-version docs and Azure behavior are unchanged.

How to test

With Node.js and Go installed:

npm --prefix docs ci
npm --prefix docs run build

Expect a successful build and highlighted sample. Compare the embedded template with the samples companion. Follow the tutorial with TLS-capable Recipes, wait for rollout readiness, then create, refresh, and delete a todo.

Local npm --prefix docs run build, focused spelling, certificate/hostname checks, offline Secret-shape checks, and cleanup-command checks passed. Earlier integration verified database operations; it was not rerun for these documentation edits.

Only rerun download-sample-app.sh after the samples companion lands on edge; expect no drift. Remote sync is not yet passing.

File change summary

File Summary of change
docs/content/getting-started/add-connection/_index.md TLS setup, highlighting, readiness, todo verification, rotation, and cleanup.
docs/static/samples/demo/app-postgresql.bicep Public CA mount and verified client TLS.

Document operator-owned server and CA Secrets, certificate lifecycle, and rollout readiness. Refresh the generated TLS reference and embedded demo configuration.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Nell Shamrell-Harrington <nells@microsoft.com>
@github-actions

Copy link
Copy Markdown

Dependency Review

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

Scanned Files

None

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Static Web App Preview

Environment Deployment
pr2054 View workflow run

Deployed from commit c855fa068dd690681c02833d9b7d86178ce941e9 via the publisher workflow.

@nellshamrell

Copy link
Copy Markdown
Contributor Author

The sample-sync check is failing because this PR includes the updated PostgreSQL template from radius-project/samples#2700, while CI compares against the published samples/edge branch, where that change hasn’t landed yet.

The only differing file is docs/static/samples/demo/app-postgresql.bicep, and it matches the samples companion exactly. The other embedded samples match edge, and all other active checks pass.

Once the samples companion merges into edge, we can rerun the failed check. No docs changes or validation bypass are needed; syncing against the current edge would revert the intended TLS configuration.

@nellshamrell

Copy link
Copy Markdown
Contributor Author

Approver assessment

1. Orientation

The implementation is coherent, but this PR is not ready to merge independently. It teaches the client side of the PostgreSQL TLS change and depends on the recipe and samples companions becoming available.

Review of #2054, head 1c9dc487, against merge base edb25e60:

  • Purpose: keep the getting-started application working when Kubernetes PostgreSQL Recipes begin enforcing TLS.
  • Placement: the tutorial, its downloadable Bicep application, and the generated PostgreSQL reference. A Recipe is the deployment template Radius uses to provision a resource.
  • Mechanism: an operator supplies the server certificate and a separate public certificate-authority (CA) certificate. The application mounts only the public CA and configures its existing PostgreSQL client to verify the server.
  • Failure surface: missing Secrets prevent startup; incorrect trust or hostname causes client connection failure; old Recipes cannot satisfy the new client configuration.
  • Review focus: certificate ownership, client verification, deployment readiness, and coordinated publication.

2. What the code changes

The diff contains three files, with 52 additions and five deletions. It matches the stated scope.

File Actual change
docs/content/getting-started/add-connection/_index.md Adds certificate prerequisites, client-trust explanation, restart/data-loss warnings, and a database rollout check.
docs/static/samples/demo/app-postgresql.bicep Adds two client environment variables and a public-CA Secret mount.
docs/content/reference/resources/radius.data/2025-08-01-preview/postgresqldatabases/index.md Updates the generated tls description to describe Kubernetes enforcement and the operator-owned certificate requirement.

There are no application-code, dependency, generator, or workflow changes. The removal of highlighted sample line numbers avoids leaving incorrect highlights after inserting the TLS configuration.

3. Data and control flow

The intended path is:

  1. The operator issues a server certificate whose Subject Alternative Name identifies postgresql-<environment>.<namespace>.svc.cluster.local.
  2. The operator creates two Kubernetes Secrets in the actual environment namespace: one containing the server certificate/private key, and one containing only the public CA.
  3. Radius deploys the PostgreSQL resource and application. The server Recipe reads the server Secret; the Container Recipe mounts the separate CA Secret.
  4. The demo's existing PostgreSQL client receives the connection hostname and credentials, requests TLS, and verifies the certificate against its trusted CA and hostname.
  5. The tutorial waits for the database Deployment before forwarding the application port.

The names agree across the tutorial and application: the database is postgresql-${environmentName}, and the client CA Secret is postgresql-${environmentName}-ca. See the tutorial prerequisites, lines 37–48 and sample, lines 50–74.

I also checked the companion server Recipes: both expect <resource-name>-tls in the environment namespace and return the Service hostname described here.

4. Key implementation decisions

Does this configure verified TLS, or merely encrypted traffic?

Code's answer: it sets PGSSLMODE=verify-full and NODE_EXTRA_CA_CERTS=/etc/postgresql-ca/ca.crt, then mounts the CA at that location. See sample, lines 29–42.

Why it matters: encryption alone does not establish which server the client reached. Certificate-chain validation establishes trust in the issuer; hostname verification checks that the certificate identifies the requested server.

The existing demo constructs a PostgreSQL URL without an explicit TLS override and passes it to pg.Pool. That makes environment configuration a reasonable way to enable the client's existing TLS support.

Alternative: modify the application to construct an explicit TLS configuration object. That would require application tests and image publication. For this bounded sample change, configuration is simpler. Reported live tests include rejection of the wrong CA and hostname, which is stronger evidence than a successful connection alone.

Who owns the certificates?

Code's answer: the operator owns both certificate Secrets; the application owns its separate password Secret. The client never receives the server private key. See tutorial, lines 42–48 and cleanup, lines 87–90.

Why it matters: deleting the application must not silently delete externally managed certificates. Conversely, users need explicit cleanup instructions because those Secrets survive application deletion.

This boundary is sound. Generating a CA inside the sample would add private-key management and obscure the distinction between a demonstration certificate and an organization's trusted issuer.

What proves deployment success?

Code's answer: kubectl rollout status waits up to 180 seconds for the database Deployment. See tutorial, lines 58–62.

Why it matters: successful submission through rad deploy is not proof that PostgreSQL started. The companion Recipes' readiness probes perform a TLS database query, so the rollout check catches more than a running process.

It still does not prove the demo client trusts the certificate. The server probe uses its own connection settings, not the application's mounted CA and hostname verification. A successful todo operation remains the stronger end-to-end check.

5. Failure handling and operations

Missing or invalid server certificate material prevents the companion server from serving successfully; it does not silently fall back to plaintext. Missing client CA material prevents the intended verified connection. The tutorial correctly rejects optional as a workaround for an absent server certificate.

Rotation is manual. The operator updates the server Secret and restarts the database Deployment; clients must restart when their startup-loaded CA changes. The tutorial states this at line 52.

The same paragraph warns that replacement Pods start with empty databases because these Recipes do not provision durable storage. This is important: reverting the documentation or template cannot recover lost data. Recovery requires a backup.

6. Security and compatibility

The new client mount contains public trust material, not a private key. The application's password flow remains unchanged, including the existing warning that the demo displays injected connection variables.

The template deliberately becomes incompatible with plaintext-only PostgreSQL Recipes. The tutorial makes that explicit at line 50. It also preserves TLS on an optional server, which is the safer client choice.

The preceding installation page selects edge CLI installers for the script-based paths. That supports the intended unreleased scope, but it does not prove that an existing environment has refreshed its Recipe Pack. Recipe publication and environment configuration remain deployment prerequisites.

No Azure Recipe implementation changes appear in this PR. The Kubernetes-specific CA mount should not be interpreted as a portable Azure deployment example.

7. Test evidence and gaps

Verified during this review:

  • git diff origin/edge...HEAD --check passes.
  • The embedded sample's Git blob matches the samples companion exactly.
  • The generated TLS reference text matches the recipe companion's source-schema description and allowed values.
  • Current CI passes Hugo, Bicep validation, spellcheck, DCO, and preview deployment.

Reported evidence, not independently rerun here: local integration checks exercised both recipe drivers, required/optional policies, actual database operations, wrong-CA and wrong-hostname rejection, and password precedence.

Limits: build and compiler checks cannot prove the published Recipe Pack or current demo image behaves correctly. This review checked the generated text against its source but did not rerun the full generation pipeline or deploy a cluster.

The sole failing CI check is sample synchronization. It compares against published samples/edge, where the companion has not landed.

8. Risk register

Risk Likelihood / impact Detection and recovery
Docs publish before compatible samples/Recipes High if merged independently; tutorial deployment or connection fails Sample-sync catches the sample mismatch, but not stale recipe artifacts. Publish dependencies first; updating the pack and redeploying may require backup precautions.
Wrong CA, hostname, or namespace Medium during manual setup; connection unavailable Rollout detects server problems; todo operations detect client trust problems. Correct the Secret/certificate and restart affected workloads.
Certificate rotation replaces an ephemeral database Pod Expected during rotation; high impact for retained data Explicit warning mitigates surprise. Recovery depends on a backup, not a Git revert.

9. Findings

Blocking findings: no code defect established. There is a concrete merge dependency: radius-project/samples#2700 must reach edge, and compatible recipe artifacts must be available before publishing this tutorial.

Non-blocking concern: tutorial lines 58–65 stop at database readiness and visible connection entries. Add an explicit create/list/delete todo step so readers verify the newly introduced client-trust configuration, rather than only resource discovery.

Strengths: private-key separation, accurate Secret ownership, explicit data-loss warnings, and configuration-only reuse of the existing client.

Residual risks: mutable published artifacts and previously initialized environments can diverge from the tested combination.

10. Approval recommendation

Comment. The implementation is suitable for the intended Kubernetes tutorial, but I would hold approval until the samples dependency clears and the compatible recipe publication path is confirmed. Once those gates are satisfied, the remaining tutorial verification improvement is non-blocking.

11. Coverage and confidence

Read all three changed files and the merge-base diff; inspected the issue, PR discussion, checks, sample client path, companion certificate/readiness contracts, Container Recipe mappings, and publication/sync behavior. Covered ownership, trust, failure behavior, cleanup, compatibility, and generated-output consistency.

Performance, concurrency, and database-schema migration review were not applicable to this configuration/documentation diff. No files were changed and no approval was submitted.

Confidence: medium. Source consistency is strong; live integration evidence is reported rather than independently reproduced, and publication dependencies remain unresolved.

@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
Restore sample highlights, add self-contained development certificate setup and cleanup, and verify the demo connection with todo operations. Defer the generated reference update to the authoritative bot pipeline.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Nell Shamrell-Harrington <nells@microsoft.com>
@nellshamrell

Copy link
Copy Markdown
Contributor Author

Added a create/refresh/delete todo step after opening the demo. This checks the client's database connection rather than treating connection metadata or database readiness as proof that client TLS trust works.

Deploy the updated definition from its published URL. The `--parameters` argument sets the database password, and `$(openssl rand -hex 16)` generates a random value on each deploy. This works in Bash and Zsh; in PowerShell, generate the value separately and pass it in:
### Prepare PostgreSQL TLS

Before deploying this edge sample, use a PostgreSQL Kubernetes Recipe with TLS support and prepare the operator-owned certificates. The default `required` policy rejects plaintext TCP connections. `optional` permits plaintext but still requires a server certificate; it is not a workaround for missing TLS configuration.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (non-blocking): after radius-project/resource-types-contrib#419 (0e9d2f7), the Recipes generate a self-signed certificate when <name>-tls is absent, so optional no longer requires an operator certificate. The tutorial still needs one because the demo uses verify-full and needs a CA it trusts. Could we give that reason here instead? radius-project/samples#2700 now words it this way.

This branch was previously deployed

1 inactive deployment
edge — c855fa06 Deployed Oct 23, 2026 by nellshamrell via Dispatch Static Web App Preview #7063
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants