What you will implement
You will deploy a customer-owned C# or Python workload as an hourly Azure Container Apps Job. The implementation uses a user-assigned managed identity to read its certificate from Azure Key Vault, authenticate to the Wavac Customer API, call discovery, and write a bounded result to a private Azure Blob container.
The supplied deployment creates an Azure resource group, Azure Container Registry, RBAC-enabled Key Vault, Blob container, managed identity, Container Apps environment, and scheduled job. These resources can incur Azure charges. Use the deployment as a working starting point. Before production use, bring the source into your organization's normal source control, infrastructure, security review, and release process.
Each job execution:
- Uses managed identity to read the current certificate envelope from one stable Key Vault secret.
- Renews the certificate with a new RSA key held only in memory when fewer than six hours remain.
- Requests a 10-minute token, proves it through the authentication-session operation, and calls discovery once.
- Replaces the stable Key Vault secret only after the new certificate succeeds.
- Writes only the timestamp, outcome, resource count, site count, or a bounded failure code to private Blob storage.
The implementation does not log or upload enrollment tokens, private keys, PKCS#12 values, passwords, certificates, CSRs, access tokens, response bodies, or customer identifiers. Redirects, automatic request replay, malformed responses, non-600-second tokens, and responses over 1 MiB fail closed.
Before you begin
In the Wavac Dashboard, select your customer and open Settings > Customer API. Use this page to manage your integration and issue its enrollment token. If the tab is unavailable, ask your customer administrator for access.
As a Customer API administrator, use the
Azure reference package download
on the Customer API documentation page. Sign in with your Dashboard account.
The package downloads in place and contains C# and Python source under
examples/azure-discovery-job/dotnet and examples/azure-discovery-job/python, plus the
examples/azure-discovery-job/deploy-platform-managed-mtls.sh deployment
script. You do not need access to the Admin portal or platform-operator permission.
You also need:
- An Azure subscription and permission to create resources and role assignments.
- Azure CLI, Bash, and OpenSSL on the workstation used for deployment.
- A customer-controlled source repository for the adopted implementation.
- An approved Azure region, resource naming policy, network policy, monitoring plan, and release process.
- A Platform managed Customer API integration with the
customer.discovery.readscope.
Sign in with az login and select the intended subscription. Ask an
customer administrator to select Issue enrollment for the integration in
Settings > Customer API. Save the
displayed value as ~/Downloads/cred.txt. The file contains only the token:
<43-character-one-time-bootstrap-token>
The token expires after 24 hours and is consumed by the first successful enrollment. The enrollment endpoint is fixed in the implementation. Starting bootstrap first exchanges the token for the required certificate subject without consuming it, then generates the private key and CSR in the customer workload. Issuing the token authorizes bootstrap; starting the bootstrap execution is your acceptance and proof of private-key possession.
Do not paste the bootstrap token into source control, shell arguments, logs, deployment manifests, or support messages. Store it only long enough to complete enrollment.
Adopt the implementation
The package demonstrates authentication, protected credential storage, renewal, and manual recovery. Both languages follow the same platform-managed mTLS flow; the package does not supply a customer-managed assertion example. It is not a complete application or data importer. It does not demonstrate production Postman access, distributed token coordination, or data checkpoints. If you add data queries, use delayed windows and reconciliation rather than advancing a checkpoint to the current minute.
Plan HTTP client and connection-pool lifetimes, DNS refresh, certificate replacement and connection draining, cancellation, timeouts, concurrency, and safe retries for your application. The example is not a general-purpose client library. Review these decisions alongside durable processing and monitoring before adopting it as part of a complete solution.
The Python TLS loader briefly uses private temporary files to load the client certificate and key, then deletes them. Provide protected ephemeral storage and do not assume Python can erase private-key bytes from memory.
Keep scheduled and manual runs from overlapping for the same integration. The reference token cache is in memory for one process; a newly started job requests its own token. Budget that token and any candidate-certificate proof against the authentication and renewal limits. The supplied hourly schedule leaves room for renewal, but independently launched copies do not share a lock or a fresh allowance.
Copy the supplied source and deployment assets into a customer-controlled repository. Review the implementation and replace the sample deployment with your approved infrastructure-as-code and delivery process when required by your organization.
Preserve these behavior and configuration contracts when you adapt it:
| Contract | Required implementation behavior |
|---|---|
| Workload identity | Use the user-assigned identity selected by AZURE_CLIENT_ID; do not add stored Azure credentials. |
| Certificate storage | Read and replace the stable secret named by AZURE_KEY_VAULT_CERTIFICATE_SECRET in the vault at AZURE_CERTIFICATE_VAULT_URI. |
| Bootstrap token | Pass WAVAC_BOOTSTRAP_SEED_VAULT_URI and WAVAC_BOOTSTRAP_SEED_SECRET only to a manually approved bootstrap or recovery execution. The referenced secret contains only the one-time token. |
| Diagnostics | Write only bounded status and aggregate counts to the container named by AZURE_BLOB_CONTAINER at AZURE_BLOB_SERVICE_URI. |
| Scheduling | Run more frequently than every six hours so a healthy execution can renew before the 24-hour certificate expires. The supplied schedule runs hourly. |
| Execution retries | Keep the Container Apps replica retry limit at zero so infrastructure retries cannot replay an execution. |
| HTTP handling | Reject redirects and invalid contracts; do not automatically replay Customer API operations. |
| Token lifetime | Keep access tokens in process memory only and never share a token cache between integrations. |
Apply least-privilege Azure roles to the job identity: AcrPull on the
registry, Key Vault Secrets Officer on the certificate vault and temporary
seed vault, and Storage Blob Data Contributor on the diagnostics container.
If your organization separates the seed and certificate vaults, grant the
identity access to both and remove seed-vault access after bootstrap.
Before releasing the image, review and approve the selected runtime and dependency versions, transitive dependencies, licenses, advisories, base image, and image digest. Configure your own alerts for failed or missing executions. The Blob result is deliberately bounded and is not a replacement for workload monitoring.
Deploy the customer workload
From the extracted or adopted package root, review the script and set the Azure region approved by your organization. You can provide a stable six-character, lowercase hexadecimal suffix for repeatable resource names; otherwise the script creates a random suffix.
AZURE_LOCATION=eastus \
AZURE_RESOURCE_SUFFIX=a1b2c3 \
examples/azure-discovery-job/deploy-platform-managed-mtls.sh \
"$HOME/Downloads/cred.txt"
The default language is C#. To use Python, add WAVAC_REFERENCE_LANGUAGE=python
to the environment assignments in the command above. Both choices use the same
configuration, bootstrap instructions, certificate storage format, and schedule.
The script creates and configures the Azure resources, builds the selected image in Azure Container Registry, stores the token in Key Vault, and creates the scheduled job without consuming it. It prints the resource group, vault, job, diagnostics container, and a safe manual bootstrap command. The command contains only a Key Vault URI and secret name, never the token.
Role assignments can take up to two minutes to propagate. The script retries only the initial Key Vault and Blob setup operations. It does not retry Wavac enrollment.
If you replace the script with your own deployment, configure the job with the five persistent environment variables in the contract table, assign the Azure roles above, set a 180-second execution timeout, use one replica with no parallel execution, and set the replica retry limit to zero.
Enroll the integration
Run the manual bootstrap command printed by the deployment script. It has this form:
az containerapp job start \
--name wavac-customer-api-mtls \
--resource-group <resource-group> \
--container-name discovery \
--image <registry>/wavac/customer-api-mtls:<tag> \
--cpu 0.25 \
--memory 0.5Gi \
--env-vars \
AZURE_CLIENT_ID=<managed-identity-client-id> \
AZURE_CERTIFICATE_VAULT_URI=https://<certificate-vault>.vault.azure.net/ \
AZURE_KEY_VAULT_CERTIFICATE_SECRET=wavac-customer-api-mtls \
AZURE_BLOB_SERVICE_URI=https://<storage-account>.blob.core.windows.net/ \
AZURE_BLOB_CONTAINER=diagnostics \
WAVAC_BOOTSTRAP_SEED_VAULT_URI=https://<seed-vault>.vault.azure.net/ \
WAVAC_BOOTSTRAP_SEED_SECRET=wavac-customer-api-bootstrap
Azure CLI treats any start-time container setting as a complete execution override. Keep the image, resources, and five persistent environment variables in the command printed by the deployment script; omitting them does not inherit those values from the scheduled template.
The runner first checks for a working current certificate. If none works, it uses the token to retrieve the required CSR subject without consuming it, generates an RSA private key and CSR in memory, completes enrollment, proves token, authentication-session, and discovery access, stores the password-protected PKCS#12 envelope in the certificate vault, and deletes the seed secret. Successful enrollment activates the platform-managed credential; no second administrator approval is required.
Verify the implementation
Use the resource group printed by the deployment:
az containerapp job execution list \
--name wavac-customer-api-mtls \
--resource-group <resource-group> \
--output table
az storage blob list \
--account-name <storage-account> \
--container-name diagnostics \
--auth-mode login \
--query '[].{name:name,size:properties.contentLength}' \
--output table
Confirm that the bootstrap execution succeeded and that its diagnostic is
smaller than 2 KiB with outcome: success plus aggregate resource and site
counts. Inspecting application logs is not required to retrieve the result. If
the job fails before the diagnostic writer is available, Container Apps reports
the failed execution without a blob.
After the next scheduled interval, confirm that another execution and diagnostic appear. Record the job, vault, secret, storage account, integration owner, and alert destination in your operational inventory.
Operate certificate renewal
The hourly schedule renews before expiry. Wavac accepts the predecessor for the 10-minute access-token overlap, while only the replacement can initiate the next renewal. The runner keeps a candidate private key in process memory until the new certificate completes token, authentication-session, and discovery requests. A failed candidate never replaces the stable Key Vault secret.
The token manager keys each in-memory token by certificate thumbprint and
normalized scopes, starts refresh 90 to 120 seconds before expiry, and lets
concurrent callers share one refresh. A new token becomes visible only after
HEAD /api/customer/v1/auth/session succeeds with the same certificate. A
transient refresh failure may use the old token only while more than 30 seconds
remain; an authentication failure discards it. Keep one token manager for each
credential and fixed scope set in a long-running service. Do not persist its
cache.
If the certificate remains expired for one hour, the next run deletes the stable secret and fails closed. Key Vault creates an internal version on every overwrite and cannot delete one secret version, so historical versions remain subject to the vault's soft-delete retention and purge protection.
Recover an integration
If renewal repeatedly fails or the stored key is lost, revoke the inaccessible credential and ask an administrator for a new platform-managed enrollment token. Store the token-only file in a Key Vault the job identity can read and delete:
az keyvault secret set \
--vault-name <seed-vault> \
--name wavac-customer-api-bootstrap \
--file "$HOME/Downloads/cred.txt" \
--expires "$(date -u -d '+24 hours' +%Y-%m-%dT%H:%M:%SZ)"
Start the manual recovery execution using the command from Enroll the integration. The runner does not consume the handoff when the current certificate passes live token and discovery proof. Otherwise it prepares the same token-only enrollment, generates a new key, validates the certificate contract, proves the new mTLS identity, and only then replaces the stable certificate secret.
The certificate and seed vaults can be separate. If proof fails, the existing certificate secret and bootstrap seed remain unchanged. Revoke a newly activated but unstored credential before retrying with a new handoff.
There is no customer-side JWK vault in this platform-managed implementation. The certificate vault remains customer-owned, while the federation JWK signer remains inside Wavac's separately protected, HSM-backed boundary. Customer-managed mode uses the separate signer implementation described in Use Microsoft Entra and Key Vault.
Remove an evaluation deployment
Do not remove a production resource group as a cleanup step. For an evaluation deployment that you no longer need, revoke its Customer API credential first, then delete only the resource group created for that evaluation. Key Vault purge protection retains the deleted vault for its configured retention period.
az group delete --name <resource-group> --yes
Review Customer API security before production release. The Azure design also follows Microsoft guidance for Container Apps Jobs, managed identities, Key Vault RBAC, and Blob data access roles.