Proveedor de configuración de Azure Key Vault en ASP.NET Core

En este artículo se explica cómo usar el proveedor de configuración de Azure Key Vault para cargar valores de configuración de aplicaciones desde secretos de Azure Key Vault. Azure Key Vault es un servicio basado en la nube que protege los secretos y las claves criptográficas usados por los servicios y las aplicaciones en la nube. Entre los escenarios comunes para usar Azure Key Vault con aplicaciones de ASP.NET Core se incluyen:

  • Control del acceso a datos de configuración confidenciales.
  • Cumplimiento del requisito de módulos de seguridad de hardware validados por FIPS 140-2 nivel 2 al almacenar datos de configuración.

Paquetes

Agregue referencias de paquete para los paquetes siguientes:

Aplicación de ejemplo

La aplicación de ejemplo se ejecuta en cualquiera de los dos modos determinados por la directiva de preprocesador #define de la parte superior de Program.cs:

  • Certificate: muestra el uso de un id. de cliente de Azure Key Vault y un certificado X.509 para acceder a los secretos almacenados en Azure Key Vault. Este ejemplo se puede ejecutar desde cualquier ubicación, tanto si se implementa en Azure App Service como en cualquier host que pueda servir a una aplicación de ASP.NET Core.
  • Managed: muestra cómo usar identidades administradas para recursos de Azure. La identidad administrada autentica la aplicación en Azure Key Vault con la autenticación de Azure Active Directory (AD) sin almacenar credenciales en el código ni en la configuración de la aplicación. La versión Managed del ejemplo debe implementarse en Azure. Siga las instrucciones de la sección Uso de las identidades administradas para recursos de Azure .

Para obtener más información sobre cómo configurar una aplicación de ejemplo mediante directivas de preprocesador (#define), consulte Información general de ASP.NET Core.

Vea o descargue el código de ejemplo (cómo descargarlo)

Almacenamiento de secretos en el entorno de desarrollo

Establezca los secretos localmente mediante el Administrador de secretos. Cuando la aplicación de ejemplo se ejecuta en el equipo local en el entorno de desarrollo, los secretos se cargan desde el almacén de secretos de usuario local.

El Administrador de secretos requiere una propiedad <UserSecretsId> en el archivo del proyecto de la aplicación. Establezca el valor de propiedad ({GUID}) en cualquier GUID único:

<PropertyGroup>
  <UserSecretsId>{GUID}</UserSecretsId>
</PropertyGroup>

Los secretos se crean como pares nombre-valor. Los valores jerárquicos (secciones de configuración) usan : (dos puntos) como separador en los nombres de clave de configuración de ASP.NET Core.

El Administrador de secretos se usa desde un shell de comandos abierto en la raíz de contenido del proyecto, donde {SECRET NAME} es el nombre y {SECRET VALUE} es el valor:

dotnet user-secrets set "{SECRET NAME}" "{SECRET VALUE}"

Ejecute los siguientes comandos en un shell de comandos desde la raíz de contenido del proyecto para establecer los secretos de la aplicación de ejemplo:

dotnet user-secrets set "SecretName" "secret_value_1_dev"
dotnet user-secrets set "Section:SecretName" "secret_value_2_dev"

Cuando estos secretos se almacenan en Azure Key Vault en la sección Almacenamiento de secretos en el entorno de producción con Azure Key Vault, el sufijo _dev se cambia a _prod. El sufijo proporciona una indicación visual en la salida de la aplicación que indica el origen de los valores de configuración.

Almacenamiento de secretos en el entorno de producción con Azure Key Vault

Haga lo siguiente para crear una instancia de Azure Key Vault y almacenar en ella los secretos de la aplicación de ejemplo. Para más información, consulte Inicio rápido: Establecimiento y recuperación de un secreto desde Azure Key Vault mediante la CLI de Azurede Azure.

  1. Abra Azure Cloud Shell con cualquiera de los métodos siguientes en Azure Portal:

    • Seleccione Pruébelo en la esquina superior derecha de un bloque de código. Use la cadena de búsqueda "CLI de Azure" en el cuadro de texto.
    • Abra Cloud Shell en el explorador con el botón Iniciar Cloud Shell.
    • Seleccione el botón de Cloud Shell en el menú de la esquina superior derecha de Azure Portal.

    Para obtener más información, consulte CLI de Azure e Introducción a Azure Cloud Shell.

  2. Si aún no se ha autenticado, inicie sesión con el comando az login.

  3. Cree un grupo de recursos con el siguiente comando, donde {RESOURCE GROUP NAME} es el nombre del nuevo grupo de recursos y {LOCATION} es la región de Azure:

    az group create --name "{RESOURCE GROUP NAME}" --location {LOCATION}
    
  4. Cree un almacén de claves en el grupo de recursos con el siguiente comando, donde {KEY VAULT NAME} es el nombre del nuevo almacén de claves y {LOCATION} es la región de Azure:

    az keyvault create --name {KEY VAULT NAME} --resource-group "{RESOURCE GROUP NAME}" --location {LOCATION}
    
  5. Cree secretos en el almacén de claves como pares nombre-valor.

    Los nombres de secretos de Azure Key Vault se limitan a caracteres alfanuméricos y guiones. Los valores jerárquicos (secciones de configuración) usan -- (dos guiones) como delimitador, ya que no se permiten dos puntos en los nombres de secretos del almacén de claves. Los dos puntos delimitan una sección de una subclave en la configuración de ASP.NET Core. La secuencia de dos guiones se reemplaza por dos puntos cuando los secretos se cargan en la configuración de la aplicación.

    Los secretos siguientes se usan con la aplicación de ejemplo. Los valores incluyen un sufijo _prod para distinguirlos de los valores de sufijo _devcargados en el entorno de desarrollo del Administrador de secretos. Reemplace {KEY VAULT NAME} por el nombre del almacén de claves que creó en el paso anterior:

    az keyvault secret set --vault-name {KEY VAULT NAME} --name "SecretName" --value "secret_value_1_prod"
    az keyvault secret set --vault-name {KEY VAULT NAME} --name "Section--SecretName" --value "secret_value_2_prod"
    

Uso del id. de aplicación y el certificado X.509 para aplicaciones no hospedadas en Azure

Configure Azure AD, Azure Key Vault y la aplicación para usar un id. de aplicación de Azure AD y un certificado X.509 para autenticarse en un almacén de claves cuando la aplicación esté hospedada fuera de Azure. Para más información, consulte el artículo About keys, secrets, and certificates (Claves, secretos y certificados).

Nota:

Aunque se admite el uso de un id. de aplicación y un certificado X.509 para las aplicaciones hospedadas en Azure, no se recomienda. En lugar de eso, use identidades administradas para recursos de Azure al hospedar una aplicación en Azure. Las identidades administradas no requieren almacenar un certificado en la aplicación ni en el entorno de desarrollo.

La aplicación de ejemplo usa un id. de aplicación y un certificado X.509 si la directiva de preprocesado #define de la parte superior de Program.cs está establecida en Certificate.

  1. Cree un certificado de archivo PKCS#12 (.pfx). Entre las opciones para crear certificados se incluyen New-SelfSignedCertificate en Windows y OpenSSL.
  2. Instale el certificado en el almacén de certificados personal del usuario actual. Marcar la clave como exportable es opcional. Anote la huella digital del certificado, ya que se usa más adelante en este proceso.
  3. Exporte el certificado de archivo PKCS#12 (.pfx) como certificado con codificación DER (.cer).
  4. Registre la aplicación con Azure AD (Registros de aplicaciones).
  5. Cargue el certificado con codificación DER (.cer) en Azure AD:
    1. Seleccione la aplicación en Azure AD.
    2. Vaya a Certificados y secretos.
    3. Seleccione Cargar certificado para cargar el certificado, que contiene la clave pública. Son aceptables los certificados .cer, .pem y .crt.
  6. Almacene el nombre del almacén de claves, el id. de aplicación y la huella digital del certificado en el archivo de appsettings.json la aplicación.
  7. Vaya a Almacenes de claves en Azure Portal.
  8. Seleccione el almacén de claves que creó en la sección Almacenamiento de secretos en el entorno de producción con Azure Key Vault.
  9. Seleccione Directivas de acceso.
  10. Seleccione Agregar directiva de acceso.
  11. Abra Permisos de secretos y proporcione a la aplicación los permisos Get y List.
  12. Seleccione Seleccionar principal y seleccione la aplicación registrada por nombre. Seleccione el botón Seleccionar.
  13. Seleccione Aceptar.
  14. Seleccione Guardar.
  15. Implemente la aplicación.

La aplicación de ejemplo Certificate obtiene sus valores de configuración de IConfigurationRoot con el mismo nombre que el nombre del secreto:

  • Valores no jerárquicos: el valor de SecretName se obtiene con config["SecretName"].
  • Valores jerárquicos (secciones): use la notación : (dos puntos) o el método GetSection. Use cualquiera de estos enfoques para obtener el valor de configuración:
    • config["Section:SecretName"]
    • config.GetSection("Section")["SecretName"]

El certificado X.509 lo administra el sistema operativo. La aplicación llama a AddAzureKeyVault con valores proporcionados por el archivo appsettings.json:


using System.Security.Cryptography.X509Certificates;
using Azure.Identity;

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsProduction())
{
    using var x509Store = new X509Store(StoreLocation.CurrentUser);

    x509Store.Open(OpenFlags.ReadOnly);

    var x509Certificate = x509Store.Certificates
        .Find(
            X509FindType.FindByThumbprint,
            builder.Configuration["AzureADCertThumbprint"],
            validOnly: false)
        .OfType<X509Certificate2>()
        .Single();

    builder.Configuration.AddAzureKeyVault(
        new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"),
        new ClientCertificateCredential(
            builder.Configuration["AzureADDirectoryId"],
            builder.Configuration["AzureADApplicationId"],
            x509Certificate));
}

