Securely Connecting Azure Container Apps to Azure OpenAI Using User Managed Identity

Yoshio Terada - Sep 16 - - Dev Community

Today, I want to share two key points from this entry:

  1. Deploying Java Applications to Azure Container Apps Without Containerization
  2. Secure Connection from Azure Container Apps to Azure OpenAI Using User Managed Identity

The source code used here can be found in the following GitHub repository.
GitHub: Secure-OpenAI-Java-App-on-Azure-Container-Apps

1. Deploying Java Applications to Azure Container Apps Without Containerization

On September 11, 2024, the announcement "Announcing the General Availability of Java Experiences on Azure Container Apps" was made.

As detailed in the Java on Azure Container Apps overview, support for Java on Azure Container Apps has been enhanced. For example, Azure Container Apps now supports the following Spring components as managed services:

  • Eureka Server for Spring
  • Config Server for Spring
  • Admin for Spring

Additionally, as explained in the Quickstart: Launch Your First Java Application in Azure Container Apps, Azure Container Apps now offers a new feature called Cloud Build Service. This allows you to deploy applications directly to Azure Container Apps from Java artifacts like JAR or WAR files. The service automatically creates and deploys container images from the specified Java artifacts, eliminating the need to manually write Dockerfile container definitions or handle container builds and pushes.

To achieve this with Azure Container Apps, you use the az containerapp up command and specify the Java artifact as an argument. Detailed steps are provided later in the guide (see: 2.8 Creating an Azure Container Apps Instance).

This significantly simplifies deploying Java applications to Azure Container Apps. Azure Container Apps can also scale the number of instances from zero as needed, making it a highly convenient service. We encourage you to try it.

2. Securely Connecting Azure Container Apps to Azure OpenAI Using User Managed Identity

In recent times, security measures have become increasingly important, and it is essential for businesses to build more secure systems. Microsoft recommends using Managed Identity for connections instead of password-based access when creating secure environments, such as production environments. This approach utilizes Microsoft Entra ID authentication.

This method allows you to grant specific permissions for resources within a defined scope, making security management more flexible. For more details on Managed Identity, refer to the article "What are Managed Identities for Azure Resources?". In this entry, I will clearly explain how to set up a User Managed Identity, step by step.

Following these steps will help you understand how to configure it, and they can also serve as a reference for setting up other resources.

Steps to Set Up User Managed Identity

To connect Azure Container Apps to Azure OpenAI using a User Managed Identity, follow these steps to set up the environment:

  1. Set Environment Variables
  2. Create a Resource Group
  3. Create an Azure OpenAI Instance
  4. Create a User Managed Identity
  5. Assign Roles to the User Managed Identity for Azure OpenAI
  6. Create an Azure Container Apps Environment
  7. Develop a Spring Boot Web Application
  8. Create an Azure Container Apps Instance
  9. Assign the User Managed Identity to Azure Container Apps
  10. Verify the Setup

2.1. Set Environment Variables

When setting up the environment, configure environment variables to avoid repetitive input.

export RESOURCE_GROUP=yoshio-OpenAI-rg
export LOCATION=eastus2
export AZURE_OPENAI_NAME=yt-secure-openai
export OPENAI_DEPLOY_MODEL_NAME=gpt-4o
export USER_MANAGED_IDENTITY_NAME=yoshio-user-managed-id
export SUBSCRIPTION=$(az account show --query id --output tsv)
Enter fullscreen mode Exit fullscreen mode

Below are the environment variable names and their descriptions. If you need to change the names to suit your environment, please refer to the descriptions below to modify each resource name accordingly.

Environment Variable Name Description
RESOURCE_GROUP Name of the resource group to create
LOCATION Location where the environment will be set up
USER_MANAGED_IDENTITY_NAME Name of the User Managed Identity
AZURE_OPENAI_NAME Name of the Azure OpenAI
OPENAI_DEPLOY_MODEL_NAME Name of the AI model to be deployed
SUBSCRIPTION Subscription ID to be used

