In almost every Azure Tenant I use, I have one Azure Function App that helps me to be more productive. Since the first day of the introduction Azure Functions, I used them to
automate different tasks. Over the time, the collection of functions became quite big and I would love to share them with you so you can benefit from them as well 🙂

Table of Contents

  1. Setup the Environment
  2. Functions

This is the first part of many to come!

Disclaimer:
PowerShell for Azure Functions is not officially supported by Microsoft yet, it’s still in preview (v2). Azure Functions is available in two major versions, v1 and v2. PowerShell is available in both, but v2 offers much more capabilities, is more modular and is also the version that will be supported in the future. Azure Function v1 with PowerShell will officially never leave the experimental stage.

Setup the Environment

First things first, setting up the Function App.

I’d recommend to create a new Resource Group just for automation purposes - If you already have one dedicated to this, great, just use that one!

Create Function AppCreate Function App

Afterwards, we need to create the Managed Identity.
Navigate to the Platform features and click Identity.

Managed IdentityManaged Identity

Managed IdentityManaged Identity

This creates an managed identity within the Azure AD of your tenant, also Known as App registration or Enterprise Application.
Depending on your permissions and/or the scope you intend to use the Azure Function, you now can set the permissions of the managed identity to your
desired subscription, resource group, key vault or any other resource that support Azure RBAC.

Now we are ready to start!

If you want to learn more about Azure Functions, check out the links below:
Azure Functions Introduction
Azure Functions PowerShell
Azure Functions PowerShell Developer Guide

Functions

This section is dedicated to the functions I wrote. In this post, I will focus on functions for accessing and managing Azure KeyVault.
In general, I write all functions to interact directly with the Azure APIs.

Wait a second Christoph, why don’t you just use the Azure PowerShell az Module instead?

Great question, I’m so glad you asked!

Generally, this would work of course - just add the module to the Function App and you’re golden. But if you want your function to be as fast as possible - and this should be the goal for serverless computing - use the Azure REST-API directly. This takes away the overhead produced by the Azure cmdlets and will boost the performance tremendously!
Besides that, the management of adding and updating modules in Azure Functions is not the most pleasant experience.

Ok, lets get to the functions!

There are different ways to create a function in a function app. You can create a Function in the Azure Portal, you can use the excellent Visual Studio Code extension or you can just use the Azure Functions Core Tools and any editor you are familiar with. The Azure portal is the least fluent development experience, but it is sill improving. In a later post, we will give you an example for deployment via CI/CD-Pipeline using Azure DevOps and an introduction to Azure Functions Core Tools.

Key Vault has actually two different APIs - one for the management portion and one for accessing data (aka. dataplane API).
We’re focussing on the dataplane API since we want to mange secrets.

All Functions are available in my Github repo AzureMaintenanceFunctions.

The function GetKeyVaultSecrets retrieves all secret in the specified KeyVault:

run.ps1link
1
<#
2
    .SYNOPSIS
3
        Recieve all secrets from a specific Azure Key Vault.
4
    .DESCRIPTION
5
        Recieve all secrets from a specific Azure Key Vault.
6
    .PARAMETER vaultName
7
        Specifies the name of the Azure Key Vault you wnat to recieve secrets from.
8
        Mandatory
9
    .EXAMPLE
10
        Invoke-RestMethod -Method Get -Uri 'https://<functionName>.azurewebsites.net/api/GetVaultSecrets?vaultName=[vaultName]code=[token]'
11
#>
12
13
using namespace System.Net
14
15
param($Request, $TriggerMetadata)
16
17
$vaultName = $Request.Query.vaultName
18
19
# Acquire Access Token
20
$apiVersion = '2017-09-01'
21
$resourceURI = 'https://vault.azure.net'
22
$tokenAuthURI = $env:MSI_ENDPOINT + "?resource=$resourceURI&api-version=$apiVersion"
23
$tokenResponse = Invoke-RestMethod -Method Get -Headers @{"Secret" = "$env:MSI_SECRET" } -Uri $tokenAuthURI
24
$authHeader = @{Authorization = "Bearer $($tokenResponse.access_token)" }
25
26
$param = @{
27
    'Uri'         = "https://$vaultName.vault.azure.net/secrets?api-version=7.0"
28
    'Method'      = 'Get'
29
    'Header'      = $authHeader
30
    'ErrorAction' = 'Stop'
31
    'ContentType' = 'application/json'
32
}
33
34
try {
35
    $response = Invoke-RestMethod @param
36
    $status = 200
37
} catch {
38
    $status = 500
39
    $response = @{
40
        'value' = $_.Exception.Message
41
    }
42
}
43
44
[System.Collections.ArrayList]$arr = @()
45
if ($status -eq 200) {
46
    foreach ($secret in $($response.Value)) {
47
        $name = $secret.id -replace "https://$vaultName.vault.azure.net/secrets/"
48
49
        Add-Member -MemberType NoteProperty -Name 'Name' -Value $name -InputObject $secret
50
        $arr.Add($secret) | Out-Null
51
    }
52
}
53
54
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
55
        StatusCode = $status
56
        Body       = (Convertto-json -inputObject $arr -depth 10 -compress)
57
    })

You can apply the name of your KeyVault by adding the parameter to the Function URI:

1
$response = Invoke-RestMethod -Method 'GET' -Uri 'https://[functionName].azurewebsites.net/api/GetVaultSecrets?vaultName=[vaultName]code=[token]'

When the Manged Identity of the Function has permission to list secrets in the specified KeyVault, all of them will be casted into the $response variable.

To actually retrieve the secret, you have to use the Function GetKeyVaultSecret.

1
$response = Invoke-RestMethod -Method 'GET' -Uri 'https://[functionName].azurewebsites.net/api/GetVaultSecrets?vaultName=[vaultName]&secretName=[secretName]&code=[token]'

You can specify the version of the secret as well, if you need it:

1
$response = Invoke-RestMethod -Method 'GET' -Uri 'https://[functionName].azurewebsites.net/api/GetVaultSecrets?vaultName=[vaultName]&secretName=[secretName]&secretVersion=[secretVersion]&code=[token]'

You can list all versions using the GetKeyVaultSecretVersion Function.

Retrieving secrets is great, but we also want to create or change some.

The Function SetKeyVaultSecret is dedicated to this.

1
$param = @{
2
  "secretValue" = "secretValue",
3
  "vaultName"   = "vaultName",
4
  "secretName"  = "secretName"
5
}
6
7
$response = Invoke-RestMethod -Method 'POST' -Uri 'https://<functionName>.azurewebsites.net/api/GetVaultSecrets?code=[token]' -Body (ConvertTo-Json -InputObject $param)

If the secret with the specified name does not exists yet, a new secret will be created - if it’s already there, a new version will be applied.

This concludes the first part - in future posts I will get into more details of the design of the functions, how the authentication works and some bits and bobs a I learned over the time.