var app = builder.Build();

Valores de ejemplo:

  • Nombre de la instancia del almacén de claves: contosovault
  • Id. de la aplicación: 627e911e-43cc-61d4-992e-12db9c81b413
  • Huella digital de certificado: fe14593dd66b2406c5269d742d04b6e1ab03adb1

appsettings.json:

{
  "KeyVaultName": "Key Vault Name",
  "AzureADApplicationId": "Azure AD Application ID",
  "AzureADCertThumbprint": "Azure AD Certificate Thumbprint",
  "AzureADDirectoryId": "Azure AD Directory ID"
}

Al ejecutar la aplicación, una página web muestra los valores de secretos cargados. En el entorno de desarrollo, los valores de secretos se cargan con el sufijo _dev. En el entorno de producción, los valores se cargan con el sufijo _prod.

Uso de identidades administradas para recursos de Azure

Una aplicación implementada en Azure puede aprovechar las identidades administradas para recursos de Azure. Una identidad administrada permite que la aplicación se autentique con Azure Key Vault mediante la autenticación de Azure AD sin almacenar credenciales en el código ni en la configuración de la aplicación.

La aplicación de ejemplo usa una identidad administrada asignada por el sistema cuando la directiva de preprocesador #define de la parte superior de Program.cs está establecida en Managed. Para crear una identidad administrada para una aplicación de Azure App Service, consulte Cómo usar identidades administradas para App Service y Azure Functions. Una vez creada la identidad administrada, anote el id. de objeto de la aplicación que se muestra en Azure Portal en el panel Identity de App Service.

Escriba el nombre del almacén en el archivo de appsettings.json la aplicación. La aplicación de ejemplo no requiere un id. de aplicación ni una contraseña (secreto de cliente) cuando se establece en la versión Managed, por lo que se pueden omitir esas entradas de configuración. La aplicación se implementa en Azure, que autentica la aplicación para acceder a Azure Key Vault solo con el nombre del almacén almacenado en el archivo appsettings.json.

Implementación de la aplicación de ejemplo en Azure App Service.

Con la CLI de Azure y el id. de objeto de la aplicación, proporcione a la aplicación los permisos list y get para acceder al almacén de claves:

az keyvault set-policy --name {KEY VAULT NAME} --object-id {OBJECT ID} --secret-permissions get list

Reinicie la aplicación mediante la CLI de Azure, PowerShell o el Azure Portal.

La aplicación de ejemplo crea una instancia de la clase DefaultAzureCredential. La credencial intenta obtener un token de acceso del entorno para los recursos de Azure:

