One of the most unnerving decisions you have to make in your journey to relative automation with your MSP and PowerShell is taking a decision on how exactly you will store the credentials that you end up using within your scripts, whether they are API keys, passwords, tokens or any other information that you need to retrieve. I think I’ve been through pretty much all of these at some points in my career from embedding API keys directly in a PowerShell Script (do not do this!) to my most recent discovery of Azure Key Vault (a way to safeguard cryptographic keys and other secrets used by cloud apps and services). There ARE ways to securely store your credentials locally on the machine you are running from, but it’s a pain in the ass to maintain, especially when you are dealing with all of the additional RefreshTokens, ApplicationIDs etc that you will need to store for Office 365 AND update periodically (we will cover these in a later part of the series). It’s unmanageable. If you wish to pursue the stored credential locally method, it’s outlined here. As per my previous blog, I am not omniscient. This is my interpretation of the best way to do this, a lot of which was done through trial and error. There may be a better way, but this is my way. As always, any improvements welcome!

How can Azure Key Vault help me?

It helps you by securely storing all of your credentials and making them available from a central location. There is a cost associated with this, a measly $0.03 / 10,000 transactions. That’s 3 cents (or £0.023 freedom bucks to my fellow GBP brethren) for 10,000 lookups. That’s the only cost. Bargain! This is much better than storing credentials locally, for a number of reasons:

  1. When you need to update a password, API Key, credential, you only need to update it in one place. This is especially important if you are utilising a script on say a developer machine and a production machine
  2. Updating a password, API key, credential can be done programmatically; this will become important later down the line when you get the pleasure of being introduced to refresh tokens
  3. It is SIMPLER to use once configured

Installing the appropriate AZ PowerShell Module

Install-Module -Name Az -AllowClobber -Scope AllUsers

Please note – this module conflicts with the AzureRM Module. I had to fully uninstall the AzureRM module before this worked for me. This module is a replacement for the AzureRM module.

Setting up an Azure Key Vault (One time only)

I have broken this down in to a number of steps. I am going to do my best to explain each step, as I understand it, and why it is necessary. All YOLO PowerShellers will find the full script at the bottom of this section utilising variables for each step but I absolutely recommend you run this line by line.

# Enable Aliases from the previous Azure RM
Enable-AzureRMAlias

# Connect interactively with an account that has sufficient access to Azure
Connect-AzAccount

# Set the appropriate context for the subscription you are creating in
Set-AzContext -SubscriptionName "Other Credit Card"

This is relatively self explanatory, the first will pop-up a modern authentication prompt. You’ll need someone who has full rights in Azure. The second, if you need it, switches to the subscription that you will create the Key Vault against.

# First we have to create the Azure Resource Group that will hold the KeyVault
New-AzResourceGroup -Name "YourMSP-KeyVaultRG" -Location "UK West"

# Create the KeyVault in the newly created Azure Resource Group
New-AzKeyVault -Name "YourMSP-CredsKeyVault" -ResourceGroupName "YourMSP-KeyVaultRG" -Location "UK West"

First creates a resource group in Azure, this is a way of grouping different things together so they are all under one group. The next creates the actual key vault, putting it in the resource group I just created and in the one true Azure location, UK West. Change as appropriate 😉

# Generate a certificate locally which will be used to Authenticate
$cert = New-SelfSignedCertificate -CertStoreLocation "cert:\CurrentUser\My" -Subject "CN=ImpactKeyVault" -KeySpec KeyExchange -NotAfter (Get-Date).AddMonths(36)
$keyValue = [System.Convert]::ToBase64String($cert.GetRawCertData())

This is a little more complicated. What we are doing here is generating a self-signed certificate on your local machine. This certificate is going to be what is responsible for securing and allowing your access to the key vault from within scripts. You can modify the Subject to be whatever you require. This generates a certificate for a three-year period.

# Generate a service principal (Application) that we will use to Authenticate with
$sp = New-AzADServicePrincipal -DisplayName "YourMSP Key Vault" -CertValue $keyValue -EndDate $cert.NotAfter -StartDate $cert.NotBefore

