diff --git a/docs/user-guide/providers/azure/getting-started-azure.mdx b/docs/user-guide/providers/azure/getting-started-azure.mdx index 77e02ebfeb..7e11d20d46 100644 --- a/docs/user-guide/providers/azure/getting-started-azure.mdx +++ b/docs/user-guide/providers/azure/getting-started-azure.mdx @@ -57,13 +57,29 @@ Azure supports two authentication methods in the add-provider wizard. Prowler Cl #### Certificate Authentication (Recommended) -The quick-start flow: click **Deploy to Azure** in the wizard to start a deployment in the Azure Portal with the Prowler Bicep template pre-loaded. The template creates the App Registration / Service Principal, the `Reader` role assignment, and the custom `ProwlerRole` bound to a certificate credential. You still fill the certificate parameter and confirm the deployment inside the Portal. +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. + 1. In the Azure wizard, select **Certificate Authentication (Recommended)**. -2. Click the **Deploy to Azure** button. This opens the Azure Portal deployment blade pre-loaded with the [Prowler Bicep template](https://github.com/prowler-cloud/prowler/blob/master/permissions/templates/azure/bicep/prowler-scan.bicep). -3. Generate a certificate locally and paste the base64-encoded public certificate into the Bicep `certificateBase64` parameter. See [Certificate Authentication → Generating the certificate](/user-guide/providers/azure/authentication#generating-the-certificate) for the exact commands. -4. When the deployment finishes, copy the `tenantId` and `applicationId` outputs into Prowler along with the base64-encoded **PEM bundle** (certificate + private key) you generated earlier, and paste it into the Certificate Content field. -5. Click **Next**, then **Launch Scan**. +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**. #### Service Principal with Client Secret diff --git a/permissions/templates/azure/bicep/README.md b/permissions/templates/azure/bicep/README.md index 1a81d5d31c..4bd244f828 100644 --- a/permissions/templates/azure/bicep/README.md +++ b/permissions/templates/azure/bicep/README.md @@ -5,30 +5,65 @@ button in the Prowler UI (`add-provider` wizard → Azure → Certificate authentication). It is the Azure equivalent of the CloudFormation quick-create stack under `../../cloudformation/`. -Deploying `prowler-scan.bicep` at subscription scope provisions everything -Prowler needs to authenticate against the subscription and enumerate resources: +Deploying `prowler-scan.bicep` at subscription scope grants a **pre-existing** +App Registration the read-only permissions Prowler needs: -1. An Entra ID App Registration with an X.509 `keyCredentials` entry - (certificate authentication — no client secrets). -2. A Service Principal for that application. -3. A subscription-scoped assignment of the built-in `Reader` role. -4. A subscription-scoped custom role (`ProwlerRole`) with the two extra +1. A subscription-scoped assignment of the built-in `Reader` role. +2. A subscription-scoped custom role (`ProwlerRole`) with the two extra read/list actions the built-in Reader is missing, and its role assignment. The template is idempotent: role definitions and role assignments use -deterministic GUIDs derived from `subscription().id` and `applicationName`, -so redeploying updates the existing resources instead of creating duplicates. +deterministic GUIDs derived from `subscription().id` and the deployment +label, so redeploying updates the existing resources instead of creating +duplicates. + +## Why the template doesn't create the App Registration itself + +Microsoft.Graph Bicep resources (`Microsoft.Graph/applications`, +`Microsoft.Graph/servicePrincipals`, etc.) are **not supported by the +Azure Portal "Deploy to Azure" URL flow**. This is a documented Microsoft +limitation, not a permission or configuration problem — see +[microsoftgraph/msgraph-bicep-types#294](https://github.com/microsoftgraph/msgraph-bicep-types/issues/294) +(closed as documented limitation) and Microsoft's own +[permissions and privileges docs](https://learn.microsoft.com/en-us/graph/templates/bicep/concept-permissions-and-privileges), +which list only Azure CLI and Azure PowerShell as supported deployment paths +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. + +## 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 +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. ## Files -- `prowler-scan.bicep` — Bicep source (source of truth). Uses the - `Microsoft.Graph` extension registered in `bicepconfig.json`. +- `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 `#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` — registers the Microsoft.Graph Bicep extension against - the public Microsoft Container Registry. +- `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). @@ -51,45 +86,58 @@ After editing `prowler-scan.bicep`, run: make build ``` -Requires the Bicep CLI **>= 0.36.1** (needed by the GA release of the -Microsoft Graph Bicep extension, `v1.0.0`). Install with `az bicep install` -or grab the standalone binary from -. Note: the `apiVersion: v1.0` on -`Microsoft.Graph/applications` in the .bicep source refers to the Graph API -version, not the extension release channel — the extension version is pinned -in `bicepconfig.json`. +Requires the Bicep CLI. Install with `az bicep install` or grab the +standalone binary from . -## Generating the certificate +## Manual App Registration + certificate steps (what the wizard guides) -The user supplies the certificate — the template only receives the -base64-encoded DER of the public certificate. Prowler never sees the private -key during deployment; Microsoft Graph computes and stores the SHA-1 -thumbprint from the certificate bytes and returns it as a template output. +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. +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 + 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. -### macOS / Linux (OpenSSL) +### 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. + +If you prefer the command line: + +#### macOS / Linux (OpenSSL) ```bash -# 1. Generate a 4096-bit RSA private key and a matching self-signed cert +# 1. Generate a 4096-bit RSA private key and matching self-signed cert openssl req -x509 -newkey rsa:4096 -keyout prowler.key -out prowler.crt \ -days 365 -nodes -subj "/CN=Prowler" -# 2. Base64-encode the DER form of the public certificate. Feeds -# `certificateBase64`. IMPORTANT: use `-outform DER` — piping a PEM -# file through base64 double-encodes it and the deployment will fail. -CERT_BASE64=$(openssl x509 -in prowler.crt -outform DER | base64 | tr -d '\n') +# 2. Upload prowler.crt to the App Registration (Portal, step 2 above). -# 3. Concatenate the certificate AND the private key into a single PEM -# bundle, then base64-encode the whole thing. `azure.identity.CertificateCredential` -# needs BOTH parts — a key-only file fails with "No certificate found". -# Keep this value secret. Using `< file` (not `-i`) works on macOS and Linux. +# 3. Bundle certificate + private key into a single PEM and base64-encode +# it. `azure.identity.CertificateCredential` needs BOTH parts — a +# key-only file fails with "No certificate found". Keep this value +# secret; it is what Prowler uses to authenticate. cat prowler.crt prowler.key > prowler-bundle.pem CERT_BUNDLE_BASE64=$(base64 < prowler-bundle.pem | tr -d '\n') -echo "certificateBase64 (paste into the Bicep parameter): $CERT_BASE64" -echo "certificate bundle (paste into Prowler UI, keep secret): $CERT_BUNDLE_BASE64" +echo "Certificate Private Key for Prowler wizard: $CERT_BUNDLE_BASE64" ``` -### Windows (PowerShell) +#### Windows (PowerShell) ```powershell $cert = New-SelfSignedCertificate -Subject "CN=Prowler" ` @@ -97,16 +145,15 @@ $cert = New-SelfSignedCertificate -Subject "CN=Prowler" ` -KeyExportPolicy Exportable -KeySpec Signature ` -KeyLength 4096 -HashAlgorithm SHA256 -# certificateBase64: DER of the public certificate, base64-encoded -$certBase64 = [Convert]::ToBase64String($cert.RawData) +# Save the .cer to upload in the Portal +Export-Certificate -Cert $cert -FilePath prowler.cer -# Private key (PKCS#12), base64-encoded — paste into Prowler as -# "Certificate Content", keep secret +# Private key (PKCS#12), base64-encoded — paste into the Prowler wizard as +# Certificate Private Key. Keep secret. $pfxBytes = $cert.Export('Pfx', '') $keyBase64 = [Convert]::ToBase64String($pfxBytes) -Write-Host "certificateBase64 (paste into the Bicep parameter): $certBase64" -Write-Host "private key (paste into Prowler UI, keep secret): $keyBase64" +Write-Host "Certificate Private Key for Prowler wizard: $keyBase64" ``` ## Deploying manually (CLI, when the button is not an option) @@ -117,7 +164,7 @@ Deploy the compiled ARM JSON (recommended, matches what the Portal loads): az deployment sub create \ --location westeurope \ --template-file prowler-scan.json \ - --parameters certificateBase64="$CERT_BASE64" + --parameters servicePrincipalObjectId= ``` Or deploy the Bicep source directly (Azure CLI compiles it on the fly): @@ -126,22 +173,21 @@ Or deploy the Bicep source directly (Azure CLI compiles it on the fly): az deployment sub create \ --location westeurope \ --template-file prowler-scan.bicep \ - --parameters certificateBase64="$CERT_BASE64" + --parameters servicePrincipalObjectId= ``` ## After the deployment Paste the following into the Prowler wizard's Certificate Authentication form: -- **Tenant ID** — `tenantId` output. -- **Client ID** — `applicationId` output. -- **Certificate Content** — the base64-encoded **PEM bundle** (certificate + - private key) or PKCS#12 export you generated above. The Bicep template - received only the public certificate; Prowler needs the certificate paired - with the private key to sign token requests. - -The `certificateThumbprint` output is informational; use it to verify the -deployed keyCredential matches the certificate you generated locally. +- **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. 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 36f9ec9193..5dc571177a 100644 --- a/permissions/templates/azure/bicep/prowler-scan.bicep +++ b/permissions/templates/azure/bicep/prowler-scan.bicep @@ -1,49 +1,45 @@ // ----------------------------------------------------------------------------- -// Prowler quick-start deployment +// Prowler quick-start deployment (RBAC only) // -// One-shot Bicep template that stands up everything Prowler needs to scan an -// Azure subscription with certificate-based authentication: -// 1. An Entra ID App Registration with an X.509 certificate credential -// (`keyCredentials`) — no client secrets, no deploymentScripts. -// 2. A Service Principal for that App Registration. -// 3. A role assignment granting the built-in `Reader` role at subscription -// scope so Prowler can inventory resources. -// 4. A subscription-scoped custom "ProwlerRole" that grants the two extra +// Subscription-scoped template that grants a pre-existing App Registration +// / Service Principal the read-only permissions Prowler needs to scan an +// Azure subscription: +// +// 1. Assignment of the built-in `Reader` role at subscription scope so +// Prowler can inventory resources. +// 2. A subscription-scoped custom "ProwlerRole" that grants the two extra // read/list actions the built-in Reader is missing // (`Microsoft.Web/sites/host/listkeys/action` and // `Microsoft.Web/sites/config/list/Action`), plus its role assignment. // +// The template does NOT create the App Registration itself. Microsoft.Graph +// Bicep resources are not supported by the Azure Portal "Deploy to Azure" +// flow (Microsoft docs: `msgraph-bicep-types` issue #294, closed as a +// documented limitation), so any template that includes them fails at +// deploy time with `Authorization_RequestDenied` regardless of the user's +// Entra ID role. The wizard therefore guides the user to create the App +// Registration and upload the certificate manually in the Portal first, +// then deploys this template with the resulting service principal's +// Object ID. +// // This mirrors the AWS CloudFormation quick-create flow shipped in // `permissions/templates/cloudformation/prowler-scan-role.yml`, and matches // the "manual" instructions in // `docs/user-guide/providers/azure/authentication.mdx`. Keep them in sync // when adding or removing permissions here. // -// After the deployment succeeds, copy the outputs (tenantId, clientId, and -// certificateThumbprint) into Prowler along with the private key that pairs -// with `certificateBase64`. +// After the deployment succeeds, copy the outputs (tenantId, subscriptionId) +// into Prowler along with the App Registration's Client ID and the private +// key that pairs with the certificate uploaded to Entra ID manually. // ----------------------------------------------------------------------------- targetScope = 'subscription' -@description('Name for the App Registration Prowler will use. Keep the default unless you need a custom name.') -param applicationName string = 'ProwlerApp' +@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.') +param servicePrincipalObjectId string -@description('Paste the PUBLIC certificate from the Prowler wizard (prowler-cert-base64.txt) or your own openssl output. Prowler never receives the matching private key.') -@secure() -param certificateBase64 string - -@description('Label for the certificate inside the App Registration. Cosmetic — keep the default if unsure.') -param certificateDisplayName string = 'Prowler Certificate' - -@description('When the certificate becomes valid. Defaults to now.') -param certificateStartDateTime string = utcNow() - -// Derived from `certificateStartDateTime` (not a fresh `utcNow()`) so the -// pair is always exactly 1 year apart — Microsoft Graph rejects certificates -// whose `endDateTime - startDateTime` exceeds the 1-year cap. -@description('When the certificate expires. Defaults to 1 year after certificateStartDateTime. Rotate before this date.') -param certificateEndDateTime string = dateTimeAdd(certificateStartDateTime, 'P1Y') +@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.') +param deploymentLabel string = 'Prowler' @description('Name of the extra role Prowler creates. Keep the default unless your org already uses this name.') param customRoleName string = 'ProwlerRole' @@ -52,53 +48,12 @@ param customRoleName string = 'ProwlerRole' // re-deploying the same template into the same subscription is idempotent — // the role definition and the two role assignments are found and updated // instead of duplicated. `guid()` is safe to call at subscription scope. -// The role-assignment names cannot reference `servicePrincipal.id` -// because ARM needs those names calculable before the deployment starts; -// pinning to `applicationName` gives a stable identity per app. var customRoleDefinitionName = guid(subscription().id, customRoleName) var readerRoleDefinitionId = subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7' // built-in Reader ) -// Microsoft Graph resources are provisioned through the `Microsoft.Graph` -// Bicep extension. `apiVersion: v1.0` is the stable channel; the resource -// names below are the type names Graph exposes for Bicep, not ARM. - -extension microsoftGraphV1 - -// `uniqueName` on Microsoft.Graph/applications is tenant-scoped (an existing -// value UPSERTs the resource), so two subscriptions in the same tenant -// deploying with the default `applicationName` would otherwise overwrite -// each other's keyCredentials and silently break the previous scan. Suffix -// with the subscription id to keep each deployment isolated. The visible -// display name stays as the user-provided `applicationName`. -resource application 'Microsoft.Graph/applications@v1.0' = { - uniqueName: '${applicationName}-${subscription().subscriptionId}' - displayName: applicationName - description: 'Deployed by Prowler quick-start Bicep template for read-only security scanning.' - signInAudience: 'AzureADMyOrg' - keyCredentials: [ - { - displayName: certificateDisplayName - type: 'AsymmetricX509Cert' - usage: 'Verify' - // Microsoft Graph computes `customKeyIdentifier` (the base64-encoded - // SHA-1 thumbprint) from the certificate bytes automatically, so we - // deliberately omit it. Passing the hex thumbprint here would fail the - // keyCredentials contract, which expects base64-encoded bytes. - key: certificateBase64 - startDateTime: certificateStartDateTime - endDateTime: certificateEndDateTime - } - ] -} - -resource servicePrincipal 'Microsoft.Graph/servicePrincipals@v1.0' = { - appId: application.appId - displayName: applicationName -} - // Custom role: only the two read/list actions the built-in Reader is missing. // Keep this list in sync with `permissions/prowler-azure-custom-role.json`. resource prowlerRole 'Microsoft.Authorization/roleDefinitions@2022-05-01-preview' = { @@ -125,18 +80,18 @@ resource prowlerRole 'Microsoft.Authorization/roleDefinitions@2022-05-01-preview } resource readerAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(subscription().id, applicationName, 'Reader') + name: guid(subscription().id, servicePrincipalObjectId, deploymentLabel, 'Reader') properties: { - principalId: servicePrincipal.id + principalId: servicePrincipalObjectId principalType: 'ServicePrincipal' roleDefinitionId: readerRoleDefinitionId } } resource prowlerRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(subscription().id, applicationName, customRoleName) + name: guid(subscription().id, servicePrincipalObjectId, deploymentLabel, customRoleName) properties: { - principalId: servicePrincipal.id + principalId: servicePrincipalObjectId principalType: 'ServicePrincipal' roleDefinitionId: prowlerRole.id } @@ -144,11 +99,4 @@ resource prowlerRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04- output tenantId string = subscription().tenantId output subscriptionId string = subscription().subscriptionId -output applicationId string = application.appId -output servicePrincipalObjectId string = servicePrincipal.id -// Microsoft Graph populates `customKeyIdentifier` (the base64-encoded SHA-1 -// thumbprint) on the created keyCredential; expose it as an output so users -// can reconcile the deployed cert against the fingerprint they generated -// locally. -output certificateThumbprint string = application.keyCredentials[0].customKeyIdentifier output prowlerRoleDefinitionId string = prowlerRole.id diff --git a/permissions/templates/azure/bicep/prowler-scan.json b/permissions/templates/azure/bicep/prowler-scan.json index e14bf25a34..0643b1d228 100644 --- a/permissions/templates/azure/bicep/prowler-scan.json +++ b/permissions/templates/azure/bicep/prowler-scan.json @@ -1,47 +1,25 @@ { "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", - "languageVersion": "2.0", "contentVersion": "1.0.0.0", "metadata": { "_generator": { "name": "bicep", "version": "0.46.1.21595", - "templateHash": "10591927438861815055" + "templateHash": "17956756534127211092" } }, "parameters": { - "applicationName": { + "servicePrincipalObjectId": { "type": "string", - "defaultValue": "ProwlerApp", "metadata": { - "description": "Name for the App Registration Prowler will use. Keep the default unless you need a custom name." + "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." } }, - "certificateBase64": { - "type": "securestring", - "metadata": { - "description": "Paste the PUBLIC certificate from the Prowler wizard (prowler-cert-base64.txt) or your own openssl output. Prowler never receives the matching private key." - } - }, - "certificateDisplayName": { + "deploymentLabel": { "type": "string", - "defaultValue": "Prowler Certificate", + "defaultValue": "Prowler", "metadata": { - "description": "Label for the certificate inside the App Registration. Cosmetic — keep the default if unsure." - } - }, - "certificateStartDateTime": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "When the certificate becomes valid. Defaults to now." - } - }, - "certificateEndDateTime": { - "type": "string", - "defaultValue": "[dateTimeAdd(parameters('certificateStartDateTime'), 'P1Y')]", - "metadata": { - "description": "When the certificate expires. Defaults to 1 year after certificateStartDateTime. Rotate before this date." + "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": { @@ -56,45 +34,8 @@ "customRoleDefinitionName": "[guid(subscription().id, parameters('customRoleName'))]", "readerRoleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]" }, - "imports": { - "microsoftGraphV1": { - "provider": "MicrosoftGraph", - "version": "1.0.0" - } - }, - "resources": { - "application": { - "import": "microsoftGraphV1", - "type": "Microsoft.Graph/applications@v1.0", - "properties": { - "uniqueName": "[format('{0}-{1}', parameters('applicationName'), subscription().subscriptionId)]", - "displayName": "[parameters('applicationName')]", - "description": "Deployed by Prowler quick-start Bicep template for read-only security scanning.", - "signInAudience": "AzureADMyOrg", - "keyCredentials": [ - { - "displayName": "[parameters('certificateDisplayName')]", - "type": "AsymmetricX509Cert", - "usage": "Verify", - "key": "[parameters('certificateBase64')]", - "startDateTime": "[parameters('certificateStartDateTime')]", - "endDateTime": "[parameters('certificateEndDateTime')]" - } - ] - } - }, - "servicePrincipal": { - "import": "microsoftGraphV1", - "type": "Microsoft.Graph/servicePrincipals@v1.0", - "properties": { - "appId": "[reference('application').appId]", - "displayName": "[parameters('applicationName')]" - }, - "dependsOn": [ - "application" - ] - }, - "prowlerRole": { + "resources": [ + { "type": "Microsoft.Authorization/roleDefinitions", "apiVersion": "2022-05-01-preview", "name": "[variables('customRoleDefinitionName')]", @@ -118,34 +59,30 @@ ] } }, - "readerAssignment": { + { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "name": "[guid(subscription().id, parameters('applicationName'), 'Reader')]", + "name": "[guid(subscription().id, parameters('servicePrincipalObjectId'), parameters('deploymentLabel'), 'Reader')]", "properties": { - "principalId": "[reference('servicePrincipal').id]", + "principalId": "[parameters('servicePrincipalObjectId')]", "principalType": "ServicePrincipal", "roleDefinitionId": "[variables('readerRoleDefinitionId')]" - }, - "dependsOn": [ - "servicePrincipal" - ] + } }, - "prowlerRoleAssignment": { + { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "name": "[guid(subscription().id, parameters('applicationName'), parameters('customRoleName'))]", + "name": "[guid(subscription().id, parameters('servicePrincipalObjectId'), parameters('deploymentLabel'), parameters('customRoleName'))]", "properties": { - "principalId": "[reference('servicePrincipal').id]", + "principalId": "[parameters('servicePrincipalObjectId')]", "principalType": "ServicePrincipal", "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('customRoleDefinitionName'))]" }, "dependsOn": [ - "prowlerRole", - "servicePrincipal" + "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('customRoleDefinitionName'))]" ] } - }, + ], "outputs": { "tenantId": { "type": "string", @@ -155,18 +92,6 @@ "type": "string", "value": "[subscription().subscriptionId]" }, - "applicationId": { - "type": "string", - "value": "[reference('application').appId]" - }, - "servicePrincipalObjectId": { - "type": "string", - "value": "[reference('servicePrincipal').id]" - }, - "certificateThumbprint": { - "type": "string", - "value": "[reference('application').keyCredentials[0].customKeyIdentifier]" - }, "prowlerRoleDefinitionId": { "type": "string", "value": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('customRoleDefinitionName'))]" diff --git a/ui/changelog.d/azure-deploy-to-azure-bicep-quick-start.added.md b/ui/changelog.d/azure-deploy-to-azure-bicep-quick-start.added.md index 3f7d3e719c..6faa7feb22 100644 --- a/ui/changelog.d/azure-deploy-to-azure-bicep-quick-start.added.md +++ b/ui/changelog.d/azure-deploy-to-azure-bicep-quick-start.added.md @@ -1 +1 @@ -Add a Deploy to Azure quick-start to the add-provider wizard. Azure now shows a credential-type selector (Certificate authentication recommended, Service Principal with Client Secret fallback), and the certificate form exposes a one-click button that opens the Azure Portal deployment blade pre-loaded with the Prowler Bicep template (App Registration + Service Principal + Reader + custom ProwlerRole + certificate credential) +Add a Deploy to Azure quick-start to the add-provider wizard. Azure now shows a credential-type selector (Certificate authentication recommended, Service Principal with Client Secret fallback). The certificate form guides the user through creating the App Registration and uploading the certificate in the Portal (with an in-browser "Generate certificate for me" helper that keeps the private key client-side), then a one-click Deploy to Azure button that opens the Portal deployment blade pre-loaded with the Prowler Bicep template to assign the built-in Reader role and a custom ProwlerRole to that App Registration's service principal diff --git a/ui/components/providers/workflow/forms/select-credentials-type/azure/credentials-type/azure-certificate-credentials-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/azure/credentials-type/azure-certificate-credentials-form.tsx index 458ae88c4a..da808036af 100644 --- a/ui/components/providers/workflow/forms/select-credentials-type/azure/credentials-type/azure-certificate-credentials-form.tsx +++ b/ui/components/providers/workflow/forms/select-credentials-type/azure/credentials-type/azure-certificate-credentials-form.tsx @@ -17,6 +17,12 @@ import { getAzureDeploymentQuickLink } from "@/lib/external-urls"; import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields"; import { AzureCertificateCredentials } from "@/types"; +const AZURE_PORTAL_NEW_APP_REGISTRATION_URL = + "https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/CreateApplicationBlade"; + +const AZURE_PORTAL_ENTERPRISE_APPLICATIONS_URL = + "https://portal.azure.com/#view/Microsoft_AAD_IAM/StartboardApplicationsMenuBlade/~/AppAppsPreview"; + export const AzureCertificateCredentialsForm = ({ control, }: { @@ -24,9 +30,10 @@ export const AzureCertificateCredentialsForm = ({ }) => { // The Deploy-to-Azure link is a plain constant today: unlike the AWS // organisation link, it does not need any wizard-collected value in the - // URL (subscription selection happens inside the Portal deployment blade). - // If Prowler ever needs to pin the deployment to a specific subscription - // or region, thread that value through `getAzureDeploymentQuickLink` here. + // URL (subscription and the service principal Object ID are filled inside + // the Portal deployment blade). Prefilling the SP Object ID via the URL + // would need the Portal's uiFormDefinitionUri machinery, which is more + // moving parts than the copy-paste we already ask the user to do. const deployToAzureUrl = getAzureDeploymentQuickLink(); // Feedback state for the in-browser certificate generator. Kept local @@ -54,7 +61,7 @@ export const AzureCertificateCredentialsForm = ({ { shouldValidate: true, shouldDirty: true, shouldTouch: true }, ); // Hand the public certificate to the user as a file: they upload it to - // the Bicep `Certificate Base64` parameter in the Azure Portal. + // the App Registration's Certificates blade in the Azure Portal. downloadPublicCertificateFile(result.publicCertificateBase64Der); setGeneratedThumbprint(result.thumbprintHex); } catch (error) { @@ -75,62 +82,164 @@ export const AzureCertificateCredentialsForm = ({ Certificate Authentication (Recommended)
- The Deploy to Azure quick-start opens the Azure Portal with the - Prowler Bicep template pre-loaded to provision the App Registration / - Service Principal and read-only roles. Paste the resulting credentials - below when the deployment finishes. + Prowler authenticates against Azure with an X.509 certificate bound + to an Entra ID App Registration. The setup is a two-step flow: create + the App Registration and upload the certificate through the Azure + Portal, then click Deploy to Azure below to grant + that App Registration the read-only permissions Prowler needs on the + subscription.
-
- -

