Sharing an example of using the Initialize.ps1 to connect an Azure KeyVault

Product: PowerShell Universal
Version: 3.5.5

I was having some trouble getting Azure KeyVault to work, so here’s what I ended up doing.

I wanted to use Certificate Authentication so that I wouldn’t have to worry about any secrets, so this process describes setting up with a self-signed certificate.

Generate a Self-Signed Certificate

# create a non-exportable self-signed certificate
$Params = @{
CertStoreLocation = 'Cert:\CurrentUser\My'
Subject           = 'AzureKeyVaultAccess'
Provider          = 'Microsoft Enhanced RSA and AES Cryptographic Provider'
KeyExportPolicy   = 'NonExportable'
}
$Cert = New-SelfSignedCertificate @Params

# save a copy of the public cert to your downloads folder
$Cert | Export-Certificate -Type CERT -FilePath ~\Downloads\AzureKeyVaultAccess.crt

Create an App Registration

  1. Open Azure AD
  2. Open App Registrations
  3. Create a new app registratio
  4. Upload the exported certificate to the “Certificates & secrets” section

Add my App’s Service Principal to the KeyVault

  1. Open my key vault “Access policies”
  2. Create a new policy
    a. Permissions select “Key and Secret Management” from the template drop down
    b. Principal search for app by name and selected the Service Principal from the list
  3. Finish

Note: I also configured the firewall on the KeyVault to only allow requests from specific IP addresses since I will be using this on-prem

Add my App’s Service Principal to the Subscription

  1. Open Subscriptions
  2. Under the correct Subscription open “Access Control (IAM)” and assign you Service Principal the Reader role

Configure PowerShell Universal
Create a new initialize.ps1 in your .universal folder with the following code

# found in Subscriptions
$SubscriptionId = '00000000-0000-0000-0000-000000000000'

# found in app registration
$TenantId       = '00000000-0000-0000-0000-000000000000'
$ApplicationId  = '00000000-0000-0000-0000-000000000000'

# found in your KeyVault
$VaultName      = 'MyPSUVault'

# find your self-signed certificate
$Certificate = Get-Item Cert:\CurrentUser\My\* |
    Where-Object {
        $_.Subject -like 'CN=AzureKeyVaultAccess' -and
        $_.NotBefore -le (Get-Date) -and
        $_.NotAfter -ge (Get-Date) } |
    Sort-Object NotAfter -Descending |
    Select-Object -First 1

# needed if modules are installed in PSU and not system since this
# runs before PSU updates the module path; alternatively load the
# specific modules by full path
$RepoModules = Resolve-Path "$PSScriptRoot\..\Modules" | Convert-Path
$env:PSModulePath += ";$RepoModules"

# connect to Azure
Connect-AzAccount `
    -Subscription          $SubscriptionId `
    -Tenant                $TenantId `
    -ApplicationId         $ApplicationId `
    -CertificateThumbprint $Certificate.Thumbprint `
    -ErrorAction           Stop

# (re)register your secret vault
Register-SecretVault -ModuleName Az.KeyVault -Name AzureKeyVault -VaultParameters @{ 
    AZKVaultName   = $VaultName
    SubscriptionId = $SubscriptionId
} -AllowClobber

I hope someone else finds this useful

6 Likes

Is there a way to make this work for PSU running under IIS with the SYSTEM user? I need to be able to utilize the Run-As user feature that relies on SYSTEM, but I haven’t been able to successfully store secrets, which is rather necessary for my use cases.

-Robert

I realize I didn’t say this in the original post, and I can’t edit it anymore… you need to enroll the certificate under the same security context as you will be using it. If that is SYSTEM, or as a service account… whatever, that’s where you need to enroll it.

So there is no reason you couldn’t do this with it running as SYSTEM under IIS, just as long as the security context is the same.

What I usually do is put the enrollment script in a script inside PSU, and then just run it from there.

I used psexec to run the certificate enrollment as SYSTEM and uploaded the result into Azure. When I run the initialize script in powershell under psexec, it doesn’t produce any errors (at least, not after I created a Modules folder it complained was missing), but I don’t see the Azure vault as an option for saving variables in.
Possibly related, I’m getting errors about not having a Local Application Data folder, but I’m not sure that the Azure vault problem is related since they call out BuiltInLocalVault and PSUSecretStore, but not the Azure vault:
2023-02-02 15:21:49.737 -06:00 [ERR] Error registering vault BuiltInLocalVault
2023-02-02 15:21:49.737 -06:00 [ERR] Exception:
System.InvalidOperationException: Unable to find a Local Application Data folder location for the current user, which is needed to store vault registry information.
Windows built-in accounts do not provide the Location Application Data folder and are not currently supported.
at Microsoft.PowerShell.SecretManagement.RegisteredVaultCache.CheckFilePath()
at Microsoft.PowerShell.SecretManagement.RegisteredVaultCache.WriteSecretVaultRegistry(Hashtable vaultInfo, String defaultVaultName)
at Microsoft.PowerShell.SecretManagement.RegisterSecretVaultCommand.EndProcessing()
at System.Management.Automation.Cmdlet.DoEndProcessing()
at System.Management.Automation.CommandProcessorBase.Complete()
2023-02-02 15:21:49.902 -06:00 [ERR] Error registering vault PSUSecretStore
2023-02-02 15:21:49.902 -06:00 [ERR] Exception:
System.InvalidOperationException: Unable to find a Local Application Data folder location for the current user, which is needed to store vault registry information.
Windows built-in accounts do not provide the Location Application Data folder and are not currently supported.
at Microsoft.PowerShell.SecretManagement.RegisteredVaultCache.CheckFilePath()
at Microsoft.PowerShell.SecretManagement.RegisteredVaultCache.WriteSecretVaultRegistry(Hashtable vaultInfo, String defaultVaultName)
at Microsoft.PowerShell.SecretManagement.RegisterSecretVaultCommand.EndProcessing()
at System.Management.Automation.Cmdlet.DoEndProcessing()
at System.Management.Automation.CommandProcessorBase.Complete()
2023-02-02 15:21:49.902 -06:00 [ERR] Exception:
System.InvalidOperationException: Unable to find a Local Application Data folder location for the current user, which is needed to store vault information for this configuration scope.
Windows built-in accounts do not provide the Location Application Data folder and are not currently supported for this configuration scope.
at Microsoft.PowerShell.SecretStore.SecureStoreFile.CheckFilePath()
at Microsoft.PowerShell.SecretStore.SecureStoreFile.ReadConfigFile(SecureStoreConfig& configData, String& errorMsg)
at Microsoft.PowerShell.SecretStore.SecureStoreFile.get_ConfigRequiresPassword()
at Microsoft.PowerShell.SecretStore.LocalSecretStore.get_PasswordRequired()
at Microsoft.PowerShell.SecretStore.SetSecretStoreConfiguration.EndProcessing()
at System.Management.Automation.Cmdlet.DoEndProcessing()
at System.Management.Automation.CommandProcessorBase.Complete()

-Robert

Are you running the initialize.ps1 directly? It’s meant to be run by PSU, you drop the script into your .universal folder.

I’ve tried both. As indicated, I think the crux is that the ‘SYSTEM’ user doesn’t have a normal user profile, hence the lack of an Application Data folder.

-R