Conexión Segura: Acceso a Azure Storage desde Azure Function

En nuestra publicación anterior, exploramos cómo utilizar Identidades Administradas(Azure Managed Identity) para acceder al almacenamiento en Azure, evitando así el uso de cadenas de conexión para acceder a los recursos. Sin embargo, aún nos queda una tarea pendiente: configurar la cuenta de almacenamiento utilizada por el tiempo de ejecución de Azure Function(Runtime) para que también use una identidad administrada en lugar de depender de la cadena de conexión.

Entendiendo el funcionamiento de las Azure Funcions

Cuando creamos un proyecto de Azure Function y trabajamos localmente con un almacenamiento(Azurite), se genera un archivo llamado local.settings.json. Este archivo tendrá una estructura similar a la siguiente:

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet"
  }
}

Me gustaría enfocarme en el parámetro AzureWebJobsStorage, el cual representa la cadena de conexión de una cuenta de almacenamiento que el entorno de ejecución de Azure Functions utiliza para realizar operaciones de coordinación general. Algunas de esta operaciones incluyen la administración de claves, la gestión de desencadenadores, temporizadores y los puntos de comprobación de Event Hubs.

En otras palabras, este parámetro representa la cuenta de almacenamiento que el entorno de Azure Functions utiliza internamente para almacenar información necesaria para su correcto funcionamiento. Por ejemplo, cuando se trabaja con Azure Function Durable, se generan tablas específicas utilizadas para el registro y control de actividades ejecutadas.

Cuando se utiliza UseDevelopmentStorage=true, se hace referencia al emulador Azurite, el cual se emplea para el desarrollo local de Azure Storage. Sin embargo, en producción, esta configuración debería apuntar a una cadena de conexión de una cuenta de almacenamiento real en Azure. Es fundamental comprender que esta cuenta de almacenamiento es utilizada por el entorno de ejecución de Azure Functions sin importar el tipo de desencadenador que se esté utilizando. Por eso al momento de crear el proyecto de Azure Function se solicita que indiquemos una cuenta de almacenamiento.

Modificando nuestra Azure Function

Por defecto, Azure Function usa la configuración «AzureWebJobsStorage» como variable para obtener la cadena de conexión que se va usar para conectarse a la cuenta de almacenamiento, Si deseamos usar entidades administradas debemos agregar algunas configuraciones adicionales, en el desarrollo local podemos usar el local.settings.json, en Azure, estas configuraciones deben debe ir en la configuración del proyecto de App Functions.

{
  "AzureWebJobsStorage__blobServiceUri": "https://<storage_account_name>.blob.core.windows.net",
  "AzureWebJobsStorage__queueServiceUri": "https://<storage_account_name>.queue.core.windows.net",
  "AzureWebJobsStorage__tableServiceUri": "https://<storage_account_name>.table.core.windows.net"
}

Como puedes observar, tenemos varias URIs para cada tipo de recurso disponible en la cuenta de almacenamiento (Blob, Table u Queue).

NOTA: Si la cuenta no está en una nube soberana y no tiene un DNS personalizado, puede usar la siguiente configuración en lugar de especificar los URIs de manera independiente (Para Azurite local no funciona).

"AzureWebJobsStorage__accountName": "<storage_account_name>"

Ahora si queremos usar una entidad administrada por el usuario en vez del por sistema debemos también indicar lo siguiente:

{
  "AzureWebJobsStorage__clientId":"<Client_ID>",
  "AzureWebJobsStorage__credential":"managedidentity"
}

Luego de agregar las configuraciones es muy importante eliminar la configuración AzureWebJobsStorage porque ahora nuestra aplicación va usar la entidad administrada. El resultado final seria al similar a los siguiente:

{
  "AzureWebJobsStorage__blobServiceUri": "https://<storage_account_name>.blob.core.windows.net",
  "AzureWebJobsStorage__queueServiceUri": "https://<storage_account_name>.queue.core.windows.net",
  "AzureWebJobsStorage__tableServiceUri": "https://<storage_account_name>.table.core.windows.net",
  "AzureWebJobsStorage__clientId":"<Client_ID>",
  "AzureWebJobsStorage__credential":"managedidentity"
}

Con esto, terminamos los cambios que se requieren en la configuración de nuestra aplicación. Pero ahora necesitamos hacer algo adicional para poder completar este proceso que explicaremos a continuación.

Asignación de permisos a la entidad administrada de nuestra Azure Function

Como explicamos en la publicación anterior, cuando queremos acceder a un recurso usando una entidad administrada, debemos asignar roles que permitan hacer las acciones requeridas, por ejemplo si nuestra aplicación necesitaba leer y escribir sobre un blob dentro de una cuenta de almacenamiento, podíamos asignar el rol Contribuidor el cual entre sus acciones permiten escribir y leer del recurso.

Para el caso de la cuenta de almacenamiento usada por Azure Function también necesitamos conceder permisos necesarios para las operaciones administrativas (Crear leer, escribir etc.). Por ejemplo, las Azure Fuction Durable generan varias tablas para control de sus actividad, por lo cual necesitaría permiso para esta acción de crear y escribir una tabla en la cuenta de almacenamiento.

Cada disparador de función(trigger) utiliza de manera diferente el recuso de almacenamiento, por lo cual debemos asignar permisos que cubran cada una de las necesidades. Eso nos obligaría a estudiar el funcionamiento interno funciones y entender como usan el recurso de almacenamiento. Pero como Microsoft nos quiere facilitar el trabajo, ellos generaron una tabla donde se explica el permiso necesario según el disparador que usemos (En enlace de referencia puedes ver la tabla).

Permisos requeridos para las Azure Function

En la primera fila nos dice que siempre se va requerir el permiso de Propietario, los demás permisos se requieren según la necesidad de la aplicación, por ejemplo si solo tienes desencadenador de tipo temporizador, no necesitamos los permisos de Contribuidor de tabla y contribuidor de colas.

Con la información de la tabla anterior puedes ir al recurso de almacenamiento y conceder los roles requeridos por nuestra aplicación.

Conclusión

Configurar nuestra Azure Function para el uso de entidades administradas en nuestras aplicaciones, puede parecer un proceso complejo en comparación al uso de una cadena de conexión, pero es una alternativa que nos permite una mayor seguridad y control de acceso al recurso. Ademas pienso que hacer este tipo de configuraciones un poco más avanzadas nos permiten aprender sobre el funcionamiento de Azure Function.

Te invito a ver mis otros artículos.

Referencias