Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the correct way to authenticate an Azure function app using a service principal?

Tags:

azure

I have some code I want to automate with an Azure function app. The code is for cloud governance purposes and will only be used internally by the governance team. The purpose of the code is to retrieve information about public IP addresses and write it to a blob. It will do this automatically every day.

I would like to use a dedicated cloud governance service principal to carry out the actions instead of a user account. How can I authenticate the service principal for the function? Do I need to use Key Vault and authenticate within the code? If so, how can I give the function permissions to use Key Vault?

like image 241
supernova Avatar asked Jan 20 '26 05:01

supernova


1 Answers

The high level steps are:

  1. Create an AAD Application (service principal). Assign it whatever permissions you want.
  2. Create a Key Vault
  3. Create a certificate in Key Vault (see CreateKVSPNCertificate.ps1 below)
  4. Add that certificate to the AAD Application (see CreateKVSPNCertificate.ps1 below)
  5. Create a managed identity for the Azure Function app
  6. Give the Function app's managed identity Get Secrets permission on the Key Vault
  7. In your Functions code, use AzureServiceTokenProvider along with a connectionstring to your KeyVault to authenticate your Functions code as the service principal from step #1. (see GetAuthCredsFromKeyVault below)

CreateKVSPNCertificate.ps1

# This script will have Key Vault create a certificate and associate the certificate with an Azure AD Application.  
# This allows applications to get the private key (secret) from Key Vault to authenticate as the service principal associated with the Azure AD app.

[CmdletBinding()]
param(
  [Parameter(Mandatory = $true)]
  [String]$keyVaultName,
  [Parameter(Mandatory = $true)]
  [String]$appId,
  [Parameter()]
  [int]$validityInMonths = 12
)

# Key Vault will create a certificate, returning the cert from this function so the public key can be added to the AAD Application
function New-KeyVaultSelfSignedCert {
    param($keyVault, $certificateName, $subjectName, $validityInMonths, $renewDaysBefore)

    # Define the configuration for how the certificate will be created
    $policy = New-AzKeyVaultCertificatePolicy `
                -SubjectName $subjectName `
                -ReuseKeyOnRenewal `
                -IssuerName 'Self' `
                -ValidityInMonths $validityInMonths `
                -RenewAtNumberOfDaysBeforeExpiry $renewDaysBefore

    # Have Key Vault create the certificate.  This returns an operation status that needs to be waited on until it is complete
    $op = Add-AzKeyVaultCertificate `
                -VaultName $keyVault `
                -CertificatePolicy $policy `
                -Name $certificateName

    if ($op -eq $null)
    {
        # Certificate may have been soft-deleted which means the name is still reserved.
        if ((Get-AzKeyVaultCertificate -VaultName $keyvault -InRemovedState).Count -gt 0)
        {
            # Purge the soft deleted key and try adding the new one again
            # If the Purge fails with "Operation returned an invalid status code 'Forbidden'", then make sure your account explicitly has the Purge feature enabled in the Key Vault Access Policies (this access is not automatically granted)
            Write-Host "Previous certificate with same name $certificateName was in soft-delete state.  Attempting to Purge previous certificate and create new one.  Purge may take some time, in case of failure retry after a couple minutes."
            Remove-AzKeyVaultCertificate -VaultName $keyVault -Name $certificateName -InRemovedState -Force
            Start-Sleep -Seconds 15
            $op = Add-AzKeyVaultCertificate `
                -VaultName $keyVault `
                -CertificatePolicy $policy `
                -Name $certificateName
        }
    }

    while ( $op.Status -eq 'inProgress' ) 
    {
        Start-Sleep -Seconds 1
        $op = Get-AzKeyVaultCertificateOperation -VaultName $keyVault -Name $certificateName
    }
    if ($op.Status -ne 'completed')
    {
        Write-Error "Add-AzKeyVaultCertificate failed to complete"
        Write-Error $op
        return $null
    }

    # Get the certificate that was just created and return it.  This gets the public cert, not the private cert
    (Get-AzKeyVaultCertificate -VaultName $keyVault -Name $certificateName).Certificate
}

# Get the Azure AD Application in order to get the display name
$existingApp = Get-AzADApplication -ApplicationId $appId
$appName = $existingApp.DisplayName

if ($existingApp = $null)
{
    Write-Error "Couldn't find existing AAD Application $appId"
    break
}

# Have Key Vault create a certificate
$certName = "SPCert-" + $appName
$cert = New-KeyVaultSelfSignedCert -keyVault $keyVaultName `
                                   -certificateName $certName `
                                   -subjectName "CN=$appName" `
                                   -validityInMonths $validityInMonths `
                                   -renewDaysBefore 1

if ($cert -eq $null) { break }

Write-Output ""
Write-Output "Certificate generated with:"
Write-Output "   Thumbprint = $($cert.Thumbprint)"
Write-Output "   Secret Name = $certName"
$certString = [Convert]::ToBase64String($cert.GetRawCertData())
# Associate the public key with the Azure AD Application
New-AzADAppCredential -ApplicationId $appId -CertValue $certString -EndDate $cert.NotAfter.AddDays(-1)

In Functions code, authenticate using the Key Vault certificate

        private AzureCredentials GetAuthCredsFromKeyVault()
        {
            string AuthVaultName = System.Environment.GetEnvironmentVariable("AuthVaultName");
            string AuthAppId = System.Environment.GetEnvironmentVariable("AuthAppId");
            string AuthSecretName = System.Environment.GetEnvironmentVariable("AuthSecretName");
            string connectionString = string.Format("RunAs = App; AppId = {0}; KeyVaultCertificateSecretIdentifier = https://{1}.vault.azure.net/secrets/{2}", AuthAppId, AuthVaultName, AuthSecretName);

            AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider(connectionString);
            string accessTokenARM = azureServiceTokenProvider.GetAccessTokenAsync("https://management.azure.com").Result;
            string accessTokenGraph = azureServiceTokenProvider.GetAccessTokenAsync("https://graph.windows.net").Result;
            AzureCredentials creds = new AzureCredentials(new TokenCredentials(accessTokenARM), new TokenCredentials(accessTokenGraph), Constants.TenantId, AzureEnvironment.AzureGlobalCloud);

            return creds;
        }

like image 98
kwill Avatar answered Jan 23 '26 02:01

kwill