- After deployment, copy Tenant ID and{" "} - Application ID from the deployment outputs. The - certificate private key you generated locally goes in the - “Certificate Private Key” field below. -

-
-
+
- Don't have a certificate yet? + Before you start

- Generate a self-signed X.509 certificate right in your browser. - - {" "} - The certificate and private key are generated locally - - . The base64-encoded certificate/private-key bundle goes into the - field below and is submitted with the rest of the form. The public - certificate downloads as a text file for you to paste into the Bicep{" "} - Certificate Base64 parameter in the Portal. + Your Azure account needs permissions in two separate systems. + The wizard cannot work around a missing role — request them from your + Azure administrator if needed:

- - {generatorError && ( -

{generatorError}

- )} - {generatedThumbprint && ( -

- Done. Certificate SHA-1 thumbprint:{" "} - {generatedThumbprint}. Downloaded{" "} - prowler-cert-base64.txt — paste its contents into the - Bicep Certificate Base64 parameter, then keep filling - the fields below. -

- )} +
    +
  • + Microsoft Entra ID:{" "} + Application Administrator,{" "} + Cloud Application Administrator, or{" "} + Global Administrator — needed to create the App + Registration and upload the certificate in step 1. 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 Deploy to Azure in step 3 to + assign the Reader and custom ProwlerRole{" "} + roles. +
  • +
