3 May 2023
Security is the main focus for every application nowadays. Several tools/principles are available in Azure to help you secure your application. This article will give you information on implementing and using managed identities.
Managed Identities
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.
How it works
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.
Example
Infrastructure
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:
- identity block: This is how we ask azure to assign an identity to our function app.
- app_settings block: This block lets us define the environment variable for our function app.
- azurerm_storage_account.example_data_source resource: A resource that we will try to access from our function app using Managed identity
- azurerm_role_assignement resource: It grants access read access to the function app identity over the second storage account
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!
Function app code
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.
Pros
The main advantages I have discovered after implementing managed identities in my projects are:
- It's easy to implement and to use. You don't have to rework all your infrastructure to use managed identities. You can use them on a pre-existing application.
- Azure SDK is available in a lot of languages with complete documentation. It's quite easy to find the needed object and a lot of examples are available.
- Your KeyVault will contain fewer credentials. You can't manage permission over a secret. You only do so for the complete Key Vault. The fact that fewer services need to access this resource will improve the security of your platform.
Cons
If you want to implement managed identities in your system, you should still take care of several issues:
- Your application code will only work in Azure. You will need to do some software updates to work with another cloud provider.
- It may take time to synchronize Developers and SRE squads. We took some time to update each component to be ready to use managed identities in my projects. Then, we asked developers to use them in their software, which took some time. Then, developers noticed us, and we removed all the old configurations, for example, the environment variable containing the connection string, which managed identities that are now rendered obsolete.
- Some permission might be tricky. In the previous example, we granted Function app access to a storage account. If you try, you will find that you can retrieve the list of storage containers in the storage account but can't access the content of those containers.
Even if you are the "Owner" of the storage containers, you still need the "Storage Blob Data Reader" permission. As you can see, you might need some tests to have your managed identities fully working.
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!