From c230fdf9ce454a3380b2c3eb3b168ff672dafe69 Mon Sep 17 00:00:00 2001 From: "Hugo P.Brito" Date: Fri, 21 Aug 2026 09:45:21 +0100 Subject: [PATCH] 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 --- docs/assets/templates/azure/prowler-scan.json | 100 +++++++++ docs/snippets/azure-prowler-scan-template.mdx | 105 ++++++++++ .../providers/azure/authentication.mdx | 196 +++++++++++------- .../providers/azure/getting-started-azure.mdx | 78 ++----- permissions/templates/azure/bicep/Makefile | 30 +-- permissions/templates/azure/bicep/README.md | 121 ++++++----- .../templates/azure/bicep/prowler-scan.bicep | 4 +- .../templates/azure/bicep/prowler-scan.json | 6 +- .../azure/bicep/sync_docs_template.py | 68 ++++++ .../azure/bicep/sync_docs_template_test.py | 79 +++++++ 10 files changed, 587 insertions(+), 200 deletions(-) create mode 100644 docs/assets/templates/azure/prowler-scan.json create mode 100644 docs/snippets/azure-prowler-scan-template.mdx create mode 100644 permissions/templates/azure/bicep/sync_docs_template.py create mode 100644 permissions/templates/azure/bicep/sync_docs_template_test.py diff --git a/docs/assets/templates/azure/prowler-scan.json b/docs/assets/templates/azure/prowler-scan.json new file mode 100644 index 0000000000..deb9b5ee19 --- /dev/null +++ b/docs/assets/templates/azure/prowler-scan.json @@ -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'))]" + } + } +} diff --git a/docs/snippets/azure-prowler-scan-template.mdx b/docs/snippets/azure-prowler-scan-template.mdx new file mode 100644 index 0000000000..f3120f3db6 --- /dev/null +++ b/docs/snippets/azure-prowler-scan-template.mdx @@ -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'))]" + } + } +} + +``` diff --git a/docs/user-guide/providers/azure/authentication.mdx b/docs/user-guide/providers/azure/authentication.mdx index 0c3420583f..3138f164cd 100644 --- a/docs/user-guide/providers/azure/authentication.mdx +++ b/docs/user-guide/providers/azure/authentication.mdx @@ -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) + 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 ``` + ### 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). @@ -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) + 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 --scope /subscriptions/ ``` + #### 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/` or `/providers/Microsoft.Management/managementGroups/`. + 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" - } - ``` - - + ``` + + + + ### 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. - - -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. - - ## 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. + + + 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. + + +### ARM JSON Template + +The following JSON is generated from the same canonical template that Azure Portal retrieves from Prowler documentation: + + + ### 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="" +export AZURE_CERTIFICATE_CONTENT="" 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 -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. ### 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 --resource-group --query principalId -o tsv ``` + **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 --name --query identity.principalId -o tsv ``` + @@ -400,6 +446,7 @@ The Managed Identity needs the **Reader** role on each subscription to scan. Thi 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. + ```console @@ -413,6 +460,7 @@ The Managed Identity needs the **Reader** role on each subscription to scan. Thi --assignee-principal-type ServicePrincipal \ --scope /subscriptions/ ``` + @@ -448,6 +496,7 @@ The ProwlerRole is a custom role required for specific security checks. First, c --assignee-principal-type ServicePrincipal \ --scope /subscriptions/ ``` + 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`. -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. ```console @@ -495,7 +546,8 @@ prowler azure --managed-identity-auth --subscription-ids ``` -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. ### 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: diff --git a/docs/user-guide/providers/azure/getting-started-azure.mdx b/docs/user-guide/providers/azure/getting-started-azure.mdx index 7e11d20d46..b0dc118cbf 100644 --- a/docs/user-guide/providers/azure/getting-started-azure.mdx +++ b/docs/user-guide/providers/azure/getting-started-azure.mdx @@ -1,13 +1,20 @@ --- -title: 'Getting Started With Azure on Prowler' +title: "Getting Started With Azure on Prowler" --- ## Prowler Cloud - + > Walkthrough video onboarding an Azure Subscription using Service Principal. - **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`). - - -**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). - - - -**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. - +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="" -``` - -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: diff --git a/permissions/templates/azure/bicep/Makefile b/permissions/templates/azure/bicep/Makefile index 46785c1617..53b5a55d72 100644 --- a/permissions/templates/azure/bicep/Makefile +++ b/permissions/templates/azure/bicep/Makefile @@ -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: diff --git a/permissions/templates/azure/bicep/README.md b/permissions/templates/azure/bicep/README.md index 4bd244f828..953b3a1025 100644 --- a/permissions/templates/azure/bicep/README.md +++ b/permissions/templates/azure/bicep/README.md @@ -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/` 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/`, -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 . +Download the standalone binary from +. 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= ``` -## 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 diff --git a/permissions/templates/azure/bicep/prowler-scan.bicep b/permissions/templates/azure/bicep/prowler-scan.bicep index 5dc571177a..83fa3a6440 100644 --- a/permissions/templates/azure/bicep/prowler-scan.bicep +++ b/permissions/templates/azure/bicep/prowler-scan.bicep @@ -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 diff --git a/permissions/templates/azure/bicep/prowler-scan.json b/permissions/templates/azure/bicep/prowler-scan.json index 0643b1d228..deb9b5ee19 100644 --- a/permissions/templates/azure/bicep/prowler-scan.json +++ b/permissions/templates/azure/bicep/prowler-scan.json @@ -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'))]" } } -} \ No newline at end of file +} diff --git a/permissions/templates/azure/bicep/sync_docs_template.py b/permissions/templates/azure/bicep/sync_docs_template.py new file mode 100644 index 0000000000..156e7b0f94 --- /dev/null +++ b/permissions/templates/azure/bicep/sync_docs_template.py @@ -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()) diff --git a/permissions/templates/azure/bicep/sync_docs_template_test.py b/permissions/templates/azure/bicep/sync_docs_template_test.py new file mode 100644 index 0000000000..385ff4ac53 --- /dev/null +++ b/permissions/templates/azure/bicep/sync_docs_template_test.py @@ -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()