# Allow for the service principal to propagate in Azure
Start-Sleep 20

This line generates a new service principal in Azure. An Azure service principal is an identity created for use with applications, hosted services, and automated tools to access Azure resources. This access is restricted by the roles assigned to the service principal, giving you control over which resources can be accessed and at which level. You can see that we are passing the certificate details in to this service principal, because that is what will be used to authenticate. This service principal presents itself in Azure as an application. The sleep is to allow the service principal to propagate in Azure.

# Assign the appropriate role to the service principal
New-AzRoleAssignment -RoleDefinitionName Reader -ServicePrincipalName $sp.ApplicationId -ResourceGroupName "YourMSP-KeyVaultRG" -ResourceType "Microsoft.KeyVault/vaults" -ResourceName "YourMSP-CredsKeyVault"
# Set the appropriate access to the secrets for the application
Set-AzKeyVaultAccessPolicy -VaultName 'YourMSP-CredsKeyVault' -ObjectId $sp.id -PermissionsToSecrets Get,Set

This section is applying the appropriate permissions in Azure for the service principal. The first line is setting an role based access control of Reader while limiting the scope of the service principal to a specific resource group, a specific part of Azure and even to our specific vault. The next line is actually a PowerShell command for Azure Vaults. It sets the service principal (in the form of the application we created previously) to be able to get secrets out of the vault.

Write-Host -BackgroundColor Green -ForegroundColor Black "!!You will need to save these details. This is the Tenant ID and Application ID!!"
Write-Host -BackgroundColor Yellow -ForegroundColor Black "Tenant ID: $((Get-AzSubscription -SubscriptionName "Other Credit Card").TenantId)"
Write-Host -BackgroundColor Yellow -ForegroundColor Black "Application ID: $($sp.ApplicationId.Guid)"


Disconnect-AzAccount

This is the final bit, and it will give you two bits of information. These are the only two bits of information you will ever need to be able to access your key vault. The tenant ID of your subscription and the application ID of our service principal. Neither of these are particularly sensitive and so can be embedded in scripts.

The Full Script

#--------------Editable Variables----------------
$AzureResourceGroup = "YourMSP-KeyVaultRG"
$AzureKeyVaultName = "YourMSP-CredsKeyVault"
$AzureSubscriptionName = "Other Credit Card"
$AzureLocation = "ukwest" # Other valid Locations northcentralus,eastus,northeurope,westeurope,eastasia,southeastasia,eastus2,centralus,southcentralus,westus etc see Azure documentation for exhaustive list
$AzureServicePrincipalAppName = "YourMSP Key Vault"
$CertificateSubjectName = "CN=ImpactKeyVault"
$CertificateExpiryInMonths = 36
$CertificateStorageLocation = "cert:\CurrentUser\My"
#$CertificateStorageLocation = "cert:\LocalMachine\My" # Uncomment this line to create the certificate at a machine level as opposed to a user level
#----------Do not edit below this line-----------

# Enable Aliases from the previous Azure RM
Enable-AzureRMAlias

# Connect interactively with an account that has sufficient access to Azure
Connect-AzAccount
 
# Set the appropriate context for the subscription you are creating in
Set-AzContext -SubscriptionName $AzureSubscriptionName
 
# First we have to create the Azure Resource Group that will hold the KeyVault
New-AzResourceGroup -Name $AzureResourceGroup -Location $AzureLocation
 
# Create the KeyVault in the newly created Azure Resource Group
New-AzKeyVault -Name $AzureKeyVaultName -ResourceGroupName $AzureResourceGroup -Location $AzureLocation
 
# Generate a certificate locally which will be used to Authenticate
$cert = New-SelfSignedCertificate -CertStoreLocation $CertificateStorageLocation -Subject $CertificateSubjectName -KeySpec KeyExchange -NotAfter (Get-Date).AddMonths($CertificateExpiryInMonths)
$keyValue = [System.Convert]::ToBase64String($cert.GetRawCertData())
 
