How to automatically rotate Azure AD app registration client secrets using Azure Functions (with Java) and Key Vault

A very common requirement is to change secrets regularly (such as on a schedule or if they are exposed). Azure AD app registrations can have client secrets or certificates that are used by applications to authenticate with Azure AD. These client secrets have maximum lifetimes (6 months to 2 years) and need to be rotated. Naturally, you want to automate this process so secrets are rotated without impacting applications. However, it can be hard to know when a secret is about to expire.

Azure Key Vault has the ability to fire Event Grid notifications when a secret is about to expire (30 days out). Using this notification with Azure Functions makes it so we can fully automate this process.

My GitHub repo shows you how to do this. It includes both the Infrastructure as Code (using Azure Bicep) and a Java-based Azure Function to generate a new secret in Azure AD & store the secret in Key Vault.

A key part of this solution is the use of a Managed Identity. This eliminates the need for a separate service principal with secrets that also have to be protected & rotated. The Managed Identity is granted both an Azure Key Vault access policy so it can get & set keys in Key Vault as well as an Azure AD Graph API role so it can get and set client secrets in Azure AD.

For Azure, the Managed Identity needs the ‘Get’, ‘List’ and ‘Set’ access policy permissions on the Key Vault. For Azure AD, the Managed Identity needs the ‘Application.ReadWrite.OwnedBy’ scope. Naturally, the Managed Identity must be listed as an Owner over the app registration.

As you can see in the screenshot below, Key Vault has been set up to fire an Event Grid notification when a secret is near expiring. An Azure Function will receive this message.

The Azure Function will use the Managed Identity to get an access token so it can access the Graph API and set the new client secret. Notice the specific scope we need to ask for to get a Graph API token using the Managed Identity.

C#
DefaultAzureCredential defaultCredential = new DefaultAzureCredentialBuilder()
    .managedIdentityClientId(managedIdentityClientId)
    .build();

GraphServiceClient<Request> graphClient = GraphServiceClient.builder()
    .authenticationProvider(
        new TokenCredentialAuthProvider(Arrays.asList("https://graph.microsoft.com/.default"), defaultCredential))
    .buildClient();

PasswordCredential passwordCredential = new PasswordCredential();

Long numberOfDaysUntilExpiry = Long.parseLong(System.getenv("NUMBER_OF_DAYS_UNTIL_EXPIRY"));
passwordCredential.displayName = "Set via automation";
passwordCredential.endDateTime = OffsetDateTime.now().plusDays(numberOfDaysUntilExpiry);

// create new client secret
passwordCredential = graphClient.applications(appRegistrationObjectId)
    .addPassword(
        ApplicationAddPasswordParameterSet
            .newBuilder()
            .withPasswordCredential(passwordCredential)
            .build())
    .buildRequest().post();

Finally, it will set a new secret version in Key Vault with the new client secret generated before.

C#
PasswordCredential newPasswordCredential = SetAppRegistrationClientSecret(context, appRegistrationObjectId,
    previousClientSecretKeyId);

KeyVaultSecret newKeyVaultSecret = new KeyVaultSecret(appRegistrationObjectId, newPasswordCredential.secretText);

newKeyVaultSecret.setProperties(new SecretProperties()
    .setContentType(newPasswordCredential.keyId.toString())
    .setExpiresOn(newPasswordCredential.endDateTime));

secretClient.setSecret(newKeyVaultSecret);

Leave a Reply

Your email address will not be published. Required fields are marked *