Patrón Valet Key

Azure
Azure Storage

Usa un token que proporciona a los clientes acceso directo restringido a un recurso específico, con el fin de descargar la transferencia de datos desde la aplicación. Esto es especialmente útil en aplicaciones que usan sistemas o colas de almacenamiento hospedado en la nube, ya que puede minimizar los costos y maximizar la escalabilidad y el rendimiento.

Contexto y problema

Los programas cliente y los exploradores web a menudo necesitan leer flujos de datos del almacenamiento de una aplicación o escribirlos en él. Normalmente, la aplicación administra el movimiento de los datos, o bien los captura del almacenamiento y los transmite al cliente o lee el flujo cargado del cliente y lo almacena en el almacén de datos. Sin embargo, este enfoque consume recursos valiosos, como proceso, memoria y ancho de banda.

Los almacenes de datos disponen de la posibilidad de cargar y descargar los datos directamente, sin necesidad de que la aplicación realice ningún procesamiento para mover estos datos. Sin embargo, para lograr esto es necesario normalmente que el cliente tenga acceso a las credenciales de seguridad del almacén. Esta técnica puede ser útil para reducir los costos de transferencia de datos y el requisito de escalado horizontal de la aplicación, y para aumentar el rendimiento. Sin embargo, significa que la aplicación ya no es capaz de administrar la seguridad de los datos. Una vez que el cliente tiene una conexión al almacén de datos para el acceso directo, la aplicación no puede actuar como equipo selector. Ya no tiene el control del proceso y no puede impedir las sucesivas cargas o descargas del almacén de datos.

Este no es un enfoque realista en sistemas distribuidos que deben atender a clientes en los que no se confía. Por el contrario, las aplicaciones deben poder controlar con seguridad el acceso a los datos de forma granular, y aun así reducir la carga en el servidor, para lo cual configuran esta conexión y luego permiten que el cliente se comunique directamente con el almacén de datos para realizar las operaciones de lectura y escritura necesarias.

Solución

Debe resolver el problema de controlar el acceso a un almacén de datos cuando el almacén no pueda administrar la autenticación y la autorización de los clientes. Una solución típica consiste en restringir el acceso a la conexión pública del almacén de datos y proporcionar al cliente una clave o un token que dicho almacén pueda validar.

Esta clave o token se conoce normalmente como clave auxiliar. Esta clave proporciona acceso limitado a recursos específicos y permite únicamente operaciones predefinidas con control detallado, como escritura en almacenamiento, pero no lectura ni carga y descarga en un explorador web. Las aplicaciones pueden crear y emitir claves auxiliares a dispositivos cliente y exploradores web de manera rápida y fácil, lo que permite que los clientes realicen las operaciones necesarias sin necesidad de que la aplicación administre directamente la transferencia de datos. De esta manera, se elimina la sobrecarga de procesamiento, y el impacto sobre el rendimiento y la escalabilidad, de la aplicación y el servidor.

El cliente usa este token para acceder a un recurso concreto en el almacén de datos durante un período de tiempo específico, y con restricciones específicas sobre los permisos de acceso, como se muestra en la ilustración. Tras el período especificado, la clave deja de ser válida y no permite el acceso al recurso.

Diagrama de un flujo de trabajo típico del patrón de clave de acceso limitado.

Diagrama que muestra un ejemplo del flujo de trabajo de un sistema que usa el patrón de clave de acceso limitado. El paso 1 muestra al usuario que solicita el recurso de destino. El paso 2 muestra la aplicación de clave de acceso limitado que comprueba la validez de la solicitud y genera un token de acceso. El paso 3 muestra el token que se devuelve al usuario. El paso 4 muestra al usuario que accede al recurso de destino mediante el token.

También es posible configurar una clave que tenga otras dependencias, como el ámbito de los datos. Por ejemplo, según las capacidades del almacén de datos, la clave puede especificar una tabla completa de un almacén de datos o solo filas específicas de una tabla. En sistemas de almacenamiento de nube, la clave puede especificar un contenedor o simplemente un elemento específico dentro de un contenedor.

