Enable Application Insights on Azure Web App with Powershell

So its been a busy couple of months… A couple of weeks ago i ran into an interesting problem. I have a group of Azure web apps that require some monitoring to be configured and for various reasons I was not able to achieve this though the ARM template. This setup is however possible to be solved though ARM templates (ill leave some references to this below) however this was not an option in my case.

The requirement was that I need to have 10 Azure Web Apps in a resource group send their telemetry data to an Application Insights instance in the same resource group. Below I will go through the process I followed to get there and will supply the script I wrote to perform this action.

I set about to find a solution to this problem and thought to myself, surely there must be a powershell cmdlet out there that can be used to perform this task. After searching for this illusive cmdlet for a few hours I gave up. I did however found enough information to spark some ideas on how this problem could be solved. So there are 3 parts to getting this working.
Enabling the application insights on the web app through the portal does this in the back ground for you.

  1. Applications insights extension need to be enable
  2. App settings need to be updated with some settings that configures your web app to send its telemetry data to Application Insights.
  3. Restart the web app.

Enable the extension


I found that this extension is also accessible through the kudu portal of the web app but can also be enabled by the following powerhell.

$resourceName = "<name of web app resource>"
$resourceNameString = $resourceName + "/Microsoft.ApplicationInsights.AzureWebSites"

New-AzResource -ResourceType "Microsoft.Web/sites/siteextensions" -ResourceGroupName $resourceGroupName -Name $resourceNameString -ApiVersion "2018-02-01" -Force -ErrorAction Stop

Set app settings

This is a bit harder to figure out. Because of the some specifics with your web app and options of your environment you might need to taylor this to your situation. I followed the following process to determine my settings.

  1. Create a new Web App in Azure portal.
  2. Ensure that the app settings are empty
  3. Enable the Application Insights Extension in Portal and setup the settings. (See the screenshot in figure 1)
  4. Get the app settings. (See the screenshot in figure 2)
  5. Use these app settings when configuring your app settings through powerhell.
  6. Delete this webapp.
Figure 1
Figure 2

The web app I was targeting already has some app settings configured through the pipeline that deploy’s it so i needed a way to amend the app settings rather than replace it. So the following code will achieve that.

#Get instrumentation key from ENV application insights resource
$appInsightsInstrumentationKey = (Get-AzApplicationInsights -Name $appinsightsResource -ResourceGroupName $resourceGroupName).InstrumentationKey

#Set the appseting to send telemetry to common applicaiton insights.
$webAppSettings = $webApp.SiteConfig.AppSettings
$hash = @{ }
Write-Host "Clearing hash table" -ForegroundColor Green
foreach ($setting in $webAppSettings) {
  $hash[$setting.Name] = $setting.Value
}
$hash['APPINSIGHTS_INSTRUMENTATIONKEY'] = "$($appInsightsInstrumentationKey)" #its important to include the syntax around the variable eg. "$($var)"" if not supplied like this it will change the hash table's object type.
        $hash['ApplicationInsightsAgent_EXTENSION_VERSION'] = "~2"
        $hash['XDT_MicrosoftApplicationInsights_Mode'] = "recommended"
        $hash['APPINSIGHTS_PROFILERFEATURE_VERSION'] = "1.0.0"
        $hash['DiagnosticServices_EXTENSION_VERSION'] = "~3"
        $hash['APPINSIGHTS_SNAPSHOTFEATURE_VERSION'] = "1.0.0"
        $hash['SnapshotDebugger_EXTENSION_VERSION'] = "disabled"
        $hash['InstrumentationEngine_EXTENSION_VERSION'] = "disabled"
        $hash['XDT_MicrosoftApplicationInsights_BaseExtensions'] = "disabled"
#Write back app settings into web app
Write-Host "Writing back updated appsettings to app service" $resourceName -ForegroundColor Green
Set-AzWebApp -AppSettings $hash -Name $resourceName -ResourceGroupName $resourceGroupName -verbose
         

Restart the web app

And then the only thing that remains is to restart the web app. This code does exactly that.

Restart-AzWebApp -ResourceGroupName $resourceGroupName -Name $resourceName  

With all the functional bits done and working I needed to get a way to perform this to 10 web apps in the shortest amount possible I introduced some Start-Jobs cmdlets in combination with a for each statement, and some outputs for debugging and the result….

https://github.com/Dries-Venter/PowershellScripts/blob/master/configureAppInsightsParallelJob.ps1

<#
.SYNOPSIS
     Configures App services in ENV resource group to send telemitry to the Application Insights instance in the ENV RG