using Azure.Identity;

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsProduction())
{
    builder.Configuration.AddAzureKeyVault(
        new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"),
        new DefaultAzureCredential());
}

Valor de ejemplo del nombre del almacén de claves: contosovault

appsettings.json:

{
  "KeyVaultName": "Key Vault Name"
}

En el caso de las aplicaciones que usan una identidad administrada asignada por el usuario, configure el id. de cliente de la identidad administrada mediante uno de los métodos siguientes:

  1. Establezca la variable de entorno AZURE_CLIENT_ID.

  2. Establezca la propiedad DefaultAzureCredentialOptions.ManagedIdentityClientId al llamar a AddAzureKeyVault:

    builder.Configuration.AddAzureKeyVault(
        new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"),
        new DefaultAzureCredential(new DefaultAzureCredentialOptions
        {
            ManagedIdentityClientId = builder.Configuration["AzureADManagedIdentityClientId"]
        }));
    

Al ejecutar la aplicación, una página web muestra los valores de secretos cargados. En el entorno de desarrollo, los valores de secretos tienen el sufijo _dev porque los proporciona el Administrador de secretos. En el entorno de producción, los valores se cargan con el sufijo _prod porque los proporciona Azure Key Vault.

Si recibe el error Access denied, confirme que la aplicación está registrada en Azure AD y que proporcionó acceso al almacén de claves. Confirme que ha reiniciado el servicio en Azure.

Para obtener información sobre el uso del proveedor con una identidad administrada y Azure Pipelines, consulte Creación de una conexión de servicio de Azure Resource Manager a una VM con una identidad de servicio administrada.

Opciones de configuración

AddAzureKeyVault puede aceptar un objeto AzureKeyVaultConfigurationOptions:

// using Azure.Extensions.AspNetCore.Configuration.Secrets;

builder.Configuration.AddAzureKeyVault(
    new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"),
    new DefaultAzureCredential(),
    new AzureKeyVaultConfigurationOptions
    {
        // ...
    });

El objeto AzureKeyVaultConfigurationOptions contiene las siguientes propiedades:

Propiedad Descripción
Manager Instancia de KeyVaultSecretManager que se usa para controlar la carga de secretos.
ReloadInterval TimeSpan para esperar entre los intentos de sondeo del almacén de claves para comprobar los cambios. El valor predeterminado es null (la configuración no se vuelve a cargar).

Uso de un prefijo de nombre de clave

AddAzureKeyVault proporciona una sobrecarga que acepta una implementación de KeyVaultSecretManager, que permite controlar cómo se convierten los secretos del almacén de claves en claves de configuración. Por ejemplo, se puede implementar la interfaz para cargar valores de secretos en función de un valor de prefijo que se proporcione en el inicio de la aplicación. Esta técnica permite, por ejemplo, cargar secretos en función de la versión de la aplicación.

Advertencia

No deben usarse prefijos en los secretos del almacén de claves para:

  • Colocar secretos para varias aplicaciones en el mismo almacén.
  • Colocar secretos de entorno (por ejemplo, secretos de desarrollo frente a secretos de producción) en el mismo almacén.

Las distintas aplicaciones y entornos de desarrollo y producción deben usar almacenes de claves independientes para aislar los entornos de aplicación con el fin de lograr el mayor nivel de seguridad.

En el ejemplo siguiente, se establece un secreto en el almacén de claves (y se usa el Administrador de secretos para el entorno de desarrollo) para 5000-AppSecret (no se permite usar puntos en los nombres de secretos del almacén de claves). Este secreto representa un secreto de aplicación para la versión 5.0.0.0 de la aplicación. Para otra versión de la aplicación, 5.1.0.0, se agrega un secreto al almacén de claves (mediante el Administrador de secretos) para 5100-AppSecret. Cada versión de la aplicación carga el valor de secreto de la versión correspondiente en la configuración como AppSecret, quitando la versión a medida que carga el secreto.

Se llama a AddAzureKeyVault con una implementación de KeyVaultSecretManager personalizada:

// using Azure.Extensions.AspNetCore.Configuration.Secrets;

builder.Configuration.AddAzureKeyVault(
    new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"),
    new DefaultAzureCredential(),
    new SamplePrefixKeyVaultSecretManager("5000"));

La implementación reacciona a los prefijos de versión de los secretos para cargar el secreto adecuado en la configuración:

  • Load carga un secreto cuando su nombre comienza con el prefijo. No se cargan otros secretos.
  • GetKey:
    • Quita el prefijo del nombre del secreto.
    • Reemplaza dos guiones en cualquier nombre por KeyDelimiter, que es el delimitador usado en la configuración (normalmente dos puntos). Azure Key Vault no permite usar dos puntos en nombres de secretos.
public class SamplePrefixKeyVaultSecretManager : KeyVaultSecretManager
{
    private readonly string _prefix;

    public SamplePrefixKeyVaultSecretManager(string prefix)
        => _prefix = $"{prefix}-";

    public override bool Load(SecretProperties properties)
        => properties.Name.StartsWith(_prefix);

    public override string GetKey(KeyVaultSecret secret)
        => secret.Name[_prefix.Length..].Replace("--", ConfigurationPath.KeyDelimiter);
}

Un algoritmo de proveedor llama al método Load que recorre en iteración los secretos del almacén para buscar los secretos con el prefijo de versión. Cuando se encuentra un prefijo de versión con Load, el algoritmo usa el método GetKey para devolver el nombre de configuración del nombre del secreto. Quita el prefijo de versión del nombre del secreto. El resto del nombre del secreto se devuelve para cargarlo en los pares nombre-valor de configuración de la aplicación.

