Skip to content

Connect to Azure SQL with certificate validation instead of disabling it - #32

Merged
paolosalvatori merged 2 commits into
mainfrom
feature/validate-sql-server-certificate
Sep 18, 2026
Merged

paolosalvatori merged 2 commits into
mainfrom
feature/validate-sql-server-certificate

Conversation

@paolosalvatori

@paolosalvatori paolosalvatori commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Motivation

The sample connected to Azure SQL with TrustServerCertificate=yes, which keeps the connection encrypted but turns certificate validation off. That was a workaround for LocalStack for Azure, where the SQL endpoint used to present a self-signed certificate that no CA chain could validate, and the sample's own comment said so: "tells the ODBC driver to accept self-signed certificates without verification".

Two things make it unnecessary now. LocalStack for Azure serves a publicly trusted certificate for the host name it returns in fullyQualifiedDomainName, so a pod with default driver settings validates it, and Microsoft's guidance is explicit that disabling validation is the option of last resort: "Install a certificate on the server that the client trusts. This option is the recommended one, and it's the only one that keeps both encryption and validation" (ODBC driver version differences).

Leaving the keyword in place also taught the wrong habit: a reader copying this sample into an application would carry an adversary-in-the-middle exposure into production, for no benefit.

Changes

TrustServerCertificate is removed from the connection string the application builds, rather than set to no. Its default is already no in ODBC Driver 18, so writing the default value only adds noise; the keyword belongs in a connection string when you deviate from the default. Encrypt=yes stays explicit, because that default does differ between driver 17 and 18.

The code comment now says what the absence means, and when you would still need the keyword: if LocalStack cannot download its public certificate (for example with SKIP_SSL_CERT_DOWNLOAD=1), the server presents a certificate issued by the LocalStack root certificate authority, and a client then needs that authority installed or TrustServerCertificate=yes.

Fixes SMF-897

Tests

The sample is otherwise unchanged.

LocalStack for Azure (emulator started with RUNTIME_COMPONENTS=azure, cluster created by scripts/01-user-assigned-managed-identity.sh with one system and one user node):

  • The cluster script and all four deployment scripts exit 0.
  • Three vacation-planner-sql pods Running.
  • A port-forward to the vacation-planner-sql service returns HTTP 200, <title>Vacation Planner</title>, and 9 table cells rendered from the SQL database.

Real Azure (a real AKS cluster in italynorth, invented prefix, resource group deleted afterwards):

  • The cluster, the user-assigned identity, the container registry and both node pools are created, the cluster reports Succeeded and both nodes Ready.
  • 01-deploy-resources.sh, 02-build-docker-image.sh, 04-push-docker-image.sh and 05-deploy-app.sh all exit 0.
  • Both vacation-planner-sql pods reach the Ready condition, and a port-forward to the service returns HTTP 200, <title>Vacation Planner</title>, and 27 table cells rendered from the Azure SQL database.

One note on how these runs authenticated, which does not affect the sample. The cluster script creates the cluster with --enable-aad --enable-azure-rbac --aad-admin-group-object-ids, so Kubernetes API calls are authorized through Microsoft Entra: a member of that admin group runs kubectl with a plain az aks get-credentials. These runs authenticate as a service principal that belongs to no such group and holds no AKS RBAC role, and subscription Owner does not help, because Owner carries dataActions: [] while in-cluster access lives in the dataActions of roles such as Azure Kubernetes Service RBAC Cluster Admin. The kubectl steps therefore used az aks get-credentials --admin for that identity only.

The other thing worth knowing before a run: the cluster needs 4 vCPU of the standardDDSv5Family quota in the region, which a leftover cluster can exhaust.

🤖 Generated with Claude Code

…sabling it

The connection string no longer carries TrustServerCertificate. Its default is already "no" in ODBC Driver 18, so the driver validates the server certificate, which is what Microsoft recommends: "Install a certificate on the server that the client trusts. This option is the recommended one, and it's the only one that keeps both encryption and validation" (https://learn.microsoft.com/sql/connect/odbc/major-version-differences#encryption-changes). Keeping the keyword at its default value only obscured that the sample was opting out of validation.

This works against LocalStack for Azure because the emulator now serves a publicly trusted certificate for the host name it returns in fullyQualifiedDomainName, so the pod needs no local exception to reach the database.

Verified on the emulator with the sample otherwise unchanged: the cluster script and all four deployment scripts exit 0, the three pods run, and a port-forward to the vacation-planner-sql service returns the app's page with 9 cells rendered from the database.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 18, 2026 12:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The updated comment still frames TrustServerCertificate=yes as the primary mitigation for LocalStack CA scenarios instead of explicitly recommending CA installation first and treating disabling validation as a last resort.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This PR hardens the Azure SQL connection behavior in the Python web-app sample by removing TrustServerCertificate=yes from the constructed ODBC connection string, ensuring certificate validation remains enabled while keeping encryption explicit.

Changes:

  • Removed TrustServerCertificate=yes from the ODBC connection string builder.
  • Updated the in-code comment to explain why the keyword is absent and when it might be needed for LocalStack.
File summaries
File Description
samples/web-app-sql-database/python/src/database.py Removes TrustServerCertificate=yes from the built connection string and updates explanatory comments about certificate validation behavior.
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread samples/web-app-sql-database/python/src/database.py Outdated
Raised by the review on aks-samples#32: the comment presented TrustServerCertificate=yes as the answer when LocalStack serves a certificate from its own root authority, which contradicts the reasoning for the change. Installing that authority in the client's trust store keeps both encryption and validation; TrustServerCertificate=yes is the last resort, and the comment now says why.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@paolosalvatori

Copy link
Copy Markdown
Collaborator Author

That summary was produced against the first version of the branch; the point it makes was raised as an inline comment and is already fixed in 2d68ead, which is the head of this PR.

The comment on samples/web-app-sql-database/python/src/database.py now reads, in this order: the keyword is absent because its default is already no in ODBC Driver 18 and the driver therefore validates the certificate; if LocalStack could not download its public certificate it serves one issued by the LocalStack root certificate authority instead, in which case you "install that authority in the client's trust store, which keeps both encryption and validation"; and TrustServerCertificate=yes is "a last resort, because it stops the driver validating the certificate at all and exposes the connection to an adversary-in-the-middle".

So CA installation is the recommendation and disabling validation is the fallback, which is the order Microsoft's guidance uses. The inline thread is resolved, and the same wording is applied to the App Service counterpart in localstack/localstack-azure-samples#122.

@paolosalvatori
paolosalvatori merged commit 5fc4d68 into main Sep 18, 2026
1 check passed
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