Its already May and I have not posted anything since my 2024 Resolutions back in January. I have been deep in Azure. Going over the free online training. And getting some hands-on at work. In particular with Azure Identity and Access Management. Along the way I fell into some rabbit holes. Such as Development Containers 🤯. Building a dev container for AEM will get its own post.
The Azure cloud is huge! There are way to many services and tools. And several ways to authenticate. For this post I will focus on Azure storage (blob & queue). And Azure identity with a service principal. With integration from AEM by using the Azure SDK for Java.
Create the Azure Service Principal
The first thing we need to do is create the service principal. John Savill puts out some good Azure training videos. Watch this one on service principals. He will explain it way better than I could.
What I am doing here is creating the app registration. This is the globally unique object for my app. And it lives in my Entra ID tenant. Second, I am creating the service principal. This is local manifestation of the app registration in my tenant.
⚠️ I had to tag the service principal. So that it would show up on the portal as an Enterprise Application. |
You will need to save the output of this command. It contains the app id and client secret (password). You will need this to get a token.
Getting a Token with HttpClient
With the service principal, I now have the basic resource to get OAuth access tokens. Using an OAuth 2.0 client Credentials flow request, this is how I get the token with HttpClient
Here I request a token for my service principal. Scoped to the Storage API. And with the token I can authenticate with the Storage REST API. In fact, there is a REST API for everything in Azure. But working with HttpClient can be very cumbersome. Soon you will find yourself writing a lot of code to integrate with a few REST APIs. I will use the Azure SDK for Java. If for some reason you can't use this SDK, I would suggest using OpenFeign. To help cut down on the code.
Include the Azure SDK for Java
The Azure SDK for Java is modular. That means you only include what you need. It is EXTREMELY well documented. And it is open source. In fact a lot of Microsoft things are now a days. Which means if you have a tough time reading documentation you might have better luck with code. Or vice versa 😆
Here is how I included all the required dependencies into an AEM archetype project. I did this with AEM SDK 2024.4.15977.20240418T174835Z-240400.
In the core bundle pom.xml, add the following sections. Starting with the BOM in the dependencyManagement section.
Next I added the decencies I needed. Jackson for JSON. OkHttp for for the HTTP client. And the Azure identity client library. Notice I am excluding Netty, the default HTTP client. In favor of OkHttp. This was OkHttp has less dependencies to import
And finally, update the bnd-maven-plugin configuration. To include the resources needed by the SDK clients.
This is all we need for now to get credentials. I will add a couple of more client libraries later on.
Getting Credentials
With the basic set of dependencies included in the core bundle, we can begin to use the SDK to get some tokens. I already showed you the basic HttpClient example. Lets build up from there.
First up is the ConfidentialClientApplication. This is part of the MSAL4J client library. The reason I am showing it here is because it is the underlying library used by the Azure Identity client. And is useful for acquiring tokens.
Next up is ClientSecretCredential. We are now in the Azure identity client library. The ClientSecretCredential implements the TokenCredential interface. It is important to note that there are are other implementations. And this is what the different service clients will use to get access tokens.
But with so many implementations, which one do we choose? And when? That is where DefaultAzureCredential comes in useful.
⚠️ Earlier I created a service principal. And received a tenant id, client id, and password. To resolve the credential, the DefaultAzureCredential will need these in 3 environment variables. AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET. |
Our code becomes much simpler. The underlying implementation will iterate through a list of possible credential sources. And use the first match. For example, if you are running AEM outside of Azure, and given the correct environment variables, it will use EnvironmentCredential. Or if you are running AEM on Azure VMs with managed identities, it will use the ManagedIdentityCredential. Or if you are running on your local machine, and you have logged in through the Azure CLI, it will use ManagedIdentityCredential.
The DefaultAzureCredential should work in most cases. But it can be a little inflexible. For example I could not find a way to pass the client secret, id and tenant. Except through environment variables. And that may be ok because you can set environment variables through Cloud Manager. If for some reason you can't, you can use the credential builder that better suits your needs.
Create the Storage Account, Container & Queue
The service principal can only get created with the CLI or PowerShell. But for resources, there are many different ways. I learned about ARM templates. Bicep templates. Azure PowerShell and Azure CLI. Again, I have chosen Azure CLI because it is simpler.
Here I am creating a resource group. All resources need to go into a group. Next I create the storage account. And finally the blob container and queue within the storage account. Those are 2 of the 4 types of storage an account can host.
There is one last step, that is setting the role-based access control. So that the service principal has access to the container and queue. But I won't get to that until later.
The Blob Service Client
If we are going to integrate with the blob container, we will need the correct client dependency. In this case azure-storage-blob. Add the following dependency. And the bnd-maven-plugin configuration above should include it into the core bundle.
And I wrote this test servlet that will upload a blob. And list the contents of the container.
But if you run this code, you will get an access error. The final step is assigning the role to the service principal. We assign the least amount of access required. The "Storage Blob Data Contributor", scoped to the container.
The Queue Client
Integrating with the queue client is also a three-step process. First add the dependency to the queue client, azure-storage-queue.
Then update the servlet by including the code to enqueue a message. Using the same credential.
And assign the least amount of access required for the service principal to do the job. In this case the "Storage Queue Data Message Sender". We are assuming that the servlet will only enqueue message. And that some other process will dequeue them and do something.
Conclusion
On the Azure side, we created a service principal. The required Azure resources. In this case a storage account with a queue and blob container. And assigned the necessary roles to the principal for it to do its job.
On the AEM side, we included the Azure SDK for Java into our core project bundle. And its required dependencies. Set the correct environment variables for DefaultAzureCredential to resolve the service principal credential. And leveraged the SDK clients to do what we needed.
The important take-ways here are these. First its Azure's RBAC. It allows granular access to anything. And it is customizable. I could have used a Shared Access Token (SAS) to connect to the storage account. But that solution would only be good for storage. As opposed to OAuth and RBAC. Which is the global security solution. Second, the Azure SDK for Java. It lets you integrate with every Azure service that has a REST API. It is modular, well documented. And very intuitive. As opposed to plain old REST API integration with an HTTP client.
Comments