.DESCRIPTION
    The script retrieves the ENV app insights instrumentation key from the ENV application instance and then add app settings to all the 
    app service instances in the ENV (application environment) resource group.
    I have included Start-Job to allow for jobs to be executed in paralel if you have more than one webapp in the resourcegroup
    I have included a section with logic to check that jobs completed before main scripts closes down. And also a debug section to 
    receive the completed job and dump a output log that can be used for troubleshooting. The debug section and the "check jobs status"
    sections can be commemnted out.
.INPUTS
    $(custom_resourceGroupName) Pipeline variable for resource group of ENV 

.OUTPUTS
    Writes updated app settings to app service instances

.NOTES
    Script assumes that there are at least one ipplication insights resource and one webapp or function app in the specified 
    resource group.
    Because of our specific nameing convention it will calculate the names of the application insights based on the resource group name. if this is not 
    disired then you can include another parameter to feed it the name if the application insight resource
    
.EXAMPLE
    .\configureAppInsights.ps1 -envResourceGroupName <insert resource group name that you are targeting>

.AUTHOR
    Dries Vemter

#>

#---------------------------------------------------------[Initialisations]--------------------------------------------------------
#region parameters
[CmdletBinding()]
param (
    $envResourceGroupName
)
#regionend parameters
$ErrorActionPreference = "Stop"

#----------------------------------------------------------[Declarations]----------------------------------------------------------
#region variables
$resourceGroupName = $envResourceGroupName
$webAppNames = (Get-AzWebApp -ResourceGroupName $resourceGroupName).Name
$appinsightsResource = ($resourceGroupName.Replace("-", "")).ToLower()
$MaximumWaitMinutes = 10
$DelayBetweenCheckSeconds = 10
#endregion variables

#-----------------------------------------------------------[Functions]------------------------------------------------------------
#region Functions

#endregion Functions

#-----------------------------------------------------------[Execution]------------------------------------------------------------
#region script main

#Get instrumentation key from ENV application insights resource
$appInsightsInstrumentationKey = (Get-AzApplicationInsights -Name $appinsightsResource -ResourceGroupName $resourceGroupName).InstrumentationKey
#enumirate the app service instances within the specified resource group then amend the exisiting app setting with the additional settings below.
#Script Block to allow for parallel jobs to be executed.
$setWebAppConfig = {
    param (
        [string] $resourceGroupName,
        [string] $webAppName, 
        [string] $appInsightsInstrumentationKey
    )
    try {
        #Local Vars
        $resourceName = "$webAppName"
        $resourceNameString = $resourceName + "/Microsoft.ApplicationInsights.AzureWebSites"
        #Get the web app object
        $webApp = Get-AzwebApp -ResourceGroupName $resourceGroupName -Name $webAppName
        Write-host "Targeting Web APP: " $webApp.Name
        #Set the appseting to send telemetry to common applicaiton insights.
        $webAppSettings = $webApp.SiteConfig.AppSettings
        $hash = @{ }
        Write-Host "Clearing hash table" -ForegroundColor Green
        foreach ($setting in $webAppSettings) {
            $hash[$setting.Name] = $setting.Value
        }
        $hash['APPINSIGHTS_INSTRUMENTATIONKEY'] = "$appInsightsInstrumentationKey" #its important to include the syntax around the variable eg. "$($var)"" if not supplied like this it will change the hash table's object type.
        $hash['ApplicationInsightsAgent_EXTENSION_VERSION'] = "~2"
        $hash['XDT_MicrosoftApplicationInsights_Mode'] = "recommended"
        $hash['APPINSIGHTS_PROFILERFEATURE_VERSION'] = "1.0.0"
        $hash['DiagnosticServices_EXTENSION_VERSION'] = "~3"
        $hash['APPINSIGHTS_SNAPSHOTFEATURE_VERSION'] = "1.0.0"
        $hash['SnapshotDebugger_EXTENSION_VERSION'] = "disabled"
        $hash['InstrumentationEngine_EXTENSION_VERSION'] = "disabled"
        $hash['XDT_MicrosoftApplicationInsights_BaseExtensions'] = "disabled"
        #Write back app settings into web app
        Write-Host "Writing back updated appsettings to app service" $resourceName -ForegroundColor Green
        Set-AzWebApp -AppSettings $hash -Name $resourceName -ResourceGroupName $resourceGroupName -verbose -ErrorAction stop
        
        #Enable Application insight extention
        $resourceName = "$webAppName"
        $resourceNameString = $resourceName + "/Microsoft.ApplicationInsights.AzureWebSites"
        Write-host "Enabling Application Insights Extension on" $resourceName -ForegroundColor	DarkYellow
        Write-host "'$resourceNameString'" #debug
        Write-host "'$resourceGroupName'" #debug
        New-AzResource -ResourceType "Microsoft.Web/sites/siteextensions" -ResourceGroupName $resourceGroupName -Name $resourceNameString -ApiVersion "2018-02-01" -Force -ErrorAction Stop
        Write-host "Completed enabling app insights extention" \

        #Restart Web App
        Write-host "Restarting WebApp" -ForegroundColor Green
        Write-Host $resourceName -ForegroundColor Green
        Restart-AzWebApp -ResourceGroupName $resourceGroupName -Name $resourceName   
    }
    catch {
        Write-Host "Configuration did not complete on " $resourceName
        $ErrorMessage = $_.Exception.Message
        $FailedItem = $_.Exception.ItemName
        Write-host $ErrorMessage
        Write-host $FailedItem
    } 
}