Cuando se aplica este enfoque:

  1. La versión de la aplicación especificada en el archivo de proyecto de la aplicación. En el ejemplo siguiente, la versión de la aplicación se establece en 5.0.0.0:

    <PropertyGroup>
      <Version>5.0.0.0</Version>
    </PropertyGroup>
    
  2. Confirme que la <UserSecretsId> propiedad está presente en el archivo del proyecto de la aplicación, donde {GUID} es un GUID proporcionado por el usuario:

    <PropertyGroup>
      <UserSecretsId>{GUID}</UserSecretsId>
    </PropertyGroup>
    

    Guarde los siguientes secretos localmente mediante el Administrador de secretos:

    dotnet user-secrets set "5000-AppSecret" "5.0.0.0_secret_value_dev"
    dotnet user-secrets set "5100-AppSecret" "5.1.0.0_secret_value_dev"
    
  3. Los secretos se guardan en Azure Key Vault mediante los siguientes comandos de la CLI de Azure:

    az keyvault secret set --vault-name {KEY VAULT NAME} --name "5000-AppSecret" --value "5.0.0.0_secret_value_prod"
    az keyvault secret set --vault-name {KEY VAULT NAME} --name "5100-AppSecret" --value "5.1.0.0_secret_value_prod"
    
  4. Cuando se ejecuta la aplicación, se cargan los secretos del almacén de claves. El secreto de cadena de 5000-AppSecret coincide con la versión de la aplicación especificada en el archivo del proyecto de la aplicación (5.0.0.0).

  5. La versión, 5000 (con el guión), se quita del nombre de la clave. A lo largo de la aplicación, la lectura de la configuración con la clave AppSecret carga el valor de secreto.

  6. Si se cambia la versión de la aplicación en el archivo del proyecto a 5.1.0.0 y se vuelve a ejecutar la aplicación, el valor secreto es 5.1.0.0_secret_value_dev en el entorno de desarrollo y 5.1.0.0_secret_value_prod en el de producción.

Nota:

También se puede crear una implementación de SecretClient propia en AddAzureKeyVault. Un cliente personalizado permite compartir una única instancia del cliente en toda la aplicación.

Enlace de una matriz a una clase

El proveedor puede leer los valores de configuración en una matriz para enlazarlos a una matriz POCO.

Al leer desde un origen de configuración que permite que las claves contengan separadores de dos puntos (:), se usa un segmento de clave numérico para distinguir las claves que componen una matriz (:0:, :1:, ... :{n}:). Para obtener más información, vea Configuración: Enlace de una matriz a una clase.

Las claves de Azure Key Vault no pueden usar dos puntos como separador. El enfoque descrito en este artículo usa guiones dobles (--) como separador para valores jerárquicos (secciones). Las claves de matriz se almacenan en Azure Key Vault con guiones dobles y segmentos de clave numéricos (--0--, --1--, ... --{n}--).

Examine la siguiente configuración del proveedor de registro Serilog proporcionada por un archivo JSON. Hay dos literales de objeto definidos en la matriz WriteTo que reflejan dos receptores de Serilog, que describen los destinos para la salida de registro:

"Serilog": {
  "WriteTo": [
    {
      "Name": "AzureTableStorage",
      "Args": {
        "storageTableName": "logs",
        "connectionString": "DefaultEnd...ountKey=Eby8...GMGw=="
      }
    },
    {
      "Name": "AzureDocumentDB",
      "Args": {
        "endpointUrl": "https://contoso.documents.azure.com:443",
        "authorizationKey": "Eby8...GMGw=="
      }
    }
  ]
}

La configuración que se muestra en el archivo JSON anterior se almacena en Azure Key Vault mediante la notación de guiones dobles (--) y segmentos numéricos:

Llave Value
Serilog--WriteTo--0--Name AzureTableStorage
Serilog--WriteTo--0--Args--storageTableName logs
Serilog--WriteTo--0--Args--connectionString DefaultEnd...ountKey=Eby8...GMGw==
Serilog--WriteTo--1--Name AzureDocumentDB
Serilog--WriteTo--1--Args--endpointUrl https://contoso.documents.azure.com:443
Serilog--WriteTo--1--Args--authorizationKey Eby8...GMGw==

Recarga de secretos

De forma predeterminada, el proveedor de configuración almacena en caché los secretos durante la vigencia de la aplicación. La aplicación omite los secretos que se han deshabilitado o actualizado posteriormente en el almacén.

Para volver a cargar secretos, llame a IConfigurationRoot.Reload:

config.Reload();

Para volver a cargar los secretos periódicamente, en un intervalo especificado, establezca la propiedad AzureKeyVaultConfigurationOptions.ReloadInterval. Para obtener más información, consulte Opciones de configuración.

Secretos deshabilitados y expirados

Los secretos deshabilitados y expirados se incluyen de forma predeterminada en el proveedor de configuración. Para excluir los valores de estos secretos en la configuración de la aplicación, actualice el secreto deshabilitado o expirado o proporcione la configuración mediante un proveedor de configuración personalizado:

class SampleKeyVaultSecretManager : KeyVaultSecretManager
{
  public override bool Load(SecretProperties properties) =>
    properties.ExpiresOn.HasValue &&
    properties.ExpiresOn.Value > DateTimeOffset.Now;
}

Pase este KeyVaultSecretManager personalizado a AddAzureKeyVault:

// using Azure.Extensions.AspNetCore.Configuration.Secrets;

builder.Configuration.AddAzureKeyVault(
    new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"),
    new DefaultAzureCredential(),
    new SampleKeyVaultSecretManager());

Los secretos deshabilitados no se pueden recuperar de Key Vault y nunca se incluyen.

Solución de problemas

Cuando la aplicación no puede cargar la configuración mediante el proveedor, se escribe un mensaje de error en la infraestructura de registro de ASP.NET Core. Las condiciones siguientes impedirán que se cargue la configuración:

  • La aplicación o el certificado no están configurados correctamente en Azure AD.
  • El almacén de claves no existe en Azure Key Vault.
  • La aplicación no está autorizada para acceder al almacén de claves.
  • La directiva de acceso no incluye los permisos Get ni List.
  • En el almacén de claves, los datos de configuración (par nombre-valor) se han denominado incorrectamente, faltan, están deshabilitados o han expirado.
  • La aplicación tiene un nombre de almacén de claves (KeyVaultName), un id. de aplicación de Azure AD (AzureADApplicationId) una huella digital de certificado de Azure AD (AzureADCertThumbprint) o un id. de directorio de Azure AD (AzureADDirectoryId) incorrectos.
  • Al agregar la directiva de acceso del almacén de claves para la aplicación, se creó la directiva, pero no se seleccionó el botón Guardar en la interfaz de usuario de directivas de acceso.

Recursos adicionales