La clave también se puede invalidar mediante la aplicación. Este enfoque es útil si el cliente notifica al servidor que la operación de transferencia de datos ha finalizado. Luego, el servidor puede invalidar dicha clave para impedir que se vuelva a acceder.

Con este patrón, puede simplificar la administración del acceso a los recursos porque no hay necesidad de crear y autenticar a un usuario, conceder permisos y luego quitar de nuevo el usuario o, lo que es peor, dejar ese permiso como permanente. También permite limitar de forma fácil la ubicación, el permiso y el período de validez, todo ello mediante la simple generación de una clave en tiempo de ejecución. Los factores importantes son limitar el período de validez, y especialmente la ubicación del recurso, lo más estrictamente posible para que el destinatario solo pueda usarse para el fin que se ha previsto.

Problemas y consideraciones

Tenga en cuenta los puntos siguientes al decidir cómo implementar este patrón:

Administrar el estado de validez y el período de la clave. Si se ha filtrado o puesto en peligro, la clave desbloquea de manera efectiva el elemento de destino y permite que esté disponible para su uso malintencionado durante el período de validez. Una clave normalmente se puede revocar o deshabilitar, dependiendo de cómo se emitiera. Las directivas del lado servidor se pueden cambiar, o la clave de servidor con la que se han firmado se puede invalidar. Especifique un período de validez corto para minimizar el riesgo de permitir que tengan lugar operaciones no autorizadas contra el almacén de datos. Sin embargo, si el período de validez es demasiado corto, el cliente no podrá completar la operación antes de que expire la clave. Permita que los usuarios autorizados renueven la clave antes de que expire el período de validez si se requieren varios accesos al recurso protegido.

Controlar el nivel de acceso que proporcionará la clave. Normalmente, la clave debe permitir al usuario realizar únicamente las acciones necesarias para completar la operación, como el acceso de solo lectura si el cliente no debería poder cargar los datos en el almacén de datos. Con las cargas de archivos, es habitual especificar una clave que proporcione permisos de solo escritura, así como la ubicación y el período de validez. Es fundamental especificar con precisión el recurso o el conjunto de recursos a los que se aplica la clave.

Considerar cómo controlar el comportamiento de los usuarios. Implementar este patrón significa cierta pérdida de control sobre los recursos a los que se concede acceso a los usuarios. El nivel de control que se puede ejercer está limitado por las funcionalidades de las directivas y los permisos disponibles para el servicio o el almacén de datos de destino. Por ejemplo, lo habitual es que no sea posible crear una clave que limite el tamaño de los datos que se van a escribir en el almacenamiento, o el número de veces que se puede usar la clave para acceder a un archivo. Esta situación puede dar lugar a que aumenten los costos de transferencia de datos de forma inesperada, incluso cuando esta clave la usa el cliente previsto, y podría deberse a un error en el código que provoca cargas y descargas repetidas. Para limitar el número de veces que se puede cargar un archivo, siempre que sea posible, obligue al cliente a que notifique a la aplicación cuando una operación finalice. Por ejemplo, algunos almacenes de datos generan eventos que el código de aplicación puede usar para supervisar las operaciones y controlar el comportamiento del usuario. Sin embargo, resulta difícil exigir cuotas para usuarios individuales en un escenario multiinquilino donde todos los usuarios de un mismo inquilino usan la misma clave. Conceder a los usuarios permisos de creación puede ayudarle a controlar la cantidad de datos que se actualizan haciendo que los tokens sean de un solo uso. El permiso de creación no permite sobrescrituras, por lo que cada token se puede usar para una única actividad de escritura.

Validar y, opcionalmente, corregir, todos los datos cargados. Un usuario malintencionado que consiga acceso a la clave podría cargar datos diseñados para poner en peligro el sistema. También, los usuarios autorizados podrían cargar datos que no son válidos que, cuando se procesan, dan lugar a un error o un error del sistema. Para evitar este problema, asegúrese de que todos los datos cargados se validen y se comprueben en busca de contenido malintencionado antes de su uso.