#Initiating parallel run
ForEach ($webAppName in $webAppNames) {
    Start-Job -ScriptBlock $setWebAppConfig -ArgumentList $resourceGroupName, $webAppName, $appInsightsInstrumentationKey
}
#logic to check that jobs completed before main scripts closes down. 
$Timeout = new-timespan -Minutes $MaximumWaitMinutes
$Stopwatch = [diagnostics.stopwatch]::StartNew()    
Write-Host "Waiting for all jobs to complete - ($($DelayBetweenCheckSeconds) second(s) check / $($MaximumWaitMinutes) minute(s) max wait)";
while ($Stopwatch.elapsed -lt $Timeout) { 
    # Get job status
    $Status = Get-Job | Where-object { $_.State -eq "Running" }
    $status
    $status.count    #to be used for debugging
    Write-host "Waiting for app services configuration to complete"

    # break if all completed
    if ($status.count -eq 0) { break }
    #if (!($Status)) { break; }
    # Wait until next check
    Start-Sleep -seconds $DelayBetweenCheckSeconds
}

#debug step (not required for normal running but helps you see what is going on inside the job)
Write-host "About to get status of Jobs" -ForegroundColor green
$jobs = Get-Job
foreach ($job in $jobs) {
    Receive-job -id $job.id
}
Write-host "Done"
#endregion script main 
   

I was struggling with getting it working in the pipeline but manage to get it doing its thing by adjusting the timeouts on the jobs. Should all be working now. Let me know what you think.

Resources

The Script:
https://github.com/Dries-Venter/PowershellScripts/blob/master/configureAppInsightsParallelJob.ps1

Microsoft Docs:
https://docs.microsoft.com/en-us/azure/azure-monitor/app/powershell-azure-diagnostics

Automate the locking of Azure resource groups

When it comes to the day to day administration of Azure resources I always do it with an extra touch of caution. And for the most part my cautious approach has paid off and I can say that I have never accidentally deleted resources that should not have been deleted. I have had one near miss though and was it not for a resource lock it might have been a very uncomfortable conversation with a manager. To ensure that my teammates and I do not land in the proverbial hot soup I have created this automation script. The script enumerates all the subscriptions the running context have access to and then loop through each subscription and enumerate the resource groups in that subscription and then loops through the resource groups and set a CannotDelete lock on each resource group.

So what are resource groups?

Resource lock helps you prevent unexpected changes. There are two levels of resource locks:

  • CannotDelete. Personally, I use this type of lock the most. Basically, when you set this type of lock on a resource you are able to change and update the properties of the resource but you are unable to delete the resource without removing the lock.
  • RealOnly. This type of lock, if implemented will restrict all change to the resource. This is useful if you would like to ensure that there are no configuration drift on resources

Lock inheritance is something to keep in mind. Locks can be implemented at a subscription level, resource group level and at a resource level. When set at a resource level only the resource that has the lock set is effected. If set at a resource group level all resources in the resource group will inherit the lock. and if set at a subscription level all the resource groups and the resources they contain will inherit the lock.

Automating the locking process

The script below can be used as a runbook in an Azure Automation account and scheduled to run the script on a daily basis to go through and ensure that the resource groups have a resource lock set on them. The script uses the AZ module which means that you will need to ensure that the correct modules are running on our automation account. Follow this link to get your storage account working with the AZ Modules

<#
.SYNOPSIS
     Create resource locks for resource groups in a given subscription  

.DESCRIPTION
    Enumirates all subscriptions then filters by the qualifying criteria and then enumirates through the resource group in that
    subascription and set as canotdelete level lock on the resource group level. the script requires the user context to have
    owner RBAC rights on the subscription level to be able to set the resource group lock.


