Background
A few months ago, I have written a Demo application that shows how to send email and SMS via Azure Communication Service. The code was written in Spring Boot (Java) and hosted on Azure Container Apps. During the demo, I have used Connection strings of Azure Communication Service which is not ideal for production scenarios. Today I will share the code to use User Assigned Managed Identity of Azure Container Apps to make the workflow secret-less.
Identity Packages
We will start adding the identity packages into the application to be able to use Managed Identity. Let’s modify the POM.xml
:
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-communication-identity</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.2.3</version>
</dependency>
With that, we will use the Token credentials instead of connection strings.
Credentials
Let’s write a supporting method that returns a Token credential instead of connection strings:
public TokenCredential getCredential() {
TokenCredential tokenCredentail;
var userAssignedManagedIdentity = environment.getProperty("AZ_UAMI_CLIENTID", "");
if (userAssignedManagedIdentity == "") {
tokenCredentail = new DefaultAzureCredentialBuilder().build();
} else {
tokenCredentail = new DefaultAzureCredentialBuilder()
.managedIdentityClientId(userAssignedManagedIdentity)
.build();
}
return tokenCredentail;
}
Here, I am using AZ_UAMI_CLIENTID
an environment variable that read the client id of user assigned managed identity. We can configure this into the Azure Container Apps.
Sending emails
Now we can send email with that credentail,
var client = new EmailClientBuilder()
.endpoint(acsEndpoint)
.credential(credential)
.buildClient();
EmailMessage emailMessage = new EmailMessage()
.setSenderAddress(senderAddress)
.setToRecipients(email.to())
.setSubject(email.subject())
.setBodyPlainText(email.content());
SyncPoller<EmailSendResult, EmailSendResult> poller = client.beginSend(emailMessage);
Sending SMS
Likewise, sending SMS will be as following:
SmsClient smsClient = new SmsClientBuilder()
.endpoint(acsEndpoint)
.credential(credential)
.buildClient();
SmsSendResult sendResult = smsClient.send(
smsPayload.from(),
smsPayload.to(),
smsPayload.content());
Infrastructure as code
Let’s create the User assigned managed identity and assign that to the Azure Container apps which will host the above-code as Docker Image. I will be using Bicep for that.
Let’s create a user-assigned-managed-identity.bicep file:
@description('Specifies the name of the user assigned managed identity.')
param uamiName string
param location string = resourceGroup().location
resource uami 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = {
name: uamiName
location: location
}
output principalId string = uami.properties.principalId
output tenantId string = uami.properties.tenantId
output clientId string = uami.properties.clientId
With that Managed Identity, we will have assign roles (I will go for Contributor role) to Azure Communication service.
@description('This is the built-in Contributor role. See https://docs.microsoft.com/azure/role-based-access-control/built-in-roles')
var roleDefinitionId = 'b24988ac-6180-42a0-ab88-20f7382dd24c'
resource roleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
name: roleDefinitionId
}
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: communicationService
name: guid(communicationservicename)
properties: {
roleDefinitionId: roleDefinition.id
principalId: uami.properties.principalId
}
}
Now we can create the Azure Container App. I will not go step-by-step here, as I have written that part before extensively.
resource uami 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' existing = {
name: userAssignedIdentityName
}
resource environment 'Microsoft.App/managedEnvironments@2022-03-01' existing = {
name: environmentName
}
resource containerApp 'Microsoft.App/containerApps@2022-03-01' = {
name: containerAppName
location: location
identity: hasIdentity ? {
type: 'UserAssigned'
userAssignedIdentities: {
'${uami.id}': {}
}
} : null
properties: {
managedEnvironmentId: environment.id
configuration: {
activeRevisionsMode: revisionMode
secrets: secrets
registries: isPrivateRegistry ? [
{
server: containerRegistry
identity: useManagedIdentityForImagePull ? uami.id : null
username: useManagedIdentityForImagePull ? null : containerRegistryUsername
passwordSecretRef: useManagedIdentityForImagePull ? null : registryPassword
}
] : null
ingress: enableIngress ? {
external: isExternalIngress
targetPort: containerPort
transport: 'auto'
traffic: [
{
latestRevision: true
weight: 100
}
]
} : null
dapr: {
enabled: true
appPort: containerPort
appId: containerAppName
}
}
template: {
revisionSuffix: sanitizedRevisionSuffix
containers: [
{
image: containerImage
name: containerAppName
env: env
}
]
scale: {
minReplicas: minReplicas
maxReplicas: 1
}
}
}
}
output fqdn string = enableIngress ? containerApp.properties.configuration.ingress.fqdn : 'Ingress not enabled'
That is it.
Conclusion
This is just a quick hint for somebody who is using Java to send email/SMS using Azure Communication Service. The entire source can be found here.