Azure · Azure Communication Service · Azure Container Registry · AzureContainerApps · Bicep · Bicep · IAC · Identity · Infrastructure As Code · managed-identity · ServicePrincipal

Azure Communication Service with Managed Identity

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.

Leave a comment