In a previous blog series, I discussed how to use certificate authentication for PowerShell scripts running in a standard Windows environment. (See “Authentication Options for Automated Azure PowerShell Scripts, Part 1: Service Account vs. App Registration,” as well as Part 2 and Part 3.) In this blog, I discuss remediating basic authentication in a different environment: an Azure Functions app. I also cover how to use certificate authentication with the Microsoft Exchange Online PowerShell V2 (EXO V2) module, which has its own quirks.
Basic authentication to Entra ID (formerly Azure Active Directory) does not support modern security controls such as Conditional Access policies and multi-factor authentication (MFA). In addition, basic authentication exposes an organization to security vulnerabilities such as password spraying attacks and simple password leaks. Microsoft discourages the use of basic authentication; in fact, Microsoft will block basic authentication in Exchange Online in October 2022. Organizations are therefore highly encouraged to identify and remediate its use.
A common source of basic authentication occurs in PowerShell scripts that authenticate to AAD (e.g., to perform operations in SharePoint or Exchange Online). Older versions of the PowerShell modules for these services did not support any other mechanism for authentication, so scripts using these legacy modules are configured for basic authentication. Even newer scripts can suffer from basic authentication if appropriate development guidelines have not been communicated to ensure that developers leverage the certificate-based authentication that most modules now support.
Diagnosing the Problem
In a client environment in which I was working, audits of basic authentication to AAD had identified a service account that was using basic authentication. This account was tracked to an existing Azure Functions app that was pulling information from Azure using the Security & Compliance PowerShell endpoint.
The code had been written when basic authentication was the only option for automated scripts—but it was at least storing the credentials in an Azure Key Vault, rather than in plain text in the function code.
The Azure Functions app needed modernization to leverage an app registration and certificate authentication instead of basic authentication with a user ID and password
Enabling an App Registration to Work with Microsoft Exchange Online PowerShell
The Security & Compliance PowerShell endpoint shares the EXO V2 module with the Exchange Online endpoint. The process for using certification authentication against this endpoint is mostly the same as for using it against the Exchange Online endpoint.
Using app registration to authenticate against Microsoft Exchange Online PowerShell is enabled in version 2.0.3 of the module; however, it is still in preview (at the time of writing) for Security & Compliance PowerShell, requiring version 2.0.6 Preview5 or later.
For an app registration to work with EXO V2, a special API permission, Exchange.ManageAsApp, must be granted to the app registration. This permission is not selectable through the UI. It must be added by editing the App Registrations Manifest, as documented here. Once the permission is added, it can be admin consented as usual.
The actual permissions the app registration has through the EXO V2 module are granted by adding the app registration to one of the supported AAD roles rather than by granting additional API permissions.
In this case, we granted the app registration the Compliance Administrator role by going to Entra ID -> Roles and administrators, searching for the Compliance Administrator role, and adding the app registration to it.
Storing the Certificate in Key Vault
After creating the app registration and associating a certificate with it, you must store the certificate somewhere that is accessible to the code running in the Azure Functions app.
Because the problematic Functions app was already storing secrets in an Azure Key Vault, it would seem logical to store the certificate there as well. However, this is not the correct approach.
Firstly, the Get-AzureKeyVaultCertificate cmdlet doesn’t return an actual certificate object. To get the certificate, you must instead retrieve the corresponding secret and construct the certificate object from the returned data; for example:
$secret = Get-AzKeyVaultSecret -VaultName secretstorage -Name FunctionAppComplianceCenterAuth
$base64 = ConvertFrom-SecureString -AsPlaintext -SecureString $secret.SecretValue
$byteArray = [Convert]::FromBase64String($base64)
$certificate = [System.Security.Cryptography.X509Certificates.X509Certificate2] $byteArray
$certificate
Thumbprint Subject EnhancedKeyUsageList
---------- ------- --------------------
2BA907135652EF66463177A9E244859A66762E18 CN=Function App Com… {Client Authentication, Server Authentication}
Code language: PowerShell (powershell)
This code would retrieve a certificate named “FunctionAppComplianceCenterAuth” from the Key Vault “secretstorage” and turn it into a local certificate object that could then theoretically be used for certificate authentication.
Unfortunately, when you run the above code in a Functions app, it errors out, as shown in the following output.
ERROR: Exception calling ".ctor" with "1" argument(s): "The system cannot find the file specified."
After doing some testing, we discovered that it is not possible to create a certificate object in the Azure Functions app environment with a known private key. You can create certificate objects that don’t have a private key, but for authentication to Azure in PowerShell, you must know the private key.
If Not Key Vault, Then Where?
It turns out that certificates imported through the Functions app’s TLS/SSL settings can be made available to the PowerShell code. This is a two-step process.
1. First, import the certificate .pfx file via Settings->TLS/SSL settings->Private Key Certificates (.pfx)->Upload Certificate, as the following screenshot shows.
2. Second, tell the runtime environment to import the certificate by adding or modifying the WEBSITE_LOAD_CERTIFICATES application setting under Settings->Configuration->Application Settings, per the following screenshot.
The WEBSITE_LOAD_CERTIFICATES setting can be either the wildcard * or a list of certificate thumbprints to load. Since in this case there is only one certificate, using the wildcard is the simplest approach. Changing the application settings will automatically restart the application.
Once this is done, you can verify that the certificate is available by using the debug console for the application. Select Development Tools->Advanced Tools->Go, as the following screenshot shows.
get-childitem cert://currentuser/my
The output will list all the certificates in the local user store, including the certificate that was uploaded to the TLS/SSL settings and that was imported through the WEBSITE_LOAD_CERTIFICATES setting, as the following output shows.
Importing the Correct PowerShell Module Version
As mentioned earlier, to use app registration to authenticate to the Security & Compliance PowerShell endpoint, we need version 2.0.6 Preview5 or later of the EXO V2 module. For the correct version to be accessible in the Functions app, it must be added to the Functions app’s requirements.psd1 file. This file can be accessed in the portal via Functions->App Files and by selecting requirements.psd1 from the drop-down menu, as the following screenshot shows.
At the time of writing, the latest version of the module was 2.0.6 Preview6, so that is what we added to the requirements file.
Putting It All Together
We have now completed the following steps:
- Enabled the app registration to work with the EXO V2 module
- Assigned the necessary AAD role to the app registration service principal
- Imported the certificate and private key into the Functions app and published it into the PowerShell runtime used by the app
- Ensured the correct version of the EXO V2 module is loaded in the runtime
At this point we should be able to authenticate to EXO V2 using the app registration inside the Functions app and run any required commands.
I find it helpful to create a test function in the Functions app to validate the authentication; for example:
param($Timer)
Write-Host "Connecting to Security & Compliance PowerShell"
Connect-IPPSSession -CertificateThumbPrint "2BA907135652EF66463177A9E244859A66762E18" -AppID "453569bb-f1ca-4340-b691-97ce48e5c4a8" -Organization "foobarqux.onmicrosoft.com" -CommandName @("Get-DlpComplianceRule")
Get-DlpComplianceRule
Code language: PowerShell (powershell)
This simple function can be run manually to test that authentication is working and that the relevant permissions have been assigned.
Whereas Connect-ExchangeOnline is used to connect to an Exchange Online PowerShell session, the Connect-IPPSSession cmdlet is used to connect to the Security & Compliance PowerShell endpoint. The parameters are mostly the same for the two commands; Connect-ExchangeOnline can be substituted into the above code example to connect to Exchange Online rather than the Security & Compliance PowerShell endpoint.
The EXO V2 module exports a few hundred commands by default. The -CommandName parameter allows us to limit the imported commands to a smaller list, which can improve performance.
Once we know we have a working configuration, we can update the existing functions in the Functions app to use certificate-based authentication and remove the dependency on basic authentication.
Summary
PowerShell scripts are a common cause of basic authentication traffic in an organization. In my previous blog series, I provided guidance on converting simple scripts to avoid basic authentication. In this blog, I discussed how to deal with PowerShell in other environments, such as Azure Functions apps—including the additional challenges of using the Microsoft Exchange Online PowerShell module.
If you need assistance in identifying and remediating sources of basic authentication against AAD, contact the experts at Ravenswood Technology Group. We’re here to help!