- 14 minutes to read

Azure Role Deployment Automation for Nodinite Monitoring

Automate complete Azure role deployment for Nodinite Monitoring and Logging Agents with a comprehensive solution that includes:

  • ARM Template - Deploy custom RBAC role with control plane permissions (Actions)
  • PowerShell/Bash Scripts - Automate data plane role assignments for Storage, Event Hub, and Service Bus
  • Graph API Configuration - Step-by-step Microsoft Graph and Application Insights API setup

This complete automation eliminates manual configuration, ensures consistent permissions across all environments, and reduces deployment time from hours to minutes.

On this page

Why Use This Automation?

  • Complete Automation – Deploy custom roles AND data plane permissions with scripts
  • Consistent Configuration – Ensure identical permissions across environments (Dev, Test, Prod)
  • Infrastructure as Code – Version control your entire security configuration
  • Least Privilege – Pre-configured with only the required permissions for Nodinite agents
  • Easy Maintenance – Update permissions centrally and redeploy
  • Time Saving – What takes hours manually completes in minutes

ARM Template

Save this template as nodinite-custom-role.json:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "roleName": {
      "type": "string",
      "defaultValue": "Nodinite Monitoring",
      "metadata": {
        "description": "Name of the custom role for Nodinite monitoring and logging agents"
      }
    },
    "roleDescription": {
      "type": "string",
      "defaultValue": "Custom role with least-privilege permissions for Nodinite Monitoring and Logging Agents to access Azure resources",
      "metadata": {
        "description": "Description of the custom role"
      }
    }
  },
  "variables": {
    "roleDefName": "[guid(subscription().id, parameters('roleName'))]"
  },
  "resources": [
    {
      "type": "Microsoft.Authorization/roleDefinitions",
      "apiVersion": "2022-04-01",
      "name": "[variables('roleDefName')]",
      "properties": {
        "roleName": "[parameters('roleName')]",
        "description": "[parameters('roleDescription')]",
        "type": "CustomRole",
        "isCustom": true,
        "permissions": [
          {
            "actions": [
              "*/read",
              "Microsoft.ApiManagement/service/*",
              "Microsoft.Web/sites/write",
              "Microsoft.Web/sites/config/list/action",
              "Microsoft.Web/sites/config/write",
              "Microsoft.Web/sites/publishingcredentials/read",
              "Microsoft.Web/sites/publishxml/action",
              "Microsoft.Insights/components/api/read",
              "Microsoft.Insights/components/query/read",
              "Microsoft.DataFactory/factories/write",
              "Microsoft.DataFactory/factories/pipelines/write",
              "Microsoft.EventHub/namespaces/eventhubs/send/action",
              "Microsoft.EventHub/namespaces/eventhubs/listen/action",
              "Microsoft.KeyVault/vaults/secrets/read",
              "Microsoft.Logic/workflows/enable/action",
              "Microsoft.Logic/workflows/disable/action",
              "Microsoft.Logic/workflows/runs/action",
              "Microsoft.ServiceBus/namespaces/authorizationRules/listkeys/action",
              "Microsoft.ServiceBus/namespaces/queues/authorizationRules/listkeys/action",
              "Microsoft.Storage/storageAccounts/listkeys/action",
              "Microsoft.Storage/storageAccounts/blobServices/containers/write",
              "Microsoft.Storage/storageAccounts/blobServices/containers/delete",
              "Microsoft.Storage/storageAccounts/fileServices/fileshares/write",
              "Microsoft.Storage/storageAccounts/queueServices/queues/write"
            ],
            "notActions": [],
            "dataActions": [],
            "notDataActions": []
          }
        ],
        "assignableScopes": [
          "[subscription().id]"
        ]
      }
    }
  ],
  "outputs": {
    "roleDefinitionId": {
      "type": "string",
      "value": "[resourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefName'))]"
    },
    "roleName": {
      "type": "string",
      "value": "[parameters('roleName')]"
    }
  }
}

Deployment Instructions

