Uso de referencias de Key Vault para App Service y Azure Functions

En este tema se muestra cómo trabajar con secretos de Azure Key Vault en la aplicación de App Service o Azure Functions sin necesidad de realizar cambios en el código. Azure Key Vault es un servicio que proporciona administración centralizada de los secretos, con control total sobre las directivas de acceso y el historial de auditoría.

Concesión a la aplicación de acceso a Key Vault

Para leer secretos desde Key Vault, debe tener creado un almacén y proporcionar a la aplicación permiso para acceder a él.

  1. Para crear un almacén de claves, siga la guía de inicio rápido de Key Vault.

  2. Cree una identidad administrada para la aplicación.

    Las referencias de Key Vault usarán la identidad asignada del sistema de la aplicación de forma predeterminada, pero puede especificar una identidad asignada por el usuario.

  3. Cree una directiva de acceso en Key Vault para la identidad de aplicación que creó anteriormente. Habilite el permiso secreto "Get" en esta directiva. No configure la "aplicación autorizada" o la configuración applicationId, ya que no es compatible con una identidad administrada.

Acceso a almacenes restringidos de red

Si el almacén está configurado con restricciones de red, también deberá asegurarse de que la aplicación tenga acceso a la red.

  1. Asegúrese de que la aplicación tenga configuradas funcionalidades de red de salida, tal como se describe en Características de red de App Service y Opciones de red de Azure Functions.

    Las aplicaciones de Linux que intentan usar puntos de conexión privados requieren además que la aplicación se configure explícitamente para que todo el tráfico se enrute a través de la red virtual. Este requisito se quitará en una actualización futura. Para establecer esta opción, use el siguiente comando de la CLI:

    az webapp config set --subscription <sub> -g <rg> -n <appname> --generic-configurations '{"vnetRouteAllEnabled": true}'
    
  2. Asegúrese de que la configuración del almacén tenga en cuenta la red o la subred mediante la que cual la aplicación tendrá acceso.

Nota

El contenedor de Windows no admite actualmente referencias de Key Vault a través de la integración con red virtual.

Acceso a los almacenes con una identidad asignada por el usuario

Algunas aplicaciones necesitan hacer referencia a secretos en el momento de la creación, cuando una identidad asignada por el sistema aún no está disponible. En estos casos, se puede crear una identidad asignada por el usuario y concederle acceso al almacén de antemano.

Una vez que haya concedido permisos a la identidad asignada por el usuario, siga estos pasos:

  1. Asigne la identidad a la aplicación si aún no lo ha hecho.

  2. Configure la aplicación para que use esta identidad para las operaciones de referencia de Key Vault estableciendo la propiedad keyVaultReferenceIdentity en el id. de recurso de la identidad asignada por el usuario.

    userAssignedIdentityResourceId=$(az identity show -g MyResourceGroupName -n MyUserAssignedIdentityName --query id -o tsv)
    appResourceId=$(az webapp show -g MyResourceGroupName -n MyAppName --query id -o tsv)
    az rest --method PATCH --uri "${appResourceId}?api-version=2021-01-01" --body "{'properties':{'keyVaultReferenceIdentity':'${userAssignedIdentityResourceId}'}}"
    

Esta configuración se aplicará a todas las referencias de la aplicación.

Sintaxis de referencia

Una referencia de Key Vault tiene el formato @Microsoft.KeyVault({referenceString}), donde {referenceString} se sustituye por una de las opciones siguientes:

Cadena de referencia Descripción
SecretUri=secretUri SecretUri debe ser el URI completo del plano de datos de un secreto en Key Vault y, opcionalmente, puede incluir una versión; por ejemplo, https://myvault.vault.azure.net/secrets/mysecret/ o https://myvault.vault.azure.net/secrets/mysecret/ec96f02080254f109c51a1f14cdb1931.
VaultName=vaultName;SecretName=secretName;SecretVersion=secretVersion VaultName es un parámetro obligatorio y debe ser el nombre del recurso de Key Vault. SecretName es un parámetro obligatorio y debe ser el nombre del secreto de destino. SecretVersion es opcional, pero si está presente indica la versión del secreto que se usará.

Por ejemplo, una referencia completa se parecería a la siguiente:

@Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/mysecret/)

O bien:

@Microsoft.KeyVault(VaultName=myvault;SecretName=mysecret)

Rotación

Si no se especifica una versión en la referencia, la aplicación usará la versión más reciente que exista en Key Vault. Cuando haya disponibles versiones más recientes, como con un evento de rotación, la aplicación se actualizará automáticamente y comenzará a usar la versión más reciente en el plazo de un día. Los cambios de configuración realizados en la aplicación actualizarán inmediatamente a las versiones más recientes todos los secretos a los que se hace referencia.

Configuración de la aplicación de origen desde Key Vault

