feat(docs): publish Azure deployment template

- Serve the generated ARM template from public Mintlify documentation
- Display the same canonical JSON in the Azure authentication guide
- Enforce byte-for-byte synchronization with drift tests
This commit is contained in:
Hugo P.Brito committed 2026-08-21 09:45:21 +01:00
1 parent 970bb22df2
commit c230fdf9ce
10 files changed
+587 -200

No files matched your search

@@ -0,0 +1,100 @@
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.46.1.21595",
"templateHash": "4092263301802395344"
}
},
"parameters": {
"servicePrincipalObjectId": {
"type": "string",
"metadata": {
"description": "Object ID of the Service Principal for the App Registration Prowler will use. Find it in Azure Portal → Microsoft Entra ID → Enterprise applications → your app → Overview → Object ID. This is NOT the same as the App Registration's Object ID; the Service Principal has its own separate Object ID."
}
},
"deploymentLabel": {
"type": "string",
"defaultValue": "Prowler",
"metadata": {
"description": "Cosmetic label used inside the role assignment names so multiple deployments do not collide. Free text; keep the default unless you need to distinguish multiple Prowler deployments."
}
},
"customRoleName": {
"type": "string",
"defaultValue": "ProwlerRole",
"metadata": {
"description": "Name of the extra role Prowler creates. Keep the default unless your org already uses this name."
}
}
},
"variables": {
"customRoleDefinitionName": "[guid(subscription().id, parameters('customRoleName'))]",
"readerRoleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]"
},
"resources": [
{
"type": "Microsoft.Authorization/roleDefinitions",
"apiVersion": "2022-05-01-preview",
"name": "[variables('customRoleDefinitionName')]",
"properties": {
"roleName": "[parameters('customRoleName')]",
"description": "Role used by Prowler for checks that require Azure actions beyond the built-in Reader role.",
"type": "CustomRole",
"assignableScopes": [
"[subscription().id]"
],
"permissions": [
{
"actions": [
"Microsoft.Web/sites/host/listkeys/action",
"Microsoft.Web/sites/config/list/Action"
],
"notActions": [],
"dataActions": [],
"notDataActions": []
}
]
}
},
{
"type": "Microsoft.Authorization/roleAssignments",
"apiVersion": "2022-04-01",
"name": "[guid(subscription().id, parameters('servicePrincipalObjectId'), parameters('deploymentLabel'), 'Reader')]",
"properties": {
"principalId": "[parameters('servicePrincipalObjectId')]",
"principalType": "ServicePrincipal",
"roleDefinitionId": "[variables('readerRoleDefinitionId')]"
}
},
{
"type": "Microsoft.Authorization/roleAssignments",
"apiVersion": "2022-04-01",
"name": "[guid(subscription().id, parameters('servicePrincipalObjectId'), parameters('deploymentLabel'), parameters('customRoleName'))]",
"properties": {
"principalId": "[parameters('servicePrincipalObjectId')]",
"principalType": "ServicePrincipal",
"roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('customRoleDefinitionName'))]"
},
"dependsOn": [
"[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('customRoleDefinitionName'))]"
]
}
],
"outputs": {
"tenantId": {
"type": "string",
"value": "[subscription().tenantId]"
},
"subscriptionId": {
"type": "string",
"value": "[subscription().subscriptionId]"
},
"prowlerRoleDefinitionId": {
"type": "string",
"value": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('customRoleDefinitionName'))]"
}
}
}
@@ -0,0 +1,105 @@
{/* AUTO-GENERATED from permissions/templates/azure/bicep/prowler-scan.json. Do not edit manually. */}
```json
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.46.1.21595",
"templateHash": "4092263301802395344"
}
},
"parameters": {
"servicePrincipalObjectId": {
"type": "string",
"metadata": {
"description": "Object ID of the Service Principal for the App Registration Prowler will use. Find it in Azure Portal → Microsoft Entra ID → Enterprise applications → your app → Overview → Object ID. This is NOT the same as the App Registration's Object ID; the Service Principal has its own separate Object ID."
}
},
"deploymentLabel": {
"type": "string",
"defaultValue": "Prowler",
"metadata": {
"description": "Cosmetic label used inside the role assignment names so multiple deployments do not collide. Free text; keep the default unless you need to distinguish multiple Prowler deployments."
}
},
"customRoleName": {
"type": "string",
"defaultValue": "ProwlerRole",
"metadata": {
"description": "Name of the extra role Prowler creates. Keep the default unless your org already uses this name."
}
}
},
"variables": {
"customRoleDefinitionName": "[guid(subscription().id, parameters('customRoleName'))]",
"readerRoleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]"
},
"resources": [
{
"type": "Microsoft.Authorization/roleDefinitions",
"apiVersion": "2022-05-01-preview",
"name": "[variables('customRoleDefinitionName')]",
"properties": {
"roleName": "[parameters('customRoleName')]",
"description": "Role used by Prowler for checks that require Azure actions beyond the built-in Reader role.",
"type": "CustomRole",
"assignableScopes": [
"[subscription().id]"
],
"permissions": [
{
"actions": [
"Microsoft.Web/sites/host/listkeys/action",
"Microsoft.Web/sites/config/list/Action"
],
"notActions": [],
"dataActions": [],
"notDataActions": []
}
]
}
},
{
"type": "Microsoft.Authorization/roleAssignments",
"apiVersion": "2022-04-01",
"name": "[guid(subscription().id, parameters('servicePrincipalObjectId'), parameters('deploymentLabel'), 'Reader')]",
"properties": {
"principalId": "[parameters('servicePrincipalObjectId')]",
"principalType": "ServicePrincipal",
"roleDefinitionId": "[variables('readerRoleDefinitionId')]"
}
},
{
"type": "Microsoft.Authorization/roleAssignments",
"apiVersion": "2022-04-01",
"name": "[guid(subscription().id, parameters('servicePrincipalObjectId'), parameters('deploymentLabel'), parameters('customRoleName'))]",
"properties": {
"principalId": "[parameters('servicePrincipalObjectId')]",
"principalType": "ServicePrincipal",
"roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('customRoleDefinitionName'))]"
},
"dependsOn": [
"[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('customRoleDefinitionName'))]"
]
}
],
"outputs": {
"tenantId": {
"type": "string",
"value": "[subscription().tenantId]"
},
"subscriptionId": {
"type": "string",
"value": "[subscription().subscriptionId]"
},
"prowlerRoleDefinitionId": {
"type": "string",
"value": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('customRoleDefinitionName'))]"
}
}
}
```
@@ -1,12 +1,14 @@
---
title: 'Azure Authentication in Prowler'
title: "Azure Authentication in Prowler"
---
import AzureProwlerScanTemplate from "/snippets/azure-prowler-scan-template.mdx";
Prowler for Azure supports multiple authentication types. Authentication methods vary between Prowler Cloud and Prowler CLI:
**Prowler Cloud:**
- [**Certificate Authentication**](#certificate-authentication) (**Recommended** — one-click setup via the [Deploy to Azure](#deploy-to-azure-quick-start) quick-start)
- [**Certificate Authentication**](#certificate-authentication) (**Recommended**)
- [**Service Principal Application**](#service-principal-application-authentication-recommended) with a client secret
**Prowler CLI:**
@@ -60,6 +62,7 @@ Replace `Directory.Read.All` with `Domain.Read.All` for more restrictive permiss
![Grant Admin Consent](/images/providers/grant-admin-consent.png)
![Granted Admin Consent](/images/providers/granted-admin-consent.png)
</Tab>
<Tab title="Azure CLI">
1. To grant permissions to a Service Principal, execute the following command in a terminal:
@@ -67,6 +70,7 @@ Replace `Directory.Read.All` with `Domain.Read.All` for more restrictive permiss
```console
az ad app permission add --id {appId} --api 00000003-0000-0000-c000-000000000000 --api-permissions 7ab1d382-f21e-4acd-a863-ba3e13f7da61=Role 246dd0d5-5bd0-4def-940b-0421030a5b68=Role b0afded3-3588-46d8-8b3d-9842eff778da=Role
```
</Tab>
</Tabs>
### Subscription Scope Permissions
@@ -76,8 +80,8 @@ These permissions are required to perform security checks against Azure resource
- `Reader` – Grants read-only access to Azure resources.
- `ProwlerRole` – A custom role with minimal permissions needed for some specific checks, defined in the [prowler-azure-custom-role](https://github.com/prowler-cloud/prowler/blob/master/permissions/prowler-azure-custom-role.json).
#### Assigning "Reader" Role at the Subscription Level
By default, Prowler scans all accessible subscriptions. If you need to audit specific subscriptions, you must assign the necessary role `Reader` for each one. For streamlined and less repetitive role assignments in multi-subscription environments, refer to the [following section](/user-guide/providers/azure/subscriptions#recommendation-for-managing-multiple-subscriptions).
<Tabs>
@@ -96,6 +100,7 @@ By default, Prowler scans all accessible subscriptions. If you need to audit spe
6. Click "Review + assign" to finalize and apply the role assignment.
![Adding the Reader Role to a Subscription](/images/providers/add-reader-role.png)
</Tab>
<Tab title="Azure CLI">
1. Open a terminal and execute the following command to assign the `Reader` role to the identity that is going to be assumed by Prowler:
@@ -103,6 +108,7 @@ By default, Prowler scans all accessible subscriptions. If you need to audit spe
```console
az role assignment create --role "Reader" --assignee <user, group, or service principal> --scope /subscriptions/<subscription-id>
```
</Tab>
</Tabs>
#### Assigning "ProwlerRole" Permissions at the Subscription Level
@@ -142,6 +148,7 @@ The checks requiring this `ProwlerRole` can be found in this [section](/user-gui
The `assignableScopes` field in the JSON custom role file must be updated to reflect the correct subscription or management group. Use one of the following formats: `/subscriptions/<subscription-id>` or `/providers/Microsoft.Management/managementGroups/<management-group-id>`.
</Note>
</Tab>
<Tab title="Azure CLI">
1. To create a new custom role, open a terminal and execute the following command:
@@ -161,40 +168,42 @@ The checks requiring this `ProwlerRole` can be found in this [section](/user-gui
}'
```
2. If the command is executed successfully, the output is going to be similar to the following:
2. If the command is executed successfully, the output is going to be similar to the following:
```json
{
"assignableScopes": [
"/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
],
"createdBy": null,
"createdOn": "YYYY-MM-DDTHH:MM:SS.SSSSSS+00:00",
"description": "Role used for checks that require read-only access to Azure resources and are not covered by the Reader role.",
"id": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/providers/Microsoft.Authorization/roleDefinitions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"name": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"permissions": [
```json
{
"actions": [
"Microsoft.Web/sites/host/listkeys/action",
"Microsoft.Web/sites/config/list/Action"
"assignableScopes": [
"/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
],
"condition": null,
"conditionVersion": null,
"dataActions": [],
"notActions": [],
"notDataActions": []
"createdBy": null,
"createdOn": "YYYY-MM-DDTHH:MM:SS.SSSSSS+00:00",
"description": "Role used for checks that require read-only access to Azure resources and are not covered by the Reader role.",
"id": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/providers/Microsoft.Authorization/roleDefinitions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"name": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"permissions": [
{
"actions": [
"Microsoft.Web/sites/host/listkeys/action",
"Microsoft.Web/sites/config/list/Action"
],
"condition": null,
"conditionVersion": null,
"dataActions": [],
"notActions": [],
"notDataActions": []
}
],
"roleName": "ProwlerRole",
"roleType": "CustomRole",
"type": "Microsoft.Authorization/roleDefinitions",
"updatedBy": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"updatedOn": "YYYY-MM-DDTHH:MM:SS.SSSSSS+00:00"
}
],
"roleName": "ProwlerRole",
"roleType": "CustomRole",
"type": "Microsoft.Authorization/roleDefinitions",
"updatedBy": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"updatedOn": "YYYY-MM-DDTHH:MM:SS.SSSSSS+00:00"
}
```
</Tab>
</Tabs>
```
</Tab>
</Tabs>
### Additional Resources
@@ -216,30 +225,22 @@ The following security checks require the `ProwlerRole` permissions for executio
- `app_function_ftps_deployment_disabled`
- `app_function_latest_runtime_version`
---
## Deploy to Azure (Quick-Start)
Prowler ships a public [Bicep template](https://github.com/prowler-cloud/prowler/blob/master/permissions/templates/azure/bicep/prowler-scan.bicep) that provisions the App Registration / Service Principal, the built-in `Reader` role assignment, and the custom `ProwlerRole` — all bound to an X.509 certificate credential — in a single deployment. This is the Azure analogue of the AWS CloudFormation quick-create flow.
Use it from the Prowler Cloud **add-provider wizard**:
1. Pick **Azure**, then choose **Certificate Authentication (Recommended)** in the credential-type selector.
2. Click **Deploy to Azure**. The button opens the Azure Portal deployment blade pre-loaded with the template.
3. Generate a certificate (see [Certificate Authentication](#certificate-authentication) below) and paste the base64-encoded public certificate into the Bicep `certificateBase64` parameter.
4. Deploy and, when the stack finishes, copy the `tenantId` and `applicationId` outputs into Prowler along with the base64-encoded PEM bundle (certificate + private key) you generated locally. The `certificateThumbprint` output is informational — use it to reconcile that the deployed keyCredential matches your local certificate.
<Note>
The Deploy to Azure button is not available for sovereign clouds (Azure Government, Azure China, Azure Germany) or personal Microsoft accounts. Follow the manual [Certificate Authentication](#certificate-authentication) or [Service Principal](#service-principal-application-authentication-recommended) instructions instead.
</Note>
## Certificate Authentication
Certificate authentication is the recommended way to run Prowler against Azure because Entra ID keeps only the public certificate (no shared secrets to rotate), and Bicep can bind the certificate to the App Registration natively via `keyCredentials`.
Certificate authentication is the recommended way to run Prowler against Azure because Microsoft Entra ID stores only the public certificate, so no shared client secret needs to be rotated. Upload the public certificate to the App Registration and provide Prowler with the matching private bundle.
### Generating the certificate
### Prerequisites
Prowler never sees the private key during deployment — the user generates the keypair locally.
Certificate authentication requires:
- Permission to create or manage a Microsoft Entra App Registration.
- An administrator authorized to grant tenant-wide consent for the required Microsoft Graph application permissions.
- `Owner` on each target subscription when using the deployment template. `Contributor` cannot create role definitions or role assignments.
- The Service Principal Object ID from **Microsoft Entra ID** > **Enterprise applications** > the application > **Overview**. This value differs from the Object ID under **App registrations**.
### Generating the Certificate
Generate the key pair locally. Upload only the public certificate to Microsoft Entra ID and keep the private key secure.
**macOS / Linux (OpenSSL):**
@@ -247,7 +248,7 @@ Prowler never sees the private key during deployment — the user generates the
openssl req -x509 -newkey rsa:4096 -keyout prowler.key -out prowler.crt \
-days 365 -nodes -subj "/CN=Prowler"
# Base64 of the public certificate — feeds `certificateBase64` in the Bicep template
# Base64 of the public certificate, if a base64 representation is needed
openssl x509 -in prowler.crt -outform DER | base64 | tr -d '\n'
# Base64 of a PEM bundle containing the certificate AND the private key —
@@ -264,41 +265,78 @@ $cert = New-SelfSignedCertificate -Subject "CN=Prowler" `
-KeyExportPolicy Exportable -KeySpec Signature `
-KeyLength 4096 -HashAlgorithm SHA256
[Convert]::ToBase64String($cert.RawData) # certificateBase64
$cert.Thumbprint # certificateThumbprint
Export-Certificate -Cert $cert -FilePath prowler.cer
# Export a PKCS#12 bundle for Prowler. Keep this value secret.
$pfxBytes = $cert.Export('Pfx', '')
[Convert]::ToBase64String($pfxBytes)
```
### Deploying Subscription Permissions
The Prowler Azure Resource Manager (ARM) template creates the subscription permissions required by Prowler for an existing Service Principal. The template does not create an App Registration, create a Service Principal, upload a certificate, or grant Microsoft Graph permissions.
To deploy the template:
1. Copy the Service Principal Object ID from **Enterprise applications**.
2. Click [**Deploy to Azure**](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fdocs.prowler.com%2Fassets%2Ftemplates%2Fazure%2Fprowler-scan.json).
3. Select the target subscription, enter the Service Principal Object ID, and review the optional deployment label and custom role name.
4. Create the deployment.
The deployment creates:
- The subscription-scoped `ProwlerRole` custom role definition.
- A `Reader` role assignment for the existing Service Principal.
- A `ProwlerRole` role assignment for the existing Service Principal.
The deployment outputs the tenant ID, subscription ID, and `ProwlerRole` definition ID. Return to Prowler with the App Registration's Application (client) ID and the certificate bundle after the deployment succeeds.
<Note>
Azure Portal loads the public ARM JSON from
`https://docs.prowler.com/assets/templates/azure/prowler-scan.json`. For a
sovereign cloud or a deployment that cannot retrieve this public URL, download
the [ARM JSON template](/assets/templates/azure/prowler-scan.json) and deploy
it with the Azure CLI or Azure PowerShell.
</Note>
### ARM JSON Template
The following JSON is generated from the same canonical template that Azure Portal retrieves from Prowler documentation:
<AzureProwlerScanTemplate />
### Prowler Cloud
Paste the following into the Certificate Authentication form of the Azure add-provider wizard:
- **Tenant ID** — copy from the deployment outputs.
- **Client ID** — copy from the deployment outputs (`applicationId`).
- **Certificate Content** — the base64-encoded **PEM bundle** (certificate + private key) or PKCS#12 export. The Bicep template only received the public certificate; Prowler needs both parts here to sign the token request.
- **Tenant ID** — copy the Directory (tenant) ID from the App Registration overview.
- **Client ID** — copy the Application (client) ID from the App Registration overview.
- **Certificate Content** — provide the base64-encoded **PEM bundle** (certificate + private key) or PKCS#12 export. Prowler needs both parts to sign the token request.
### Prowler CLI
Set the following environment variables and invoke Prowler with `--certificate-auth`:
Set the certificate environment variables and run Prowler with `--certificate-auth`:
```console
export AZURE_CLIENT_ID="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
export AZURE_TENANT_ID="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
export AZURE_CERTIFICATE_CONTENT="<base64-encoded private key>"
export AZURE_CERTIFICATE_CONTENT="<base64-encoded certificate and private key bundle>"
prowler azure --certificate-auth
```
Alternatively, point Prowler at a certificate file on disk:
Alternatively, provide a PEM or PKCS#12 file that contains both the certificate and matching private key:
```console
prowler azure --certificate-auth --certificate-path /path/to/prowler.key
prowler azure --certificate-auth --certificate-path /path/to/prowler-bundle.pem
```
## Service Principal Application Authentication
This client-secret flow is the supported fallback when [Certificate Authentication](#certificate-authentication) is not an option — for example on sovereign clouds where the Deploy to Azure quick-start is unavailable, or when an organisation policy forbids certificate-based service principals. New Prowler Cloud onboardings should prefer certificate authentication.
This client-secret flow is the supported fallback when [Certificate Authentication](#certificate-authentication) is not an option or when an organization policy forbids certificate-based Service Principals. New Prowler Cloud onboardings should prefer certificate authentication.
### Creating the Service Principal
For more information, see [Creating Prowler Service Principal](/user-guide/providers/azure/create-prowler-service-principal).
### Environment Variables (CLI)
@@ -315,7 +353,7 @@ Execution with the `--sp-env-auth` flag fails if these variables are not set or
## AZ CLI Authentication
*Available only for Prowler CLI*
_Available only for Prowler CLI_
Use stored Azure CLI credentials:
@@ -325,7 +363,7 @@ prowler azure --az-cli-auth
## Managed Identity Authentication
*Available only for Prowler CLI*
_Available only for Prowler CLI_
Authenticate via Azure Managed Identity when running Prowler on Azure resources (VMs, Container Instances, Azure Functions, etc.):
@@ -341,7 +379,13 @@ Before using Managed Identity authentication, the following steps are required:
2. **Assign the required permissions** to the Managed Identity on the target subscription(s) to scan
<Warning>
A common misconception is that enabling a Managed Identity on a resource automatically grants it permissions. **This is not the case.** Without explicit role assignments, Prowler will be unable to scan subscriptions and will return authorization errors, resulting in incomplete security assessments. The Managed Identity itself is a service principal that must be explicitly granted Reader and ProwlerRole permissions on each subscription to scan.
A common misconception is that enabling a Managed Identity on a resource
automatically grants it permissions. **This is not the case.** Without
explicit role assignments, Prowler will be unable to scan subscriptions and
will return authorization errors, resulting in incomplete security
assessments. The Managed Identity itself is a service principal that must be
explicitly granted Reader and ProwlerRole permissions on each subscription to
scan.
</Warning>
### Step-by-Step Setup Guide
@@ -365,6 +409,7 @@ A common misconception is that enabling a Managed Identity on a resource automat
# Get the principal ID
az vm identity show --name <vm-name> --resource-group <resource-group> --query principalId -o tsv
```
</Tab>
<Tab title="Azure Container Instance">
**Via Azure CLI:**
@@ -379,6 +424,7 @@ A common misconception is that enabling a Managed Identity on a resource automat
# Get the principal ID
az container show --resource-group <resource-group> --name <container-name> --query identity.principalId -o tsv
```
</Tab>
</Tabs>
@@ -400,6 +446,7 @@ The Managed Identity needs the **Reader** role on each subscription to scan. Thi
<Note>
When scanning a subscription different from where the VM is located, ensure the role is assigned on the **target subscription**, not the VM's subscription.
</Note>
</Tab>
<Tab title="Azure CLI">
```console
@@ -413,6 +460,7 @@ The Managed Identity needs the **Reader** role on each subscription to scan. Thi
--assignee-principal-type ServicePrincipal \
--scope /subscriptions/<target-subscription-id>
```
</Tab>
</Tabs>
@@ -448,6 +496,7 @@ The ProwlerRole is a custom role required for specific security checks. First, c
--assignee-principal-type ServicePrincipal \
--scope /subscriptions/<target-subscription-id>
```
</Tab>
<Tab title="Azure Portal">
Follow the same process as creating the ProwlerRole in the [Assigning ProwlerRole Permissions](/user-guide/providers/azure/authentication#assigning-prowlerrole-permissions-at-the-subscription-level) section, then assign it to the Managed Identity using the same steps as the Reader role assignment.
@@ -459,7 +508,9 @@ The ProwlerRole is a custom role required for specific security checks. First, c
For Entra ID (Azure AD) checks, the Managed Identity needs Microsoft Graph API permissions: `Directory.Read.All`, `Policy.Read.All`, and `AuditLog.Read.All`.
<Note>
Assigning Microsoft Graph API permissions to a Managed Identity requires Azure CLI or PowerShell - it cannot be done through the Azure Portal's standard role assignment interface.
Assigning Microsoft Graph API permissions to a Managed Identity requires Azure
CLI or PowerShell - it cannot be done through the Azure Portal's standard role
assignment interface.
</Note>
```console
@@ -495,7 +546,8 @@ prowler azure --managed-identity-auth --subscription-ids <subscription-id>
```
<Note>
Wait a few minutes after assigning roles for Azure to propagate permissions. Role assignments are not always immediately effective.
Wait a few minutes after assigning roles for Azure to propagate permissions.
Role assignments are not always immediately effective.
</Note>
### Troubleshooting
@@ -505,6 +557,7 @@ Wait a few minutes after assigning roles for Azure to propagate permissions. Rol
**Cause:** The Managed Identity does not have the Reader role assigned on any subscription.
**Solution:**
- Verify the Managed Identity has the Reader role assigned on at least one subscription.
- Wait a few minutes after role assignment for Azure to propagate permissions.
- Verify role assignments:
@@ -517,6 +570,7 @@ Wait a few minutes after assigning roles for Azure to propagate permissions. Rol
**Cause:** The Managed Identity lacks the Reader role on the target subscription.
**Solution:**
- Ensure the Reader role is assigned to the **Managed Identity's principal ID**, not the VM resource.
- Verify the role is assigned on the **target subscription** to scan, not just the VM's resource group.
- Check role assignments:
@@ -529,6 +583,7 @@ Wait a few minutes after assigning roles for Azure to propagate permissions. Rol
**Cause:** Managed Identity is not enabled on the resource, or Prowler is running outside of Azure.
**Solution:**
- Verify Managed Identity is enabled on the Azure resource.
- Ensure Prowler is running from within the Azure resource (not a local machine).
- Check Managed Identity status:
@@ -541,12 +596,13 @@ Wait a few minutes after assigning roles for Azure to propagate permissions. Rol
**Cause:** The Managed Identity lacks Microsoft Graph API permissions.
**Solution:**
- Assign the required Graph API permissions as shown in Step 4.
- These permissions are optional for basic resource scanning but required for Entra ID security checks.
## Browser Authentication
*Available only for Prowler CLI*
_Available only for Prowler CLI_
Authenticate using the default browser:
@@ -1,13 +1,20 @@
---
title: 'Getting Started With Azure on Prowler'
title: "Getting Started With Azure on Prowler"
---
## Prowler Cloud
<iframe width="560" height="380" src="https://www.youtube-nocookie.com/embed/v1as8vTFlMg" title="Prowler Cloud Onboarding Azure" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="1"></iframe>
<iframe
width="560"
height="380"
src="https://www.youtube-nocookie.com/embed/v1as8vTFlMg"
title="Prowler Cloud Onboarding Azure"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen="1"
></iframe>
> Walkthrough video onboarding an Azure Subscription using Service Principal.
<Note>
**Government Cloud Support**
@@ -27,8 +34,8 @@ For detailed instructions on how to create the Service Principal and configure p
1. Go to the [Azure Portal](https://portal.azure.com/#home) and search for `Subscriptions`
2. Locate and copy your Subscription ID
![Search Subscription](/images/providers/search-subscriptions.png)
![Subscriptions Page](/images/providers/get-subscription-id.png)
![Search Subscription](/images/providers/search-subscriptions.png)
![Subscriptions Page](/images/providers/get-subscription-id.png)
---
@@ -37,19 +44,19 @@ For detailed instructions on how to create the Service Principal and configure p
1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app)
2. Navigate to `Configuration` > `Providers`
![Providers Page](/images/prowler-app/cloud-providers-page.png)
![Providers Page](/images/prowler-app/cloud-providers-page.png)
3. Click `Add Provider`
![Add a Provider](/images/prowler-app/add-cloud-provider.png)
![Add a Provider](/images/prowler-app/add-cloud-provider.png)
4. Select `Microsoft Azure`
![Select Microsoft Azure](/images/providers/select-azure-prowler-cloud.png)
![Select Microsoft Azure](/images/providers/select-azure-prowler-cloud.png)
5. Add the Subscription ID and an optional alias, then click `Next`
![Add Subscription ID](/images/providers/add-subscription-id.png)
![Add Subscription ID](/images/providers/add-subscription-id.png)
### Step 3: Add Credentials to Prowler Cloud
@@ -57,43 +64,26 @@ Azure supports two authentication methods in the add-provider wizard. Prowler Cl
#### Certificate Authentication (Recommended)
Certificate authentication uses an Entra ID App Registration with an X.509 keyCredential. The Prowler wizard splits the setup in two steps: the manual pieces that only the Azure Portal UI supports (creating the App Registration and uploading the certificate), and the RBAC pieces the Prowler Bicep template deploys with one click on Azure Portal (`Reader` + custom `ProwlerRole`).
<Note>
**Why the split?** Microsoft Graph resources in Bicep (`Microsoft.Graph/applications`, `Microsoft.Graph/servicePrincipals`) are [not supported by Portal `Deploy to Azure` URL deployments](https://github.com/microsoftgraph/msgraph-bicep-types/issues/294). A template that contains them fails at deploy time with `Authorization_RequestDenied` regardless of the deploying user's Entra ID role — even a Global Administrator hits the wall. The wizard therefore creates the App Registration through the Portal UI (which works normally) and reserves the Bicep template for the RBAC assignments (which Portal deployments handle cleanly).
</Note>
<Note>
**Required permissions in the Azure account you run the setup with**
Two separate permission systems apply:
- **Microsoft Entra ID**: `Application Administrator`, `Cloud Application Administrator`, or `Global Administrator` — needed for the manual App Registration + certificate upload in steps 1-2 below. Not required if your tenant already allows all users to register applications (Entra ID → Users → User settings).
- **Azure RBAC**: `Owner` on the target subscription — needed by the Deploy to Azure click in step 3 to assign `Reader` and the custom `ProwlerRole`.
If you do not have both, ask your Azure administrator to grant them or to complete the missing step on your behalf.
</Note>
Certificate authentication uses a Microsoft Entra ID App Registration with an X.509 certificate. Complete the App Registration, certificate upload, Microsoft Graph permissions, and subscription permissions by following [Azure Certificate Authentication](/user-guide/providers/azure/authentication#certificate-authentication).
1. In the Azure wizard, select **Certificate Authentication (Recommended)**.
2. **Create the App Registration** — click *Open Azure Portal → New App Registration* in the wizard, sign in to the tenant you want Prowler to scan, and register an application. Any name works (e.g. `Prowler`); keep the default single-tenant audience and no redirect URI.
3. **Upload the certificate** — in the wizard, click *Generate certificate for me* to produce a keypair in your browser (the private key auto-fills the form; the public certificate downloads as a base64 text file). Decode it with `base64 -D -i prowler-cert-base64.txt -o prowler.cer`, then in the Portal open your new App Registration → *Certificates & secrets → Certificates → Upload certificate* and upload `prowler.cer`. Prefer the CLI? Follow the openssl / PowerShell steps in [Certificate Authentication → Generating the certificate](/user-guide/providers/azure/authentication#generating-the-certificate).
4. **Copy the Service Principal Object ID** — Portal → Entra ID → *Enterprise applications* → search for the app you just created → *Overview → Object ID*. Beware this is a different Object ID than the one on the *App registrations* blade (Enterprise applications and App registrations are two separate objects with separate Object IDs but the same Application (client) ID).
5. **Grant Prowler read access** — click **Deploy to Azure** in the wizard. The Azure Portal opens with the Prowler Bicep template pre-loaded. Pick your target subscription, paste the Service Principal Object ID from step 4 into the `servicePrincipalObjectId` field, and confirm the deployment.
6. **Fill the wizard** — back in Prowler, paste the `Tenant ID` from Entra ID → Overview, the `Application (client) ID` from your App Registration's Overview page, and confirm the `Certificate Private Key` field is populated from step 3. Click **Next**, then **Launch Scan**.
2. Paste the Directory (tenant) ID and Application (client) ID from the App Registration.
3. Paste the base64-encoded certificate and private key bundle. To create a new key pair, click **Generate certificate**, upload the downloaded `prowler-cert.cer` file to the App Registration, and keep the generated bundle in the form.
4. Click **Next**, then **Launch Scan**.
#### Service Principal with Client Secret
The manual fallback that mirrors the previous Prowler Cloud flow, and the only option for sovereign clouds and personal accounts.
Client-secret authentication remains available when certificate authentication is not suitable.
1. Follow [Creating Prowler Service Principal](/user-guide/providers/azure/create-prowler-service-principal) to create the App Registration, assign the [Entra](/user-guide/providers/azure/create-prowler-service-principal#assigning-proper-permissions) and [Subscription](/user-guide/providers/azure/subscriptions) scope permissions, and issue a client secret.
2. In the Azure wizard, select **Service Principal with Client Secret**.
3. Paste `Tenant ID`, `Client ID`, and `Client Secret`.
![Prowler Cloud Azure Credentials](/images/providers/add-credentials-azure-prowler-cloud.png)
![Prowler Cloud Azure Credentials](/images/providers/add-credentials-azure-prowler-cloud.png)
4. Click `Next`, then **Launch Scan**.
![Launch Scan Azure](/images/providers/launch-scan.png)
![Launch Scan Azure](/images/providers/launch-scan.png)
---
@@ -105,28 +95,6 @@ To authenticate with Azure, Prowler CLI supports multiple authentication methods
For detailed authentication setup instructions, see [Authentication](/user-guide/providers/azure/authentication).
**Certificate Authentication (Recommended)**
Set up environment variables with the base64-encoded private key:
```console
export AZURE_CLIENT_ID="XXXXXXXXX"
export AZURE_TENANT_ID="XXXXXXXXX"
export AZURE_CERTIFICATE_CONTENT="<base64-encoded private key>"
```
Then run:
```console
prowler azure --certificate-auth
```
Or point Prowler at a certificate file on disk:
```console
prowler azure --certificate-auth --certificate-path /path/to/prowler.key
```
**Service Principal with Client Secret**
Set up environment variables:
+17 -13
View File
@@ -3,28 +3,32 @@
# accepts ARM JSON, not raw Bicep source, so the .json file is the artifact
# actually consumed by users — keep it committed and in sync with the .bicep.
#
# Requires Bicep CLI (>= 0.30 for the Microsoft.Graph extension). Install
# with `az bicep install` (via Azure CLI) or download the standalone binary
# from https://github.com/Azure/bicep/releases.
#
# `bicepconfig.json` in this directory registers the Microsoft.Graph
# extension against the public Microsoft Container Registry, so the CLI can
# resolve `extension microsoftGraphV1` without extra flags.
# Defaults to the standalone Bicep CLI. Set `BICEP=az bicep` to use the
# Azure CLI wrapper installed by `az bicep install`.
BICEP ?= bicep
BICEP_BUILD := $(BICEP) build
.PHONY: build check clean
ifeq ($(strip $(BICEP)),az bicep)
BICEP_BUILD += --file
endif
build: prowler-scan.json
.PHONY: build check clean sync-docs
prowler-scan.json: prowler-scan.bicep bicepconfig.json
$(BICEP) build $<
build: prowler-scan.json sync-docs
prowler-scan.json: prowler-scan.bicep
$(BICEP_BUILD) $<
sync-docs: prowler-scan.json
python3 sync_docs_template.py --sync
# CI hook: fail if the JSON is out of sync with the Bicep source. Run
# `make build` locally and commit both files before opening the PR.
check: prowler-scan.bicep bicepconfig.json
$(BICEP) build prowler-scan.bicep --outfile /tmp/prowler-scan.check.json
check: prowler-scan.bicep
$(BICEP_BUILD) prowler-scan.bicep --outfile /tmp/prowler-scan.check.json
diff -q prowler-scan.json /tmp/prowler-scan.check.json
python3 sync_docs_template.py --check
rm /tmp/prowler-scan.check.json
clean:
+64 -57
View File
@@ -1,12 +1,11 @@
# Prowler Azure Bicep quick-start
# Prowler Azure Bicep Template
This directory hosts the Bicep template that powers the **Deploy to Azure**
button in the Prowler UI (`add-provider` wizard → Azure → Certificate
authentication). It is the Azure equivalent of the CloudFormation quick-create
stack under `../../cloudformation/`.
This directory contains the Bicep source and compiled Azure Resource Manager
(ARM) JSON template documented in the [Azure authentication
guide](../../../../docs/user-guide/providers/azure/authentication.mdx).
Deploying `prowler-scan.bicep` at subscription scope grants a **pre-existing**
App Registration the read-only permissions Prowler needs:
App Registration the subscription permissions Prowler needs:
1. A subscription-scoped assignment of the built-in `Reader` role.
2. A subscription-scoped custom role (`ProwlerRole`) with the two extra
@@ -32,89 +31,98 @@ for templates containing `Microsoft.Graph/*` resources.
A template that includes those resources fails at Portal deploy time with
`Authorization_RequestDenied: Insufficient privileges to complete the
operation` from Microsoft Graph, regardless of the deploying user's Entra
ID role — a **Global Administrator** hits the same wall. The wizard
therefore splits the flow: the user creates the App Registration and
uploads the certificate manually in the Portal (both operations *are*
supported through the Portal UI), then this template only grants that
existing service principal the RBAC roles Prowler needs.
ID role — a **Global Administrator** hits the same wall. Create the App
Registration and upload the certificate separately, then use this template to
grant the existing Service Principal the RBAC roles Prowler needs.
## Required permissions to run the deployment
The account that clicks **Deploy to Azure** (or runs `az deployment sub
create` locally) needs `Owner` on the target subscription. That is enough
The account that deploys the template needs `Owner` on the target subscription.
That is enough
to create the custom role definition and assign both roles. `Contributor`
is not sufficient because it cannot create role assignments.
Creating the App Registration and uploading its certificate (the manual
Portal steps *before* the Deploy to Azure click) require either the tenant
setting *Users can register applications* to be enabled, or one of
`Application Administrator`, `Cloud Application Administrator`, or
`Global Administrator` in Entra ID. The wizard surfaces these prerequisites
on the Certificate Authentication step.
Creating and managing the App Registration requires the relevant Microsoft
Entra ID permission. App Registration creation is available without an
administrator when the tenant setting _Users can register applications_ is
enabled. An administrator authorized to grant tenant-wide consent must approve
the Microsoft Graph application permissions.
## Files
- `prowler-scan.bicep` — Bicep source (source of truth). Vanilla ARM only —
no `Microsoft.Graph` extension, so it deploys cleanly through the Portal.
- `prowler-scan.json` — compiled ARM JSON. **This is the artifact the
Deploy to Azure button loads** — Azure Portal's
- `prowler-scan.json` — compiled ARM JSON. Azure Portal's
`#create/Microsoft.Template/uri/<url>` deep link only accepts ARM JSON,
not raw Bicep source. Keep it committed and in sync with the `.bicep`.
- `bicepconfig.json` — kept for the `az bicep build` invocation; no
extensions are required now that Microsoft.Graph resources have moved
to the manual Portal step, but the config file lets us re-add the
extension if a future Microsoft update supports Portal deployments.
- `Makefile` — `make build` regenerates the JSON; `make check` fails when
the JSON is stale relative to the Bicep source (used by CI).
- `sync_docs_template.py` — copies the canonical JSON bytes to the public docs
asset and generates the MDX code snippet shown in the authentication guide.
- `Makefile` — `make build` regenerates the JSON and synchronizes both docs
outputs; `make check` fails when the Bicep build or either docs output drifts.
Both files are hosted at:
Prowler documentation serves the compiled JSON at:
```text
https://prowler-cloud-public.s3.eu-west-1.amazonaws.com/permissions/templates/azure/bicep/prowler-scan.json
https://prowler-cloud-public.s3.eu-west-1.amazonaws.com/permissions/templates/azure/bicep/prowler-scan.bicep
https://docs.prowler.com/assets/templates/azure/prowler-scan.json
```
The Deploy to Azure button opens
The **Deploy to Azure** link in the authentication guide opens
`https://portal.azure.com/#create/Microsoft.Template/uri/<encoded-json-url>`,
which loads the ARM JSON into the Portal deployment wizard.
which loads the documentation-hosted ARM JSON into Azure Portal. The Bicep
source remains in this directory and is not served as a public asset.
## Regenerating the ARM JSON
After editing `prowler-scan.bicep`, run:
The Makefile uses the standalone Bicep CLI by default. After editing
`prowler-scan.bicep`, run:
```bash
make build
make check
```
Requires the Bicep CLI. Install with `az bicep install` or grab the
standalone binary from <https://github.com/Azure/bicep/releases>.
Download the standalone binary from
<https://github.com/Azure/bicep/releases>. To use the Azure CLI wrapper
instead, install it and pass the wrapper command explicitly:
## Manual App Registration + certificate steps (what the wizard guides)
```bash
az bicep install
make build BICEP='az bicep'
make check BICEP='az bicep'
```
The Makefile adds Azure CLI's required `--file` option when
`BICEP='az bicep'` is set.
## Manual App Registration and Certificate Steps
1. **Create the App Registration** in Portal → **Microsoft Entra ID** →
**App registrations** → **New registration**. Give it any name, keep
the default *single tenant* audience, no redirect URI.
the default _single tenant_ audience, no redirect URI.
2. **Upload the certificate** on the same App Registration → **Certificates
and secrets** → **Certificates** tab → **Upload certificate**. Upload
the `.cer` (public) file you generated. Prowler's *Generate certificate
for me* button in the wizard produces a base64 file that decodes back to
the required `.cer` (see the wizard for the exact one-liner).
3. **Copy the Service Principal Object ID**: Portal → **Microsoft Entra
the public `.cer` file. Prowler's **Generate certificate** button downloads
`prowler-cert.cer` directly and fills the private bundle field.
3. **Grant Microsoft Graph permissions** on the App Registration. Add the
`AuditLog.Read.All`, `Directory.Read.All` (or `Domain.Read.All`), and
`Policy.Read.All` application permissions, then grant admin consent.
4. **Copy the Service Principal Object ID**: Portal → **Microsoft Entra
ID** → **Enterprise applications** → search for the app you just
created → click it → **Object ID** on the Overview page. That is the
value the Bicep template asks for as `servicePrincipalObjectId`. It is
NOT the same as the App Registration's Object ID (Enterprise
applications and App registrations are two separate objects with
separate Object IDs — same App ID / Client ID, different Object IDs).
4. **Copy the Application (client) ID** from the App Registration overview
— you paste this in the Prowler wizard's *Client ID* field.
5. **Copy the Application (client) ID** from the App Registration overview
— provide this value in Prowler's _Client ID_ field.
### Certificate generation cheatsheet
`Generate certificate for me` in the wizard is the easiest option — it
generates a keypair in your browser, auto-fills the private key into the
wizard, and downloads a text file with the base64-encoded public
certificate you upload to the App Registration.
**Generate certificate** in Prowler is the easiest option — it
generates a keypair in your browser, auto-fills the base64-encoded
certificate and private key bundle into the wizard, and downloads the raw
DER public certificate as `prowler-cert.cer` for upload to the App
Registration.
If you prefer the command line:
@@ -134,7 +142,7 @@ openssl req -x509 -newkey rsa:4096 -keyout prowler.key -out prowler.crt \
cat prowler.crt prowler.key > prowler-bundle.pem
CERT_BUNDLE_BASE64=$(base64 < prowler-bundle.pem | tr -d '\n')
echo "Certificate Private Key for Prowler wizard: $CERT_BUNDLE_BASE64"
echo "Certificate and Private Key Bundle for Prowler: $CERT_BUNDLE_BASE64"
```
#### Windows (PowerShell)
@@ -148,12 +156,12 @@ $cert = New-SelfSignedCertificate -Subject "CN=Prowler" `
# Save the .cer to upload in the Portal
Export-Certificate -Cert $cert -FilePath prowler.cer
# Private key (PKCS#12), base64-encoded — paste into the Prowler wizard as
# Certificate Private Key. Keep secret.
# Certificate and private key bundle (PKCS#12), base64-encoded — paste into
# Prowler. Keep secret.
$pfxBytes = $cert.Export('Pfx', '')
$keyBase64 = [Convert]::ToBase64String($pfxBytes)
Write-Host "Certificate Private Key for Prowler wizard: $keyBase64"
Write-Host "Certificate and Private Key Bundle for Prowler: $keyBase64"
```
## Deploying manually (CLI, when the button is not an option)
@@ -176,18 +184,17 @@ az deployment sub create \
--parameters servicePrincipalObjectId=<sp-object-id>
```
## After the deployment
## After the Deployment
Paste the following into the Prowler wizard's Certificate Authentication form:
Paste the following into Prowler's Certificate Authentication form:
- **Tenant ID** — the `tenantId` output (also visible in Portal → Entra ID
→ Overview).
- **Client ID** — the Application (client) ID of the App Registration you
created manually in step 1 of the manual flow above.
- **Certificate Private Key** — the base64-encoded PEM bundle (certificate
+ private key) or PKCS#12 export from the generation step. This is
Prowler's copy of the private half of the keypair; it never leaves the
wizard and never touches Azure.
- **Certificate and Private Key Bundle** — the base64-encoded PEM bundle
(certificate + private key) or PKCS#12 export from the generation step.
This bundle is submitted to Prowler and never touches Azure.
The manual fallback described in the Azure authentication docs (client
secrets, sovereign clouds, personal accounts) remains supported for
@@ -2,7 +2,7 @@
// Prowler quick-start deployment (RBAC only)
//
// Subscription-scoped template that grants a pre-existing App Registration
// / Service Principal the read-only permissions Prowler needs to scan an
// / Service Principal the permissions Prowler needs to scan an
// Azure subscription:
//
// 1. Assignment of the built-in `Reader` role at subscription scope so
@@ -60,7 +60,7 @@ resource prowlerRole 'Microsoft.Authorization/roleDefinitions@2022-05-01-preview
name: customRoleDefinitionName
properties: {
roleName: customRoleName
description: 'Role used by Prowler for checks that require read-only access to Azure resources beyond the built-in Reader role.'
description: 'Role used by Prowler for checks that require Azure actions beyond the built-in Reader role.'
type: 'CustomRole'
assignableScopes: [
subscription().id
@@ -5,7 +5,7 @@
"_generator": {
"name": "bicep",
"version": "0.46.1.21595",
"templateHash": "17956756534127211092"
"templateHash": "4092263301802395344"
}
},
"parameters": {
@@ -41,7 +41,7 @@
"name": "[variables('customRoleDefinitionName')]",
"properties": {
"roleName": "[parameters('customRoleName')]",
"description": "Role used by Prowler for checks that require read-only access to Azure resources beyond the built-in Reader role.",
"description": "Role used by Prowler for checks that require Azure actions beyond the built-in Reader role.",
"type": "CustomRole",
"assignableScopes": [
"[subscription().id]"
@@ -97,4 +97,4 @@
"value": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('customRoleDefinitionName'))]"
}
}
}
}
@@ -0,0 +1,68 @@
import argparse
import json
import sys
from pathlib import Path
REPOSITORY_ROOT = Path(__file__).resolve().parents[4]
DEFAULT_SOURCE = Path(__file__).with_name("prowler-scan.json")
DEFAULT_ASSET = (
REPOSITORY_ROOT / "docs/assets/templates/azure/prowler-scan.json"
)
DEFAULT_SNIPPET = REPOSITORY_ROOT / "docs/snippets/azure-prowler-scan-template.mdx"
SNIPPET_PREFIX = (
b"{/* AUTO-GENERATED from permissions/templates/azure/bicep/"
b"prowler-scan.json. Do not edit manually. */}\n\n```json\n"
)
SNIPPET_SUFFIX = b"\n```\n"
def parse_args():
parser = argparse.ArgumentParser(
description="Synchronize the Azure ARM template with Prowler documentation."
)
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument("--sync", action="store_true")
mode.add_argument("--check", action="store_true")
parser.add_argument("--source", type=Path, default=DEFAULT_SOURCE)
parser.add_argument("--asset", type=Path, default=DEFAULT_ASSET)
parser.add_argument("--snippet", type=Path, default=DEFAULT_SNIPPET)
return parser.parse_args()
def expected_snippet(source):
return SNIPPET_PREFIX + source + SNIPPET_SUFFIX
def main():
args = parse_args()
source = args.source.read_bytes()
json.loads(source)
snippet = expected_snippet(source)
if args.sync:
args.asset.parent.mkdir(parents=True, exist_ok=True)
args.snippet.parent.mkdir(parents=True, exist_ok=True)
args.asset.write_bytes(source)
args.snippet.write_bytes(snippet)
return 0
drifted = []
if not args.asset.exists() or args.asset.read_bytes() != source:
drifted.append(args.asset)
if not args.snippet.exists() or args.snippet.read_bytes() != snippet:
drifted.append(args.snippet)
if drifted:
paths = ", ".join(str(path) for path in drifted)
print(
f"Azure documentation template drift detected: {paths}",
file=sys.stderr,
)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,79 @@
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
SCRIPT = Path(__file__).with_name("sync_docs_template.py")
class SyncDocsTemplateTest(unittest.TestCase):
def setUp(self):
self.temp_dir = tempfile.TemporaryDirectory()
root = Path(self.temp_dir.name)
self.source = root / "prowler-scan.json"
self.asset = root / "docs/assets/prowler-scan.json"
self.snippet = root / "docs/snippets/prowler-scan.mdx"
self.canonical = b'{"contentVersion":"1.0.0.0"}'
self.source.write_bytes(self.canonical)
def tearDown(self):
self.temp_dir.cleanup()
def run_script(self, mode):
return subprocess.run(
[
sys.executable,
SCRIPT,
mode,
"--source",
self.source,
"--asset",
self.asset,
"--snippet",
self.snippet,
],
capture_output=True,
text=True,
check=False,
)
def test_sync_copies_asset_and_embeds_exact_canonical_bytes(self):
result = self.run_script("--sync")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(self.asset.read_bytes(), self.canonical)
snippet = self.snippet.read_bytes()
self.assertIn(b"```json\n" + self.canonical + b"\n```", snippet)
def test_check_fails_when_generated_output_drifts(self):
self.assertEqual(self.run_script("--sync").returncode, 0)
self.asset.write_text('{"contentVersion":"stale"}')
result = self.run_script("--check")
self.assertEqual(result.returncode, 1)
self.assertIn("Azure documentation template drift detected", result.stderr)
def test_check_fails_when_displayed_snippet_drifts(self):
self.assertEqual(self.run_script("--sync").returncode, 0)
self.snippet.write_text("```json\n{}\n```\n")
result = self.run_script("--check")
self.assertEqual(result.returncode, 1)
self.assertIn("Azure documentation template drift detected", result.stderr)
def test_check_fails_when_canonical_template_changes(self):
self.assertEqual(self.run_script("--sync").returncode, 0)
self.source.write_text('{"contentVersion":"2.0.0.0"}')
result = self.run_script("--check")
self.assertEqual(result.returncode, 1)
self.assertIn("Azure documentation template drift detected", result.stderr)
if __name__ == "__main__":
unittest.main()