Prerequisites

  • Azure subscription with Owner or User Access Administrator permissions
  • Azure PowerShell or Azure CLI installed
  • Appropriate permissions to create custom RBAC roles

Option 1: Deploy Using Azure PowerShell

# Connect to Azure
Connect-AzAccount

# Select your subscription
Set-AzContext -SubscriptionId "YOUR-SUBSCRIPTION-ID"

# Deploy the template at subscription scope
New-AzSubscriptionDeployment `
    -Name "NodiniteCustomRole" `
    -Location "westeurope" `
    -TemplateFile ".\nodinite-custom-role.json" `
    -Verbose

# Optional: Customize the role name
New-AzSubscriptionDeployment `
    -Name "NodiniteCustomRole" `
    -Location "westeurope" `
    -TemplateFile ".\nodinite-custom-role.json" `
    -roleName "Nodinite Agent Access" `
    -Verbose

Option 2: Deploy Using Azure CLI

# Login to Azure
az login

# Select your subscription
az account set --subscription "YOUR-SUBSCRIPTION-ID"

# Deploy the template at subscription scope
az deployment sub create \
    --name NodiniteCustomRole \
    --location westeurope \
    --template-file ./nodinite-custom-role.json \
    --verbose

# Optional: Customize the role name
az deployment sub create \
    --name NodiniteCustomRole \
    --location westeurope \
    --template-file ./nodinite-custom-role.json \
    --parameters roleName="Nodinite Agent Access" \
    --verbose

Option 3: Deploy via Azure Portal

  1. Navigate to the Azure Portal
  2. Search for Deploy a custom template
  3. Select Build your own template in the editor
  4. Copy and paste the ARM template JSON
  5. Click Save
  6. Select your Subscription
  7. Optionally modify the Role Name parameter
  8. Click Review + create
  9. Click Create

Assigning the Custom Role

After deploying the template, assign the role to your App Registration(s):

Using Azure Portal

  1. Navigate to your Subscription or Resource Group
  2. Select Access Control (IAM)
  3. Click AddAdd role assignment
  4. Search for your custom role (e.g., "Nodinite Monitoring")
  5. Select Next
  6. Click Select members
  7. Search for and select your App Registration
  8. Click Review + assign

Using PowerShell

# Get the custom role
$role = Get-AzRoleDefinition -Name "Nodinite Monitoring"

# Assign to App Registration at Resource Group scope
New-AzRoleAssignment `
    -ObjectId "YOUR-APP-REGISTRATION-OBJECT-ID" `
    -RoleDefinitionId $role.Id `
    -Scope "/subscriptions/YOUR-SUBSCRIPTION-ID/resourceGroups/YOUR-RESOURCE-GROUP"