Las referencias de Key Vault pueden usarse como valores en Configuración de la aplicación, lo que le permite conservar los secretos en Key Vault en lugar de en la configuración del sitio. La configuración de la aplicación se cifra de forma segura en reposo, pero si necesita funcionalidades de administración de secretos, debe incluirse en Key Vault.

Para usar una referencia de Key Vault para la configuración de la aplicación, establezca la referencia como valor de la configuración. La aplicación puede hacer referencia al secreto mediante su clave de la forma habitual. No se requiere ningún cambio de código.

Sugerencia

La mayoría de los valores de configuración de la aplicación que usan referencias de Key Vault se deben marcar como configuración de ranuras, así que debe tener distintos almacenes para cada entorno.

Consideraciones para el montaje de Azure Files

Las aplicaciones pueden usar la configuración de la aplicación WEBSITE_CONTENTAZUREFILECONNECTIONSTRING para montar Azure Files como el sistema de archivos. Esta configuración tiene comprobaciones de validación adicionales para garantizar que la aplicación se puede iniciar correctamente. La plataforma se basa en tener un recurso compartido de contenido dentro de Azure Files y supone un nombre predeterminado a menos que se especifique uno a través de la configuración WEBSITE_CONTENTSHARE. Para las solicitudes que modifican esta configuración, la plataforma intentará validar si existe este recurso compartido de contenido, e intentará crearlo si no existe. Si no se puede buscar o crear el recurso compartido de contenido, la solicitud se bloqueará.

Cuando se usen referencias de Key Vault para esta configuración, esta comprobación de validación producirá un error de manera predeterminada, ya que el secreto no se puede resolver al procesar la solicitud entrante. Para evitar este problema, puede omitir la validación al establecer el valor de WEBSITE_SKIP_CONTENTSHARE_VALIDATION en "1". Esto omitirá todas las comprobaciones y el recurso compartido de contenido no se creará automáticamente. Debe asegurarse de crearlo de antemano.

Precaución

Si omite la validación y, además, la cadena de conexión o el recurso compartido de contenido no son válidos, la aplicación no podrá iniciarse correctamente y solo generará errores HTTP 500.

Como parte de la creación del sitio, también es posible que se pueda generar un error al intentar montar el recurso compartido de contenido debido a que no se hayan propagado los permisos de identidad administrada, o a que no se haya configurado la integración de red virtual. Puede aplazar la configuración de Azure Files hasta más adelante en la plantilla de implementación para realizar esto. Para más información, consulte Implementación de Azure Resource Manager. App Service usará sistema de archivos predeterminado hasta que Azure Files esté configurado y los archivos no se hayan copiado, por lo que deberá asegurarse de que no se produzca ningún intento de implementación durante el período transitorio antes de montar Azure Files.

Implementación de Azure Resource Manager

Al automatizar las implementaciones de recursos mediante plantillas de Azure Resource Manager, puede que deba secuenciar sus dependencias en un orden determinado para que esta característica funcione. Cabe señalar que deberá definir la configuración de la aplicación como su propio recurso en lugar de usar una propiedad siteConfig en la definición del sitio. El motivo es que primero es necesario definir el sitio para que la identidad asignada por el sistema se cree con él y se pueda usar en la directiva de acceso.

Una seudoplantilla de ejemplo para una aplicación de función podría tener este aspecto:

