Skip to main content

Connect Azure Subscription

Connect your Azure subscription using federated credentials for secure, secret-free authentication.

Prerequisites

  • Azure subscription with active billing
  • Owner or User Access Administrator role on the subscription
  • Azure CLI installed and configured

Federated credentials allow NightOps to authenticate without storing client secrets.

Step 1: Create App Registration

# Create the app registration
az ad app create \
--display-name "NightOps" \
--sign-in-audience AzureADMyOrg

# Get the app ID
APP_ID=$(az ad app list --display-name "NightOps" --query "[0].appId" -o tsv)
echo "App ID: $APP_ID"

Step 2: Create Service Principal

az ad sp create --id $APP_ID

# Get the service principal object ID
SP_OBJECT_ID=$(az ad sp show --id $APP_ID --query "id" -o tsv)
echo "Service Principal Object ID: $SP_OBJECT_ID"

Step 3: Add Federated Credentials

# Get your NightOps org ID from the dashboard
NIGHTOPS_ORG_ID="your-org-id-from-dashboard"

# Create federated credential
az ad app federated-credential create \
--id $APP_ID \
--parameters '{
"name": "nightops-federation",
"issuer": "https://auth.nightops.io",
"subject": "org:'$NIGHTOPS_ORG_ID'",
"audiences": ["api://AzureADTokenExchange"]
}'

Step 4: Assign RBAC Role

SUBSCRIPTION_ID=$(az account show --query "id" -o tsv)

# Create custom role for NightOps
az role definition create --role-definition '{
"Name": "NightOps Resource Manager",
"Description": "Allows NightOps to manage compute resources for cost optimization",
"Actions": [
"Microsoft.Compute/virtualMachines/read",
"Microsoft.Compute/virtualMachines/start/action",
"Microsoft.Compute/virtualMachines/deallocate/action",
"Microsoft.Compute/virtualMachines/powerOff/action",
"Microsoft.Compute/virtualMachineScaleSets/read",
"Microsoft.Compute/virtualMachineScaleSets/write",
"Microsoft.Sql/servers/databases/read",
"Microsoft.Sql/servers/databases/pause/action",
"Microsoft.Sql/servers/databases/resume/action",
"Microsoft.ContainerService/managedClusters/read",
"Microsoft.ContainerService/managedClusters/agentPools/read",
"Microsoft.ContainerService/managedClusters/agentPools/write",
"Microsoft.Resources/subscriptions/resourceGroups/read"
],
"NotActions": [],
"AssignableScopes": ["/subscriptions/'$SUBSCRIPTION_ID'"]
}'

# Assign the role to the service principal
az role assignment create \
--assignee $SP_OBJECT_ID \
--role "NightOps Resource Manager" \
--scope "/subscriptions/$SUBSCRIPTION_ID"

Step 5: Get Configuration Values

# Get tenant ID
TENANT_ID=$(az account show --query "tenantId" -o tsv)

echo "Tenant ID: $TENANT_ID"
echo "Subscription ID: $SUBSCRIPTION_ID"
echo "Client ID: $APP_ID"

Step 6: Configure in NightOps

  1. Go to Providers → Add Provider → Azure
  2. Enter your Tenant ID
  3. Enter your Subscription ID
  4. Enter your Client ID (App ID)
  5. Select which Regions to scan
  6. Click Test Connection
  7. Click Save Provider

For environments that don't support federated credentials:

Create Client Secret

# Create secret (valid for 1 year)
az ad app credential reset \
--id $APP_ID \
--years 1

# Output includes the client secret - save it securely

Configure in NightOps

  1. Go to Providers → Add Provider → Azure
  2. Select Client Secret
  3. Enter the client secret
  4. Click Save Provider
warning

Client secrets are less secure than federated credentials. Secrets can be leaked, require rotation, and are harder to audit. Use federated credentials when possible.


Multi-Region Support

NightOps can manage resources across multiple Azure regions.

When adding the provider, select which regions to scan:

Regions: eastus, westus2, westeurope

Multi-Subscription Setup

For organizations with multiple Azure subscriptions:

Option A: Separate Providers

Create an app registration and role assignment in each subscription. Add each as a separate provider in NightOps.

Option B: Management Group Access

Assign the role at the management group level to cover multiple subscriptions:

MANAGEMENT_GROUP_ID="your-management-group-id"

az role assignment create \
--assignee $SP_OBJECT_ID \
--role "NightOps Resource Manager" \
--scope "/providers/Microsoft.Management/managementGroups/$MANAGEMENT_GROUP_ID"

Terraform Module

# nightops.tf

variable "subscription_id" {
type = string
}

variable "nightops_org_id" {
description = "NightOps organization ID from dashboard"
type = string
}

data "azurerm_subscription" "current" {}

data "azuread_client_config" "current" {}

# App Registration
resource "azuread_application" "nightops" {
display_name = "NightOps"
owners = [data.azuread_client_config.current.object_id]
}