# Assign at Subscription scope
New-AzRoleAssignment `
    -ObjectId "YOUR-APP-REGISTRATION-OBJECT-ID" `
    -RoleDefinitionId $role.Id `
    -Scope "/subscriptions/YOUR-SUBSCRIPTION-ID"

Using Azure CLI

# Assign to App Registration at Resource Group scope
az role assignment create \
    --assignee YOUR-APP-REGISTRATION-OBJECT-ID \
    --role "Nodinite Monitoring" \
    --resource-group YOUR-RESOURCE-GROUP

# Assign at Subscription scope
az role assignment create \
    --assignee YOUR-APP-REGISTRATION-OBJECT-ID \
    --role "Nodinite Monitoring" \
    --subscription YOUR-SUBSCRIPTION-ID

Additional Required Permissions

Important

The ARM template creates the Azure RBAC role with control plane permissions only. Azure uses two separate permission systems:

  1. Control Plane (Actions) - For managing Azure resources (create, configure, delete) - Included in ARM template
  2. Data Plane (Data Actions) - For accessing data within resources (read/write blobs, messages, etc.) - Must be assigned separately

Microsoft Graph API Permissions

Add these permissions in Microsoft Entra IDApp registrations → your app → API permissions:

  1. Click Add a permission
  2. Select Microsoft Graph
  3. Select Application permissions
  4. Add:
    • Application.Read.All
    • User.Read.All
  5. Click Grant admin consent

Data Plane Roles

Data plane roles CANNOT be included in the ARM template because they use dataActions instead of actions. However, you CAN automate their assignment using PowerShell or Azure CLI scripts after deploying the custom role.

Option 1: Automated Script for All Data Plane Roles

Save this PowerShell script as assign-nodinite-data-plane-roles.ps1:

<#
.SYNOPSIS
    Assigns all required data plane roles for Nodinite Monitoring Agents
.DESCRIPTION
    This script assigns Storage, Event Hub, and Service Bus data plane roles
    to the specified App Registration for Nodinite monitoring agents.
.PARAMETER AppRegistrationObjectId
    The Object ID of the App Registration (Service Principal)
.PARAMETER SubscriptionId
    The Azure Subscription ID
.PARAMETER ResourceGroupName
    The Resource Group containing the Azure resources
.EXAMPLE
    .\assign-nodinite-data-plane-roles.ps1 -AppRegistrationObjectId "12345678-1234-1234-1234-123456789012" `
        -SubscriptionId "87654321-4321-4321-4321-210987654321" `
        -ResourceGroupName "MyResourceGroup"
#>

param(
    [Parameter(Mandatory=$true)]
    [string]$AppRegistrationObjectId,
    
    [Parameter(Mandatory=$true)]
    [string]$SubscriptionId,
    
    [Parameter(Mandatory=$true)]
    [string]$ResourceGroupName
)

# Connect to Azure (if not already connected)
if (-not (Get-AzContext)) {
    Connect-AzAccount
}

# Set context to the correct subscription
Set-AzContext -SubscriptionId $SubscriptionId

Write-Host "Starting data plane role assignments for App Registration: $AppRegistrationObjectId" -ForegroundColor Cyan

# Get the resource group
$rg = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction Stop

# Storage Accounts - Assign data plane roles
Write-Host "`nProcessing Storage Accounts..." -ForegroundColor Yellow
$storageAccounts = Get-AzStorageAccount -ResourceGroupName $ResourceGroupName

foreach ($sa in $storageAccounts) {
    Write-Host "  - Storage Account: $($sa.StorageAccountName)"
    
    # Storage Blob Data Contributor
    try {
        New-AzRoleAssignment `
            -ObjectId $AppRegistrationObjectId `
            -RoleDefinitionName "Storage Blob Data Contributor" `
            -Scope $sa.Id `
            -ErrorAction SilentlyContinue | Out-Null
        Write-Host "    ✓ Assigned: Storage Blob Data Contributor" -ForegroundColor Green
    } catch {
        Write-Host "    ℹ Storage Blob Data Contributor already assigned or failed" -ForegroundColor Gray
    }
    
    # Storage Queue Data Contributor
    try {
        New-AzRoleAssignment `
            -ObjectId $AppRegistrationObjectId `
            -RoleDefinitionName "Storage Queue Data Contributor" `
            -Scope $sa.Id `
            -ErrorAction SilentlyContinue | Out-Null
        Write-Host "    ✓ Assigned: Storage Queue Data Contributor" -ForegroundColor Green
    } catch {
        Write-Host "    ℹ Storage Queue Data Contributor already assigned or failed" -ForegroundColor Gray
    }
    
    # Storage File Data SMB Share Contributor
    try {
        New-AzRoleAssignment `
            -ObjectId $AppRegistrationObjectId `
            -RoleDefinitionName "Storage File Data SMB Share Contributor" `
            -Scope $sa.Id `
            -ErrorAction SilentlyContinue | Out-Null
        Write-Host "    ✓ Assigned: Storage File Data SMB Share Contributor" -ForegroundColor Green
    } catch {
        Write-Host "    ℹ Storage File Data SMB Share Contributor already assigned or failed" -ForegroundColor Gray
    }
}

