Skip to content

Connect Azure

An Engineer or Admin can configure the Azure connector from the guided setup without a human handoff. It assumes 1 subscription. You create an app registration and service principal; Venturi’s workload identity federates into it using the issuer and subject values Venturi provides in guided setup. No customer-side workload identity is required.

No long-lived cloud credentials by default: the federated identity credential holds no secret. The client-secret fallback is the exception. See Docs authority & product state.

Access boundary

The module creates a Microsoft Entra ID app registration and service principal with these subscription-scope read roles:

  • Reader for resource inventory.
  • Cost Management Reader for billed cost.
  • Monitoring Reader for Azure Monitor metrics and logs.

The APIM and Event Hubs telemetry grants are off by default. Enable each grant together with its resource ID:

  • enable_apim_access with apim_log_analytics_workspace_id assigns Log Analytics Reader on the workspace that receives your API Management language-model logs.
  • enable_event_hub_access with event_hub_namespace_id assigns Azure Event Hubs Data Receiver on the namespace that receives Azure OpenAI diagnostic logs.

If you enable a grant without its resource ID, the module assigns the role at subscription scope instead.

Content-bearing logs. Read access to a log source is not storage of its content. Event Hubs diagnostic streams can carry request or response detail when that diagnostic category is enabled, and the Reader and Monitoring Reader roles can query Log Analytics workspace tables in the subscription, including API Management language-model logs that hold prompts and completions when message logging is enabled. Scope the Event Hubs grant to the named namespace and the Log Analytics grant to the named workspace. Workspace scoping limits only the added Log Analytics Reader grant: subscription-scope Reader and Monitoring Reader can still query the API Management language-model log table, so for Log Analytics the in-memory field allowlist and non-persistence below are the controlling boundary. Azure Monitor protected tables, a Microsoft preview feature, can close that path on your side: a table set to Protected returns no rows to standard read roles such as Reader, Monitoring Reader, and Log Analytics Reader. For any record that still contains content, the collector applies a field allowlist in memory, discards prompt and completion fields before a record is materialized, and never persists, logs, retries, or exports them. The normalized InvocationEvent schema has no field for prompt or completion text.

These setup permissions are separate from the standing read-only identity:

  • A human setup operator needs permission to register applications and their service principals in Microsoft Entra ID, such as the Application Developer role, plus Owner, User Access Administrator, or Role Based Access Control Administrator on the subscription for the role assignments.
  • A CI principal needs the Microsoft Graph application permission Application.ReadWrite.OwnedBy with admin consent, plus the same subscription role-assignment authority. With that permission, the CI principal can manage only objects it owns, so it must be an owner of both the app registration and the service principal.
  • Global Administrator alone does not grant Azure RBAC on the subscription. A Global Administrator can elevate access to User Access Administrator at root scope, assign the subscription role, and then remove the elevated access.
  • The module pins the azurerm provider at ~> 3.80, which by default tries to register every Azure resource provider it supports that is not yet registered on the subscription. Owner can register resource providers; User Access Administrator and Role Based Access Control Administrator cannot. If the setup identity is not Owner, set ARM_SKIP_PROVIDER_REGISTRATION=true before terraform init. The module’s own resources are Microsoft Entra ID objects and role assignments, which need no additional resource provider.

Steps

1. Generate configuration

Role: Engineer or Admin in Venturi; authorized Microsoft Entra ID and Azure RBAC setup principal for the apply.

Select Azure in guided setup. The tenant flow generates the exact issuer, subject, connector ID, and pinned Terraform artifact in-product. Download the pinned Terraform artifact and extract it: the extracted infra/azure/onboarding/ directory holds the module, backend.tf, terraform.tfvars.example, and scripts/verify.sh. Save the generated values below as infra/azure/onboarding/terraform.tfvars, run every command on this page from that directory, and keep it, with its Terraform state or backend configuration, for rotation and removal.