Auditar todas las operaciones. Numerosos mecanismos basados en claves pueden registrar operaciones como cargas, descargas y errores. Estos registros se pueden incorporar normalmente a un proceso de auditoría y también se usan para facturación si el usuario paga según el volumen de datos o el tamaño de los archivos. Use los registros para detectar errores de autenticación que podrían deberse a problemas con el proveedor de claves o a la eliminación accidental de una directiva de acceso almacenada.

Entregar la clave de forma segura. Se puede insertar en una dirección URL que el usuario activa en una página web, o se puede usar en una operación de redireccionamiento del servidor para que la descarga se produzca automáticamente. Use siempre HTTPS para entregar la clave por un canal seguro.

Proteger los datos confidenciales en tránsito. La entrega de los datos confidenciales a través de la aplicación normalmente se realiza mediante TLS y así debe ser para los clientes que acceden al almacén de datos directamente.

Otros aspectos que se deben tener en cuenta al implementar este patrón son:

  • Si el cliente no notifica, o no puede notificar, al servidor el fin de la operación y el único límite es el período de expiración de la clave, la aplicación no podrá realizar operaciones de auditoría, como contar el número de cargas o descargas o impedir varias cargas o descargas.

  • La flexibilidad de las directivas de clave que se pueden generar podría ser limitada. Por ejemplo, algunos mecanismos solo permiten el uso de un período de expiración con un tiempo fijado. Otros usuarios no pueden especificar una granularidad suficiente de permisos de lectura y escritura.

  • Si se especifica la hora de inicio del período de validez del token o de la clave, asegúrese de que sea un poco anterior a la hora actual del servidor para permitir que se sincronicen los relojes del cliente que podrían estar ligeramente fuera de sincronización. Si no se especifica, el valor predeterminado es normalmente la hora actual del servidor.

  • La dirección URL que contiene la clave podría registrarse en los archivos de registro del servidor. Si bien la clave habrá expirado normalmente antes de que los archivos de registro se utilicen para el análisis, asegúrese de limitar el acceso a ellos. Si los datos de registro se trasmiten a un sistema de supervisión o se almacenan en otra ubicación, considere la posibilidad de implementar un retraso para impedir la fuga de claves hasta después de que haya expirado el periodo de validez.

  • Si el código de cliente se ejecuta en un explorador web, es posible que el explorador tenga que ser compatible con el uso compartido de recursos entre orígenes (CORS) para permitir que el código que se ejecuta en él acceda a los datos de un dominio diferente a aquel que entregó la página. Algunos exploradores más antiguos y algunos almacenes de datos no son compatibles con CORS, y el código que se ejecuta en estos exploradores es posible que no pueda usar una clave de acceso limitado para proporcionar acceso a los datos de un dominio diferente, como una cuenta de almacenamiento en la nube.

  • Aunque el cliente no necesita tener preconfigurada la autenticación para el recurso final, debe establecer previamente los medios de autenticación en el servicio de clave de acceso limitado.

  • Las claves solo se deben entregar a los clientes autenticados con la autorización adecuada.

  • La generación de tokens de acceso es una acción con privilegios, por lo que el servicio de clave de acceso limitado se debe proteger con directivas de acceso estrictas. El servicio puede permitir el acceso de terceros a sistemas confidenciales, lo que hace que su seguridad sea esencial.

Cuándo usar este patrón