En este artículo se explica cómo usar el proveedor de configuración de Azure Key Vault para cargar valores de configuración de aplicaciones desde secretos de Azure Key Vault. Azure Key Vault es un servicio basado en la nube que protege los secretos y las claves criptográficas usados por los servicios y las aplicaciones en la nube. Entre los escenarios comunes para usar Azure Key Vault con aplicaciones de ASP.NET Core se incluyen:

  • Control del acceso a datos de configuración confidenciales.
  • Cumplimiento del requisito de módulos de seguridad de hardware validados por FIPS 140-2 nivel 2 al almacenar datos de configuración.

Paquetes

Agregue referencias de paquete para los paquetes siguientes:

Aplicación de ejemplo

La aplicación de ejemplo se ejecuta en cualquiera de los dos modos determinados por la directiva de preprocesador #define de la parte superior de Program.cs:

  • Certificate: muestra el uso de un id. de cliente de Azure Key Vault y un certificado X.509 para acceder a los secretos almacenados en Azure Key Vault. Este ejemplo se puede ejecutar desde cualquier ubicación, tanto si se implementa en Azure App Service como en cualquier host que pueda servir a una aplicación de ASP.NET Core.
  • Managed: muestra cómo usar identidades administradas para recursos de Azure. La identidad administrada autentica la aplicación en Azure Key Vault con la autenticación de Azure Active Directory (AD) sin las credenciales almacenadas en el código ni en la configuración de la aplicación. Cuando se usan identidades administradas para autenticarse, no se requiere un id. de aplicación de Azure AD ni una contraseña (secreto de cliente). La versión Managed del ejemplo debe implementarse en Azure. Siga las instrucciones de la sección Uso de las identidades administradas para recursos de Azure .

Para obtener más información sobre cómo configurar una aplicación de ejemplo mediante directivas de preprocesador (#define), consulte Información general de ASP.NET Core.

Vea o descargue el código de ejemplo (cómo descargarlo)

Almacenamiento de secretos en el entorno de desarrollo

Establezca los secretos localmente mediante el Administrador de secretos. Cuando la aplicación de ejemplo se ejecuta en el equipo local en el entorno de desarrollo, los secretos se cargan desde el almacén de secretos de usuario local.

El Administrador de secretos requiere una propiedad <UserSecretsId> en el archivo del proyecto de la aplicación. Establezca el valor de propiedad ({GUID}) en cualquier GUID único:

<PropertyGroup>
  <UserSecretsId>{GUID}</UserSecretsId>
</PropertyGroup>

Los secretos se crean como pares nombre-valor. Los valores jerárquicos (secciones de configuración) usan : (dos puntos) como separador en los nombres de clave de configuración de ASP.NET Core.

El Administrador de secretos se usa desde un shell de comandos abierto en la raíz de contenido del proyecto, donde {SECRET NAME} es el nombre y {SECRET VALUE} es el valor:

dotnet user-secrets set "{SECRET NAME}" "{SECRET VALUE}"

Ejecute los siguientes comandos en un shell de comandos desde la raíz de contenido del proyecto para establecer los secretos de la aplicación de ejemplo:

dotnet user-secrets set "SecretName" "secret_value_1_dev"
dotnet user-secrets set "Section:SecretName" "secret_value_2_dev"

Cuando estos secretos se almacenan en Azure Key Vault en la sección Almacenamiento de secretos en el entorno de producción con Azure Key Vault, el sufijo _dev se cambia a _prod. El sufijo proporciona una indicación visual en la salida de la aplicación que indica el origen de los valores de configuración.

Almacenamiento de secretos en el entorno de producción con Azure Key Vault

Haga lo siguiente para crear una instancia de Azure Key Vault y almacenar en ella los secretos de la aplicación de ejemplo. Para más información, consulte Inicio rápido: Establecimiento y recuperación de un secreto desde Azure Key Vault mediante la CLI de Azurede Azure.

  1. Abra Azure Cloud Shell con cualquiera de los métodos siguientes en Azure Portal:

    • Seleccione Pruébelo en la esquina superior derecha de un bloque de código. Use la cadena de búsqueda "CLI de Azure" en el cuadro de texto.
    • Abra Cloud Shell en el explorador con el botón Iniciar Cloud Shell.
    • Seleccione el botón de Cloud Shell en el menú de la esquina superior derecha de Azure Portal.

    Para obtener más información, consulte CLI de Azure e Introducción a Azure Cloud Shell.

  2. Si aún no se ha autenticado, inicie sesión con el comando az login.

  3. Cree un grupo de recursos con el siguiente comando, donde {RESOURCE GROUP NAME} es el nombre del nuevo grupo de recursos y {LOCATION} es la región de Azure:

    az group create --name "{RESOURCE GROUP NAME}" --location {LOCATION}
    
  4. Cree un almacén de claves en el grupo de recursos con el siguiente comando, donde {KEY VAULT NAME} es el nombre del nuevo almacén de claves y {LOCATION} es la región de Azure:

    az keyvault create --name {KEY VAULT NAME} --resource-group "{RESOURCE GROUP NAME}" --location {LOCATION}
    
  5. Cree secretos en el almacén de claves como pares nombre-valor.

    Los nombres de secretos de Azure Key Vault se limitan a caracteres alfanuméricos y guiones. Los valores jerárquicos (secciones de configuración) usan -- (dos guiones) como delimitador, ya que no se permiten dos puntos en los nombres de secretos del almacén de claves. Los dos puntos delimitan una sección de una subclave en la configuración de ASP.NET Core. La secuencia de dos guiones se reemplaza por dos puntos cuando los secretos se cargan en la configuración de la aplicación.

    Los secretos siguientes se usan con la aplicación de ejemplo. Los valores incluyen un sufijo _prod para distinguirlos de los valores de sufijo _devcargados en el entorno de desarrollo del Administrador de secretos. Reemplace {KEY VAULT NAME} por el nombre del almacén de claves que creó en el paso anterior:

    az keyvault secret set --vault-name {KEY VAULT NAME} --name "SecretName" --value "secret_value_1_prod"
    az keyvault secret set --vault-name {KEY VAULT NAME} --name "Section--SecretName" --value "secret_value_2_prod"
    

Uso del id. de aplicación y el certificado X.509 para aplicaciones no hospedadas en Azure

Configure Azure AD, Azure Key Vault y la aplicación para usar un id. de aplicación de Azure AD y un certificado X.509 para autenticarse en un almacén de claves cuando la aplicación esté hospedada fuera de Azure. Para más información, consulte el artículo About keys, secrets, and certificates (Claves, secretos y certificados).

Nota:

Aunque se admite el uso de un id. de aplicación y un certificado X.509 para las aplicaciones hospedadas en Azure, no se recomienda. En lugar de eso, use identidades administradas para recursos de Azure al hospedar una aplicación en Azure. Las identidades administradas no requieren almacenar un certificado en la aplicación ni en el entorno de desarrollo.

La aplicación de ejemplo usa un id. de aplicación y un certificado X.509 si la directiva de preprocesado #define de la parte superior de Program.cs está establecida en Certificate.

  1. Cree un certificado de archivo PKCS#12 (.pfx). Entre las opciones para crear certificados se incluyen New-SelfSignedCertificate en Windows y OpenSSL.
  2. Instale el certificado en el almacén de certificados personal del usuario actual. Marcar la clave como exportable es opcional. Anote la huella digital del certificado, ya que se usa más adelante en este proceso.
  3. Exporte el certificado de archivo PKCS#12 (.pfx) como certificado con codificación DER (.cer).
  4. Registre la aplicación con Azure AD (Registros de aplicaciones).
  5. Cargue el certificado con codificación DER (.cer) en Azure AD:
    1. Seleccione la aplicación en Azure AD.
    2. Vaya a Certificados y secretos.
    3. Seleccione Cargar certificado para cargar el certificado, que contiene la clave pública. Son aceptables los certificados .cer, .pem y .crt.
  6. Almacene el nombre del almacén de claves, el id. de aplicación y la huella digital del certificado en el archivo de appsettings.json la aplicación.
  7. Vaya a Almacenes de claves en Azure Portal.
  8. Seleccione el almacén de claves que creó en la sección Almacenamiento de secretos en el entorno de producción con Azure Key Vault.
  9. Seleccione Directivas de acceso.
  10. Seleccione Agregar directiva de acceso.
  11. Abra Permisos de secretos y proporcione a la aplicación los permisos Get y List.
  12. Seleccione Seleccionar principal y seleccione la aplicación registrada por nombre. Seleccione el botón Seleccionar.
  13. Seleccione Aceptar.
  14. Seleccione Guardar.
  15. Implemente la aplicación.

La aplicación de ejemplo Certificate obtiene sus valores de configuración de IConfigurationRoot con el mismo nombre que el nombre del secreto:

  • Valores no jerárquicos: el valor de SecretName se obtiene con config["SecretName"].
  • Valores jerárquicos (secciones): use la notación : (dos puntos) o el método GetSection. Use cualquiera de estos enfoques para obtener el valor de configuración:
    • config["Section:SecretName"]
    • config.GetSection("Section")["SecretName"]

El certificado X.509 lo administra el sistema operativo. La aplicación llama a AddAzureKeyVault con valores proporcionados por el archivo appsettings.json:

// using System.Linq;
// using System.Security.Cryptography.X509Certificates;
// using Azure.Extensions.AspNetCore.Configuration.Secrets;
// using Azure.Identity;

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((context, config) =>
        {
            if (context.HostingEnvironment.IsProduction())
            {
                var builtConfig = config.Build();

                using var store = new X509Store(StoreLocation.CurrentUser);
                store.Open(OpenFlags.ReadOnly);
                var certs = store.Certificates.Find(
                    X509FindType.FindByThumbprint,
                    builtConfig["AzureADCertThumbprint"], false);

                config.AddAzureKeyVault(new Uri($"https://{builtConfig["KeyVaultName"]}.vault.azure.net/"),
                                        new ClientCertificateCredential(builtConfig["AzureADDirectoryId"], builtConfig["AzureADApplicationId"], certs.OfType<X509Certificate2>().Single()),
                                        new KeyVaultSecretManager());

                store.Close();
            }
        })
        .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());