Terraform
subscription_id                 = "00000000-0000-0000-0000-000000000000"
display_name                    = "venturi-readonly-integration"
use_federated_credential        = true
federated_credential_issuer     = "<generated in guided setup>"
federated_credential_subject    = "<generated exact subject>"
enable_apim_access              = false
apim_log_analytics_workspace_id = ""
enable_event_hub_access         = false
event_hub_namespace_id          = ""

display_name is the display name of the app registration the module creates; the module default is venturi-readonly-integration.

2. Deploy and prove the boundary

Role: the setup operator for the apply; a separate identity that holds only Reader on the subscription for verify.sh.

Bash
cd infra/azure/onboarding
az login
export SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000"
az account set --subscription "$SUBSCRIPTION_ID"
# Only if the setup identity is not Owner on the subscription:
export ARM_SKIP_PROVIDER_REGISTRATION=true
terraform init
terraform plan
terraform apply
CLIENT_ID="$(terraform output -raw client_id)"
OBJECT_ID="$(terraform output -raw object_id)"
export CLIENT_ID OBJECT_ID

This is the only terraform apply in the setup path; the Deploy step later activates the connector without applying the module again. Capture the outputs before you switch identity, as the last 3 lines do: a Reader-only identity cannot read Terraform state kept in the azurerm backend. Then sign in to az as the Reader-only identity, select the same subscription, and run the checks:

Bash
az login
az account set --subscription "$SUBSCRIPTION_ID"
./scripts/verify.sh
az role assignment list --assignee "$OBJECT_ID" --all --output table

The script confirms the service principal, its subscription-scope role allowlist, the federated credential, and the password-credential count. Its write probes run as the identity signed in to az, not as the service principal. From the Reader-only identity they are always denied, so they test only that identity. The service principal’s boundary is proven by the --all listing, which shows its role assignments at every scope, and by automated credential validation. The script’s role allowlist is not that proof: it also accepts roles the module never grants, including Reader and Data Access, which can read and write all data in a storage account through its account keys, and Billing Reader, and it checks subscription scope only. The listing must show only Reader, Cost Management Reader, and Monitoring Reader on the subscription, plus Log Analytics Reader on the named workspace and Azure Event Hubs Data Receiver on the named namespace when you enable those grants. See Verify & confirm for the exact checks. Connector activation performs positive Cost Management, Resource Graph, Monitor, and enabled scoped telemetry reads through federation.

3. Register outputs and validate

Enter the client ID, object ID, tenant ID, subscription ID, and optional scoped resource IDs in the connector step. Automated credential validation checks the issuer and subject match, federation exchange, positive reads, and write denial.

4. Inspect evidence

Open the readiness result and /invocations. Inspect coverage, unknown identity or cost dimensions, and freshness before enabling exports.

Client-secret fallback

Use the fallback only when your tenant cannot accept the federated credential. Set use_federated_credential = false; the module then creates a client secret that expires 90 days after creation instead of a federated credential.

The secret value is not a Terraform output, but Terraform stores it in plain text in the state file. Keep state in a remote backend with encryption at rest and restricted access: uncomment and configure the azurerm backend block in backend.tf before terraform init, or use an equivalent encrypted, access-restricted backend. If state already exists locally, configure the backend and run terraform init -migrate-state. Never commit terraform.tfstate to source control.

Read the value only to enter it in the Azure connector step of guided setup, which stores it as an encrypted credential reference. Never send it by email, chat, or support ticket.

Bash
terraform show -json | python3 -c 'import json, sys; resources = json.load(sys.stdin)["values"]["root_module"]["resources"]; print(next(r["values"]["value"] for r in resources if r["type"] == "azuread_application_password"))'

Renew the secret before it expires; see Rotate & offboard.

