> For the complete documentation index, see [llms.txt](https://cortex-docs.paloaltonetworks.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cortex-docs.paloaltonetworks.com/microsoft-azure-manual-onboarding/azure-manual-onboarding-guide/azure-manual-onboarding-management-group-or-tenant-scope/phase-4-audit-logs.md).

# Phase 4: Audit Logs

## Phase 4: Audit Logs

**Module:** AUDIT LOGS

The audit log pipeline is deployed once in the host subscription. It is built with three components: an Event Hub, a user-assigned managed identity (UAMI), and a checkpoint storage account.

> **Note:** The namespace and Event Hub must belong to the specific Azure subscription being onboarded. Cross-subscription or centralized logging is not currently supported.

To route logs globally, the pipeline uses two configurations:

1. **Management group settings:** A single diagnostic setting (Step 4.11) at the root management group forwards administrative activity logs from all child subscriptions to the Event Hub. This automatically covers any new subscriptions created in the future.
2. **Tenant-level settings:** A tenant-level diagnostic setting (Step 4.12) that sends Microsoft Entra ID logs, such as sign-ins, directory audit logs, and service principal activity, into the same Event Hub.

Switch to your host subscription context before running steps 4.2 through 4.10:

{% code overflow="wrap" %}

```bash
az account set --subscription "<HOST_SUBSCRIPTION_ID>"
```

{% endcode %}

### 4.1 Create the shared resource group in the host subscription

Skip this step if the `cortex-platform-rg` resource group was already created in the host subscription during a previous capability deployment.

{% code overflow="wrap" %}

```bash
az group create   --name "<RG_NAME>"   --location "<LOCATION>"   --tags managed_by=paloaltonetworks
```

{% endcode %}

Where:

* `<RG_NAME>` is your chosen resource group name (e.g. `cortex-platform-rg`)
* `<LOCATION>` is the Azure region where you want to create your resource group (e.g. `eastus`)

### 4.2 Create the user-assigned managed identity (UAMI)

The user-assigned managed identity (UAMI) serves as the workload identity for the Cortex collector. Because it leverages workload identity federation, there are no static credentials or secrets to rotate.

{% code overflow="wrap" %}

```bash
az identity create   --resource-group "<RG_NAME>"   --name "<AUDIT_UAMI_NAME>"   --location "<LOCATION>"   --tags managed_by=paloaltonetworks

# Capture the two identifiers needed below
export AUDIT_UAMI_CLIENT_ID=$(az identity show   --resource-group "<RG_NAME>"   --name "<AUDIT_UAMI_NAME>" --query clientId -o tsv)
export AUDIT_UAMI_PRINCIPAL_ID=$(az identity show   --resource-group "<RG_NAME>"   --name "<AUDIT_UAMI_NAME>" --query principalId -o tsv)

echo "AUDIT_UAMI_CLIENT_ID=${AUDIT_UAMI_CLIENT_ID}"
echo "AUDIT_UAMI_PRINCIPAL_ID=${AUDIT_UAMI_PRINCIPAL_ID}"
```

{% endcode %}

Where:

* `<RG_NAME>` is your chosen resource group name (e.g. `cortex-platform-rg`)
* `<AUDIT_UAMI_NAME>` is your chosen UAMI name (e.g. `cortex-audit-uami`)
* `<LOCATION>` is the Azure region where you want to create your UAMI (e.g. `eastus`)

> **Note:**
>
> * The `clientId` is the application ID used by Cortex to authenticate via workload identity federation
> * The `principalId` is the unique object ID used to bind Azure RBAC role assignments
> * This command will output a value for `AUDIT_UAMI_CLIENT_ID` and `AUDIT_UAMI_PRINCIPAL_ID`. You will need to provide these values in the coming phases.

### 4.3 Configure workload identity federation for the UAMI

Establish a secure trust relationship that allows the Cortex collector's Google Service Account to authenticate as the UAMI via workload identity federation:

{% code overflow="wrap" %}

```bash
az identity federated-credential create   --resource-group "<RG_NAME>"   --identity-name "<AUDIT_UAMI_NAME>"   --name "cortex-audit-federated-cred"   --issuer "https://accounts.google.com"   --subject "<COLLECTOR_SA_UNIQUE_ID>"   --audiences "<AUDIENCE>"
```

{% endcode %}

Where:

* `<RG_NAME>` is your chosen resource group name (e.g. `cortex-platform-rg`)
* `<AUDIT_UAMI_NAME>` is your chosen UAMI name (e.g., `cortex-audit-uami`).
* `<COLLECTOR_SA_UNIQUE_ID>` is the unique ID of the Cortex Collector's Google service account, obtained from the identifiers file
* `<AUDIENCE>` is the value obtained from the identifiers file (always `api://AzureADTokenExchange` for commercial Azure)

### 4.4 Create the storage account

The Cortex Collector uses a dedicated storage account to manage Event Hub checkpoints and maintain data continuity during restarts.

{% code overflow="wrap" %}

```bash
az storage account create   --resource-group "<RG_NAME>"   --name "<STORAGE_ACCOUNT_NAME>"   --location "<LOCATION>"   --sku Standard_LRS   --kind StorageV2   --https-only true   --min-tls-version TLS1_2   --allow-blob-public-access false   --allow-cross-tenant-replication false   --default-action Deny   --bypass AzureServices   --assign-identity   --tags managed_by=paloaltonetworks

# Allow-list the Cortex Collector egress IPs (one call per IP)
for ip in $(echo "<COLLECTOR_ALLOWED_IPS>" | tr ',' ' '); do
  az storage account network-rule add     --resource-group "<RG_NAME>"     --account-name "<STORAGE_ACCOUNT_NAME>"     --ip-address "$ip"
done

# Apply blob-service properties (versioning, retention, change-feed)
az storage account blob-service-properties update   --resource-group "<RG_NAME>"   --account-name "<STORAGE_ACCOUNT_NAME>"   --enable-delete-retention true   --delete-retention-days 7   --enable-change-feed true   --enable-versioning true
```

{% endcode %}

Where:

* `<RG_NAME>` is your chosen resource group name (e.g. `cortex-platform-rg`)
* `<STORAGE_ACCOUNT_NAME>` is your chosen name for the storage account
* `<LOCATION>` is the Azure region where you want to create your storage account (e.g. `eastus`)
* `<COLLECTOR_ALLOWED_IPS>` is comma-separated list of Cortex Collector egress IPs, obtained from the identifiers file

### 4.5 Grant the UAMI Storage Blob Data Contributor role on the storage account

{% code overflow="wrap" %}

```bash
STORAGE_ACCOUNT_ID=$(az storage account show   --resource-group "<RG_NAME>"   --name "<STORAGE_ACCOUNT_NAME>" --query id -o tsv)

az role assignment create   --role "Storage Blob Data Contributor"   --assignee-object-id "${AUDIT_UAMI_PRINCIPAL_ID}"   --assignee-principal-type ServicePrincipal   --scope "${STORAGE_ACCOUNT_ID}"
```

{% endcode %}

Where:

* `<RG_NAME>` is your chosen resource group name (e.g. `cortex-platform-rg`)
* `<STORAGE_ACCOUNT_NAME>` is your chosen name for the storage account

### 4.6 Create and secure the Event Hubs namespace

{% code overflow="wrap" %}

```bash
az eventhubs namespace create   --resource-group "<RG_NAME>"   --name "<EH_NAMESPACE>"   --location "<LOCATION>"   --sku Standard   --capacity 1   --enable-auto-inflate true   --maximum-throughput-units 20   --tags managed_by=paloaltonetworks

# Allow-list the Cortex Collector egress IPs
for ip in $(echo "<COLLECTOR_ALLOWED_IPS>" | tr ',' ' '); do
  az eventhubs namespace network-rule-set ip-rule add     --resource-group "<RG_NAME>"     --namespace-name "<EH_NAMESPACE>"     --ip-rule ip-address="$ip" action=Allow
done

# Lock the namespace down to Cortex Collector IPs + trusted Azure services
az eventhubs namespace network-rule-set update   --resource-group "<RG_NAME>"   --namespace-name "<EH_NAMESPACE>"   --default-action Deny   --enable-trusted-service-access true
```

{% endcode %}

Where:

* `<RG_NAME>` is your chosen resource group name (e.g. `cortex-platform-rg`)
* `<EH_NAMESPACE>` is your chosen namespace name
* `<LOCATION>` is the Azure region where you want to create your namespace (e.g. `eastus`)
* `<COLLECTOR_ALLOWED_IPS>` is comma-separated list of Cortex Collector egress IPs, obtained from the identifiers file

### 4.7 Create the Event Hub

{% code overflow="wrap" %}

```bash
az eventhubs eventhub create   --resource-group "<RG_NAME>"   --namespace-name "<EH_NAMESPACE>"   --name "<EH_NAME>"   --partition-count 20   --cleanup-policy Delete   --retention-time-in-hours 168
```

{% endcode %}

Where:

* `<RG_NAME>` is your chosen resource group name (e.g. `cortex-platform-rg`)
* `<EH_NAMESPACE>` is your chosen namespace name
* `<EH_NAME>` is your chosen Event Hub name (e.g., `cortex-activity-logs`).

### 4.8 Create an authorization rule on the namespace

{% code overflow="wrap" %}

```bash
az eventhubs namespace authorization-rule create   --resource-group "<RG_NAME>"   --namespace-name "<EH_NAMESPACE>"   --name "<EH_NAMESPACE_AUTH_RULE>"   --rights Listen Send

# Capture the auth rule ARM ID used cross-subscription by every per-sub diagnostic setting in step 3.10. Must be captured while on the host sub.
export EH_AUTH_RULE_ID=$(az eventhubs namespace authorization-rule show   --resource-group "<RG_NAME>"   --namespace-name "<EH_NAMESPACE>"   --name "<EH_NAMESPACE_AUTH_RULE>" --query id -o tsv)

echo "EH_AUTH_RULE_ID=${EH_AUTH_RULE_ID}"
```

{% endcode %}

Where:

* `<RG_NAME>` is your chosen resource group name (e.g. `cortex-platform-rg`)
* `<EH_NAMESPACE>` is your chosen namespace name
* `<EH_NAMESPACE_AUTH_RULE>` is your chosen rule name (e.g., `CortexEventHubNamespaceAuthRule`)

### 4.9 Create the consumer group

{% code overflow="wrap" %}

```bash
az eventhubs eventhub consumer-group create   --resource-group "<RG_NAME>"   --namespace-name "<EH_NAMESPACE>"   --eventhub-name "<EH_NAME>"   --name "<EH_CONSUMER_GROUP>"
```

{% endcode %}

Where:

* `<RG_NAME>` is your chosen resource group name (e.g. `cortex-platform-rg`)
* `<EH_NAMESPACE>` is your chosen namespace name
* `<EH_NAME>` is your chosen Event Hub name (e.g., `cortex-activity-logs`)
* `<EH_CONSUMER_GROUP>` is your chosen consumer group name (e.g., `cortex-cg`)

### 4.10 Grant the UAMI Azure Event Hubs Data Receiver role on the namespace

{% code overflow="wrap" %}

```bash
EH_NAMESPACE_ID=$(az eventhubs namespace show   --resource-group "<RG_NAME>"   --name "<EH_NAMESPACE>" --query id -o tsv)

az role assignment create   --role "Azure Event Hubs Data Receiver"   --assignee-object-id "${AUDIT_UAMI_PRINCIPAL_ID}"   --assignee-principal-type ServicePrincipal   --scope "${EH_NAMESPACE_ID}"
```

{% endcode %}

Where:

* `<RG_NAME>` is your chosen resource group name (e.g. `cortex-platform-rg`)
* `<EH_NAMESPACE>` is your chosen namespace name

### 4.11 Route the root management group's Activity Log to the shared Event Hub

Deploy a management group scope diagnostic setting (`Microsoft.Insights/diagnosticSettings`) to inherit logs globally. This configuration ensures that administrative activity logs from every current and future child subscription are automatically forwarded into the centralized Event Hub pipeline:

{% code overflow="wrap" %}

```bash
cat > /tmp/cortex-mg-diag.json << EOF
{
  "properties": {
    "eventHubAuthorizationRuleId": "${EH_AUTH_RULE_ID}",
    "eventHubName": "<EH_NAME>",
    "logs": [
      { "category": "Administrative", "enabled": true }
    ]
  }
}
EOF

az rest --method put   --uri "https://management.azure.com/providers/Microsoft.Management/managementGroups/<MG_ID>/providers/Microsoft.Insights/diagnosticSettings/CortexMonitorDiagnosticSettings?api-version=2021-05-01-preview"   --body @/tmp/cortex-mg-diag.json
```

{% endcode %}

Where:

* `<EH_NAME>` is your chosen Event Hub name (e.g., `cortex-activity-logs`)
* `<MG_ID>` is the root management group name (or tenant ID for tenant root management group scope)

### 4.12 Create the tenant-level Microsoft Entra ID diagnostic settings

This configuration applies exclusively to tenant-level deployments. Skip this step if you are onboarding at the management group scope.

{% code overflow="wrap" %}

```bash
cat > /tmp/cortex-aad-diag.json << EOF
{
  "properties": {
    "eventHubAuthorizationRuleId": "${EH_AUTH_RULE_ID}",
    "eventHubName": "<EH_NAME>",
    "logs": [
      {"category": "SignInLogs", "enabled": true},
      {"category": "AuditLogs", "enabled": true},
      {"category": "NonInteractiveUserSignInLogs", "enabled": true},
      {"category": "ServicePrincipalSignInLogs", "enabled": true},
      {"category": "ManagedIdentitySignInLogs", "enabled": true},
      {"category": "ProvisioningLogs", "enabled": true},
      {"category": "ADFSSignInLogs", "enabled": true},
      {"category": "MicrosoftGraphActivityLogs", "enabled": true}
    ]
  }
}
EOF

az rest --method put   --uri "https://management.azure.com/providers/microsoft.aadiam/diagnosticSettings/cortex-tenant-aad-logs?api-version=2017-04-01-preview"   --body @/tmp/cortex-aad-diag.json
```

{% endcode %}

Where:

* `<EH_NAME>` is your chosen Event Hub name (e.g., `cortex-activity-logs`)

> **Note:** A 403 (Forbidden) error response indicates that your account is missing the `microsoft.aadiam/diagnosticsettings/write` permission. This configuration typically requires Security Administrator or Global Administrator privileges in Microsoft Entra ID. If you encounter this error, you can either omit Microsoft Entra ID log ingestion or have a directory administrator execute this specific step.

***


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://cortex-docs.paloaltonetworks.com/microsoft-azure-manual-onboarding/azure-manual-onboarding-guide/azure-manual-onboarding-management-group-or-tenant-scope/phase-4-audit-logs.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