# Event Hub Namespaces - Assign data plane roles
Write-Host "`nProcessing Event Hub Namespaces..." -ForegroundColor Yellow
$eventHubNamespaces = Get-AzEventHubNamespace -ResourceGroupName $ResourceGroupName

foreach ($ehns in $eventHubNamespaces) {
    Write-Host "  - Event Hub Namespace: $($ehns.Name)"
    
    # Azure Event Hubs Data Sender
    try {
        New-AzRoleAssignment `
            -ObjectId $AppRegistrationObjectId `
            -RoleDefinitionName "Azure Event Hubs Data Sender" `
            -Scope $ehns.Id `
            -ErrorAction SilentlyContinue | Out-Null
        Write-Host "    ✓ Assigned: Azure Event Hubs Data Sender" -ForegroundColor Green
    } catch {
        Write-Host "    ℹ Azure Event Hubs Data Sender already assigned or failed" -ForegroundColor Gray
    }
    
    # Azure Event Hubs Data Receiver
    try {
        New-AzRoleAssignment `
            -ObjectId $AppRegistrationObjectId `
            -RoleDefinitionName "Azure Event Hubs Data Receiver" `
            -Scope $ehns.Id `
            -ErrorAction SilentlyContinue | Out-Null
        Write-Host "    ✓ Assigned: Azure Event Hubs Data Receiver" -ForegroundColor Green
    } catch {
        Write-Host "    ℹ Azure Event Hubs Data Receiver already assigned or failed" -ForegroundColor Gray
    }
}

# Service Bus Namespaces - Assign data plane roles
Write-Host "`nProcessing Service Bus Namespaces..." -ForegroundColor Yellow
$serviceBusNamespaces = Get-AzServiceBusNamespace -ResourceGroupName $ResourceGroupName

foreach ($sbns in $serviceBusNamespaces) {
    Write-Host "  - Service Bus Namespace: $($sbns.Name)"
    
    # Azure Service Bus Data Owner
    try {
        New-AzRoleAssignment `
            -ObjectId $AppRegistrationObjectId `
            -RoleDefinitionName "Azure Service Bus Data Owner" `
            -Scope $sbns.Id `
            -ErrorAction SilentlyContinue | Out-Null
        Write-Host "    ✓ Assigned: Azure Service Bus Data Owner" -ForegroundColor Green
    } catch {
        Write-Host "    ℹ Azure Service Bus Data Owner already assigned or failed" -ForegroundColor Gray
    }
}

Write-Host "`n✓ Data plane role assignments completed!" -ForegroundColor Cyan
Write-Host "Note: Role assignments may take up to 5 minutes to propagate." -ForegroundColor Yellow

Azure CLI equivalent - Save as assign-nodinite-data-plane-roles.sh:

#!/bin/bash

# Assign all required data plane roles for Nodinite Monitoring Agents
# Usage: ./assign-nodinite-data-plane-roles.sh <app-registration-object-id> <subscription-id> <resource-group>

APP_OBJECT_ID=$1
SUBSCRIPTION_ID=$2
RESOURCE_GROUP=$3

if [ -z "$APP_OBJECT_ID" ] || [ -z "$SUBSCRIPTION_ID" ] || [ -z "$RESOURCE_GROUP" ]; then
    echo "Usage: $0 <app-registration-object-id> <subscription-id> <resource-group>"
    exit 1
fi

# Login if needed
az account show &> /dev/null || az login

# Set subscription
az account set --subscription "$SUBSCRIPTION_ID"

echo "Starting data plane role assignments for App Registration: $APP_OBJECT_ID"

# Storage Accounts
echo -e "\nProcessing Storage Accounts..."
STORAGE_ACCOUNTS=$(az storage account list --resource-group "$RESOURCE_GROUP" --query "[].id" -o tsv)