# Generate a service principal (Application) that we will use to Authenticate with
$sp = New-AzADServicePrincipal -DisplayName $AzureServicePrincipalAppName -CertValue $keyValue -EndDate $cert.NotAfter -StartDate $cert.NotBefore
 
# Allow for the service principal to propagate in Azure
Start-Sleep 20
 
# Assign the appropriate role to the service principal
New-AzRoleAssignment -RoleDefinitionName Reader -ServicePrincipalName $sp.ApplicationId -ResourceGroupName $AzureResourceGroup -ResourceType "Microsoft.KeyVault/vaults" -ResourceName $AzureKeyVaultName
 
# Set the appropriate access to the secrets for the application
Set-AzKeyVaultAccessPolicy -VaultName $AzureKeyVaultName -ObjectId $sp.id -PermissionsToSecrets Get,Set
 
Write-Host -BackgroundColor Green -ForegroundColor Black "!!You will need to save these details. This is the Tenant ID and Application ID!!"
Write-Host -BackgroundColor Yellow -ForegroundColor Black "Tenant ID: $((Get-AzSubscription -SubscriptionName $AzureSubscriptionName).TenantId)"
Write-Host -BackgroundColor Yellow -ForegroundColor Black "Application ID: $($sp.ApplicationId.Guid)"
Write-Host -BackgroundColor Yellow -ForegroundColor Black "Azure Key Vault Name: $($AzureKeyVaultName)"
Write-Host -BackgroundColor Yellow -ForegroundColor Black "Certificate Subject Name: $($CertificateSubjectName)"
 
Disconnect-AzAccount

Setting Secrets in the Azure Key Vault

Before we retrieve anything, we have to create a secret.

$secretvalue = ConvertTo-SecureString 'mySUPERsecretAPIkey!' -AsPlainText -Force
$secret = Set-AzKeyVaultSecret -VaultName 'YourMSP-CredsKeyVault' -Name 'ExamplePassword' -SecretValue $secretvalue

These two lines are enough to create a secret. You could also have logged in to Azure and gone in to the vault and created a secret through the GUI too.

Retrieving Secrets from any PowerShell Script

#------------Example: How to retrieve a Secret-----------
$TenantId = "EnterTenantIdHere"
$ApplicationId = "EnterApplicationIDHere"
$Thumbprint = (Get-ChildItem cert:\CurrentUser\My\ | Where-Object {$_.Subject -eq "CN=ImpactKeyVault" }).Thumbprint
Connect-AzAccount -ServicePrincipal -CertificateThumbprint $Thumbprint -ApplicationId $ApplicationId -TenantId $TenantId
$Secret = (Get-AzKeyVaultSecret -vaultName "YourMSP-CredsKeyVault" -name "ExamplePassword").SecretValueText
#------------End Example---------------------------------

This is now the last stage. This Powershell can now be run from ANYWHERE as long as the certificate exists on that device for that user. If you trust an entire device with this, you can put the certificate in the LocalMachine container, simply replace CurrentUser with LocalMachine in the $Thumbprint line. This is going to be especially helpful when you are running scripts through an RMM on a TRUSTED device as they will access the device as SYSTEM which is its own user profile.

Utilising in a credential

Lets say you have created two secrets, one called AdminUserName and another called AdminPassword.

$AdminUser = Get-AzKeyVaultSecret -VaultName "YourMSP-CredsKeyVault" -Name $AdminUserName
$AdminPass = Get-AzKeyVaultSecret -VaultName "YourMSP-CredsKeyVault" -Name $AdminPassword
$mycred = New-Object System.Management.Automation.PSCredential ("$($AdminUser.SecretValueText)", $AdminPass.SecretValue)

You now have a credential object which can be used as any standard PowerShell credential. You will notice that the this is getting two separate values, $AdminUser.SecretValueText and $AdminPass.SecretValue. What’s the difference between SecureValueText and SecretValue? SecureValueText is the property returned that is the secret in plain text. SecretValue is the property but as a secure string, and to build a credential you pass a secure string not a plain text password. In upcoming scripts I am going to be utilising Azure Key Vault to access credentials