2.2. Create a Resource Group

First, create a resource group in the Azure environment. Use the --location argument to specify the Azure region where it will be created.

az group create --name $RESOURCE_GROUP --location $LOCATION
Enter fullscreen mode Exit fullscreen mode

2.3. Create an Azure OpenAI Instance

Next, create an Azure OpenAI instance by running the az cognitiveservices account create command. In this step, specify --kind OpenAI and --custom-domain for the instance.

az cognitiveservices account create \
  --name $AZURE_OPENAI_NAME \
  --resource-group $RESOURCE_GROUP \
  --kind OpenAI \
  --custom-domain $AZURE_OPENAI_NAME \
  --sku S0 \
  --location $LOCATION
Enter fullscreen mode Exit fullscreen mode

Note:

It is crucial to specify --custom-domain when creating an Azure OpenAI instance.
If omitted, a region endpoint like https://eastus2.api.cognitive.microsoft.com/ will be automatically generated. As outlined in "Authenticate with Microsoft Entra ID", you cannot perform authentication using Microsoft Entra ID, in this case Managed Identity, without specifying this option. To enable Managed Identity authentication, you need to specify --custom-domain.

Next, deploy the OpenAI model to your newly created Azure OpenAI instance using the az cognitiveservices account deployment create command. Here, specify the model name with --model-name and the model version with --model-version. You should also define the service capacity using --sku-capacity and select the service plan with --sku-name.

az cognitiveservices account deployment create \
  --name $AZURE_OPENAI_NAME \
  --resource-group  $RESOURCE_GROUP \
  --deployment-name $OPENAI_DEPLOY_MODEL_NAME \
  --model-name $OPENAI_DEPLOY_MODEL_NAME \
  --model-version "2024-08-06"  \
  --model-format OpenAI \
  --sku-capacity "20" \
  --sku-name "GlobalStandard"
Enter fullscreen mode Exit fullscreen mode

With this, the creation of the Azure OpenAI instance is complete. After creating the instance, store the necessary information for your Java program implementation and other operations in environment variables.

export OPEN_AI_RESOURCE_ID=$(az cognitiveservices account list \
                                    -g $RESOURCE_GROUP \
                                    --query "[0].id" \
                                    --output tsv)
export OPEN_AI_ENDPOINT=$(az cognitiveservices account show \
                                    -g $RESOURCE_GROUP \
                                    --name $AZURE_OPENAI_NAME \
                                    --query "properties.endpoint" \
                                    --output tsv)
export OPEN_AI_ACCESS_KEY=$(az cognitiveservices account keys list \
                                    -g $RESOURCE_GROUP \
                                    --name $AZURE_OPENAI_NAME \
                                    --query key1 --output tsv)
Enter fullscreen mode Exit fullscreen mode

Here, we are setting the following environment variables:

Environment Variable Name Description
OPEN_AI_RESOURCE_ID Resource ID of OpenAI
(Needed for role assignment scope)
OPEN_AI_ENDPOINT Endpoint of OpenAI
(Required for Java app connection)
OPEN_AI_ACCESS_KEY Access key for OpenAI
(Needed for Java app development locally)

2.4. Create a User Managed Identity

Now that the Azure OpenAI instance is set up, the next step is to create a User Managed Identity. Use the az identity create command.

az identity create -g $RESOURCE_GROUP -n $USER_MANAGED_IDENTITY_NAME
Enter fullscreen mode Exit fullscreen mode

Once the User Managed Identity is created, retrieve the necessary information for future command execution and Java program usage, and assign it to the environment variables.

export USER_MANAGED_ID_CLIENT_ID=$(az identity list \
                                            -g $RESOURCE_GROUP \
                                            --query "[0].clientId" \
                                            -o tsv)
export USER_MANAGED_ID_PRINCIPAL_ID=$(az identity list \
                                            -g $RESOURCE_GROUP \
                                            --query "[0].principalId" \
                                            -o tsv)
