Greetings to my fellow Technology Advocates and Specialists.
In this Session, I will demonstrate How to Automate App Management with AAD Graph and DevOps.
I had the Privilege to talk on this topic in ONE Azure Communities:-
NAME OF THE AZURE COMMUNITY | TYPE OF SPEAKER SESSION |
---|---|
Azure Back to School 2023 | Virtual |
EVENT ANNOUNCEMENTS:- |
---|
THIS IS HOW IT LOOKS:- |
---|
AUTOMATION OBJECTIVES:- |
---|
# | TOPICS |
---|---|
1. | Validate if the Resource Group and the Key Vault residing in it exists. |
2. | Validate if the App Registration already exists. If No, App Registration will be created. |
3. | Secret will be generated and stored in Key Vault. |
4. | Set Redirect URI and Enable ID Token. |
5. | Set Token Configuration - Optional Claims. |
6. | Set Token Configuration - Groups Claim. |
7. | Set Microsoft Graph API Permissions. |
8. | Create App Roles. |
9. | Set App Owners. |
IMPORTANT NOTE:- |
---|
The YAML Pipeline is tested on WINDOWS BUILD AGENT Only!!! |
REQUIREMENTS:- |
---|
- Azure Subscription.
- Azure DevOps Organisation and Project.
- Service Principal with Required RBAC ( Contributor) applied on Subscription or Resource Group(s).
- Azure Resource Manager Service Connection in Azure DevOps.
- Azure Tenant by type "Azure Active Directory (AAD)" with one of the Licenses: a.) Azure AD Premium P2, OR b.) Enterprise Mobility + Security (EMS) E5 license.
- "Cloud Application Administrator" is required to create and configure App Registration.
- "Global Administrator" PIM role is required to Grant Admin Consent to Microsoft Graph API Rights.
- A test Azure Active Directory (AAD) user to add as an owner of the App.
HOW DOES MY CODE PLACEHOLDER LOOKS LIKE:- |
---|
PIPELINE CODE SNIPPET:- |
---|
AZURE DEVOPS YAML PIPELINE (azure-pipelines-app-registration-setup-config-v1.1.yml):- |
---|
trigger:
none
######################
#DECLARE PARAMETERS:-
######################
parameters:
- name: SubscriptionID
displayName: Subscription ID details follows below:-
type: string
default: 210e66cb-55cf-424e-8daa-6cad804ab604
values:
- 210e66cb-55cf-424e-8daa-6cad804ab604
- name: RGName
displayName: Please Provide the Resource Group Name:-
type: object
default:
- name: KVName
displayName: Please Provide the Keyvault Name:-
type: object
default:
- name: SPIName
displayName: Please Provide the Service Principal Name:-
type: object
default:
- name: RedirectURI
displayName: Please Provide Redirect URL:-
type: object
default: https://contoso.azurewebsites.net/.auth/login/aad/callback
- name: Username
displayName: Please Provide the Username:-
type: object
default: U1@mitra008.onmicrosoft.com
######################
#DECLARE VARIABLES:-
######################
variables:
ServiceConnection: amcloud-cicd-service-connection
BuildAgent: windows-latest
Claims: "optionalclaims.json"
MSGraphAPI: 00000003-0000-0000-c000-000000000000
MSGraphUsrRead: e1fe6dd8-ba31-4d61-89e7-88639da4683d
MSGraphEmail: 64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0
MSGraphProfile: 14dad69e-099b-42c9-810b-d002981feec1
MSGraphUsrReadAll: a154be20-db9c-4678-8ab7-66f6cc099a59
MSGraphGrpReadAll: 5f8c59db-677d-491f-a6b8-5f174b11ec1d
AppRoles: "approles.json"
#########################
# Declare Build Agents:-
#########################
pool:
vmImage: $(BuildAgent)
###################
# Declare Stages:-
###################
stages:
- stage: App_Registration_Setup_Configure
jobs:
- job: App_Registration_Setup_Configure
displayName: App Registration Setup and Configure
steps:
- task: AzureCLI@2
displayName: Validate and Create App
inputs:
azureSubscription: $(ServiceConnection)
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
az --version
az account set --subscription ${{ parameters.SubscriptionID }}
az account show
$i = az ad sp list --display-name ${{ parameters.SPIName }} --query [].appDisplayName -o tsv
if ($i -ne "${{ parameters.SPIName }}") {
$j = az group exists -n ${{ parameters.RGName }}
if ($j -eq "true") {
$k = az keyvault list --resource-group ${{ parameters.RGName }} --query [].name -o tsv
if ($k -eq "${{ parameters.KVName }}") {
$spipasswd = az ad sp create-for-rbac -n ${{ parameters.SPIName }} --query "password" -o tsv
az keyvault secret set --name ${{ parameters.SPIName }} --vault-name ${{ parameters.KVName }} --value $spipasswd
echo "##################################################################"
echo "Service Principal ${{ parameters.SPIName }} created successfully and the Secret Stored inside Key Vault ${{ parameters.KVName }} in the Resource Group ${{ parameters.RGName }}!!!"
echo "##################################################################"
}
else {
echo "##################################################################"
echo "Key Vault ${{ parameters.KVName }} DOES NOT EXISTS in Resource Group ${{ parameters.RGName }}!!!"
echo "##################################################################"
exit 1
}
}
else {
echo "##################################################################"
echo "Resource Group ${{ parameters.RGName }} DOES NOT EXISTS!!!"
echo "##################################################################"
exit 1
}
}
else {
echo "##################################################################"
echo "Service Principal ${{ parameters.SPIName }} EXISTS and hence Cannot Proceed with Deployment!!!"
echo "##################################################################"
exit 1
}
- task: AzureCLI@2
displayName: Set Redirect URI & ID Token
inputs:
azureSubscription: $(ServiceConnection)
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
$appid = az ad app list --display-name ${{ parameters.SPIName }} --query [].appId -o tsv
az ad app update --id $appid --web-redirect-uris ${{ parameters.RedirectURI }} --enable-id-token-issuance true
echo "##################################################################"
echo "Redirect URL is set and ID Token has been enabled successfully!!!"
echo "##################################################################"
- task: AzureCLI@2
displayName: Token Config - Optional Claims
inputs:
azureSubscription: $(ServiceConnection)
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
$appid = az ad app list --display-name ${{ parameters.SPIName }} --query [].appId -o tsv
az ad app update --id $appid --optional-claims $(System.DefaultWorkingDirectory)/App-Registration-Setup-Config/$(Claims)
echo "##################################################################"
echo "Token Configuration - Optional Claims configured successfully!!!"
echo "##################################################################"
- task: AzureCLI@2
displayName: Token Config - Groups Claims
inputs:
azureSubscription: $(ServiceConnection)
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
$appid = az ad app list --display-name ${{ parameters.SPIName }} --query [].appId -o tsv
az ad app update --id $appid --set groupMembershipClaims=SecurityGroup
echo "##################################################################"
echo "Token Configuration - Groups Claim has been enabled successfully!!!"
echo "##################################################################"
- task: AzureCLI@2
displayName: MS Graph API Permissions
inputs:
azureSubscription: $(ServiceConnection)
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
$appid = az ad app list --display-name ${{ parameters.SPIName }} --query [].appId -o tsv
az ad app permission add --id $appid --api $(MSGraphAPI) --api-permissions $(MSGraphUsrRead)=Scope
az ad app permission add --id $appid --api $(MSGraphAPI) --api-permissions $(MSGraphEmail)=Scope
az ad app permission add --id $appid --api $(MSGraphAPI) --api-permissions $(MSGraphProfile)=Scope
az ad app permission add --id $appid --api $(MSGraphAPI) --api-permissions $(MSGraphUsrReadAll)=Scope
az ad app permission add --id $appid --api $(MSGraphAPI) --api-permissions $(MSGraphGrpReadAll)=Scope
echo "##################################################################"
echo "MS Graph API Permissions has been configured successfully!!!"
echo "##################################################################"
- task: AzureCLI@2
displayName: App Roles
inputs:
azureSubscription: $(ServiceConnection)
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
$appid = az ad app list --display-name ${{ parameters.SPIName }} --query [].appId -o tsv
az ad app update --id $appid --app-roles $(System.DefaultWorkingDirectory)/App-Registration-Setup-Config/$(AppRoles)
echo "##################################################################"
echo "App Roles Created Successfully!!!"
echo "##################################################################"
- task: AzureCLI@2
displayName: Set App Owner
inputs:
azureSubscription: $(ServiceConnection)
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
$appid = az ad app list --display-name ${{ parameters.SPIName }} --query [].appId -o tsv
$uid = az ad user show --id ${{ parameters.Username }} --query "id" -o tsv
az ad app owner add --id $appid --owner-object-id $uid
echo "##################################################################"
echo "Owner ${{ parameters.Username }} added successfully!!!"
echo "##################################################################"
Now, let me explain each part of YAML Pipeline for better understanding.
PART #1:- |
---|
BELOW FOLLOWS PIPELINE RUNTIME VARIABLES CODE SNIPPET:- |
---|
######################
#DECLARE PARAMETERS:-
######################
parameters:
- name: SubscriptionID
displayName: Subscription ID details follows below:-
type: string
default: 210e66cb-55cf-424e-8daa-6cad804ab604
values:
- 210e66cb-55cf-424e-8daa-6cad804ab604
- name: RGName
displayName: Please Provide the Resource Group Name:-
type: object
default:
- name: KVName
displayName: Please Provide the Keyvault Name:-
type: object
default:
- name: SPIName
displayName: Please Provide the Service Principal Name:-
type: object
default:
- name: RedirectURI
displayName: Please Provide Redirect URL:-
type: object
default: https://contoso.azurewebsites.net/.auth/login/aad/callback
- name: Username
displayName: Please Provide the Username:-
type: object
default: U1@mitra008.onmicrosoft.com
PART #2:- |
---|
BELOW FOLLOWS PIPELINE VARIABLES CODE SNIPPET:- |
---|
######################
#DECLARE VARIABLES:-
######################
variables:
ServiceConnection: amcloud-cicd-service-connection
BuildAgent: windows-latest
Claims: "optionalclaims.json"
MSGraphAPI: 00000003-0000-0000-c000-000000000000
MSGraphUsrRead: e1fe6dd8-ba31-4d61-89e7-88639da4683d
MSGraphEmail: 64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0
MSGraphProfile: 14dad69e-099b-42c9-810b-d002981feec1
MSGraphUsrReadAll: a154be20-db9c-4678-8ab7-66f6cc099a59
MSGraphGrpReadAll: 5f8c59db-677d-491f-a6b8-5f174b11ec1d
AppRoles: "approles.json"
NOTE:- |
---|
Please change the values of the variables accordingly. |
The entire YAML pipeline is build using Runtime Parameters and Variables. No Values are Hardcoded. |
For all Permissions and Ids, please refer the MS Documentation link |
PART #3:- |
---|
This is a Single Stage Pipeline - App_Registration_Setup_Configure (with 7 Pipeline Tasks):- |
---|
PIPELINE TASK #1:- |
---|
Validate Resource Group and Key Vault. If either one of the Azure Resource is not Available, Pipeline will fail. |
Validate App Registration. If available with same name, Pipeline will fail else App Registration will be created successfully. The generated secret will be stored in the Keyvault. |
- stage: App_Registration_Setup_Configure
jobs:
- job: App_Registration_Setup_Configure
displayName: App Registration Setup and Configure
steps:
- task: AzureCLI@2
displayName: Validate and Create App
inputs:
azureSubscription: $(ServiceConnection)
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
az --version
az account set --subscription ${{ parameters.SubscriptionID }}
az account show
$i = az ad sp list --display-name ${{ parameters.SPIName }} --query [].appDisplayName -o tsv
if ($i -ne "${{ parameters.SPIName }}") {
$j = az group exists -n ${{ parameters.RGName }}
if ($j -eq "true") {
$k = az keyvault list --resource-group ${{ parameters.RGName }} --query [].name -o tsv
if ($k -eq "${{ parameters.KVName }}") {
$spipasswd = az ad sp create-for-rbac -n ${{ parameters.SPIName }} --query "password" -o tsv
az keyvault secret set --name ${{ parameters.SPIName }} --vault-name ${{ parameters.KVName }} --value $spipasswd
echo "##################################################################"
echo "Service Principal ${{ parameters.SPIName }} created successfully and the Secret Stored inside Key Vault ${{ parameters.KVName }} in the Resource Group ${{ parameters.RGName }}!!!"
echo "##################################################################"
}
else {
echo "##################################################################"
echo "Key Vault ${{ parameters.KVName }} DOES NOT EXISTS in Resource Group ${{ parameters.RGName }}!!!"
echo "##################################################################"
exit 1
}
}
else {
echo "##################################################################"
echo "Resource Group ${{ parameters.RGName }} DOES NOT EXISTS!!!"
echo "##################################################################"
exit 1
}
}
else {
echo "##################################################################"
echo "Service Principal ${{ parameters.SPIName }} EXISTS and hence Cannot Proceed with Deployment!!!"
echo "##################################################################"
exit 1
}
PIPELINE TASK #2:- |
---|
Set Redirect URI and Enable ID Token |
For more details, please refer the MS Documentation link |
- task: AzureCLI@2
displayName: Set Redirect URI & ID Token
inputs:
azureSubscription: $(ServiceConnection)
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
$appid = az ad app list --display-name ${{ parameters.SPIName }} --query [].appId -o tsv
az ad app update --id $appid --web-redirect-uris ${{ parameters.RedirectURI }} --enable-id-token-issuance true
echo "##################################################################"
echo "Redirect URL is set and ID Token has been enabled successfully!!!"
echo "##################################################################"
PIPELINE TASK #3:- |
---|
Token Configuration: Optional Claims |
For more details, please refer the MS Documentation link |
- task: AzureCLI@2
displayName: Token Config - Optional Claims
inputs:
azureSubscription: $(ServiceConnection)
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
$appid = az ad app list --display-name ${{ parameters.SPIName }} --query [].appId -o tsv
az ad app update --id $appid --optional-claims $(System.DefaultWorkingDirectory)/App-Registration-Setup-Config/$(Claims)
echo "##################################################################"
echo "Token Configuration - Optional Claims configured successfully!!!"
echo "##################################################################"
PIPELINE TASK #4:- |
---|
Token Configuration: Groups Claims. |
For more details, please refer the MS Documentation link |
- task: AzureCLI@2
displayName: Token Config - Groups Claims
inputs:
azureSubscription: $(ServiceConnection)
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
$appid = az ad app list --display-name ${{ parameters.SPIName }} --query [].appId -o tsv
az ad app update --id $appid --set groupMembershipClaims=SecurityGroup
echo "##################################################################"
echo "Token Configuration - Groups Claim has been enabled successfully!!!"
echo "##################################################################"
CONTENTS OF JSON FILE (optionalclaims.json) USED IN PIPELINE TASK 3 and 4:- |
---|
{
"idToken": [
{
"name": "email",
"essential": false
},
{
"name": "family_name",
"essential": false
},
{
"name": "given_name",
"essential": false
},
{
"name": "onprem_sid",
"essential": false
},
{
"name": "sid",
"essential": false
},
{
"name": "upn",
"essential": false
},
{
"name": "groups",
"additionalProperties": ["dns_domain_and_sam_account_name", "emit_as_roles"]
}
],
"accessToken": [
{
"name": "email",
"essential": false
},
{
"name": "family_name",
"essential": false
},
{
"name": "given_name",
"essential": false
},
{
"name": "onprem_sid",
"essential": false
},
{
"name": "sid",
"essential": false
},
{
"name": "upn",
"essential": false
},
{
"name": "groups",
"additionalProperties": ["dns_domain_and_sam_account_name", "emit_as_roles"]
}
]
}
PIPELINE TASK #5:- |
---|
Set Microsoft Graph API Permissions. |
For all Permissions and Ids, please refer the MS Documentation link |
- task: AzureCLI@2
displayName: MS Graph API Permissions
inputs:
azureSubscription: $(ServiceConnection)
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
$appid = az ad app list --display-name ${{ parameters.SPIName }} --query [].appId -o tsv
az ad app permission add --id $appid --api $(MSGraphAPI) --api-permissions $(MSGraphUsrRead)=Scope
az ad app permission add --id $appid --api $(MSGraphAPI) --api-permissions $(MSGraphEmail)=Scope
az ad app permission add --id $appid --api $(MSGraphAPI) --api-permissions $(MSGraphProfile)=Scope
az ad app permission add --id $appid --api $(MSGraphAPI) --api-permissions $(MSGraphUsrReadAll)=Scope
az ad app permission add --id $appid --api $(MSGraphAPI) --api-permissions $(MSGraphGrpReadAll)=Scope
echo "##################################################################"
echo "MS Graph API Permissions has been configured successfully!!!"
echo "##################################################################"
PIPELINE TASK #6:- |
---|
Create App Roles. |
For more details, please refer the MS Documentation link |
- task: AzureCLI@2
displayName: App Roles
inputs:
azureSubscription: $(ServiceConnection)
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
$appid = az ad app list --display-name ${{ parameters.SPIName }} --query [].appId -o tsv
az ad app update --id $appid --app-roles $(System.DefaultWorkingDirectory)/App-Registration-Setup-Config/$(AppRoles)
echo "##################################################################"
echo "App Roles Created Successfully!!!"
echo "##################################################################"
CONTENTS OF JSON FILE (approles.json) USED IN PIPELINE TASK 6:- |
---|
[{
"allowedMemberTypes": [
"User"
],
"description": "Readers",
"displayName": "Readers",
"isEnabled": "true",
"value": "Reader"
}]
PIPELINE TASK #7:- |
---|
Set Owner to App Registration. |
For more details, please refer the MS Documentation link |
- task: AzureCLI@2
displayName: Set App Owner
inputs:
azureSubscription: $(ServiceConnection)
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
$appid = az ad app list --display-name ${{ parameters.SPIName }} --query [].appId -o tsv
$uid = az ad user show --id ${{ parameters.Username }} --query "id" -o tsv
az ad app owner add --id $appid --owner-object-id $uid
echo "##################################################################"
echo "Owner ${{ parameters.Username }} added successfully!!!"
echo "##################################################################"
NOW ITS TIME TO TEST!!!
Hope You Enjoyed the Session!!!
Stay Safe | Keep Learning | Spread Knowledge