Valores de ejemplo:

  • Nombre de la instancia del almacén de claves: contosovault
  • Id. de la aplicación: 627e911e-43cc-61d4-992e-12db9c81b413
  • Huella digital de certificado: fe14593dd66b2406c5269d742d04b6e1ab03adb1

appsettings.json:

{
  "KeyVaultName": "Key Vault Name",
  "AzureADApplicationId": "Azure AD Application ID",
  "AzureADCertThumbprint": "Azure AD Certificate Thumbprint",
  "AzureADDirectoryId": "Azure AD Directory ID"
}

Al ejecutar la aplicación, una página web muestra los valores de secretos cargados. En el entorno de desarrollo, los valores de secretos se cargan con el sufijo _dev. En el entorno de producción, los valores se cargan con el sufijo _prod.

Uso de identidades administradas para recursos de Azure

Una aplicación implementada en Azure puede aprovechar las identidades administradas para recursos de Azure. Una identidad administrada permite que la aplicación se autentique con Azure Key Vault mediante la autenticación de Azure AD sin credenciales (id. de aplicación y contraseña o secreto de cliente) almacenadas en la aplicación.

La aplicación de ejemplo usa identidades administradas para recursos de Azure cuando la directiva de preprocesador #define de la parte superior de Program.cs está establecida en Managed.

Escriba el nombre del almacén en el archivo de appsettings.json la aplicación. La aplicación de ejemplo no requiere un id. de aplicación ni una contraseña (secreto de cliente) cuando se establece en la versión Managed, por lo que se pueden omitir esas entradas de configuración. La aplicación se implementa en Azure, que autentica la aplicación para acceder a Azure Key Vault solo con el nombre del almacén almacenado en el archivo appsettings.json.

Implementación de la aplicación de ejemplo en Azure App Service.

Una aplicación implementada en Azure App Service se registra automáticamente con Azure AD cuando se crea el servicio. Obtenga el id. de objeto de la implementación para usarlo en el siguiente comando. El id. de objeto se muestra en Azure Portal en el panel Identity de App Service.

Con la CLI de Azure y el id. de objeto de la aplicación, proporcione a la aplicación los permisos list y get para acceder al almacén de claves:

az keyvault set-policy --name {KEY VAULT NAME} --object-id {OBJECT ID} --secret-permissions get list