export USER_MANAGED_ID_RESOURCE_ID=$(az identity list \
                                            -g $RESOURCE_GROUP \
                                            --query "[0].id" \
                                            -o tsv)
Enter fullscreen mode Exit fullscreen mode

Below is an explanation of each environment variable's value and its usage:

Environment Variable Name Description
USER_MANAGED_ID_CLIENT_ID Client ID of the User Managed Identity
(Needed for Java app implementation)
USER_MANAGED_ID_PRINCIPAL_ID Principal ID of the User Managed Identity
(Required for role assignment)
USER_MANAGED_ID_RESOURCE_ID Resource ID of the User Managed Identity
(Needed for assigning ID to Container Apps)

2.5. Assign Roles to User Managed Identity for Azure OpenAI

Use the az role assignment create command to assign a role to the User Managed Identity, allowing it to interact with the OpenAI resource using the Cognitive Services OpenAI User permission.

The $OPEN_AI_RESOURCE_ID represents the OpenAI resource ID, and the role is assigned specifically to that resource. This method grants only the necessary permissions for the app to run, enhancing security by avoiding unnecessary permissions.

az role assignment create --assignee $USER_MANAGED_ID_PRINCIPAL_ID \
                              --scope $OPEN_AI_RESOURCE_ID \
                              --role "Cognitive Services OpenAI User"
Enter fullscreen mode Exit fullscreen mode

In addition to the roles mentioned, you can also assign the following roles:

  • Cognitive Services OpenAI User
  • Cognitive Services OpenAI Contributor
  • Cognitive Services Contributor
  • Cognitive Services Usages Reader

For detailed information about the capabilities of each role, please refer to the Role-based Access Control for Azure OpenAI Service.

2.6. Create an Azure Container Apps Environment

Next, create an Azure Container Apps Environment. To do this using the Azure CLI, you need to register additional extensions and providers. If you haven't executed the following commands before, please execute them now.

az upgrade
az extension add --name containerapp --upgrade -y
az provider register --namespace Microsoft.Web
az provider register --namespace Microsoft.App
az provider register --namespace Microsoft.OperationalInsights
Enter fullscreen mode Exit fullscreen mode

Then, define the name of the Azure Container Apps Environment as an environment variable. Choose a name that suits your environment.

export CONTAINER_ENVIRONMENT=YTContainerEnv3
Enter fullscreen mode Exit fullscreen mode

Finally, run the az containerapp env create command to set up the environment.

az containerapp env create --name $CONTAINER_ENVIRONMENT \
                               --enable-workload-profiles \
                               -g $RESOURCE_GROUP \
                               --location $LOCATION
Enter fullscreen mode Exit fullscreen mode

2.7. Creating a Spring Boot Web Application

With the Azure OpenAI and Azure Container Apps Environment set up, we will now create a Java project to implement a simple app that invokes an OpenAI model from a Java Application. We will use Spring Boot for this implementation.

2.7.1 Creating a Spring Boot Project

Execute the following command to create a Spring Boot project. After creating and downloading the project, unzip the archive to extract its contents.

curl https://start.spring.io/starter.zip \
  -d dependencies=web \
  -d type=maven-project \
  -d language=java \
  -d bootVersion=3.3.3 \
  -d baseDir=Yoshio-AI-App-Spring-Boot \
  -d groupId=com.yoshio3 \
  -d artifactId=Yoshio-AI-App \
  -d name=myproject \
  -d packageName=com.yoshio3 \
  -o YoshioAIProject.zip

unzip YoshioAIProject.zip
Enter fullscreen mode Exit fullscreen mode

The above command will generate a project with the following directory structure.

├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── yoshio3
    │   │           └── MyprojectApplication.java
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── java
            └── com
                └── yoshio3
                    └── MyprojectApplicationTests.java
Enter fullscreen mode Exit fullscreen mode

2.7.2 Editing the pom.xml Project File

Add the following dependencies to the pom.xml file located in the root directory. This will include the necessary libraries for connecting to and authenticating with OpenAI.