for SA_ID in $STORAGE_ACCOUNTS; do
    SA_NAME=$(az storage account show --ids "$SA_ID" --query "name" -o tsv)
    echo "  - Storage Account: $SA_NAME"
    
    # Storage Blob Data Contributor
    az role assignment create \
        --assignee "$APP_OBJECT_ID" \
        --role "Storage Blob Data Contributor" \
        --scope "$SA_ID" &> /dev/null && echo "    ✓ Assigned: Storage Blob Data Contributor" || echo "    ℹ Already assigned or failed"
    
    # Storage Queue Data Contributor
    az role assignment create \
        --assignee "$APP_OBJECT_ID" \
        --role "Storage Queue Data Contributor" \
        --scope "$SA_ID" &> /dev/null && echo "    ✓ Assigned: Storage Queue Data Contributor" || echo "    ℹ Already assigned or failed"
    
    # Storage File Data SMB Share Contributor
    az role assignment create \
        --assignee "$APP_OBJECT_ID" \
        --role "Storage File Data SMB Share Contributor" \
        --scope "$SA_ID" &> /dev/null && echo "    ✓ Assigned: Storage File Data SMB Share Contributor" || echo "    ℹ Already assigned or failed"
done

# Event Hub Namespaces
echo -e "\nProcessing Event Hub Namespaces..."
EH_NAMESPACES=$(az eventhubs namespace list --resource-group "$RESOURCE_GROUP" --query "[].id" -o tsv)

for EHNS_ID in $EH_NAMESPACES; do
    EHNS_NAME=$(az eventhubs namespace show --ids "$EHNS_ID" --query "name" -o tsv)
    echo "  - Event Hub Namespace: $EHNS_NAME"
    
    # Azure Event Hubs Data Sender
    az role assignment create \
        --assignee "$APP_OBJECT_ID" \
        --role "Azure Event Hubs Data Sender" \
        --scope "$EHNS_ID" &> /dev/null && echo "    ✓ Assigned: Azure Event Hubs Data Sender" || echo "    ℹ Already assigned or failed"
    
    # Azure Event Hubs Data Receiver
    az role assignment create \
        --assignee "$APP_OBJECT_ID" \
        --role "Azure Event Hubs Data Receiver" \
        --scope "$EHNS_ID" &> /dev/null && echo "    ✓ Assigned: Azure Event Hubs Data Receiver" || echo "    ℹ Already assigned or failed"
done

# Service Bus Namespaces
echo -e "\nProcessing Service Bus Namespaces..."
SB_NAMESPACES=$(az servicebus namespace list --resource-group "$RESOURCE_GROUP" --query "[].id" -o tsv)

for SBNS_ID in $SB_NAMESPACES; do
    SBNS_NAME=$(az servicebus namespace show --ids "$SBNS_ID" --query "name" -o tsv)
    echo "  - Service Bus Namespace: $SBNS_NAME"
    
    # Azure Service Bus Data Owner
    az role assignment create \
        --assignee "$APP_OBJECT_ID" \
        --role "Azure Service Bus Data Owner" \
        --scope "$SBNS_ID" &> /dev/null && echo "    ✓ Assigned: Azure Service Bus Data Owner" || echo "    ℹ Already assigned or failed"
done

echo -e "\n✓ Data plane role assignments completed!"
echo "Note: Role assignments may take up to 5 minutes to propagate."

Option 2: Manual Assignment by Resource Type

Storage Data Plane Roles:

  • Storage Blob Data Contributor - Read, write, and delete Azure Storage blobs

    • Required by: Azure Monitoring Agent, Logic Apps Agent, Pickup Service
  • Storage Queue Data Reader - Read queue messages

    • Required by: Azure Monitoring Agent (for queue monitoring)
  • Storage Queue Data Contributor - Read, write, and delete queue messages

    • Required by: Azure Monitoring Agent (if managing queues)
  • Storage File Data SMB Share Contributor - Read, write, and delete files in Azure file shares

    • Required by: Azure Monitoring Agent

