From 62e30166e3d636c98a144cd0252d0b270c3de80e Mon Sep 17 00:00:00 2001 From: Paolo Salvatori Date: Tue, 10 Nov 2026 19:27:02 +0100 Subject: [PATCH] Cosmos DB NoSQL samples: trust the emulator's public certificate, no LocalStack CA The emulator now serves the Cosmos DB for NoSQL endpoint (.cosmos.azure.localhost.localstack.cloud) with the LocalStack public certificate, so the Python and .NET Vacation Planner verify it with their default trust store, on the emulator as on Azure. The LocalStack-only steps that installed the LocalStack root CA are removed: the localstack-ca ConfigMap, the REQUESTS_CA_BUNDLE / SSL_CERT_FILE variables and their volume, the CA mount of the local Docker run and the LOCALSTACK_URL variable. Left in place, the Python sample's REQUESTS_CA_BUNDLE would now make it distrust the endpoint. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FvNuJLfY2KkMwaymfFnz7R --- .gitignore | 6 +- .../dotnet/README.md | 2 - .../dotnet/scripts/00-variables.sh | 4 -- .../dotnet/scripts/03-run-docker-container.sh | 55 ------------------- .../dotnet/scripts/05-deploy-app.sh | 29 ---------- .../dotnet/scripts/deployment.yml | 18 ------ .../python/README.md | 2 - .../python/scripts/00-variables.sh | 4 -- .../python/scripts/03-run-docker-container.sh | 55 ------------------- .../python/scripts/05-deploy-app.sh | 29 ---------- .../python/scripts/deployment.yml | 18 ------ 11 files changed, 2 insertions(+), 220 deletions(-) diff --git a/.gitignore b/.gitignore index fd3780f..35dbab2 100644 --- a/.gitignore +++ b/.gitignore @@ -258,10 +258,8 @@ CodeCoverage/ TestResult.xml nunit-*.xml -# Files that sample scripts or ad-hoc local runs drop into a scripts/ folder: -# the LocalStack CA written by web-app-cosmosdb-nosql-api/*/scripts/03-run-docker-container.sh -# and activity files written by a Vacation Planner run pointed at the scripts folder. -samples/**/scripts/.localstack-ca.crt +# Files that ad-hoc local runs drop into a scripts/ folder: activity files written by a +# Vacation Planner run pointed at the scripts folder. samples/**/scripts/*-activity.txt # PyPI configuration file diff --git a/samples/web-app-cosmosdb-nosql-api/dotnet/README.md b/samples/web-app-cosmosdb-nosql-api/dotnet/README.md index ae4af19..6c5716c 100644 --- a/samples/web-app-cosmosdb-nosql-api/dotnet/README.md +++ b/samples/web-app-cosmosdb-nosql-api/dotnet/README.md @@ -13,8 +13,6 @@ Before installing the sample, make sure to create an [Azure Kubernetes Service ( All commands below are run from this sample's `scripts/` folder. -> LocalStack note: when running against the emulator, the `LOCALSTACK_URL` variable in [`00-variables.sh`](scripts/00-variables.sh) triggers installation of the LocalStack root CA so the app trusts the emulated endpoint. Set it to an empty value when targeting real Azure. - > **Running on LocalStack?** Install the [lstk CLI](https://docs.localstack.cloud/aws/developer-tools/running-localstack/lstk/) and run `lstk az start-interception` to route Azure CLI calls to the emulator. See [Run against LocalStack](../../../README.md#run-against-localstack) for the full setup. ## Architecture diff --git a/samples/web-app-cosmosdb-nosql-api/dotnet/scripts/00-variables.sh b/samples/web-app-cosmosdb-nosql-api/dotnet/scripts/00-variables.sh index 3d6dea2..da92a7b 100755 --- a/samples/web-app-cosmosdb-nosql-api/dotnet/scripts/00-variables.sh +++ b/samples/web-app-cosmosdb-nosql-api/dotnet/scripts/00-variables.sh @@ -19,10 +19,6 @@ AZURECOSMOSDB_CONTAINERNAME='activities' AZURECOSMOSDB_PARTITION_KEY='/username' THROUGHPUT=400 -# LocalStack runtime URL. Set to empty when running against real Azure to skip -# the LocalStack root-CA install step in 03-run-docker-container.sh / 05-deploy-app.sh. -LOCALSTACK_URL='http://localhost:4566' - # Application config LOGIN_NAME='paolo' diff --git a/samples/web-app-cosmosdb-nosql-api/dotnet/scripts/03-run-docker-container.sh b/samples/web-app-cosmosdb-nosql-api/dotnet/scripts/03-run-docker-container.sh index bb88222..ced8671 100755 --- a/samples/web-app-cosmosdb-nosql-api/dotnet/scripts/03-run-docker-container.sh +++ b/samples/web-app-cosmosdb-nosql-api/dotnet/scripts/03-run-docker-container.sh @@ -3,17 +3,6 @@ # Variables source ./00-variables.sh -# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs -# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which -# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. -native_path() { - if command -v cygpath >/dev/null 2>&1; then - cygpath -m "$1" - else - echo "$1" - fi -} - # Retrieve the document endpoint echo "Retrieving document endpoint for Cosmos DB account [$COSMOSDB_ACCOUNT_NAME]..." AZURECOSMOSDB_ENDPOINT=$(az cosmosdb show \ @@ -44,48 +33,6 @@ else exit 1 fi -# When running against LocalStack, fetch its root CA so the container can verify -# the cosmos endpoint's TLS chain (cert is signed by LocalStack's root CA — see -# localstack-pro-azure/.../cosmos/nosql/emulator.py default_cert_store().get_or_create). -CA_MOUNT_ARGS=() -EXTRA_ENV_ARGS=() -if [ -n "$LOCALSTACK_URL" ]; then - echo "Fetching LocalStack root CA from $LOCALSTACK_URL..." - - # Fetch the target CA URL - CA_URL=$(curl -sf "$LOCALSTACK_URL/_localstack/certs" | jq -r .ca.url) - - if [ -z "$CA_URL" ] || [ "$CA_URL" = "null" ]; then - echo "Error: Failed to extract CA URL from LocalStack endpoint." >&2 - exit 1 - fi - - # Fetch the actual PEM payload - PEM_DATA=$(curl -sf "$CA_URL") - - if [ -z "$PEM_DATA" ]; then - echo "Error: Retrieved empty CA certificate payload." >&2 - exit 1 - fi - - # Write next to the script (in $CURRENT_DIR exported by 00-variables.sh). Avoid /tmp: - # on rootless / Docker Desktop / WSL2 setups, /tmp may be outside the daemon's reachable - # filesystem — in that case `-v` silently creates an empty directory at the destination - # instead of bind-mounting the file, and SSL_CERT_FILE ends up pointing at a dir. - HOST_CA_PATH="$(native_path "$CURRENT_DIR/.localstack-ca.crt")" - # Remove the CA file on script exit (Ctrl-C, normal exit, error) so we don't leave - # it lying next to the script after the container stops. - trap 'rm -f "$HOST_CA_PATH"' EXIT - printf '%s\n' "$PEM_DATA" > "$HOST_CA_PATH" - # Container runs as a non-root `app` user; make sure it can read the bind-mounted file. - chmod 644 "$HOST_CA_PATH" - echo "LocalStack root CA written to $HOST_CA_PATH" - # Use `--mount type=bind` instead of `-v`: if the source is unreachable the daemon - # errors out instead of creating a phantom empty directory at the target. - CA_MOUNT_ARGS=(--mount "type=bind,source=$HOST_CA_PATH,target=/etc/ssl/certs/localstack.crt,readonly") - EXTRA_ENV_ARGS=(-e SSL_CERT_FILE=/etc/ssl/certs/localstack.crt) -fi - # --network=host so endpoints like *.localhost.localstack.cloud resolve to the # host's loopback (where LocalStack is listening), not the container's. docker run -it \ @@ -97,7 +44,5 @@ docker run -it \ -e AZURECOSMOSDB_DATABASENAME="$AZURECOSMOSDB_DATABASENAME" \ -e AZURECOSMOSDB_CONTAINERNAME="$AZURECOSMOSDB_CONTAINERNAME" \ -e LOGIN_NAME="$LOGIN_NAME" \ - "${EXTRA_ENV_ARGS[@]}" \ - "${CA_MOUNT_ARGS[@]}" \ --name "$IMAGE_NAME" \ "$IMAGE_NAME:$IMAGE_TAG" diff --git a/samples/web-app-cosmosdb-nosql-api/dotnet/scripts/05-deploy-app.sh b/samples/web-app-cosmosdb-nosql-api/dotnet/scripts/05-deploy-app.sh index 6ff2275..939d479 100755 --- a/samples/web-app-cosmosdb-nosql-api/dotnet/scripts/05-deploy-app.sh +++ b/samples/web-app-cosmosdb-nosql-api/dotnet/scripts/05-deploy-app.sh @@ -67,35 +67,6 @@ cat namespace.yml | yq "(.metadata.name)|="\""$NAMESPACE"\" | kubectl apply -f - -# Create the LocalStack root CA configmap so the app can verify the cosmos endpoint's -# TLS chain. The cert is signed by LocalStack's root CA at emulator startup — see -# localstack-pro-azure/.../cosmos/nosql/emulator.py default_cert_store().get_or_create. -# Skipped when LOCALSTACK_URL is empty (real-Azure mode). -if [ -n "$LOCALSTACK_URL" ]; then - echo "Fetching LocalStack root CA from $LOCALSTACK_URL..." - - # Fetch the target CA URL - CA_URL=$(curl -sf "$LOCALSTACK_URL/_localstack/certs" | jq -r .ca.url) - - if [ -z "$CA_URL" ] || [ "$CA_URL" = "null" ]; then - echo "Error: Failed to extract CA URL from LocalStack endpoint." >&2 - exit 1 - fi - - # Fetch the actual PEM payload - PEM_DATA=$(curl -sf "$CA_URL") - - if [ -z "$PEM_DATA" ]; then - echo "Error: Retrieved empty CA certificate payload." >&2 - exit 1 - fi - - # Generate and apply the configmap from a literal string (avoid issues with newlines in the PEM data when using --from-file or --from-env-file) - kubectl -n "$NAMESPACE" create configmap localstack-ca \ - --from-literal=localstack.crt="$PEM_DATA" \ - --dry-run=client -o yaml | kubectl apply -f - -fi - # Create secret with the Cosmos DB primary key and SECRET_KEY cat secret.yml | yq "(.metadata.namespace)|="\""$NAMESPACE"\" | diff --git a/samples/web-app-cosmosdb-nosql-api/dotnet/scripts/deployment.yml b/samples/web-app-cosmosdb-nosql-api/dotnet/scripts/deployment.yml index 6eec136..12f2715 100644 --- a/samples/web-app-cosmosdb-nosql-api/dotnet/scripts/deployment.yml +++ b/samples/web-app-cosmosdb-nosql-api/dotnet/scripts/deployment.yml @@ -61,16 +61,6 @@ spec: secretKeyRef: name: vacation-planner-nosql-secrets key: SECRET_KEY - # Add the LocalStack root CA to the trust store .NET uses on Linux (OpenSSL honours - # SSL_CERT_FILE next to the system roots) so TLS verification of the Cosmos endpoint - # succeeds. The file is mounted from the `localstack-ca` ConfigMap created by 05-deploy-app.sh. - - name: SSL_CERT_FILE - value: /etc/ssl/certs/localstack.crt - volumeMounts: - - name: localstack-ca - mountPath: /etc/ssl/certs/localstack.crt - subPath: localstack.crt - readOnly: true resources: requests: cpu: "100m" @@ -94,11 +84,3 @@ spec: periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3 - volumes: - - name: localstack-ca - configMap: - name: localstack-ca - # `optional: true` lets the Deployment apply even in real-Azure mode where - # 05-deploy-app.sh skips creating the ConfigMap. In that case drop the - # SSL_CERT_FILE env (and the volumeMount) when switching off LocalStack. - optional: true diff --git a/samples/web-app-cosmosdb-nosql-api/python/README.md b/samples/web-app-cosmosdb-nosql-api/python/README.md index aacabbb..5ebe8e9 100644 --- a/samples/web-app-cosmosdb-nosql-api/python/README.md +++ b/samples/web-app-cosmosdb-nosql-api/python/README.md @@ -13,8 +13,6 @@ Before installing the sample, make sure to create an [Azure Kubernetes Service ( All commands below are run from this sample's `scripts/` folder. -> LocalStack note: when running against the emulator, the `LOCALSTACK_URL` variable in [`00-variables.sh`](scripts/00-variables.sh) triggers installation of the LocalStack root CA so the app trusts the emulated endpoint. Set it to an empty value when targeting real Azure. - > **Running on LocalStack?** Install the [lstk CLI](https://docs.localstack.cloud/aws/developer-tools/running-localstack/lstk/) and run `lstk az start-interception` to route Azure CLI calls to the emulator. See [Run against LocalStack](../../../README.md#run-against-localstack) for the full setup. ## Architecture diff --git a/samples/web-app-cosmosdb-nosql-api/python/scripts/00-variables.sh b/samples/web-app-cosmosdb-nosql-api/python/scripts/00-variables.sh index 89b5a9e..95ee417 100755 --- a/samples/web-app-cosmosdb-nosql-api/python/scripts/00-variables.sh +++ b/samples/web-app-cosmosdb-nosql-api/python/scripts/00-variables.sh @@ -19,10 +19,6 @@ AZURECOSMOSDB_CONTAINERNAME='activities' AZURECOSMOSDB_PARTITION_KEY='/username' THROUGHPUT=400 -# LocalStack runtime URL. Set to empty when running against real Azure to skip -# the LocalStack root-CA install step in 03-run-docker-container.sh / 05-deploy-app.sh. -LOCALSTACK_URL='http://localhost:4566' - # Application config LOGIN_NAME='paolo' diff --git a/samples/web-app-cosmosdb-nosql-api/python/scripts/03-run-docker-container.sh b/samples/web-app-cosmosdb-nosql-api/python/scripts/03-run-docker-container.sh index daa7ddc..ced8671 100755 --- a/samples/web-app-cosmosdb-nosql-api/python/scripts/03-run-docker-container.sh +++ b/samples/web-app-cosmosdb-nosql-api/python/scripts/03-run-docker-container.sh @@ -3,17 +3,6 @@ # Variables source ./00-variables.sh -# On Windows, Git Bash and MSYS2 shells pass a path such as /tmp/... unconverted to Windows programs -# (az, curl, jq, python, docker) when MSYS_NO_PATHCONV=1 is set, as the README asks. cygpath, which -# those shells provide, returns the C:/... form those programs can open; elsewhere the path is unchanged. -native_path() { - if command -v cygpath >/dev/null 2>&1; then - cygpath -m "$1" - else - echo "$1" - fi -} - # Retrieve the document endpoint echo "Retrieving document endpoint for Cosmos DB account [$COSMOSDB_ACCOUNT_NAME]..." AZURECOSMOSDB_ENDPOINT=$(az cosmosdb show \ @@ -44,48 +33,6 @@ else exit 1 fi -# When running against LocalStack, fetch its root CA so the container can verify -# the cosmos endpoint's TLS chain (cert is signed by LocalStack's root CA — see -# localstack-pro-azure/.../cosmos/nosql/emulator.py default_cert_store().get_or_create). -CA_MOUNT_ARGS=() -EXTRA_ENV_ARGS=() -if [ -n "$LOCALSTACK_URL" ]; then - echo "Fetching LocalStack root CA from $LOCALSTACK_URL..." - - # Fetch the target CA URL - CA_URL=$(curl -sf "$LOCALSTACK_URL/_localstack/certs" | jq -r .ca.url) - - if [ -z "$CA_URL" ] || [ "$CA_URL" = "null" ]; then - echo "Error: Failed to extract CA URL from LocalStack endpoint." >&2 - exit 1 - fi - - # Fetch the actual PEM payload - PEM_DATA=$(curl -sf "$CA_URL") - - if [ -z "$PEM_DATA" ]; then - echo "Error: Retrieved empty CA certificate payload." >&2 - exit 1 - fi - - # Write next to the script (in $CURRENT_DIR exported by 00-variables.sh). Avoid /tmp: - # on rootless / Docker Desktop / WSL2 setups, /tmp may be outside the daemon's reachable - # filesystem — in that case `-v` silently creates an empty directory at the destination - # instead of bind-mounting the file, and REQUESTS_CA_BUNDLE ends up pointing at a dir. - HOST_CA_PATH="$(native_path "$CURRENT_DIR/.localstack-ca.crt")" - # Remove the CA file on script exit (Ctrl-C, normal exit, error) so we don't leave - # it lying next to the script after the container stops. - trap 'rm -f "$HOST_CA_PATH"' EXIT - printf '%s\n' "$PEM_DATA" > "$HOST_CA_PATH" - # Container runs as a non-root `app` user; make sure it can read the bind-mounted file. - chmod 644 "$HOST_CA_PATH" - echo "LocalStack root CA written to $HOST_CA_PATH" - # Use `--mount type=bind` instead of `-v`: if the source is unreachable the daemon - # errors out instead of creating a phantom empty directory at the target. - CA_MOUNT_ARGS=(--mount "type=bind,source=$HOST_CA_PATH,target=/etc/ssl/certs/localstack.crt,readonly") - EXTRA_ENV_ARGS=(-e REQUESTS_CA_BUNDLE=/etc/ssl/certs/localstack.crt) -fi - # --network=host so endpoints like *.localhost.localstack.cloud resolve to the # host's loopback (where LocalStack is listening), not the container's. docker run -it \ @@ -97,7 +44,5 @@ docker run -it \ -e AZURECOSMOSDB_DATABASENAME="$AZURECOSMOSDB_DATABASENAME" \ -e AZURECOSMOSDB_CONTAINERNAME="$AZURECOSMOSDB_CONTAINERNAME" \ -e LOGIN_NAME="$LOGIN_NAME" \ - "${EXTRA_ENV_ARGS[@]}" \ - "${CA_MOUNT_ARGS[@]}" \ --name "$IMAGE_NAME" \ "$IMAGE_NAME:$IMAGE_TAG" diff --git a/samples/web-app-cosmosdb-nosql-api/python/scripts/05-deploy-app.sh b/samples/web-app-cosmosdb-nosql-api/python/scripts/05-deploy-app.sh index 30d42fd..59ee647 100755 --- a/samples/web-app-cosmosdb-nosql-api/python/scripts/05-deploy-app.sh +++ b/samples/web-app-cosmosdb-nosql-api/python/scripts/05-deploy-app.sh @@ -66,35 +66,6 @@ cat namespace.yml | yq "(.metadata.name)|="\""$NAMESPACE"\" | kubectl apply -f - -# Create the LocalStack root CA configmap so the app can verify the cosmos endpoint's -# TLS chain. The cert is signed by LocalStack's root CA at emulator startup — see -# localstack-pro-azure/.../cosmos/nosql/emulator.py default_cert_store().get_or_create. -# Skipped when LOCALSTACK_URL is empty (real-Azure mode). -if [ -n "$LOCALSTACK_URL" ]; then - echo "Fetching LocalStack root CA from $LOCALSTACK_URL..." - - # Fetch the target CA URL - CA_URL=$(curl -sf "$LOCALSTACK_URL/_localstack/certs" | jq -r .ca.url) - - if [ -z "$CA_URL" ] || [ "$CA_URL" = "null" ]; then - echo "Error: Failed to extract CA URL from LocalStack endpoint." >&2 - exit 1 - fi - - # Fetch the actual PEM payload - PEM_DATA=$(curl -sf "$CA_URL") - - if [ -z "$PEM_DATA" ]; then - echo "Error: Retrieved empty CA certificate payload." >&2 - exit 1 - fi - - # Generate and apply the configmap from a literal string (avoid issues with newlines in the PEM data when using --from-file or --from-env-file) - kubectl -n "$NAMESPACE" create configmap localstack-ca \ - --from-literal=localstack.crt="$PEM_DATA" \ - --dry-run=client -o yaml | kubectl apply -f - -fi - # Create secret with the Cosmos DB primary key and Flask secret key cat secret.yml | yq "(.metadata.namespace)|="\""$NAMESPACE"\" | diff --git a/samples/web-app-cosmosdb-nosql-api/python/scripts/deployment.yml b/samples/web-app-cosmosdb-nosql-api/python/scripts/deployment.yml index 07537ae..f95d253 100644 --- a/samples/web-app-cosmosdb-nosql-api/python/scripts/deployment.yml +++ b/samples/web-app-cosmosdb-nosql-api/python/scripts/deployment.yml @@ -61,16 +61,6 @@ spec: secretKeyRef: name: vacation-planner-nosql-secrets key: FLASK_SECRET_KEY - # Point `requests` (used by azure-cosmos under the hood) at the LocalStack - # root CA so TLS verification of the Cosmos endpoint succeeds. The file is - # mounted from the `localstack-ca` ConfigMap created by 05-deploy-app.sh. - - name: REQUESTS_CA_BUNDLE - value: /etc/ssl/certs/localstack.crt - volumeMounts: - - name: localstack-ca - mountPath: /etc/ssl/certs/localstack.crt - subPath: localstack.crt - readOnly: true resources: requests: cpu: "100m" @@ -94,11 +84,3 @@ spec: periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3 - volumes: - - name: localstack-ca - configMap: - name: localstack-ca - # `optional: true` lets the Deployment apply even in real-Azure mode where - # 05-deploy-app.sh skips creating the ConfigMap. In that case drop the - # REQUESTS_CA_BUNDLE env (and the volumeMount) when switching off LocalStack. - optional: true