azure_managed_identities_security

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 present you with how to implement and use managed identities. I will give you my feedback and detail the pros and cons of this solution. You will also be able to find code examples to enforce those practices quickly in your project too!

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 again 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 render obsolete now.
  • Some permission might be tricky. In the previous example, we've granted a 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!