Success evidence

  • terraform apply succeeds with a federated identity credential, or with 1 client secret in the client-secret fallback.
  • verify.sh exits 0 with no SEC or MISS lines; with APIM access enabled, no role: Log Analytics Reader line; and an --all listing that shows only Reader, Cost Management Reader, and Monitoring Reader on the subscription, plus Log Analytics Reader on the named workspace and Azure Event Hubs Data Receiver on the named namespace when those grants are enabled. In the client-secret fallback, a MISS line for the absent federated credential is expected; resolve every other MISS.
  • Connector positive reads pass.
  • Automated credential validation accepts the exact issuer and subject.
  • The 1st Azure-backed AttributionRecord appears in /invocations.

Recovery

Failure Action
AADSTS70021 Compare the exact generated issuer, subject, and audience (api://AzureADTokenExchange) with the federated credential, then reapply.
Cost Management returns empty For an Enterprise Agreement, an Enterprise admin must enable AO view charges on the billing account (enrollment) before subscription-scope roles can see costs. Otherwise, confirm the billing scope that holds the charges: an EA billing account, department, or enrollment account; a Microsoft Customer Agreement billing account, billing profile, or invoice section; or a CSP billing account, billing profile, or customer.
Azure OpenAI evidence is missing Identify whether telemetry flows through API Management or Event Hubs, then enable the matching grant with its workspace or namespace resource ID and apply.
Terraform application setup fails For terraform apply failures on azuread_application or azuread_service_principal, use a setup operator who can register applications, such as the Application Developer role, or a CI principal with Application.ReadWrite.OwnedBy, admin consent, and ownership of both the app registration and the service principal.
Role assignment fails Use a setup identity with Owner, User Access Administrator, or Role Based Access Control Administrator on the subscription. Global Administrator alone does not grant subscription RBAC; elevate access, assign the role, then remove the elevation.
terraform plan or apply fails while registering Azure resource providers The setup identity cannot register resource providers, which User Access Administrator and Role Based Access Control Administrator cannot do. Set ARM_SKIP_PROVIDER_REGISTRATION=true, then run terraform init and terraform plan again.
The --all listing shows a role other than the access boundary, such as Reader and Data Access or Billing Reader Security finding, even when verify.sh prints the role as OK. Disable the connector, remove the assignment, and rerun the checks.
verify.sh reports write probes SUCCEEDED The script ran under a privileged az session. Under Owner, the role-assignment and resource-group probes succeed: delete the venturi-verify-must-fail-<timestamp> resource group. Under User Access Administrator or Role Based Access Control Administrator, only the role-assignment probe succeeds and no resource group is created. In both cases the script removes the Contributor assignment; confirm with the --all listing, then rerun as the Reader-only identity.
verify.sh prints role: Log Analytics Reader APIM access is enabled with an empty apim_log_analytics_workspace_id, so the role sits at subscription scope and reaches every workspace. Set the workspace resource ID, apply, and rerun the checks.
Freshness becomes stale Check the readiness probe and latest Monitor or billing timestamp.

Support is an optional escalation path after the failed probe and correlation ID are available.

Rotate or revoke

The module models 1 federated identity credential, so subject or issuer rotation is a coordinated cutover. Review it at least every 12 months:

  1. In Integrations / Azure, generate the next exact subject, and issuer if it changes.
  2. Set federated_credential_subject and federated_credential_issuer in terraform.tfvars, then run terraform plan and terraform apply. The credential now accepts only the new values.
  3. Immediately activate the new values in the connector settings and rerun automated credential validation. Venturi cannot exchange tokens between the apply and this step, so complete steps 2 and 3 together.
  4. Rerun the checks in step 2 as the Reader-only identity.

The client-secret fallback renews through its own cutover; see Rotate & offboard.

To revoke access, disable the connector in Integrations / Azure, then run:

Bash
cd infra/azure/onboarding
terraform destroy

Confirm the application, service principal, federated credential or client secret, and role assignments are absent and record the revocation result.

Reference

Give feedback on this page

Sign in to send feedback. Opens in a new tab.

Copy page details
Docs feedback: Connect Azure
Page: https://docs.venturi.systems/onboarding/azure/