Este patrón es útil en las situaciones siguientes:

  • Para minimizar la carga de recursos y maximizar el rendimiento y la escalabilidad. Con una clave auxiliar, no es necesario bloquear el recurso, no se requiere ninguna llamada al servidor remoto, no hay ningún límite en el número de claves auxiliares que se pueden emitir y se evita que exista un único punto de error derivado de realizar la transferencia de datos a través del código de aplicación. La creación de una clave auxiliar es normalmente una operación criptográfica sencilla que consiste en firmar una cadena con una clave.

  • Para minimizar el costo operativo. Habilitar el acceso directo a los almacenes y las colas resulta rentable y permite hacer un uso eficiente de los recursos, puede dar lugar a menos recorridos de ida y vuelta en la red y podría permitir una reducción en el número de recursos de proceso necesarios.

  • Cuando los clientes cargan o descargan datos de forma periódica, en especial en caso de grandes volúmenes o cuando en cada operación intervienen archivos de gran tamaño.

  • Cuando la aplicación tiene recursos de proceso limitados disponibles, debido a las limitaciones de hospedaje o a aspectos relacionados con el costo. En este escenario, el patrón es incluso más útil si hay muchas cargas o descargas de datos simultáneas, ya que libera a la aplicación de tener que administrar la transferencia de datos.

  • Cuando los datos se almacenan en un almacén de datos remoto o en otra región. Si uno de los requisitos fuera que la aplicación funcionara como equipo selector, podría haber un cargo por el uso de ancho de banda adicional que supone transferir los datos entre regiones, o en redes públicas y privadas, entre el cliente y la aplicación y luego entre la aplicación y el almacén de datos.

Este patrón podría no ser útil en las siguientes situaciones:

  • Si los clientes ya se pueden autenticar de forma única en el servicio back-end, por ejemplo, con RBAC, no use este patrón.

  • Si la aplicación debe realizar alguna tarea en los datos antes de que se almacenen o antes de enviarlos al cliente. Por ejemplo, si la aplicación necesita realizar la validación, registrar el éxito de acceso o ejecutar una transformación en los datos. Sin embargo, algunos almacenes de datos y clientes pueden negociar y llevar a cabo transformaciones sencillas, como la compresión y descompresión (por ejemplo, un explorador web normalmente puede administrar formatos GZip).

  • Si el diseño de una aplicación existente dificulta incorporar el patrón. El uso de este patrón normalmente requiere un enfoque de arquitectura diferente en la entrega y recepción de los datos.

  • Si es necesario mantener registros de auditoría o controlar la cantidad de veces que se ejecuta una operación de transferencia de datos, y el mecanismo de clave auxiliar en uso no admite notificaciones que el servidor pueda usar para administrar estas operaciones.

  • Si es necesario limitar el tamaño de los datos, especialmente durante las operaciones de carga. La única solución a ello es que la aplicación compruebe el tamaño de los datos después de que la operación finaliza, o comprobar el tamaño de las cargas tras un período especificado o según una programación.

Diseño de cargas de trabajo

Un arquitecto debe evaluar cómo se puede usar el patrón Valet Key en el diseño de su carga de trabajo para abordar los objetivos y principios descritos en los pilares del Marco de buena arquitectura de Azure. Por ejemplo:

Fundamento Cómo apoya este patrón los objetivos de los pilares
Las decisiones de diseño de seguridad ayudan a garantizar la confidencialidad, integridad y disponibilidad de los datos y sistemas de su carga de trabajo. Este patrón permite que un cliente acceda directamente a un recurso sin necesidad de credenciales permanentes o duraderas. Todas las solicitudes de acceso comienzan con una transacción auditable. A continuación, el acceso concedido se limita en el ámbito y en la duración. Este patrón también facilita la revocación del acceso concedido.

- SE:05 Administración de identidades y accesos
La optimización de costos se centra en mantener y mejorar el retorno de la inversión de la carga de trabajo. Este diseño descarga el procesamiento como una relación exclusiva entre el cliente y el recurso sin agregar un componente para controlar directamente todas las solicitudes de cliente. La ventaja es más dramática cuando las solicitudes de cliente son frecuentes o lo suficientemente grandes como para requerir recursos de proxy significativos.

- CO:09 Costos de flujo
La eficiencia del rendimiento ayuda a que la carga de trabajo satisfaga eficazmente las demandas mediante optimizaciones en el escalado, los datos y el código. El hecho de no utilizar un recurso intermediario para delegar el acceso descarga el procesamiento como una relación exclusiva entre el cliente y el recurso sin requerir un componente embajador que tenga que administrar todas las solicitudes de los clientes de forma eficiente. La ventaja de usar este patrón es más importante cuando el proxy no agrega valor a la transacción.