Event Hubs Data Plane Roles:

  • Azure Event Hubs Data Sender - Send messages to Event Hubs

    • Required by: Azure Monitoring Agent, Pickup Service
  • Azure Event Hubs Data Receiver - Receive messages from Event Hubs

    • Required by: Logic Apps Agent, Pickup Service

Service Bus Data Plane Roles:

  • Azure Service Bus Data Owner - Send, receive, and manage Service Bus messages
    • Required by: Azure Service Bus Monitoring

How to assign data plane roles:

# Example: Assign Storage Blob Data Contributor
New-AzRoleAssignment `
    -ObjectId "YOUR-APP-REGISTRATION-OBJECT-ID" `
    -RoleDefinitionName "Storage Blob Data Contributor" `
    -Scope "/subscriptions/YOUR-SUBSCRIPTION-ID/resourceGroups/YOUR-RESOURCE-GROUP/providers/Microsoft.Storage/storageAccounts/YOUR-STORAGE-ACCOUNT"
# Example: Assign Azure Event Hubs Data Sender
az role assignment create \
    --assignee YOUR-APP-REGISTRATION-OBJECT-ID \
    --role "Azure Event Hubs Data Sender" \
    --scope "/subscriptions/YOUR-SUBSCRIPTION-ID/resourceGroups/YOUR-RESOURCE-GROUP/providers/Microsoft.EventHub/namespaces/YOUR-EVENTHUB-NAMESPACE"

Application Insights Delegated Permission

For Application Insights queries, add the delegated permission (not Application permission):

  1. In API permissionsAdd a permission
  2. Search for and select Application Insights API
  3. Select Delegated permissions
  4. Add Data.Read
  5. Click Grant admin consent

Verifying the Deployment

# List the custom role
Get-AzRoleDefinition -Name "Nodinite Monitoring"

# View role assignments for your App Registration
Get-AzRoleAssignment -ObjectId "YOUR-APP-REGISTRATION-OBJECT-ID"
# List the custom role
az role definition list --name "Nodinite Monitoring" --output table

# View role assignments for your App Registration
az role assignment list --assignee YOUR-APP-REGISTRATION-OBJECT-ID --output table

Updating the Custom Role

To update permissions, modify the ARM template and redeploy:

# Redeploy with the same name to update
New-AzSubscriptionDeployment `
    -Name "NodiniteCustomRole" `
    -Location "westeurope" `
    -TemplateFile ".\nodinite-custom-role.json" `
    -Mode Incremental `
    -Verbose

Note

Role updates may take up to 5 minutes to propagate. Restart affected Nodinite agents after updating role assignments.

Removing the Custom Role

Warning

Ensure the role is not assigned to any principals before deletion.

# Get the role
$role = Get-AzRoleDefinition -Name "Nodinite Monitoring"

# Remove all assignments first
Get-AzRoleAssignment -RoleDefinitionId $role.Id | Remove-AzRoleAssignment

# Delete the role definition
Remove-AzRoleDefinition -Id $role.Id -Force
# Remove all assignments
az role assignment delete --role "Nodinite Monitoring"

# Delete the role definition
az role definition delete --name "Nodinite Monitoring"

Customizing for Specific Agents

The template includes permissions for all Nodinite Azure agents. To create agent-specific roles:

  1. Copy the template
  2. Remove unnecessary Actions from the permissions.actions array
  3. Rename the role (e.g., "Nodinite Logic Apps Only")
  4. Deploy as a separate role

Reference the detailed permissions table to identify which Actions each agent requires.

Troubleshooting

"Insufficient privileges to complete the operation"

Ensure you have Owner or User Access Administrator role at the subscription level.

"Role already exists"

The role name must be unique within the subscription. Either:

  • Change the roleName parameter
  • Delete the existing role first
  • Update the existing role by redeploying

"Invalid assignableScopes"

The template is scoped to the subscription where deployed. To scope to specific resource groups, modify the assignableScopes array:

"assignableScopes": [
  "[resourceGroup().id]"
]

And deploy at resource group level instead of subscription level.

Next Steps