<dependencies>
    ......
    <dependency>
        <groupId>com.azure</groupId>
        <artifactId>azure-ai-openai</artifactId>
        <version>1.0.0-beta.11</version>
    </dependency>
    <dependency>
        <groupId>com.azure</groupId>
        <artifactId>azure-identity</artifactId>
        <version>1.13.2</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>2.0.16</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>1.5.8</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.5.8</version>
    </dependency>
</dependencies>
Enter fullscreen mode Exit fullscreen mode

2.7.3 Implementing a RESTful Endpoint (Main Part)

Next, create an AIChatController.java file in the src/main/java/com/yoshio3 directory. Implement the following code to define a RESTful endpoint that queries OpenAI upon receiving a request.

package com.yoshio3;

import org.springframework.web.bind.annotation.RestController;
import com.azure.ai.openai.OpenAIClient;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.ai.openai.models.ChatCompletions;
import com.azure.ai.openai.models.ChatCompletionsOptions;
import com.azure.ai.openai.models.ChatRequestAssistantMessage;
import com.azure.ai.openai.models.ChatRequestMessage;
import com.azure.ai.openai.models.ChatRequestSystemMessage;
import com.azure.ai.openai.models.ChatRequestUserMessage;
import com.azure.core.credential.AzureKeyCredential;
import com.azure.identity.ManagedIdentityCredential;
import com.azure.identity.ManagedIdentityCredentialBuilder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;


@RestController
public class AIChatController {

    private static final Logger LOGGER = LoggerFactory.getLogger(AIChatController.class);

    @Value("${USER_MANAGED_ID_CLIENT_ID}")
    private String userManagedIDClientId;

    @Value("${OPENAI_ENDPOINT}")
    private String openAIEndpoint;

    @Value("${OPENAI_KEY}")
    private String openAIKey;

    private static final String OPEN_AI_CHAT_MODEL = "gpt-4o";

    /**
     * This API is used to chat with OpenAI's GPT-4 model. And if user ask somethings, it will
     * return the message with Pirate language.
     * 
     * Ex. You can invoke the API by using the following command: curl -X POST
     * http://localhost:8080/askAI -H "Content-Type: application/json" -d '{"message":"Please tell
     * me about the appeal of Spring Boot in Japanese."}'
     * 
     * @param message RequestMessage
     * @return String Response from OpenAI
     */

    @PostMapping("/askAI")
    public String chat(@RequestBody RequestMessage message) {
        return getResponseFromOpenAI(message.getMessage());
    }

    /**
     * This method is used to get the response from OpenAI.
     * 
     * For production environment, you can use Managed Identity to authenticate with OpenAI. If you
     * want to use Managed Identity, please use the ManagedIdentityCredentialBuilder.
     * 
     * For local development, you can use AzureKeyCredential to authenticate with OpenAI.
     * 
     * @param message RequestMessage
     * @return String Response from OpenAI
     */

