Managing credentials is not an easy task. It implies a lot of work. For example, you need to frequently update credentials, verify that only the required services use them, share them between services and users, etc.
Managed Identities lets you connect to a managed service by Azure without caring for any connection string. Azure fully addresses all the complexity mentioned before.
In some managed services of Azure, you have the option to assign an identity to them. It will add an identity to your service corresponding to an Enterprise application in your Azure Active Directory when activated.
You can now grant permission to this identity like to any user of your Azure Active Directory. For example, give a Function app access to a specific storage account, as you will find below.
When using this service, it will now have an attached identity that you can retrieve using the Azure SDK. This identity will let you retrieve the connection string or directly access data of other services corresponding to the right you've assigned to it.
It seems simple, right? Let's see a live example.
Here is an example of Terraform code to create a function app with a system-assigned identity.
resource "azurerm_resource_group" "example" {
name = "azure-functions-rg"
location = "West Europe"
}
resource "azurerm_app_service_plan" "example" {
name = "azure-functions-demo-asp"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
kind = "FunctionApp"
sku {
tier = "Dynamic"
size = "Y1"
}
}
resource "azurerm_storage_account" "example" {
name = "functions-app-test-sa"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_function_app" "example" {
name = "function-app-name"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
os_type = "linux"
app_service_plan_id = azurerm_app_service_plan.example
identity {
type = "SystemAssigned"
}
storage_account_name = azurerm_storage_account.example.name
storage_account_access_key = azurerm_storage_account.example.primary_access_key
app_settings = {
APP_STORAGE_ACCOUNT_URL = azurerm_storage_account.example_data_source.primary_blob_endpoint
}
}
resource "azurerm_storage_account" "example_data_source" {
name = "data-source-storage-account"
resource_group_name = data.azurerm_resource_group.rg.name
location = data.azurerm_resource_group.rg.location
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_role_assignment" "example" {
principal_id = azurerm_function_app.example.identity.principal_id
role_definition_name = "Reader"
scope = azurerm_storage_account.example_data_source.id
}
In this code example, we define several things that differ from the function app example in the Terraform documentation:
As you can see, there are no credentials in the function app environment variable, but this is all we need to access the second storage account from the function app!
Now it's time to code our function that accesses the storage! In the following example, we will use Python and the Azure SDK.
Here is the content of our requirement.txt
file:
azure-functions
azure-identity
azure-storage
azure-storage-blob
In the previous file as you can see we declare all libraries we need
Here is the content of FunctionName/__init__.py
import logging
import os
import azure.functions as func
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient
STORAGE_ACCOUNT_URL=os.getenv("APP_STORAGE_ACCOUNT_URL")
def main(req: func.HttpRequest) -> func.HttpResponse:
default_credential = DefaultAzureCredential()
blob_service_client = BlobServiceClient(account_url=STORAGE_ACCOUNT_URL,
credential=default_credential)
containers = blob_service_client.list_containers()
containersstr=""
for c in containers:
containersstr+=c.name + "\n"
return func.HttpResponse(
f"""
containers:
{containersstr}
""",
)
You only use the environment variable that contains the URL of the application storage account. When you call your function, it will return a 200 HTTP response with a string containing "containers:" followed by a container name on each line corresponding to the containers within the storage account.
The main advantages I have discovered after implementing managed identities in my projects are:
If you want to implement managed identities in your system, you should still take care of several issues:
This article aims to give you my feedback on Azure's managed identities and their implementation. As you can see, my feedback is globally positive, it's easy to implement, and the security advantage it provides is enormous.
Other tools or practices are available to improve the security of your resources on Azure. For example, the Azure security center, automatic security scan, securing internal traffic from machine to machine auth, etc.
If you have any feedback on those tools or have any questions on the content of this article, don't hesitate to give feedback here!