Script to add Azure AD gallery apps

The Microsoft Graph endpoint for instantiating Azure AD gallery apps is quite new, and I just needed them in order to instantiate 20+ apps based on the GitHub Enterprise Organization template. Here is my code, if someone has use for it 🙂

I have left out “how to get an access token”. The permissions required is Application.ReadWrite.All.

Continue reading “Script to add Azure AD gallery apps”

Using Azure AD External Identities to power self service sign-in for a web site

With Azure AD External Identities, Microsoft is bringing some of the Azure AD B2C features to “regular” Azure AD, and it is now generally available (GA). With these features, you can add public self service sign up-baed authentication flows to your services, using your existing Azure AD.

Continue reading “Using Azure AD External Identities to power self service sign-in for a web site”

Batch creating privileged access groups in Azure AD

Quick blogpost today, showing how to batch create privileged access groups for the Privileged Identity Management feature in Azure AD. The endpoint used is not currently documented in the Graph documentation.

First thing you need to do is get yourself an access token. Follow my guide for this. This script will use a hardcoded variable for the token.

# Get your access token somehow, not in scope for this example
$accesstoken = "eyJ0eXAiOiJKVGtHUniJSUzI1NiIs--- snip ---wFzrJUYkOm37KY27WJvQ"

# Groups to create
$groups = "test12345","test123468","test9577","test304934089"


# No need to edit below this line

# Build header value
$headers = @{Authorization = "Bearer $accesstoken"}
$groups | Foreach {
    # Check if group already created
    $matching = Invoke-RestMethod "https://graph.microsoft.com/beta/groups?`$filter=displayName eq '$($_)'" -Headers $headers 

    $id = $null
    # Create if no group was found
    if(!$matching.value) {
        Write-Verbose "Creating group: $($_)" -Verbose
        $body = @{
            displayName = $_
            isAssignableToRole = $true
            mailEnabled = $false
            mailNickname = $_
            securityEnabled = $true
        } | ConvertTo-Json

        $creation = Invoke-RestMethod "https://graph.microsoft.com/beta/groups" -Headers $headers -Method Post -Body $body -ContentType "application/json"
        Write-Verbose "Group '$($_)' created with object id $($creation.id)" -Verbose
        $id = $creation.id
    } else {
        Write-Verbose "Group '$($_)' already created" -Verbose
        $id = $matching.value[0].id
    }

 
    # Check for PIM registration of group
    $pimmatching = Invoke-RestMethod "https://graph.microsoft.com/beta/privilegedAccess/aadGroups/resources?`$filter=externalId eq '$id'" -Headers $headers

    # Register group in PIM, if not already registered
    if(!$pimmatching.value) {
        Write-Verbose "Registering group $($_) ($id) in PIM" -Verbose
        $body = @{
            externalId = $id
        } | ConvertTo-Json

        $registration = Invoke-RestMethod "https://graph.microsoft.com/beta/privilegedAccess/aadGroups/resources/register" -Headers $headers -Method Post -Body $body -ContentType "application/json"
    } else {
        Write-Verbose "Group $($_) already registered in PIM" -Verbose
    }
}

Finding synced accounts with Azure AD privileges

In a recent blogpost, Protecting Microsoft 365 from on-premises attacks, it is recommended to not federate (which I have been recommending for years) and not to synchronize accounts with privileged access to Azure AD and Microsoft 365. This post contains a few script to help you determine synchronized accounts with privileged access to Azure AD.

Continue reading “Finding synced accounts with Azure AD privileges”

Accessing the backend Azure AD APIs behind portal.azure.com

There are many features that are currently “UI only” with Azure AD, managed through the Azure Portal. However, some times it can be useful to being able to access these things through scripts, such as for auditing or whatever.

Behind the Azure Portal, there are several APIs that are public facing, but undocumented and not meant for being used directly. Follow this guide with caution, as there is things that can go wrong if you start posting or patching to these APIs.

Continue reading “Accessing the backend Azure AD APIs behind portal.azure.com”

Terraform – Specifying parameters to azurerm_resource_group_template_deployment

Here is a quick example on how to provide parameters to azurerm_resource_group_template_deployment, as the documentation is currently very poor.

Here is a quick main.tf:

variable "location" {
  default     = "westeurope"
  type        = string
  description = "The Azure location where all resources in this example should be created"
}

provider "azurerm" {
    features {} 
}

data "azurerm_client_config" "current" {}
data "azurerm_subscription" "current" {}

resource "azurerm_resource_group" "rg" {
    name     = "testResourceGroup1"
    location = var.location
}


resource "azurerm_resource_group_template_deployment" "example" {
    name                = "testLogicApp1"
    resource_group_name = azurerm_resource_group.rg.name
    deployment_mode     = "Complete"
    template_content    = file("logicapp.json")

    parameters_content =  jsonencode({
        azure_location                    = {value = azurerm_resource_group.rg.location}
        name                              = {value = "testLogicApp1"}
        azure_function_code               = {value = "testtesttest"}
    })
}

As you can see, the parameters_content is converted from a Terraform object to JSON using jsonencode, and all values must be provided like you would define parameters using az deployment, with “value=xx”.

And this is my ARM template for the LogicApp:

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "azure_location": {
            "defaultValue": "westeurope",
            "type": "string"
        },
        "name": {
            "type": "String"
        },
        "azure_function_code": {
            "type": "String"
        }
    },
    "variables": {
    },
    "outputs": {
        "principalId": {
            "type": "string",
            "value": "[reference(concat(resourceId('Microsoft.Logic/workflows', parameters('name')), '/providers/Microsoft.ManagedIdentity/Identities/default'), '2018-11-30').principalId]"
        },
        "tenantid": {
            "type": "string",
            "value": "[reference(concat(resourceId('Microsoft.Logic/workflows', parameters('name')), '/providers/Microsoft.ManagedIdentity/Identities/default'), '2018-11-30').tenantid]"
        },
        "resourceId": {
            "type": "string",
            "value": "[resourceId('Microsoft.Logic/workflows', parameters('name'))]"
        }
    },
    "resources": [
        {
            "type": "Microsoft.Logic/workflows",
            "apiVersion": "2017-07-01",
            "name": "[parameters('name')]",
            "location": "[parameters('azure_location')]",
            "identity": {
                "type": "SystemAssigned"
            },
            "dependsOn": [
            ],
            "resources": [],
            "properties": {
                "state": "Enabled",
                "definition": {
                    "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
                    "contentVersion": "1.0.0.0",
                    "parameters": {
                        "azurefunctioncode": {
                            "defaultValue": "[parameters('azure_function_code')]",
                            "type": "String"
                        }
                    },
                    "triggers": {
                        "Recurrence": {
                            "recurrence": {
                                "frequency": "Day",
                                "interval": 1,
                                "schedule": {
                                    "hours": [
                                        "2"
                                    ],
                                    "minutes": [
                                        5
                                    ]
                                }
                            },
                            "type": "Recurrence"
                        }
                    },
                    "actions": {
                        
                    },
                    "outputs": {}
                }
            }
        }
    ]
}

A look behind the Azure AD “Permission classifications” preview

For a long time, Azure AD has been criticized for having a too liberal approach to user consents by default, with users being able to delegate things like “Mail.ReadWrite” allowing apps to send and receive emails, as well as reading any existing emails, and “User.ReadBasic.All” allowing your users to consent to third party applications reading ALL of your users basic profiles. Combine this with the “offline_access” scope, where the 3rd party apps can essentially retain access after the user has logged out of the app, this soon becomes a bit of a nightmare.

Continue reading “A look behind the Azure AD “Permission classifications” preview”