Reinicie la aplicación mediante la CLI de Azure, PowerShell o el Azure Portal.

La aplicación de ejemplo:

  • Crea una instancia de la clase DefaultAzureCredential. La credencial intenta obtener un token de acceso del entorno para los recursos de Azure.
  • Se crea un nuevo SecretClient con la instancia de DefaultAzureCredential.
  • La instancia de SecretClient se usa con una instancia de KeyVaultSecretManager, que carga valores de secretos y reemplaza los guiones dobles (--) por dos puntos (:) en los nombres de clave.
// using Azure.Security.KeyVault.Secrets;
// using Azure.Identity;
// using Azure.Extensions.AspNetCore.Configuration.Secrets;

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((context, config) =>
        {
            if (context.HostingEnvironment.IsProduction())
            {
                var builtConfig = config.Build();
                var secretClient = new SecretClient(
                    new Uri($"https://{builtConfig["KeyVaultName"]}.vault.azure.net/"),
                    new DefaultAzureCredential());
                config.AddAzureKeyVault(secretClient, new KeyVaultSecretManager());
            }
        })
        .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());

Valor de ejemplo del nombre del almacén de claves: contosovault

appsettings.json:

{
  "KeyVaultName": "Key Vault Name"
}

Al ejecutar la aplicación, una página web muestra los valores de secretos cargados. En el entorno de desarrollo, los valores de secretos tienen el sufijo _dev porque los proporciona el Administrador de secretos. En el entorno de producción, los valores se cargan con el sufijo _prod porque los proporciona Azure Key Vault.

Si recibe el error Access denied, confirme que la aplicación está registrada en Azure AD y que proporcionó acceso al almacén de claves. Confirme que ha reiniciado el servicio en Azure.

Para obtener información sobre el uso del proveedor con una identidad administrada y Azure Pipelines, consulte Creación de una conexión de servicio de Azure Resource Manager a una VM con una identidad de servicio administrada.

Opciones de configuración

AddAzureKeyVault puede aceptar un objeto AzureKeyVaultConfigurationOptions:

config.AddAzureKeyVault(
    new SecretClient(
        new Uri("Your Key Vault Endpoint"),
        new DefaultAzureCredential(),
        new AzureKeyVaultConfigurationOptions())
    {
        ...
    });

El objeto AzureKeyVaultConfigurationOptions contiene las siguientes propiedades.

Propiedad Descripción
Manager Instancia de KeyVaultSecretManager que se usa para controlar la carga de secretos.
ReloadInterval TimeSpan para esperar entre los intentos de sondeo del almacén de claves para comprobar los cambios. El valor predeterminado es null (la configuración no se vuelve a cargar).

Uso de un prefijo de nombre de clave

AddAzureKeyVault proporciona una sobrecarga que acepta una implementación de KeyVaultSecretManager, que permite controlar cómo se convierten los secretos del almacén de claves en claves de configuración. Por ejemplo, se puede implementar la interfaz para cargar valores de secretos en función de un valor de prefijo que se proporcione en el inicio de la aplicación. Esta técnica permite, por ejemplo, cargar secretos en función de la versión de la aplicación.

Advertencia

No deben usarse prefijos en los secretos del almacén de claves para:

  • Colocar secretos para varias aplicaciones en el mismo almacén.
  • Colocar secretos de entorno (por ejemplo, secretos de desarrollo frente a secretos de producción) en el mismo almacén.

Las distintas aplicaciones y entornos de desarrollo y producción deben usar almacenes de claves independientes para aislar los entornos de aplicación con el fin de lograr el mayor nivel de seguridad.

En el ejemplo siguiente, se establece un secreto en el almacén de claves (y se usa el Administrador de secretos para el entorno de desarrollo) para 5000-AppSecret (no se permite usar puntos en los nombres de secretos del almacén de claves). Este secreto representa un secreto de aplicación para la versión 5.0.0.0 de la aplicación. Para otra versión de la aplicación, 5.1.0.0, se agrega un secreto al almacén de claves (mediante el Administrador de secretos) para 5100-AppSecret. Cada versión de la aplicación carga el valor de secreto de la versión correspondiente en la configuración como AppSecret, quitando la versión a medida que carga el secreto.

Se llama a AddAzureKeyVault con una implementación de KeyVaultSecretManager personalizada:

config.AddAzureKeyVault(
    $"https://{builtConfig["KeyVaultName"]}.vault.azure.net/",
    builtConfig["AzureADApplicationId"],
    certs.OfType<X509Certificate2>().Single(),
    new PrefixKeyVaultSecretManager(versionPrefix));

La implementación reacciona a los prefijos de versión de los secretos para cargar el secreto adecuado en la configuración:

  • Load carga un secreto cuando su nombre comienza con el prefijo. No se cargan otros secretos.
  • GetKey:
    • Quita el prefijo del nombre del secreto.
    • Reemplaza dos guiones en cualquier nombre por KeyDelimiter, que es el delimitador usado en la configuración (normalmente dos puntos). Azure Key Vault no permite usar dos puntos en nombres de secretos.
public class PrefixKeyVaultSecretManager : KeyVaultSecretManager
{
    private readonly string _prefix;

    public PrefixKeyVaultSecretManager(string prefix)
    {
        _prefix = $"{prefix}-";
    }

    public override bool Load(SecretProperties secret)
    {
        return secret.Name.StartsWith(_prefix);
    }

    public override string GetKey(KeyVaultSecret secret)
    {
        return secret.Name
            .Substring(_prefix.Length)
            .Replace("--", ConfigurationPath.KeyDelimiter);
    }
}

Un algoritmo de proveedor llama al método Load que recorre en iteración los secretos del almacén para buscar los secretos con el prefijo de versión. Cuando se encuentra un prefijo de versión con Load, el algoritmo usa el método GetKey para devolver el nombre de configuración del nombre del secreto. Quita el prefijo de versión del nombre del secreto. El resto del nombre del secreto se devuelve para cargarlo en los pares nombre-valor de configuración de la aplicación.