- PE:07 Código e infraestructura

Al igual que con cualquier decisión de diseño, hay que tener en cuenta las ventajas y desventajas con respecto a los objetivos de los otros pilares que podrían introducirse con este patrón.

Ejemplo

Azure admite firmas de acceso compartido en Azure Storage que permiten un mejor control del acceso a los datos de blobs, tablas y colas, y de colas y temas de Service Bus. Un token de firma de acceso compartido se puede configurar para proporcionar derechos de acceso específicos, como lectura, escritura, actualización y eliminación para una tabla específica; un intervalo de claves dentro de una tabla; una cola; un blob; o un contenedor de blobs. La validez puede ser un período de tiempo específico. Esta funcionalidad es adecuada para usar una clave de acceso limitado para el acceso.

Considere una carga de trabajo que tiene cientos de clientes móviles o de escritorio que cargan con frecuencia binarios de gran tamaño. Sin este patrón, la carga de trabajo tiene básicamente dos opciones. La primera consiste en proporcionar acceso permanente y configuración a todos los clientes para realizar cargas directamente a una cuenta de almacenamiento. La otra consiste en implementar el patrón Gateway Routing para configurar un punto de conexión en el que los clientes utilicen el acceso proxy al almacenamiento, pero esto podría no estar añadiendo valor adicional a la transacción. Ambos enfoques sufren problemas abordados en el contexto del patrón:

  • Secretos previamente compartidos de larga duración. Potencialmente, sin mucha forma de proporcionar diferentes claves a diferentes clientes.
  • Gastos añadidos para ejecutar un servicio de proceso que disponga de recursos suficientes para hacer frente a la recepción actual de archivos de gran tamaño.
  • Potencialmente ralentizando las interacciones del cliente al añadir una capa extra de proceso y saltos de red al procedimiento de carga.

El uso del patrón Valet Key aborda los problemas de seguridad, optimización de costos y rendimiento.

Diagrama que muestra a un cliente accediendo a una cuenta de almacenamiento tras obtener primero un token de acceso de una API.

  1. Los clientes, en el último momento responsable, se autentican en una API alojada en Azure Function ligera y escalable a cero para solicitar el acceso.

  2. La API valida la solicitud y, a continuación, obtiene y devuelve un token de SaS limitado de tiempo y ámbito.

    El token generado por la API somete al cliente a las siguientes limitaciones:

    • La cuenta de almacenamiento que debe utilizar. Es decir, el cliente no necesita conocer esta información de antemano.
    • Un contenedor y un nombre de archivo específicos para usar; garantizando que el token pueda usarse con un archivo como máximo.
    • Una ventana de operación corta, como de tres minutos. Este corto periodo de tiempo garantiza que los tokens tengan un TTL que no se extienda más allá de su utilidad.
    • Permisos para solo crear un blob; no para descargarlo, actualizarlo ni eliminarlo.
  3. Después, el cliente usa ese token, dentro del período de tiempo limitado, para cargar el archivo directamente en la cuenta de almacenamiento.

La API genera estos tokens para clientes autorizados mediante una clave de delegación de usuario basada en la propia identidad administrada de Microsoft Entra ID de la API. El registro está habilitado tanto en la(s) cuenta(s) de almacenamiento como en la API de generación de tokens, lo que permite establecer una correlación entre las solicitudes de tokens y su uso. La API puede utilizar la información de autenticación del cliente u otros datos de los que disponga para decidir qué cuenta de almacenamiento o contenedor utilizar, como en una situación de multiinquilino.

Hay un ejemplo completo disponible en GitHub en Ejemplo de patrón Valet Key. Los fragmentos de código siguientes se adaptan a partir de ese ejemplo. Este primero muestra cómo Azure Function (que se encuentra en ValetKey.Web) genera un token de firma de acceso compartido delegado por el usuario usando la propia identidad administrada de Azure Function.