.INPUTS

.OUTPUTS

.NOTES
    In my case i have subscriptions with the "Prod" in the name of the subscription and would like to ensure that all the resource 
    groups in that "prod" subscription has a CanNotDelete lock set on the resource group.
    

.EXAMPLE
    .\lockResourceGroups.ps1

#>

#---------------------------------------------------------[Initialisations]--------------------------------------------------------
#region parameters

#regionend parameters
#$ErrorActionPreference = "Stop"

#----------------------------------------------------------[Declarations]----------------------------------------------------------
#region variables
$context = Get-AzContext
if ($null -eq $context.Account ) {
    # Get the connection "AzureRunAsConnection"
    $connectionName = "AzureRunAsConnection"
    $servicePrincipalConnection = Get-AutomationConnection -Name $connectionName
    $logonAttempt = 0
    $logonResult = $False
    while(!($connectionResult) -And ($logonAttempt -le 10))
    {
        $LogonAttempt++
        # Logging in to Azure...
        $connectionResult = Connect-AzAccount -ServicePrincipal -TenantId $servicePrincipalConnection.TenantId -ApplicationId $servicePrincipalConnection.ApplicationId -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint

Start-Sleep -Seconds 30
}

}
#Criteria used when filtering subscription names
$subscriptionNameLike = "*pass*"
#endregion variables


#-----------------------------------------------------------[Functions]------------------------------------------------------------
#region Functions
function lockResourceGroups {
    # Get array of all subscriptions
    $subscriptions = Get-AzSubscription 
    #loops through every subscription in the array
    foreach ($subscription in $subscriptions) {
        #filters subscription by name 
        if ($subscription.name -like $subscriptionNameLike) {
            Write-Host $subscription.Name
            Select-AzSubscription -SubscriptionObject $subscription
            #Construct array of resource group in the subscription
            $resourceGroups = Get-AzResourceGroup
            #loops through the resource groups and sets the resource lock on each resource group 
            foreach ($resourceGroup in $resourceGroups) {
                Write-Host "Processing: " $resourceGroup.ResourceGroupName -ForegroundColor Yellow
                #$resourceLockStatus = Get-AzResourceLock -ResourceGroupName $resourceGroup.ResourceGroupName
                Set-AzResourceLock -LockName "Delete" -LockLevel CanNotDelete -LockNotes "Contains ressources that should not be deleted" -ResourceGroupName $resourceGroup.ResourceGroupName -Force
                Write-Host $resourceGroup.ResourceGroupName "now has a lock set" -ForegroundColor Green
            }
            #Get-AzResourceGroup | Set-AzResourceLock -LockLevel CanNotDelete -LockNotes "Updated note" -LockName "ContosoSiteLock" -ResourceName "ContosoSite" -ResourceType "microsoft.web/sites" -ResourceGroupName "ResourceGroup11"
        }
    }   
}
#endregion Functions


#-----------------------------------------------------------[Execution]------------------------------------------------------------
#region script main
    #Execute the function 
    lockResourceGroups

#endregion script main

References:

https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-lock-resources

https://docs.microsoft.com/en-us/azure/automation/az-modules

Happy Automating!!

Onboarding Azure Sentinel

Today Microsoft launched its born in the cloud SIEM as a service called Azure Sentinel. And being interested in security and all things Azure I thought that I might investigate this service (it is free whilst It is in preview). All up it takes about 2 minutes to add this service and connect it to an existing log analytics workspace. Luckily I already have one that I use in my subscription to log everything too. If you don’t have a Log Analytics Workspace you will need to create one but like most Azure portal deployments you are able to do so during the deployment process.

Below is a step by step walkthrough of the steps I took to on-board this service.

Login to Azure Portal with an account that has Global Admin access. Search for Azure Sentinel then click on the service that gets listed.

Click on the “Connect workspace” button.

Select a workspace if you already have one or you have an option to create then one. Click on “Add Azure Sentinel” button

You should see the following message as it is adding the service.

About a minute later it will notify you that it has successfully added Azure Sentinel to the workspace.

Refresh the Azure Sentinel blade and you should see your workspace displayed.

Click on the workspace.

That is it!

More information can be found on the Azure Sentinel Product page: https://azure.microsoft.com/en-us/services/azure-sentinel/
https://www.cso.com.au/article/658395/microsoft-introduces-integrated-darktrace-a-like-azure-sentinel/
https://youtu.be/vPmICTJW66Y

Have Fun!

Using Azure Key Vault Secrets in your ARM Templates