+
    +
  1. +
    + 1. Create the App Registration in Azure +
    +

    + Open the Portal in a new tab, sign in to the tenant you want + Prowler to scan, and register a new application. Any name works + (e.g. Prowler); keep the default single-tenant + audience and no redirect URI. +

    + +
  2. +
  3. +
    + 2. Attach a certificate to that App Registration +
    +

    + In your new App Registration, open{" "} + Certificates & secrets → Certificates → Upload + certificate and upload the public certificate file + (.cer). The button below generates the keypair in your + browser — the private key is auto-filled into the form below and + the public certificate downloads as a text file you decode and + upload. +

    +
    +

    + The certificate never leaves your browser.{" "} + Prowler generates the keypair client-side and only receives the + private half; the public half goes to Entra ID via the manual + upload. +

    + + {generatorError && ( +

    + {generatorError} +

    + )} + {generatedThumbprint && ( +

    + Done. Certificate SHA-1 thumbprint:{" "} + {generatedThumbprint}. Downloaded{" "} + prowler-cert-base64.txt — decode it back to a{" "} + .cer with{" "} + base64 -D -i prowler-cert-base64.txt -o prowler.cer{" "} + and upload prowler.cer in the Portal. +

    + )} +

    + Prefer the command line? See the{" "} + + openssl / PowerShell instructions + + . +

    +
    +
  4. +
  5. +
    + 3. Grant the App Registration read access to your subscription +
    +

    + You need the App Registration's Service Principal + Object ID — not the same as the App Registration's + Object ID. Find it in{" "} + + Entra ID → Enterprise applications + {" "} + → search for the app you just created → Overview → Object + ID. Then click Deploy to Azure below and paste that Object ID + when the Portal asks. +

    + +
  6. +
  7. +
    + 4. Come back here and fill the fields below +
    +

    + Copy the Tenant ID from Entra ID → Overview, + the Application (client) ID from your App + Registration's Overview page, and paste both into the fields + below. The private key field is already pre-filled from step 2 (or + paste your own base64-encoded PEM bundle / PKCS#12 if you + generated the keypair manually). +

    +
  8. +