[Function("FileServices")]
public async Task<StorageEntitySas> GenerateTokenAsync([HttpTrigger(...)] HttpRequestData req, ..., 
                                                        CancellationToken cancellationToken)
{
  // Authorize the caller, select a blob storage account, container, and file name.
  // Authenticate to the storage account with the Azure Function's managed identity.
  ...

  return await GetSharedAccessReferenceForUploadAsync(blobContainerClient, blobName, cancellationToken);
}

/// <summary>
/// Return an access key that allows the caller to upload a blob to this
/// specific destination for about three minutes.
/// </summary>
private async Task<StorageEntitySas> GetSharedAccessReferenceForUploadAsync(BlobContainerClient blobContainerClient, 
                                                                            string blobName,
                                                                            CancellationToken cancellationToken)
{
  var blobServiceClient = blobContainerClient.GetParentBlobServiceClient();
  var blobClient = blobContainerClient.GetBlockBlobClient(blobName);

  // Allows generating a SaS token that is evaluated as the union of the RBAC permissions on the managed identity
  // (for example, Blob Data Contributor) and then narrowed further by the specific permissions in the SaS token.
  var userDelegationKey = await blobServiceClient.GetUserDelegationKeyAsync(DateTimeOffset.UtcNow.AddMinutes(-3),
                                                                            DateTimeOffset.UtcNow.AddMinutes(3),
                                                                            cancellationToken);

  // Limit the scope of this SaS token to the following:
  var blobSasBuilder = new BlobSasBuilder
  {
      BlobContainerName = blobContainerClient.Name,     // - Specific container
      BlobName = blobClient.Name,                       // - Specific filename
      Resource = "b",                                   // - Blob only
      StartsOn = DateTimeOffset.UtcNow.AddMinutes(-3),  // - For about three minutes (+/- for clock drift)
      ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(3),  // - For about three minutes (+/- for clock drift)
      Protocol = SasProtocol.Https                      // - Over HTTPS
  };
  blobSasBuilder.SetPermissions(BlobSasPermissions.Create);

  return new StorageEntitySas
  {
      BlobUri = blobClient.Uri,
      Signature = blobSasBuilder.ToSasQueryParameters(userDelegationKey, blobServiceClient.AccountName).ToString();
  };
}

El fragmento de código siguiente es el objeto de transferencia de datos (DTO) usado por la API y el cliente.

public class StorageEntitySas
{
  public Uri? BlobUri { get; internal set; }
  public string? Signature { get; internal set; }
}

El cliente (que se encuentra en ValetKey.Client) usa el URI y el token devueltos desde la API para realizar la carga sin necesidad de recursos adicionales y con un rendimiento completo de cliente a almacenamiento.

...

// Get the SaS token (valet key)
var blobSas = await httpClient.GetFromJsonAsync<StorageEntitySas>(tokenServiceEndpoint);
var sasUri = new UriBuilder(blobSas.BlobUri)
{
    Query = blobSas.Signature
};

// Create a blob client using the SaS token as credentials
var blob = new BlobClient(sasUri.Uri);

// Upload the file directly to blob storage
using (var stream = await GetFileToUploadAsync(cancellationToken))
{
    await blob.UploadAsync(stream, cancellationToken);
}

...

Pasos siguientes

Las directrices siguientes también pueden ser pertinentes a la hora de implementar este patrón:

Los patrones siguientes también podrían ser importantes a la hora de implementar este patrón:

  • Patrón Gatekeeper. Este patrón se puede usar junto con el patrón Valet Key para proteger las aplicaciones y los servicios mediante una instancia de host dedicada que actúa como intermediario entre los clientes y la aplicación o el servicio. El equipo selector valida y corrige las solicitudes y pasa las solicitudes y los datos entre el cliente y la aplicación. Esto puede proporcionar una capa de seguridad adicional y reducir la superficie expuesta a ataques del sistema.
  • Patrón Static Content Hosting. Describe cómo implementar recursos estáticos en un servicio de almacenamiento basado en la nube que puede proporcionar estos recursos directamente al cliente para reducir la necesidad de instancias de proceso costosas. Cuando la finalidad de los recursos no es que estén públicamente disponibles, el patrón Vale Key se puede usar para protegerlos.