    private String getResponseFromOpenAI(String message) {
        try {
            // Create OpenAI client with User Managed Identity
            ManagedIdentityCredential credential =
                    new ManagedIdentityCredentialBuilder().clientId(userManagedIDClientId).build();
            OpenAIClient openAIClient = new OpenAIClientBuilder().credential(credential)
                    .endpoint(openAIEndpoint).buildClient();

            // Create OpenAI client without Managed Identity (For local development)
            // OpenAIClient openAIClient = new OpenAIClientBuilder().endpoint(openAIEndpoint)
            // .credential(new AzureKeyCredential(openAIKey)).buildClient();

            // Create Chat Request Messages
            List<ChatRequestMessage> chatMessages = new ArrayList<>();
            chatMessages.add(new ChatRequestSystemMessage("You are a helpful assistant. You will talk like a pirate."));
            chatMessages.add(new ChatRequestUserMessage("Can you help me?"));
            chatMessages.add(new ChatRequestAssistantMessage("Of course, me hearty! What can I do for ye?"));
            chatMessages.add(new ChatRequestUserMessage(message));
            ChatCompletionsOptions chatCompletionsOptions = new ChatCompletionsOptions(chatMessages);

            // Invoke OpenAI Chat API
            ChatCompletions chatCompletions = openAIClient.getChatCompletions(OPEN_AI_CHAT_MODEL, chatCompletionsOptions);
            StringBuilder response = new StringBuilder();
            chatCompletions.getChoices()
                    .forEach(choice -> response.append(choice.getMessage().getContent()));

            return response.toString();
        } catch (Exception e) {
            StackTraceElement[] stackTrace = e.getStackTrace();
            LOGGER.error(e.getMessage());
            LOGGER.error(e.getLocalizedMessage());
            Throwable cause = e.getCause();
            if (cause != null) {
                LOGGER.error(e.getCause().toString());
            }
            for (StackTraceElement element : stackTrace) {
                LOGGER.error(element.toString());
            }
            return e.getMessage();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Note that the OpenAIClient instance is described in two different ways. The current code uses ManagedIdentityCredential to connect to Azure OpenAI with a User Managed Identity. This code works only when running in an Azure environment and won't work in a local or non-Azure environment.

During development, you need to test and verify functionality locally. In such cases, a User Managed Identity cannot be used. Instead, connect using the OpenAI Access Key by uncommenting the line with AzureKeyCredential(openAIKey) to create the OpenAIClient instance.

Additionally, SLF4J and Logback are used for logging in the implementation. Configure them by creating a logback-spring.xml file in the /src/main/resources directory. While detailed logging configuration is not covered here, the original code is available on GitHub for reference if needed.

Finally, here's a brief overview of the code: When a question or message is received from a user, it responds in a pirate tone as defined by SYSTEM. Enjoy the pirate-style replies!

2.7.4 Defining the JSON Format for Endpoint Reception

Next, define the JSON data format to be sent to this RESTful service. To process messages like {"message":"What is the benefit of Spring Boot"} in the HTTP request body, define the following class.

package com.yoshio3;

public class RequestMessage {
    private String message;

    public void setMessage(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}
Enter fullscreen mode Exit fullscreen mode

With this, we have completed the minimal code necessary for operation verification.

2.7.5 Application Configuration

Next, configure the settings to connect to Azure OpenAI. During the systems setup from sections 2.1 to 2.6, all required information was stored in environment variables. Execute the following command to retrieve the necessary information in the Java program.

echo "USER_MANAGED_ID_CLIENT_ID" : $USER_MANAGED_ID_CLIENT_ID
echo "OPEN_AI_ENDPOINT" : $OPEN_AI_ENDPOINT
echo "OPEN_AI_ACCESS_KEY" : $OPEN_AI_ACCESS_KEY
Enter fullscreen mode Exit fullscreen mode

Write the execution results above into the application.properties file located in the /src/main/resources/ directory.

spring.application.name=AI-Chatbot
logging.level.root=INFO
logging.level.org.springframework.web=INFO

USER_MANAGED_ID_CLIENT_ID=********-****-****-****-************
OPENAI_ENDPOINT=https://********.openai.azure.com/
OPENAI_KEY=********************************
Enter fullscreen mode Exit fullscreen mode

Note:

OPENAI_KEY is the access key for OpenAI. Use it only during development in a development environment and avoid using it in a production environment.

2.7.6 (Optional): Verifying Operation in a Local Environment

To verify if the Java program works locally, swap the comments in the OpenAIClient instance creation part of the AIChatController class code and run it.

// ManagedIdentityCredential credential = new ManagedIdentityCredentialBuilder().clientId(userManagedIDClientId).build();
// OpenAIClient openAIClient = new OpenAIClientBuilder().credential(credential)
//                    .endpoint(openAIEndpoint).buildClient();

 OpenAIClient openAIClient = new OpenAIClientBuilder().endpoint(openAIEndpoint)
                       .credential(new AzureKeyCredential(openAIKey)).buildClient();
Enter fullscreen mode Exit fullscreen mode

After making the changes, execute the following command.

mvn spring-boot:run
Enter fullscreen mode Exit fullscreen mode

If the Spring Boot application starts successfully, it will be listening on port 8080. Use the following curl command to verify if you can query Azure OpenAI.

curl -X POST http://localhost:8080/askAI \
     -H "Content-Type: application/json" \
     -d '{"message":"What is the benefit of the Spring Boot?"}'
Enter fullscreen mode Exit fullscreen mode

The response will vary each time you run the command, but it will reply in a pirate tone, for example.

Aye, Captain! Let me explain the benefits of Spring Boot.
Spring Boot is a framework that aids rapid application development. 
It allows you to create standalone applications with minimal configuration.

It also comes with auto-configuration, reducing manual setup. Managing dependencies is easy, and it includes commonly used libraries.
It's fast to start up, easy to deploy, and suitable for cloud operations. 

It's a powerful tool for boosting developer productivity.
Anything else I can assist with?
Enter fullscreen mode Exit fullscreen mode

After completing local verification, revert the changes to generate the OpenAIClient instance using ManagedIdentityCredential.

2.7.7 Building the Application and Creating Artifacts

Once these steps are complete, build the application and create the artifacts.

mvn clean package
Enter fullscreen mode Exit fullscreen mode

Upon building the project, the artifacts will be created in the target directory. Here, the Spring Boot application is created as Yoshio-AI-App-0.0.1-SNAPSHOT.jar.

> ls -l target 
total 40876
-rw-r--r-- 1 teradayoshio wheel 41847613  9 14 14:52 Yoshio-AI-App-0.0.1-SNAPSHOT.jar
-rw-r--r-- 1 teradayoshio wheel     7003  9 14 14:52 Yoshio-AI-App-0.0.1-SNAPSHOT.jar.original
drwxr-xr-x 5 teradayoshio wheel      160  9 14 20:34 classes
drwxr-xr-x 3 teradayoshio wheel       96  9 14 14:52 generated-sources
drwxr-xr-x 3 teradayoshio wheel       96  9 14 14:52 generated-test-sources
drwxr-xr-x 3 teradayoshio wheel       96  9 14 14:52 maven-archiver
drwxr-xr-x 3 teradayoshio wheel       96  9 14 14:52 maven-status
drwxr-xr-x 4 teradayoshio wheel      128  9 14 14:52 surefire-reports
drwxr-xr-x 3 teradayoshio wheel       96  9 14 20:34 test-classes
Enter fullscreen mode Exit fullscreen mode

2.8. Creating an Azure Container Apps Instance

Now that the implementation of the Java program is complete, let's deploy this Java Application to Azure Container Apps.

Specify the name for the container application using the environment variable CONTAINER_APP_NAME. Define the path and filename of the Spring Boot artifact with JAR_FILE_PATH_AND_NAME.

export CONTAINER_APP_NAME=yoshio-ai-app
export JAR_FILE_PATH_AND_NAME=./target/Yoshio-AI-App-0.0.1-SNAPSHOT.jar
Enter fullscreen mode Exit fullscreen mode

As explained in "1. Deploying Java Applications to Azure Container Apps Without Containerization", you no longer need to create and deploy your own container image when deploying to Azure Container Apps. We only have the Java artifact Yoshio-AI-App-0.0.1-SNAPSHOT.jar, and we will deploy based on this.

To do so, use the az containerapp up command. As you can see from the arguments, you only need to specify --artifact $JAR_FILE_PATH_AND_NAME, without any container image arguments. This command will automatically set up the build environment, build the container, and deploy it.

Please execute the following command.

az containerapp up \
  --name $CONTAINER_APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --subscription $SUBSCRIPTION \
  --location $LOCATION \
  --environment $CONTAINER_ENVIRONMENT \
  --artifact $JAR_FILE_PATH_AND_NAME \
  --ingress external \
  --target-port 8080 \
  --env-vars AZURE_LOG_LEVEL=2 \
  --query properties.configuration.ingress.fqdn
Enter fullscreen mode Exit fullscreen mode

Note:

You can slightly customize the container creation. Use the environment variables provided below as needed:
Build Environment Variables for Java in Azure Container Apps.

2.9. Assigning a User Managed Identity to Azure Container Apps

Having deployed the app to Azure Container Apps, the final step is to apply a User Managed Identity to the container we created. Execute the az containerapp identity assign command.

az containerapp identity assign --name $CONTAINER_APP_NAME \
                                    --resource-group $RESOURCE_GROUP \
                                    --user-assigned $USER_MANAGED_ID_RESOURCE_ID
Enter fullscreen mode Exit fullscreen mode

With that, the User Managed Identity configuration is complete.

2.10. Verifying Operation

Now that all the settings are complete, let's verify the operation. First, obtain the FQDN host name assigned to the container's Ingress.

export REST_ENDPOINT=$(az containerapp show -n $CONTAINER_APP_NAME \
              -g $RESOURCE_GROUP \
              --query properties.configuration.ingress.fqdn --output tsv)
Enter fullscreen mode Exit fullscreen mode

Next, append the RESTful endpoint URL to the obtained host name and connect to it.

curl -X POST https://$REST_ENDPOINT/askAI \
     -H "Content-Type: application/json" \
     -d '{"message":"What is Spring Boot? Please explain around 200 words?"}'
Enter fullscreen mode Exit fullscreen mode

Each time it is executed, the response may vary, but you can expect results like those below.

Arrr, Spring Boot be a powerful framework fer buildin' Java applications with minimal fuss, savvy? 
It sails under the banner of the larger Spring ecosystem, providin' a swift and easy approach fer creatin' scalable and stand-alone web apps.

Ye see, it comes with embedded servers like Tomcat or Jetty, meanin' ye don't have the hassle of deployin' to external servers. 
Spring Boot simplifies the setup by makin' configurations automatic, allowin' developers to focus on writin' their code without worryin' about the boilerplate.

It also hoists the flag of convention over configuration, offerin' defaults that make gettin’ started as smooth as a voyage with favorable winds. 
Should ye need to adjust yer sails, customization be available through its easy-to-use properties.

And if ye treasure quick development, Spring Boot supports ye well with its rich assortment of pre-configured starters, integratin' smoothly with other technologies like databases and messaging systems.

In essence, Spring Boot be a trusty vessel fer any sea-farin' coder seekin' speed, efficiency, and a treasure chest of features in their Java web applications. 
Aye, it be a boon to all developers, whether seasoned or green as a fresh landlubber!
Enter fullscreen mode Exit fullscreen mode

2.11. Reusing the Same User Managed Identity to Access Azure OpenAI from Other Azure Resources

Previously, we discussed how to apply a User Managed Identity to Azure Container Apps to connect to Azure OpenAI. However, this User Managed Identity can also be reused by other services.

In other words, applications deployed on other Azure resources can use the same User Managed Identity to connect to Azure OpenAI.

Some resources allow you to specify this identity during creation, while for others, you can assign it later.

Below, we demonstrate how to assign a User Managed Identity to existing Azure resources, except for Azure Container Instances.

For Azure VM

To assign a User Managed Identity to an Azure VM, execute the following command:

az vm identity assign -g $RESOURCE_GROUP \
                      -n $VM_NAME \
                      --identities $USER_MANAGED_ID_RESOURCE_ID
Enter fullscreen mode Exit fullscreen mode

For Azure App Service

To assign a User Managed Identity to an Azure App Service, execute the following command:

az webapp identity assign -g $RESOURCE_GROUP \
                          --name $APP_SERVICE_NAME \
                          --identities $USER_MANAGED_ID_RESOURCE_ID
Enter fullscreen mode Exit fullscreen mode

For Azure Functions

To assign a User Managed Identity to Azure Functions, execute the following command:

az functionapp identity assign -g $RESOURCE_GROUP \
                               -n $AZURE_FUNCTION_NAME \
                               --identities $USER_MANAGED_ID_RESOURCE_ID
Enter fullscreen mode Exit fullscreen mode

For Azure Container Instances

For Azure Container Instances, you can assign a User Managed Identity during the instance creation:

az container create \
                        --resource-group $RESOURCE_GROUP  \
                        --name $CONTAINER_INSTANCE_NAME \
                        --image $CONTAINER_IMAGE \
                        --assign-identity $USER_MANAGED_ID_RESOURCE_ID
Enter fullscreen mode Exit fullscreen mode

For Azure Kubernetes Service

To assign a User Managed Identity to Azure Kubernetes Service, execute the following command:

az aks update \
                    -g $RESOURCE_GROUP \
                    --name $AKS_CLUSTER_NAME \
                    --enable-managed-identity \
                    --assign-identity $USER_MANAGED_ID_RESOURCE_ID
Enter fullscreen mode Exit fullscreen mode

Like this, by using a User Managed Identity, you can securely connect to target resources across different Azure services by reusing correctly configured access roles.

2.12. Comparing Access Keys and User Managed Identities from a Security Perspective

This section explains the security differences between using Access Keys and User Managed Identities for Azure OpenAI.

When using a User Managed Identity, you specify the client ID of the User Managed Identity when creating an instance of the ManagedIdentityCredential class in your Java Application. This client ID value is set as an environment variable $USER_MANAGED_ID_CLIENT_ID when the User Managed Identity is created.

At first glance, it might seem that this is the only critical piece of information needed for the connection, aside from the endpoint URL.

ManagedIdentityCredential credential =
        new ManagedIdentityCredentialBuilder()
                       .clientId(userManagedIDClientId)
                        .build();
OpenAIClient openAIClient = new OpenAIClientBuilder()
                                        .credential(credential)
                                        .endpoint(openAIEndpoint)
                                        .buildClient();
Enter fullscreen mode Exit fullscreen mode

Let's consider the impact of a leaked client ID versus a leaked Access Key.

An Access Key is like a regular password. If it is leaked, anyone who knows the password can access the resource. In the case of Azure OpenAI, this means anyone could use AI models like GPT-4. If the network is publicly accessible, the impact could be even greater.

On the other hand, if the client ID is leaked, the impact is limited. This is because the client ID alone cannot connect to Azure OpenAI. To use a User Managed Identity, the service must be running on Azure. Even if Azure OpenAI is publicly accessible, you cannot connect from a local environment or over a network using a Java application.

Additionally, the following role assignment is configured for the User Managed Identity:

az role assignment create --assignee $USER_MANAGED_ID_PRINCIPAL_ID \
    --scope $OPEN_AI_RESOURCE_ID \
    --role "Cognitive Services OpenAI User" 
Enter fullscreen mode Exit fullscreen mode

This sets what actions can be performed using this user ID. Here, the Cognitive Services OpenAI User role is granted for Azure OpenAI services, limiting permissions to operations within Azure OpenAI.

This means that even with this ID, you cannot perform administrative actions like adding models, not just across Azure but even within Azure OpenAI.

Furthermore, to use this user ID, you must log in to Azure as an administrator, and only the resources specified by the administrator can be accessed within the scope of this ID's permissions.

In summary, compared to the impact of a leaked access key, a leaked client ID requires multiple steps to exploit, making it more secure.

For these reasons, User Managed Identities offer a more secure way to manage operations than access keys. We encourage you to use Managed Identities to build a secure environment.

3 Summary

In this guide, we walked through a detailed, step-by-step process for securely connecting Azure Container Apps to Azure OpenAI using a User Managed Identity. While setting up a User Managed Identity might seem cumbersome at first, it offers the significant advantage of reusability. If your systems are few or flexibility isn't a major concern, a System Managed Identity might be preferable.

However, as the number of systems increases, User Managed Identities become very handy. After creating a User Managed Identity and assigning roles, you can reuse the same ID across various services like Azure App Service, Azure Functions, and Azure Kubernetes Services. The more systems or services you manage, the more beneficial this becomes.

Please try using Managed Identities to effectively build secure environments.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .