> ## Documentation Index
> Fetch the complete documentation index at: https://docs.formal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Azure

> Connect an Azure subscription for autodiscovery and Blob Storage log delivery

The Azure Cloud Account connects an Azure subscription to Formal using [workload identity federation](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation). Formal presents its AWS identity, which your tenant trusts through a federated identity credential, then authenticates as a dedicated user-assigned managed identity. No secrets are created; access is entirely federated.

## What Formal Discovers

<AccordionGroup>
  <Accordion title="Database Autodiscovery" icon="database">
    Discover Azure Database for PostgreSQL and MySQL flexible servers and add
    them as Formal resources.
  </Accordion>

  <Accordion title="AKS Autodiscovery" icon="dharmachakra">
    Discover AKS clusters for Kubernetes access control.
  </Accordion>

  <Accordion title="Virtual Machine Autodiscovery" icon="server">
    Discover Azure virtual machines for SSH access.
  </Accordion>

  <Accordion title="Blob Storage Log Delivery" icon="bucket">
    Forward Formal Connector logs to Blob Storage containers for long-term
    storage and compliance.
  </Accordion>
</AccordionGroup>

Autodiscovery for all three resource types is on by default. See [Resource Autodiscovery](/docs/guides/integrations/clouds/introduction#resource-autodiscovery) for how discovered resources behave.

## Architecture

The [`terraform-formal-azure`](https://github.com/formalco/terraform-formal-azure) module creates:

1. **Resource Group**: a group named `fml-<suffix>` holding Formal's identity, so the module needs no pre-existing group
2. **User-Assigned Managed Identity**: the identity Formal authenticates as
3. **Federated Identity Credential**: pins Formal's OIDC issuer, Formal's per-integration role ARN as the subject, and the `api://AzureADTokenExchange` audience. Entra matches all three exactly, so only that role can exchange a token.
4. **Role Assignments**: the built-in roles the enabled capabilities require, on the subscription or on a single resource group when one is set

Formal derives the roles from the capabilities you enable and returns them for the module to grant. `Reader` covers every discovery call, so it is granted once rather than per resource type, and `Storage Blob Data Contributor` is added for log delivery.

<Note>
  Azure has no region for the integration itself. The managed identity is regional, but what it can read is not, so discovery covers the whole subscription regardless of where the identity lives.
</Note>

## Setup

<Tabs>
  <Tab title="Web Console">
    <Steps>
      <Step title="Navigate to Cloud Accounts">
        Go to [Cloud Accounts](https://app.formal.ai/cloud-accounts)
      </Step>

      <Step title="Create Integration">
        Click **Create Integration** and choose **Microsoft Azure**
      </Step>

      <Step title="Enter Details">
        Enter a name and the **subscription ID** to connect. Optionally set a **resource group** to narrow Formal's access to that group instead of the whole subscription.
      </Step>

      <Step title="Choose Discovery">
        Toggle autodiscovery for **Virtual Machines**, **AKS**, and **Databases**. All are on by default.
      </Step>

      <Step title="Configure Blob Storage Access">
        To deliver logs to Blob Storage, enable **Blob Storage log writes**. Leave the storage account list empty to allow every account in scope, or name specific accounts to scope access.
      </Step>

      <Step title="Run the Setup Command">
        Formal shows a command with your integration ID and security key filled in. Paste it into an authenticated [Azure Cloud Shell](https://learn.microsoft.com/en-us/azure/cloud-shell/overview) for that subscription:

        ```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
        bash <(curl -sSL https://formal.ai/azure.sh) <INTEGRATION_ID> <SECURITY_KEY>
        ```

        It fetches the setup parameters from Formal, runs `terraform apply`, then reports the tenant and managed identity client ID back to Formal, which activates the integration.
      </Step>
    </Steps>

    <Tip>
      Azure Cloud Shell can run Bash or PowerShell, and remembers the last choice. Run this command in Bash. In PowerShell it fails with `The '<' operator is reserved for future use`; switch shells with the selector at the top of Cloud Shell, or type `bash` first.
    </Tip>

    To change access later, change the capability on the integration in Formal, then run the same command again. The script keeps its Terraform state in the subscription, so a rerun adds what the integration now needs and removes what it no longer does.
  </Tab>

  <Tab title="Terraform">
    Register the integration, provision the Azure resources with the [`terraform-formal-azure`](https://github.com/formalco/terraform-formal-azure) module, then report them back to activate it.

    ```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
    # 1. Register the Azure Cloud Account. Autodiscovery is on by default.
    #    allow_blob_access with an empty blob_storage_accounts list grants log delivery
    #    to any storage account in scope; set blob_storage_accounts to scope it.
    resource "formal_integration_cloud" "azure" {
      name = "production-azure"

      azure {
        subscription_id          = "00000000-0000-0000-0000-000000000000"
        enable_vm_autodiscovery  = true
        enable_aks_autodiscovery = true
        enable_db_autodiscovery  = true
        allow_blob_access        = true
      }
    }

    # 2. Provision the Azure-side resources with the Formal-maintained module (managed
    #    identity, federated identity credential, role assignments).
    module "formal_azure" {
      source = "github.com/formalco/terraform-formal-azure"

      integration_id   = formal_integration_cloud.azure.id
      subscription_id  = formal_integration_cloud.azure.azure[0].subscription_id
      issuer           = formal_integration_cloud.azure.azure_issuer
      subject          = formal_integration_cloud.azure.aws_formal_role_arn
      role_definitions = formal_integration_cloud.azure.azure_role_definitions
    }

    # 3. Report the created resources back to Formal to activate the integration.
    resource "formal_integration_cloud_azure_activation" "azure" {
      integration_id = formal_integration_cloud.azure.id
      tenant_id      = module.formal_azure.tenant_id
      client_id      = module.formal_azure.client_id
    }
    ```

    <Note>
      The activation resource is separate from `formal_integration_cloud` on purpose. That resource feeds the module its ID and role ARN, so reading the module outputs back into it would create a dependency cycle.
    </Note>

    Each discovery flag defaults to `true`; set any to `false` to skip that resource type.
  </Tab>
</Tabs>

## Blob Storage Log Delivery

To forward logs to Blob Storage, the integration needs Blob Storage access. Enable it when creating the integration with two settings:

| Setting                 | Description                                                                        |
| ----------------------- | ---------------------------------------------------------------------------------- |
| `allow_blob_access`     | Grants Formal's managed identity permission to write log blobs.                    |
| `blob_storage_accounts` | Storage accounts Formal may write to. Leave empty to allow every account in scope. |

Formal grants matching roles: `Storage Blob Data Contributor` on the subscription or resource group when the account list is empty, or on each named account when you restrict it.

Once access is granted, create a [Log Integration](/docs/guides/integrations/log) pointing at a container to start forwarding logs.

## Security

* **Federated, keyless**: no client secrets or certificates are created or stored.
* **Pinned role**: the federated identity credential trusts only Formal's exact per-integration role ARN, matched case sensitively alongside the issuer and audience.
* **Least privilege**: the managed identity gets only the built-in roles the capabilities you enable require.
* **Scope narrowing**: set a resource group to keep discovery and every role assignment inside that group.
* **Account scoping**: restrict `blob_storage_accounts` so Formal can write only to the accounts you name.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Setup command fails">
    **Possible causes:**

    * The Cloud Shell is not set to the target subscription
    * The account lacks permission to create resource groups, identities, or role assignments
    * The integration ID or security key is wrong
    * The command was run in PowerShell rather than Bash

    **Solution:**

    1. Run `az account set --subscription <SUBSCRIPTION_ID>` in the Cloud Shell
    2. Ensure the account has `Owner`, or `Contributor` plus `User Access Administrator`
    3. Copy the exact command Formal shows when you create the integration
  </Accordion>

  <Accordion title="Resources not discovered">
    **Possible causes:**

    * Autodiscovery disabled for that resource type
    * The integration is scoped to a resource group that does not hold the resources
    * The database server is neither a PostgreSQL nor a MySQL flexible server
    * The managed identity is missing the `Reader` role

    **Solution:**

    1. Verify the discovery toggle is enabled on the Cloud Account
    2. Confirm the resource group on the integration matches where the resources live, or clear it to cover the subscription
    3. Re-run the setup command so the module grants the roles
    4. Wait a few minutes for the initial scan
  </Accordion>

  <Accordion title="Activation reports an Entra error">
    **Possible causes:**

    * Entra has not finished replicating the new federated identity credential

    **Solution:**

    Wait a few minutes and let the next discovery run retry. A new credential can take a moment to become usable, so the first token exchange may fail with `AADSTS70021` even though setup succeeded.
  </Accordion>

  <Accordion title="Blob log forwarding not working">
    **Possible causes:**

    * Blob Storage access not enabled on the integration
    * The target account is not in the `blob_storage_accounts` allowlist
    * Log integration not configured

    **Solution:**

    1. Confirm `allow_blob_access` is enabled on the Cloud Account
    2. Add the account to `blob_storage_accounts`, or leave the list empty to allow all accounts in scope
    3. Create a [Log Integration](/docs/guides/integrations/log) pointing to this cloud integration
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Log Integration" icon="pipe" href="/docs/guides/integrations/log">
    Forward logs to Blob Storage
  </Card>

  <Card title="Add Resources" icon="database" href="/docs/guides/core-concepts/resources/introduction">
    Manually configure discovered resources
  </Card>

  <Card title="Terraform" icon="code" href="/docs/guides/configuration/terraform">
    Manage integrations as code
  </Card>

  <Card title="View Resources" icon="list" href="https://app.formal.ai/resources">
    See all autodiscovered resources
  </Card>
</CardGroup>