{
    //...
    "resources": [
        {
            "type": "Microsoft.Storage/storageAccounts",
            "name": "[variables('storageAccountName')]",
            //...
        },
        {
            "type": "Microsoft.Insights/components",
            "name": "[variables('appInsightsName')]",
            //...
        },
        {
            "type": "Microsoft.Web/sites",
            "name": "[variables('functionAppName')]",
            "identity": {
                "type": "SystemAssigned"
            },
            //...
            "resources": [
                {
                    "type": "config",
                    "name": "appsettings",
                    //...
                    "dependsOn": [
                        "[resourceId('Microsoft.Web/sites', variables('functionAppName'))]",
                        "[resourceId('Microsoft.KeyVault/vaults/', variables('keyVaultName'))]",
                        "[resourceId('Microsoft.KeyVault/vaults/secrets', variables('keyVaultName'), variables('storageConnectionStringName'))]",
                        "[resourceId('Microsoft.KeyVault/vaults/secrets', variables('keyVaultName'), variables('appInsightsKeyName'))]"
                    ],
                    "properties": {
                        "AzureWebJobsStorage": "[concat('@Microsoft.KeyVault(SecretUri=', reference(variables('storageConnectionStringResourceId')).secretUriWithVersion, ')')]",
                        "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "[concat('@Microsoft.KeyVault(SecretUri=', reference(variables('storageConnectionStringResourceId')).secretUriWithVersion, ')')]",
                        "APPINSIGHTS_INSTRUMENTATIONKEY": "[concat('@Microsoft.KeyVault(SecretUri=', reference(variables('appInsightsKeyResourceId')).secretUriWithVersion, ')')]",
                        "WEBSITE_ENABLE_SYNC_UPDATE_SITE": "true"
                        //...
                    }
                },
                {
                    "type": "sourcecontrols",
                    "name": "web",
                    //...
                    "dependsOn": [
                        "[resourceId('Microsoft.Web/sites', variables('functionAppName'))]",
                        "[resourceId('Microsoft.Web/sites/config', variables('functionAppName'), 'appsettings')]"
                    ],
                }
            ]
        },
        {
            "type": "Microsoft.KeyVault/vaults",
            "name": "[variables('keyVaultName')]",
            //...
            "dependsOn": [
                "[resourceId('Microsoft.Web/sites', variables('functionAppName'))]"
            ],
            "properties": {
                //...
                "accessPolicies": [
                    {
                        "tenantId": "[reference(resourceId('Microsoft.Web/sites/', variables('functionAppName')), '2020-12-01', 'Full').identity.tenantId]",
                        "objectId": "[reference(resourceId('Microsoft.Web/sites/', variables('functionAppName')), '2020-12-01', 'Full').identity.principalId]",
                        "permissions": {
                            "secrets": [ "get" ]
                        }
                    }
                ]
            },
            "resources": [
                {
                    "type": "secrets",
                    "name": "[variables('storageConnectionStringName')]",
                    //...
                    "dependsOn": [
                        "[resourceId('Microsoft.KeyVault/vaults/', variables('keyVaultName'))]",
                        "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
                    ],
                    "properties": {
                        "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountResourceId'),'2019-09-01').key1)]"
                    }
                },
                {
                    "type": "secrets",
                    "name": "[variables('appInsightsKeyName')]",
                    //...
                    "dependsOn": [
                        "[resourceId('Microsoft.KeyVault/vaults/', variables('keyVaultName'))]",
                        "[resourceId('Microsoft.Insights/components', variables('appInsightsName'))]"
                    ],
                    "properties": {
                        "value": "[reference(resourceId('microsoft.insights/components/', variables('appInsightsName')), '2019-09-01').InstrumentationKey]"
                    }
                }
            ]
        }
    ]
}

Nota

En este ejemplo, la implementación del control de código fuente depende de la configuración de la aplicación. Este comportamiento es normalmente poco seguro, ya que la actualización de la configuración de la aplicación se comporta de forma asincrónica. Sin embargo, como hemos incluido la configuración de la aplicación WEBSITE_ENABLE_SYNC_UPDATE_SITE, la actualización es sincrónica. Esto significa que la implementación del control de código fuente solo comenzará una vez que la configuración de la aplicación se haya actualizado completamente. Para conocer más opciones de configuración de la aplicación, vea Variables de entorno y configuración de la aplicación en Azure App Service.

Solución de problemas de las referencias de Key Vault

Si alguna referencia no se resuelve correctamente, se usará el valor de la referencia, lo que significa que en la configuración de la aplicación se creará una variable de entorno cuyo valor tiene la sintaxis @Microsoft.KeyVault(...). Esto puede provocar que la aplicación genere errores, ya que esperaba un secreto con una estructura determinada.

Habitualmente, esto se debe a una mala configuración de la directiva de acceso de Key Vault. Sin embargo, también puede deberse a que el secreto ya no existe o a un error de sintaxis de la propia referencia.

Si la sintaxis es correcta, compruebe el estado de resolución actual en el portal para ver otras posibles causas del error. Vaya a Configuración de la aplicación y seleccione "Editar" para la referencia en cuestión. En la configuración de opciones, debe ver la información sobre el estado, incluido cualquier error. La ausencia de estos implica que la sintaxis de referencia no es válida.

También puede usar uno de los detectores integrados para obtener información adicional.

Uso del detector en App Service

  1. En el portal, vaya a la aplicación.
  2. Seleccione Diagnóstico y solución de problemas.
  3. Elija Availability and Performance (Disponibilidad y rendimiento) y seleccione Web app down (La aplicación web no funciona).
  4. Busque Key Vault Application Settings Diagnostics (Diagnóstico de configuración de la aplicación Key Vault) y haga clic en Más información.

Uso del detector en Azure Functions

  1. En el portal, vaya a la aplicación.
  2. Seleccione Características de la plataforma.
  3. Seleccione Diagnóstico y solución de problemas.
  4. Elija Disponibilidad y rendimiento y seleccione Function app down or reporting errors (La aplicación de funciones no funciona o presenta errores).
  5. Haga clic en Key Vault Application Settings Diagnostics (Diagnóstico de configuración de la aplicación Key Vault).