This is Part 2 of a 3-part series that explores how to give our enterprise Java + Spring applications identities of their own via Azure AD managed identities. In this article, you will learn how to establish managed identities for a Spring Boot application deployed on Azure Kubernetes Service.
This second article continues our three-part series exploring how Azure AD managed identities help applications connect to services. Using managed identities eliminates the need to manage credentials for every application-connected service and helps protect these services from credential-related exploits. In the previous article, we explored how to use managed identities with Azure App Service. However, they work with a wide variety of services.
Here, we’ll guide you through establishing managed identities for a Spring Boot application deployed on Azure Kubernetes Service. The Spring Boot service will connect to an Azure Cosmos DB database, read data, and display it on a web page. We'll create an application-specific identity to access the required database.
Prerequisites
To follow this tutorial, you should know Java, and you'll need to set up a few things first:
- Access to Azure Cloud Platform. It’s free to sign up for 12 months of access to popular services and offers a $200 credit.
- Azure CLI installed on your local machine.
- The Rkord application. This demo Spring Boot application for making user notes is developed using Spring MVC, the Thymeleaf framework, and a database. Previously, we used this application with Azure App Service in the first article of this series. Here, we’ll refactor and integrate the same application with Cosmos DB.
You can find the complete code for this article’s version of the Rkord application on GitHub.
Creating a Spring Boot Application
First, refactor the Rkord application to integrate Cosmos DB instead of SQL Server. Perform cleanup by removing Spring Data JPA and SQL Server dependencies and the associated properties. The code will raise compilation errors for JPA annotations and the JPA repository.
Next, add the Spring Boot Cosmos DB starter dependency. This action will also import the required Cosmos DB libraries.
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>azure-spring-boot-starter-cosmos</artifactId>
<version>3.11.0</version>
</dependency>
Updating the Notes Entity
Next, update the Notes
entity with the Container
annotation. Also, update the ID
property to the String
data type instead of the int
data type. Additionally, add the Id
and GeneratedValue
annotations like this:
@Container(containerName = "notescollection")
public class Notes {
@Id
@GeneratedValue
private String id;
private String message;
}
Setting up the Notes Repository
The project adopts the Spring Data programming model to remove the boilerplate code for accessing Cosmos
DB collections. So, you only need to create an entity-specific interface by extending the ReactiveCosmosRepository
interface like this:
public interface NotesRepository extends ReactiveCosmosRepository<Notes,String> {
}
Using the Azure Cosmos DB Service
The above changes would compile the application code, but the tests would still fail. The application cannot start due to the missing Cosmos DB details. You need to connect to an instance of Cosmos DB to build and deploy the application successfully.
So, in the Azure portal, create an instance using the Azure Cosmos DB service. Provide the server name, location, and associated resource group.
Also, provide the provisioned capacity for the instance. The serverless model is suitable for development and testing needs. Alternatively, select the provisioned throughput based on the application’s needs.
Finally, click the Create button on the Review screen to generate an instance based on the specified configuration.
After generating the instance, note the access keys. Next, update application.properties with the details (location, key, and database name) of the instance generated above.
# Specify the DNS URI of your Azure Cosmos DB.
azure.cosmos.uri=https:
azure.cosmos.key=uXNFTFcAUwzAq4o7ds1l9hnhKnNnIVNrJ6fqWtvLWYhrIKKgHkRtCwdJ2PjwJIkbA216DzM8E2wyYcugGAUYOw==
azure.cosmos.database=rkord
The test should pass, providing a successful build. You can now deploy the application on your local machine.
Setting Up Azure Kubernetes Service
Now we want to deploy the application on Azure Kubernetes Service (AKS). So, create a cluster using the Azure Kubernetes Service management console.
The console opens a dialog with various configuration options. Azure provides multiple configuration presets for diverse availability needs. You can select the dev/ Test configuration to create a cluster with minimal configuration.
Provide a valid cluster name, review the configuration, and click Create.
After generating the cluster, you must update the kubeconfig file with its access details. You can use the CLI like this:
az aks get-credentials --resource-group rkord-notes-rg --name rkord-dev-aks
After updating the configuration, you can work with the cluster using the kubectl
command:
kubectl get deployments --all-namespaces=true
Deploying the Application
We can only deploy Kubernetes applications as Pods and Services. Each Pod is an application container downloaded from a container registry. We can integrate AKS with any container registry, including Docker, Harbor, and Quay.
AKS also provides a deployment console for integrating projects with the Azure Container Registry. The integration requires a project-specific Dockerfile, so add the following file to the project:
FROM azul/zulu-openjdk:11
COPY target/rkord.jar rkord.jar
EXPOSE 8080
CMD ["java", "-jar","/rkord.jar"]
Note that the Dockerfile refers to the project artifact as rkord.jar. You need to specify the name using the finalName
XML tag in the POM file’s build section:
<finalName>rkord</finalName>
Next, integrate Azure Container Registry using the Deployment console. The process will generate template deployment and service files in the manifest folder of the project directory. It will then build the project and deploy services to the cluster. After successful deployment, it will provide the application access URL from the Services and ingresses console. Access the applications using the specified IP and port.
Enabling Managed Identity
In the previous section, we successfully deployed the Rkord application. However, the application code contains the access keys to the Cosmos DB, putting our database at risk of unauthorized access. We should enable managed identity for the environment and remove the credentials from the code.
Let's start by configuring managed identity for our Kubernetes cluster:
az aks update --resource-group rkord-notes-rg --name rkord-dev-aks --enable-managed-identity
The managed identity service provisions the following credentials:
- Kubernetes service credential, used to access the API server
- Kubelet service credential, used by Nodes to access other services
The application Pod will use the Kubelet service credential to access Cosmos DB. So, determine the kubelet service principal using this command:
az aks show --resource-group rkord-notes-rg --name rkord-dev-aks
--query "identityProfile.kubeletidentity"
Enabling Cosmos RBAC
Next, we must allow Cosmos DB access for the above service principal. Cosmos DB employs role-based access control (RBAC). It has some built-in roles, but you can also customize roles for your application’s needs.
First, determine the built-in roles:
az cosmosdb SQL role definition list --account-name rkord --resource-group rkord-notes-rg
The contributor role provides full access to the database. Note the role definition ID. Then, enable the grant for the Kubelet service principal using this command:
az cosmosdb sql role assignment create --account-name rkord --resource-group rkord-notes-rg
--scope "/" --principal-id 00b01194-00a6-4dca-9ac0-0ba8a6a84a31
--role-definition-id /subscriptions/9b55b4b6-5a78-420f-a669-fdb43873b0ce/resourceGroups/
rkord-notes-rg/providers/Microsoft.DocumentDB/databaseAccounts/rkord/sqlRoleDefinitions/
00000000-0000-0000-0000-000000000002
Integrating Managed Identity
The Azure Identity SDK provides the required abstraction to load the identity. So, add the library to the set of project dependencies:
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.3.7</version>
</dependency>
The Cosmos DB Spring Boot starter doesn’t provide a way to configure the managed identity. So, we must refactor the code to load the managed identity explicitly. On the other hand, the test uses access keys to connect to the database. So, the application must support different ways to connect to Cosmos DB based on different environments.
Spring profiles provide the necessary support to achieve this diverse connection ability. Start by disabling the automatic Cosmos DB bean creation and create various environment-specific configurations:
@SpringBootApplication(exclude = CosmosAutoConfiguration.class)
public class RkordApplication {
public static void main(String[] args) {
SpringApplication.run(RkordApplication.class, args);
}
}
Now, create the following configuration class to load the managed identity (using ManagedIdentityCredential
). The configuration then passes these managed identity credentials to the Cosmos DB client. The configuration class requires properties for the Cosmos
DB location and database name, so add these properties to the application.properties file in the src/main/resources directory.
@Profile("prod")
@Configuration
public class CosmosDBConfig extends AbstractCosmosConfiguration {
@Value("${cosmos.dbname}")
private String dbname;
@Value("${cosmos.loc}")
private String url;
protected String getDatabaseName() {
return dbname;
}
@Bean
public ManagedIdentityCredential dbCredential() {
ManagedIdentityCredential managedIdentityCredential =
new ManagedIdentityCredentialBuilder()
.build();
return managedIdentityCredential;
}
@Bean
public CosmosClientBuilder cosmosClientBuilder(ManagedIdentityCredential dbCredential) {
CosmosClientBuilder cosmosClientBuilder = new CosmosClientBuilder();
cosmosClientBuilder.credential(dbCredential).endpoint(url);
return cosmosClientBuilder;
}
}
The above class is marked with profile annotations and thus works only when the respective Spring profile is activated. So, activate the container’s profile by modifying CMD
in the Dockerfile, like this:
CMD ["java", "-jar", "-Dspring.profiles.active=prod","/rkord.jar"]
Additionally, create a CosmosDBTestConfig
class that loads access keys and connects to Cosmos
DB using AzureKeyCredential
. Also, update the test-based application.properties for the associated properties.
@Configuration
public class CosmosTestConfig extends AbstractCosmosConfiguration {
@Value("${cosmos.dbname}")
private String dbname;
@Value("${cosmos.loc}")
private String url;
@Value("${cosmos.key}")
private String key;
protected String getDatabaseName() {
return dbname;
}
@Bean
public AzureKeyCredential dbCredential() {
return new AzureKeyCredential(key);
}
@Bean
public CosmosClientBuilder cosmosClientBuilder(AzureKeyCredential dbCredential) {
CosmosClientBuilder cosmosClientBuilder = new CosmosClientBuilder();
cosmosClientBuilder.credential(dbCredential).endpoint(url);
return cosmosClientBuilder;
}
}
Push the changes to the GitHub repository to invoke building and deployment. After successful deployment, determine the application URL from the AKS ingress console.
Summary
We've now explored how managed identity enables containerized Spring Boot web apps deployed on Azure Kubernetes Service. Since the process eliminates the need for credentials, it eliminates the challenges around secured credentials exchange. This way, developers can remain focused on the business they need to support rather than handling diverse deployment challenges like updating and securing credentials.
Managed identities do more than connect to applications running on Azure App Service or Azure Kubernetes Service. Continue to the third and final part of this series to learn how to use Azure AD managed identities to obtain key store credentials.
To learn more about Java Azure Identity library, check out Azure authentication with Java and Azure Identity.