Cuando se aplica este enfoque:

  1. La versión de la aplicación especificada en el archivo de proyecto de la aplicación. En el ejemplo siguiente, la versión de la aplicación se establece en 5.0.0.0:

    <PropertyGroup>
      <Version>5.0.0.0</Version>
    </PropertyGroup>
    
  2. Confirme que la <UserSecretsId> propiedad está presente en el archivo del proyecto de la aplicación, donde {GUID} es un GUID proporcionado por el usuario:

    <PropertyGroup>
      <UserSecretsId>{GUID}</UserSecretsId>
    </PropertyGroup>
    

    Guarde los siguientes secretos localmente mediante el Administrador de secretos:

    dotnet user-secrets set "5000-AppSecret" "5.0.0.0_secret_value_dev"
    dotnet user-secrets set "5100-AppSecret" "5.1.0.0_secret_value_dev"
    
  3. Los secretos se guardan en Azure Key Vault mediante los siguientes comandos de la CLI de Azure:

    az keyvault secret set --vault-name {KEY VAULT NAME} --name "5000-AppSecret" --value "5.0.0.0_secret_value_prod"
    az keyvault secret set --vault-name {KEY VAULT NAME} --name "5100-AppSecret" --value "5.1.0.0_secret_value_prod"
    
  4. Cuando se ejecuta la aplicación, se cargan los secretos del almacén de claves. El secreto de cadena de 5000-AppSecret coincide con la versión de la aplicación especificada en el archivo del proyecto de la aplicación (5.0.0.0).

  5. La versión, 5000 (con el guión), se quita del nombre de la clave. A lo largo de la aplicación, la lectura de la configuración con la clave AppSecret carga el valor de secreto.

  6. Si se cambia la versión de la aplicación en el archivo del proyecto a 5.1.0.0 y se vuelve a ejecutar la aplicación, el valor secreto es 5.1.0.0_secret_value_dev en el entorno de desarrollo y 5.1.0.0_secret_value_prod en el de producción.

Nota:

También se puede crear una implementación de SecretClient propia en AddAzureKeyVault. Un cliente personalizado permite compartir una única instancia del cliente en toda la aplicación.

Enlace de una matriz a una clase

El proveedor puede leer los valores de configuración en una matriz para enlazarlos a una matriz POCO.

Al leer desde un origen de configuración que permite que las claves contengan separadores de dos puntos (:), se usa un segmento de clave numérico para distinguir las claves que componen una matriz (:0:, :1:, ... :{n}:). Para obtener más información, vea Configuración: Enlace de una matriz a una clase.

Las claves de Azure Key Vault no pueden usar dos puntos como separador. El enfoque descrito en este artículo usa guiones dobles (--) como separador para valores jerárquicos (secciones). Las claves de matriz se almacenan en Azure Key Vault con guiones dobles y segmentos de clave numéricos (--0--, --1--, ... --{n}--).

Examine la siguiente configuración del proveedor de registro Serilog proporcionada por un archivo JSON. Hay dos literales de objeto definidos en la matriz WriteTo que reflejan dos receptores de Serilog, que describen los destinos para la salida de registro:

"Serilog": {
  "WriteTo": [
    {
      "Name": "AzureTableStorage",
      "Args": {
        "storageTableName": "logs",
        "connectionString": "DefaultEnd...ountKey=Eby8...GMGw=="
      }
    },
    {
      "Name": "AzureDocumentDB",
      "Args": {
        "endpointUrl": "https://contoso.documents.azure.com:443",
        "authorizationKey": "Eby8...GMGw=="
      }
    }
  ]
}

La configuración que se muestra en el archivo JSON anterior se almacena en Azure Key Vault mediante la notación de guiones dobles (--) y segmentos numéricos:

Llave Value
Serilog--WriteTo--0--Name AzureTableStorage
Serilog--WriteTo--0--Args--storageTableName logs
Serilog--WriteTo--0--Args--connectionString DefaultEnd...ountKey=Eby8...GMGw==
Serilog--WriteTo--1--Name AzureDocumentDB
Serilog--WriteTo--1--Args--endpointUrl https://contoso.documents.azure.com:443
Serilog--WriteTo--1--Args--authorizationKey Eby8...GMGw==

Recarga de secretos

Los secretos se almacenan en caché hasta que se llama a IConfigurationRoot.Reload. La aplicación no respeta los secretos expirados, deshabilitados o actualizados en el almacén de claves hasta que se ejecuta Reload.

Configuration.Reload();

Secretos deshabilitados y expirados

Los secretos deshabilitados y expirados se incluyen de forma predeterminada en el proveedor de configuración. Para excluir los valores de estos secretos en la configuración de la aplicación, actualice el secreto deshabilitado o expirado o proporcione la configuración mediante un proveedor de configuración personalizado:

class SampleKeyVaultSecretManager : KeyVaultSecretManager
{
  public override bool Load(SecretProperties properties) =>
    properties.ExpiresOn.HasValue &&
    properties.ExpiresOn.Value > DateTimeOffset.Now;
}

Pase este KeyVaultSecretManager personalizado a AddAzureKeyVault:

// using Azure.Extensions.AspNetCore.Configuration.Secrets;

config.AddAzureKeyVault(
    new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"),
    new DefaultAzureCredential(),
    new SampleKeyVaultSecretManager());

Los secretos deshabilitados no se pueden recuperar de Key Vault y nunca se incluyen.

Solución de problemas

Cuando la aplicación no puede cargar la configuración mediante el proveedor, se escribe un mensaje de error en la infraestructura de registro de ASP.NET Core. Las condiciones siguientes impedirán que se cargue la configuración:

  • La aplicación o el certificado no están configurados correctamente en Azure AD.
  • El almacén de claves no existe en Azure Key Vault.
  • La aplicación no está autorizada para acceder al almacén de claves.
  • La directiva de acceso no incluye los permisos Get ni List.
  • En el almacén de claves, los datos de configuración (par nombre-valor) se han denominado incorrectamente, faltan, están deshabilitados o han expirado.
  • La aplicación tiene un nombre de almacén de claves (KeyVaultName), un id. de aplicación de Azure AD (AzureADApplicationId) una huella digital de certificado de Azure AD (AzureADCertThumbprint) o un id. de directorio de Azure AD (AzureADDirectoryId) incorrectos.
  • Al agregar la directiva de acceso del almacén de claves para la aplicación, se creó la directiva, pero no se seleccionó el botón Guardar en la interfaz de usuario de directivas de acceso.

Recursos adicionales