- This is the base64-encoded private key that matches the - certificate uploaded to Entra ID by the Bicep template (not the public + This is the base64-encoded private key that matches + the certificate you uploaded to Entra ID in step 2 (not the public certificate, and not the thumbprint). Use the{" "} - Generate certificate for me button above, or follow the manual - openssl / PowerShell instructions in the{" "} + Generate certificate for me button in step 2, or follow the + manual openssl / PowerShell instructions in the{" "} export const PROWLER_CF_TEMPLATE_URL = "https://prowler-cloud-public.s3.eu-west-1.amazonaws.com/permissions/templates/aws/cloudformation/prowler-scan-role.yml"; -// ARM JSON template URL for the Prowler quick-start Azure deployment (App -// Registration + Service Principal + Reader + custom ProwlerRole + certificate -// credential). The template is authored in Bicep at -// `permissions/templates/azure/bicep/prowler-scan.bicep` and compiled to ARM -// JSON at `prowler-scan.json` — the Azure Portal `#create/Microsoft.Template` -// deep link only accepts ARM JSON, not raw Bicep source. Regenerate with -// `make -C permissions/templates/azure/bicep build` after editing the .bicep. -// URL-encoded and appended to the Portal deep link built by -// `getAzureDeploymentQuickLink` below. Points at the same public S3 bucket -// that hosts the AWS templates. +// ARM JSON template URL for the Prowler quick-start Azure deployment. The +// template only grants a pre-existing App Registration the read-only +// subscription roles Prowler needs (built-in `Reader` + custom +// `ProwlerRole`); the App Registration itself and its certificate credential +// are created through the Portal UI in earlier wizard steps, because +// `Microsoft.Graph/*` Bicep resources are not deployable via the Portal +// `Deploy to Azure` URL flow (`msgraph-bicep-types` issue #294, closed as a +// documented Microsoft limitation). +// +// Authored in Bicep at `permissions/templates/azure/bicep/prowler-scan.bicep` +// and compiled to ARM JSON at `prowler-scan.json` — the Azure Portal +// `#create/Microsoft.Template` deep link only accepts ARM JSON, not raw +// Bicep source. Regenerate with `make -C permissions/templates/azure/bicep +// build` after editing the .bicep. URL-encoded and appended to the Portal +// deep link built by `getAzureDeploymentQuickLink` below. Points at the same +// public S3 bucket that hosts the AWS templates. export const PROWLER_AZURE_BICEP_TEMPLATE_URL = "https://prowler-cloud-public.s3.eu-west-1.amazonaws.com/permissions/templates/azure/bicep/prowler-scan.json";