# Service Principal
resource "azuread_service_principal" "nightops" {
client_id = azuread_application.nightops.client_id
owners = [data.azuread_client_config.current.object_id]
}

# Federated Credential
resource "azuread_application_federated_identity_credential" "nightops" {
application_id = azuread_application.nightops.id
display_name = "nightops-federation"
issuer = "https://auth.nightops.io"
subject = "org:${var.nightops_org_id}"
audiences = ["api://AzureADTokenExchange"]
}

# Custom Role Definition
resource "azurerm_role_definition" "nightops" {
name = "NightOps Resource Manager"
scope = data.azurerm_subscription.current.id
description = "Allows NightOps to manage compute resources"

permissions {
actions = [
"Microsoft.Compute/virtualMachines/read",
"Microsoft.Compute/virtualMachines/start/action",
"Microsoft.Compute/virtualMachines/deallocate/action",
"Microsoft.Compute/virtualMachines/powerOff/action",
"Microsoft.Compute/virtualMachineScaleSets/read",
"Microsoft.Compute/virtualMachineScaleSets/write",
"Microsoft.Sql/servers/databases/read",
"Microsoft.Sql/servers/databases/pause/action",
"Microsoft.Sql/servers/databases/resume/action",
"Microsoft.ContainerService/managedClusters/read",
"Microsoft.ContainerService/managedClusters/agentPools/read",
"Microsoft.ContainerService/managedClusters/agentPools/write",
"Microsoft.Resources/subscriptions/resourceGroups/read"
]
}

assignable_scopes = [data.azurerm_subscription.current.id]
}

# Role Assignment
resource "azurerm_role_assignment" "nightops" {
scope = data.azurerm_subscription.current.id
role_definition_id = azurerm_role_definition.nightops.role_definition_resource_id
principal_id = azuread_service_principal.nightops.object_id
}

output "tenant_id" {
value = data.azuread_client_config.current.tenant_id
}

output "subscription_id" {
value = data.azurerm_subscription.current.subscription_id
}

output "client_id" {
value = azuread_application.nightops.client_id
}

ARM Template

{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"nightopsOrgId": {
"type": "string",
"metadata": {
"description": "NightOps organization ID from dashboard"
}
}
},
"variables": {
"roleDefinitionId": "[guid(subscription().id, 'NightOps Resource Manager')]"
},
"resources": [
{
"type": "Microsoft.Authorization/roleDefinitions",
"apiVersion": "2022-04-01",
"name": "[variables('roleDefinitionId')]",
"properties": {
"roleName": "NightOps Resource Manager",
"description": "Allows NightOps to manage compute resources",
"type": "CustomRole",
"permissions": [
{
"actions": [
"Microsoft.Compute/virtualMachines/read",
"Microsoft.Compute/virtualMachines/start/action",
"Microsoft.Compute/virtualMachines/deallocate/action",
"Microsoft.Compute/virtualMachineScaleSets/read",
"Microsoft.Compute/virtualMachineScaleSets/write",
"Microsoft.Sql/servers/databases/read",
"Microsoft.Sql/servers/databases/pause/action",
"Microsoft.Sql/servers/databases/resume/action",
"Microsoft.ContainerService/managedClusters/read",
"Microsoft.ContainerService/managedClusters/agentPools/read",
"Microsoft.ContainerService/managedClusters/agentPools/write"
]
}
],
"assignableScopes": [
"[subscription().id]"
]
}
}
]
}

Deploy:

az deployment sub create \
--location eastus \
--template-file nightops-role.json \
--parameters nightopsOrgId="your-org-id"

Troubleshooting

"Unauthorized" on Test Connection

  1. Verify the service principal has the role assigned
  2. Check the federated credential configuration
  3. Ensure the subscription ID is correct
# Check role assignments
az role assignment list \
--assignee $APP_ID \
--scope "/subscriptions/$SUBSCRIPTION_ID"

"Invalid tenant" error

  1. Verify the tenant ID is correct
  2. Check the app registration is in the correct tenant
  3. Ensure federated credential issuer matches
# Verify app registration
az ad app show --id $APP_ID

VMs not discovered

Ensure the role includes Microsoft.Compute/virtualMachines/read:

az role definition list \
--name "NightOps Resource Manager" \
--query "[0].permissions[0].actions"

Security Best Practices

  1. Use Federated Credentials — No secrets to rotate or leak
  2. Custom Role — Use least-privilege custom role instead of built-in roles
  3. Audit Logging — Enable Azure Activity Log for the service principal
  4. Conditional Access — Restrict authentication based on conditions

Resource Group Scoping

Limit NightOps to specific resource groups:

az role assignment create \
--assignee $SP_OBJECT_ID \
--role "NightOps Resource Manager" \
--scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/staging-rg"

Tag-Based Filtering

While Azure RBAC doesn't support tag-based conditions natively, NightOps respects resource tags:

  • Only resources tagged nightops-managed: true will be managed
  • Resources tagged nightops-exclude: true will be skipped

Next Steps