Skip to content

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

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
Contributor

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 client with default 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.

Fixes SMF-898

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 same keyword is removed from the connection string deploy.sh stores in Key Vault, since that is the value the application reads and the one a reader is most likely to copy.

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.

Tests

The sample is otherwise unchanged and was deployed end to end on both targets.

Real Azure (az cloud show reporting AzureCloud, invented prefix, resource group deleted afterwards):

  • deploy.sh exit 0, validate.sh exit 0.
  • https://<app>.azurewebsites.net returned HTTP 200 with the application's own page, <title>Vacation Planner</title>, and 9 table cells rendered from the Azure SQL database.

LocalStack for Azure (emulator started with RUNTIME_COMPONENTS=azure, azlocal start-interception):

  • deploy.sh exit 0, validate.sh exit 0.
  • http://local-webapp-test.azurewebsites.azure.localhost.localstack.cloud:4566 returned HTTP 200 with the same page and the same 9 cells.

The first run of this check was rejected on purpose: HTTP 200 came from the App Service default welcome page while the site was still warming up, which proves nothing about the database. The runs above wait for the application's own title before asserting.

The certificate the emulator serves for a SQL host was also inspected directly: issuer=C=US, O=Google Trust Services, CN=WR1, a public authority.

🤖 Generated with Claude Code

paolosalvatori and others added 2 commits September 18, 2026 12:18
…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 sample no longer needs a local exception to run.

The same keyword is gone from the connection string that deploy.sh stores in Key Vault, since the app reads that value.

Verified end to end on both targets, with the sample otherwise unchanged:

- Real Azure: deploy.sh and validate.sh exit 0, and https://<app>.azurewebsites.net returns the app's own page with 9 cells rendered from the Azure SQL database.
- LocalStack for Azure: deploy.sh and validate.sh exit 0, and the app answers on the emulator endpoint with the same page and the same 9 cells.

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. The code comment says so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
deploy.sh writes the connection string into Key Vault and the app reads it from there, so leaving the keyword in that copy would keep suggesting that validation has to be disabled, even though the app no longer sets it.

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

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.

🔵 Needs a closer look

Remove -C from the remaining sqlcmd calls so deployment consistently validates certificates.

Pull request overview

Updates the Python Azure SQL sample to use encrypted connections with server certificate validation.

Changes:

  • Removes TrustServerCertificate=yes from runtime and Key Vault connection strings.
  • Documents certificate validation and the LocalStack fallback.
  • Retains explicit encryption.
File summaries
File Summary
samples/web-app-sql-database/python/src/database.py Uses default certificate validation and documents the connection behavior.
samples/web-app-sql-database/python/scripts/deploy.sh Removes the bypass from the Key Vault string, but earlier sqlcmd calls still use -C.
Review details

Suppressed comments (1)

samples/web-app-sql-database/python/scripts/deploy.sh:363

  • This changes the Key Vault connection string to validate certificates, but every sqlcmd call earlier in this same deployment script still uses -N -C; -C explicitly trusts the server certificate without validating it. The setup, schema, and seed operations therefore continue to demonstrate and rely on disabled certificate validation. Remove -C (or explicitly install/use the LocalStack CA when needed) so the deployment script consistently validates the server certificate as well.
SQL_CONNECTION_STRING="Server=tcp:${SQL_SERVER_FQDN},1433;Database=${SQL_DATABASE_NAME};User ID=${DATABASE_USER_NAME};Password=${DATABASE_USER_PASSWORD};Encrypt=yes;Connection Timeout=30;"
  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Lite

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

@paolosalvatori
paolosalvatori merged commit 068b90a into main Sep 18, 2026
4 of 5 checks 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