Keeping your cloud infrastructure secure starts with some basic password hygiene practices. Now there are loads of opinions out there about passwords and how they should be used when it comes to infrastructure as code.
I’m, however of the opinion that if you have an opportunity not to use a user name and password combination then you should take it. If it’s unavoidable then generate a random password with a decent length (a 30 character complex password is generally my go-to) and stick it in an Azure Key Vault you should be safe. And NEVER EVER REUSE A PASSWORD!!!

The Problem:Developers, Application Support and Infrastructure Teams are high-value targets when it comes to the dark world of hacking. Typically because these individuals keep the keys to the proverbial castle. And therefore they need to take extra precautions when it comes to password management. I have seen loads of public repositories containing passwords in the ARM template or parameter files. Now in most of these cases, it was probably done to simplify the deployment example that they are trying to illustrate.
So my advice is not to just deploy these without changing these passwords. And also when you add these templates into your private repository don’t add passwords in there.
When developing your infrastructure using ARM Templates requires some patience when you start out. It is a really good practice to start securing your password early on and get into the habit of doing it.

The Solution: Using Azure Key Vault to store your usernames and passwords for these credentials is a really neat way to keep passwords out of your ARM templates. There are several techniques that can be used to achieve this, the one that I will be showing you will reference secrets using a static ID. Prerequisites:
You need an Azure Key Vault in your Azure Subscription. To create one follow this article.
Ensure the service principle deploying the Azure resources has, get and list permission on the Key Vault’s access policy. In the example below shows a couple of code snippets on how you go about achieving this. In the example, we have the password for a VM that we are deploying.
First, in your parameter file you define a password parameter that references your key vault: <SubscriptionID> refers to the ID of the subscription your Key Vault is deployed in. <ResourceGroupName> refers to the resource group that your Key Vault resides in. <KeyVaultName> refers to the Key Vault containing the secret value that is your password. Lastly, secretName is the secret’s key name.


Screen capture of template file (Credit Azure Docs)

Now when the template is parsed it will go and reference the secret in the defined Key Vault. Now, all we need to do is construct the parameters in our template file.

Screen capture of template file (Credit Azure Docs)

With this, configured you can call this parameter in your resource section of the template. When the template is parsed it will reach out to your Key Vault and grab the secret value and use that in the deployment.

A Good reference site for this: https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-manager-keyvault-parameter

Happy Deploying!

Using Google Chrome Profiles to keep your single sign on accounts separate

Over the years I have accumulated my fair share of login credentials. Currently, I have four Microsoft accounts that I work with on a daily basis; all attached to different Azure directories. I had some issues in the past switching between different accounts, just because of Single-Sign-On and the way common browsers cache logins. The way I was doing it was by opening “incognito tabs” through which I was continuously logging in and authenticating. This approach did help a bit, but there was still some conflicting sticky credentials that slowed me down. I discovered a feature in Google Chrome called Profiles, which turned out to be the game changer I was looking for. I could now use this approach on a daily basis without any issues. I decided to put this blog together, although not deeply technical, to hopefully save you the time and effort in finding a resolution.

By using this feature you will be able to:

  • Share Chrome with others
  • Keep all your Chrome information separated (bookmarks, history, passwords, settings, etc.)
  • Keep your work and personal accounts separate

So here is the setup:

  1. Install the Google Chrome browser.
  2. Add a new person by following these instructions. I named my “person” with the name of the account I use to log into the Azure portal. Use what works for you.
  3. After adding a new person/profile, log into the Azure portal.
  4. Repeat steps 2 and 3 for the other logins, naming each person/profile accordingly.
  5. You now have the option of using a specific account when you need it by clicking on the profile button next to the browser bar and selecting the appropriate profile.

Ensure that you keep the logins isolated to the profiles they are meant to run in. Do not use multiple accounts for the same profile.

Have fun!

The First…

Yep, its the 1st of January 2019 and with some new found inspiration and some heartburn (champagne last night) I got up this morning and decided this year will be the year that I will start this blog (must be a new years day thing). I have been pondering over getting this blog thing up and running for a couple years now and finally decided that this will be the year to get it done. Why you might ask. Well, I really like helping people and figured that this would be a good way to start doing that in a public forum. My intention with this blog is to share my journey delivering cloud solutions so yes this is not going to be a record of my personal memoirs but rather my professional journey and some of the technical problems that I face on a day to day basis. Currently, I’m happily employed as a Senior Cloud Engineer at the Ministry of Education in New Zealand and I am part of a team that helps to implement cloud solutions that help deliver outcomes for the Ministry and the New Zealand public. I have some high hopes for this blog and what it will achieve and hope that you will enjoy this journey with me.