Consideraciones sobre la seguridad de cargas de trabajo críticas
Las cargas de trabajo críticas deben estar intrínsecamente protegidas. Si una aplicación o su infraestructura están en peligro, la disponibilidad está en riesgo. El objetivo de esta arquitectura es maximizar la confiabilidad para que la aplicación siga siendo eficaz y esté disponible en todas las circunstancias. Los controles de seguridad se aplican principalmente con el propósito de mitigar las amenazas que afectan a la disponibilidad y la confiabilidad.
Nota:
Los requisitos empresariales pueden necesitar más medidas de seguridad. Se recomienda encarecidamente ampliar los controles de la implementación según las instrucciones de las consideraciones de seguridad de Azure Well-Architected Framework para cargas de trabajo críticas.
Administración de identidades y acceso
En el nivel de aplicación, esta arquitectura usa un esquema de autenticación simple basado en claves de API para algunas operaciones restringidas, como la creación de los artículos del catálogo o la eliminación de comentarios. Los escenarios avanzados, como la autenticación de usuario y los roles de usuario, están fuera del ámbito de la arquitectura de línea de base.
Si la aplicación requiere autenticación de usuarios y administración de cuentas, siga las recomendaciones para la administración de identidades y acceso. Algunas estrategias incluyen el uso de proveedores de identidades administradas, evitar la administración de identidades personalizada y usar la autenticación sin contraseña siempre que sea posible.
Acceso con privilegios mínimos
Configure directivas de acceso para que los usuarios y las aplicaciones obtengan el nivel mínimo de acceso que necesitan para cumplir su función. Normalmente, los desarrolladores no necesitan acceso a la infraestructura de producción, pero la canalización de implementación necesita acceso completo. Los clústeres de Kubernetes no insertan imágenes de contenedor en un registro, pero los flujos de trabajo de GitHub podrían. Las API de front-end no suelen obtener mensajes del agente de mensajes y los trabajos back-end no envían necesariamente mensajes nuevos al agente. Estas decisiones dependen de la carga de trabajo y el nivel de acceso que asigne debe reflejar la funcionalidad de cada componente.
Entre los ejemplos de la implementación de referencia crítica de Azure se incluyen:
- Cada componente de aplicación que funciona con Azure Event Hubs usa un cadena de conexión con permisos de escucha () o envío (
BackgroundProcessor
CatalogService
). Ese nivel de acceso garantiza que cada pod solo tenga el acceso mínimo necesario para cumplir su función. - La entidad de servicio del grupo de agentes de Azure Kubernetes Service (AKS) solo tiene permisos De obtención y lista de secretos en Azure Key Vault.
- La identidad de Kubelet de AKS solo tiene el permiso AcrPull para acceder al registro de contenedor global.
Identidades administradas
Para mejorar la seguridad de una carga de trabajo crítica, evite usar secretos basados en servicios, como cadena de conexión o claves de API, siempre que sea posible. Se recomienda usar identidades administradas si el servicio de Azure admite esa funcionalidad.
La implementación de referencia usa una identidad administrada asignada por el servicio en el grupo de agentes de AKS ("Identidad de Kubelet") para acceder al almacén de claves global de Azure Container Registry y un almacén de claves de una marca. Se usan los roles integrados adecuados para restringir el acceso. Por ejemplo, este código de Terraform asigna solo el AcrPull
rol a la identidad de Kubelet:
resource "azurerm_role_assignment" "acrpull_role" {
scope = data.azurerm_container_registry.global.id
role_definition_name = "AcrPull"
principal_id = azurerm_kubernetes_cluster.stamp.kubelet_identity.0.object_id
}
Secretos
Cuando sea posible, use la autenticación de Microsoft Entra en lugar de claves al acceder a los recursos de Azure. Muchos servicios de Azure, como Azure Cosmos DB y Azure Storage, admiten la opción de deshabilitar completamente la autenticación de claves. AKS admite Id. de carga de trabajo de Microsoft Entra.
En escenarios en los que no se puede usar la autenticación de Microsoft Entra, cada marca de implementación tiene una instancia dedicada de Key Vault para almacenar claves. Estas claves se crean automáticamente durante la implementación y se almacenan en Key Vault con Terraform. Ningún operador humano, excepto los desarrolladores de entornos de un extremo a otro, pueden interactuar con secretos. Además, las directivas de acceso de Key Vault se configuran para que no se permita que ninguna cuenta de usuario acceda a secretos.
Nota:
Esta carga de trabajo no usa certificados personalizados, pero se aplican los mismos principios.
En el clúster de AKS, el proveedor de Key Vault para el almacén de secretos permite a la aplicación consumir secretos. El controlador CSI carga las claves de Key Vault y las monta como archivos en pods individuales.
#
# /src/config/csi-secrets-driver/chart/csi-secrets-driver-config/templates/csi-secrets-driver.yaml
#
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: azure-kv
spec:
provider: azure
parameters:
usePodIdentity: "false"
useVMManagedIdentity: "true"
userAssignedIdentityID: {{ .Values.azure.managedIdentityClientId | quote }}
keyvaultName: {{ .Values.azure.keyVaultName | quote }}
tenantId: {{ .Values.azure.tenantId | quote }}
objects: |
array:
{{- range .Values.kvSecrets }}
- |
objectName: {{ . | quote }}
objectAlias: {{ . | lower | replace "-" "_" | quote }}
objectType: secret
{{- end }}
La implementación de referencia usa Helm con Azure Pipelines para implementar el controlador CSI que contiene todos los nombres de clave de Key Vault. El controlador también es responsable de actualizar los secretos montados si cambian en Key Vault.
En el extremo del consumidor, ambas aplicaciones de .NET usan la funcionalidad integrada para leer la configuración a partir de archivos (AddKeyPerFile
):
//
// /src/app/AlwaysOn.BackgroundProcessor/Program.cs
// + using Microsoft.Extensions.Configuration;
//
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
// Load values from Kubernetes CSI Key Vault driver mount point.
config.AddKeyPerFile(directoryPath: "/mnt/secrets-store/", optional: true, reloadOnChange: true);
// More configuration if needed...
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
La combinación de la recarga automática del controlador CSI y reloadOnChange: true
ayuda a garantizar que, cuando las claves cambian en Key Vault, los nuevos valores se montan en el clúster. Este proceso no garantiza la rotación de secretos en la aplicación. La implementación usa una instancia de cliente singleton de Azure Cosmos DB que requiere que el pod se reinicie para aplicar el cambio.
Dominios personalizados y TLS
Las cargas de trabajo basadas en web deben usar HTTPS para evitar ataques de tipo "man in the middle" en todos los niveles de interacción, como la comunicación desde el cliente a la API o desde la API a la API. Asegúrese de automatizar la rotación de certificados porque los certificados expirados siguen siendo una causa común de interrupciones y experiencias degradadas.
La implementación de referencia es totalmente compatible con HTTPS con nombres de dominio personalizados, como contoso.com
. También aplica la configuración adecuada a los int
entornos y prod
. También puede agregar dominios personalizados para e2e
entornos. Sin embargo, esta implementación de referencia no usa nombres de dominio personalizados debido a la naturaleza de corta duración de y al aumento del tiempo de e2e
implementación cuando se usan dominios personalizados con certificados SSL en Azure Front Door.
Para habilitar la automatización completa de la implementación, debe administrar el dominio personalizado a través de una zona dns de Azure. La canalización de implementación de infraestructura crea dinámicamente registros CNAME en la zona de Azure DNS y asigna estos registros automáticamente a una instancia de Azure Front Door.
Los certificados SSL administrados por Azure Front Door están habilitados, lo que elimina el requisito de renovaciones manuales de certificados SSL. TLS 1.2 se configura como la versión mínima.
#
# /src/infra/workload/globalresources/frontdoor.tf
#
resource "azurerm_frontdoor_custom_https_configuration" "custom_domain_https" {
count = var.custom_fqdn != "" ? 1 : 0
frontend_endpoint_id = "${azurerm_frontdoor.main.id}/frontendEndpoints/${local.frontdoor_custom_frontend_name}"
custom_https_provisioning_enabled = true
custom_https_configuration {
certificate_source = "FrontDoor"
}
}
Los entornos que no están aprovisionados con dominios personalizados son accesibles a través del punto de conexión predeterminado de Azure Front Door. Por ejemplo, puede ponerse en contacto con ellos en una dirección como env123.azurefd.net
.
Nota:
En el controlador de entrada del clúster, los dominios personalizados no se usan en ninguno de los casos. En su lugar, se usa un nombre DNS proporcionado por Azure como [prefix]-cluster.[region].cloudapp.azure.com
con Let's Encrypt, que puede emitir certificados SSL gratuitos para esos puntos de conexión.
La implementación de referencia usa Jetstack para cert-manager
aprovisionar automáticamente certificados SSL/TLS de Let's Encrypt para reglas de entrada. Más opciones de configuración, como , ClusterIssuer
que solicita certificados de Let's Encrypt, se implementan a través de un gráfico de Helm independiente cert-manager-config
que se almacena en src/config/cert-manager/chart.
Esta implementación usa ClusterIssuer
en lugar de para evitar tener emisores para cada espacio de Issuer
nombres. Para obtener más información, consulte la documentación de cert-manager y las notas de la versión de cert-manager.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
Configuración
Toda la configuración del entorno de ejecución de la aplicación se almacena en Key Vault, incluidos los secretos y la configuración sin distinción. Puede usar un almacén de configuración, como Azure App Configuration, para almacenar la configuración. Sin embargo, tener un único almacén reduce el número de posibles puntos de error para las aplicaciones críticas. Use Key Vault para la configuración en tiempo de ejecución para simplificar la implementación general.
La canalización de implementación debe rellenar los almacenes de claves. En la implementación, los valores necesarios se originan directamente desde Terraform, como las cadenas de conexión de base de datos, o se pasan como variables de Terraform desde la canalización de implementación.
La configuración de infraestructura e implementación de entornos individuales, como e2e
, int
y prod
, se almacena en archivos de variables que forman parte del repositorio de código fuente. Este enfoque tiene dos ventajas principales:
- Se realiza un seguimiento de todos los cambios de un entorno y se recorren las canalizaciones de implementación antes de que se apliquen al entorno.
- Los entornos individuales
e2e
se pueden configurar de forma diferente porque la implementación se basa en el código de una rama.
Una excepción es el almacenamiento de valores confidenciales para las canalizaciones. Estos valores se almacenan como secretos en grupos de variables de Azure DevOps.
Seguridad del contenedor
Es necesario proteger las imágenes de contenedor para todas las cargas de trabajo en contenedor.
Esta implementación de referencia usa contenedores de Docker de carga de trabajo basados en imágenes en tiempo de ejecución, no SDK, para minimizar la superficie expuesta a ataques y superficie expuesta a ataques. No hay otras herramientas, como ping
, wget
o curl
, instaladas.
La aplicación se ejecuta en un usuario workload
sin privilegios que se creó como parte del proceso de compilación de imágenes:
RUN groupadd -r workload && useradd --no-log-init -r -g workload workload
USER workload
La implementación de referencia usa Helm para empaquetar los manifiestos de YAML que necesita para implementar componentes individuales. Este proceso incluye su implementación de Kubernetes, servicios, configuración de escalado automático horizontal de pods y contexto de seguridad. Todos los gráficos de Helm contienen medidas de seguridad fundamentales que siguen los procedimientos recomendados de Kubernetes.
Estas medidas de seguridad son:
readOnlyFilesystem
: el sistema/
de archivos raíz de cada contenedor se establece en de solo lectura para evitar que el contenedor escriba en el sistema de archivos host. Esta restricción impide que los atacantes descarguen más herramientas y conserven código en el contenedor. Los directorios que requieren acceso de lectura y escritura se montan como volúmenes.privileged
: todos los contenedores se establecen para que se ejecuten como sin privilegios. La ejecución de un contenedor con privilegios proporciona todas las funcionalidades al contenedor y también eleva todas las limitaciones que aplica el controlador del grupo de control de dispositivos.allowPrivilegeEscalation
: impide que el interior de un contenedor obtenga más privilegios que su proceso primario.
Estas medidas de seguridad también se configuran para contenedores que no son de Microsoft y gráficos de Helm, como cert-manager
cuando sea posible. Puede usar Azure Policy para auditar estas medidas de seguridad.
#
# Example:
# /src/app/charts/backgroundprocessor/values.yaml
#
containerSecurityContext:
privileged: false
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
Cada entorno, incluido prod
, int
y cada e2e
entorno, tiene una instancia dedicada de Container Registry que tiene replicación global en cada una de las regiones donde se implementan los stamps.
Nota:
Esta implementación de referencia no usa el examen de vulnerabilidades de imágenes de Docker. Se recomienda usar Microsoft Defender para registros de contenedor, potencialmente con Acciones de GitHub.
Entrada de tráfico
Azure Front Door es el equilibrador de carga global en esta arquitectura. Todas las solicitudes web se enrutan a través de Azure Front Door, que selecciona el back-end adecuado. Las aplicaciones críticas deben aprovechar otras funcionalidades de Azure Front Door, como firewalls de aplicaciones web (WAF).
Firewall de aplicaciones web
Una funcionalidad importante de Azure Front Door es el WAF porque permite a Azure Front Door inspeccionar el tráfico que pasa. En el modo Prevención, se bloquean todas las solicitudes sospechosas. En la implementación, se configuran dos conjuntos de reglas. Estos conjuntos de reglas son Microsoft_DefaultRuleSet
y Microsoft_BotManagerRuleSet
.
Sugerencia
Al implementar Azure Front Door con WAF, se recomienda empezar con el modo de detección . Supervise estrechamente su comportamiento con el tráfico natural del cliente y ajuste las reglas de detección. Después de eliminar falsos positivos, o si los falsos positivos son raros, cambie al modo de prevención . Este proceso es necesario porque cada aplicación es diferente y algunas cargas se pueden considerar malintencionadas, aunque sean legítimas para esa carga de trabajo específica.
Enrutamiento
Solo las solicitudes que llegan a través de Azure Front Door se enrutan a los contenedores de API, como CatalogService
y HealthService
. Use una configuración de entrada nginx para ayudar a aplicar este comportamiento. Comprueba si hay un X-Azure-FDID
encabezado y si es el adecuado para la instancia global de Azure Front Door de un entorno específico.
#
# /src/app/charts/catalogservice/templates/ingress.yaml
#
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
# ...
annotations:
# To restrict traffic coming only through our Azure Front Door instance, we use a header check on the X-Azure-FDID.
# The pipeline injects the value. Therefore, it's important to treat this ID as a sensitive value.
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine On
SecRule &REQUEST_HEADERS:X-Azure-FDID \"@eq 0\" \"log,deny,id:106,status:403,msg:\'Front Door ID not present\'\"
SecRule REQUEST_HEADERS:X-Azure-FDID \"@rx ^(?!{{ .Values.azure.frontdoorid }}).*$\" \"log,deny,id:107,status:403,msg:\'Wrong Front Door ID\'\"
# ...
Las canalizaciones de implementación ayudan a garantizar que este encabezado se rellena correctamente, pero también debe omitir esta restricción para las pruebas de humo porque sondean cada clúster directamente en lugar de a través de Azure Front Door. La implementación de referencia usa el hecho de que las pruebas de humo se ejecutan como parte de la implementación. Este diseño permite conocer y agregar el valor de encabezado a las solicitudes HTTP de prueba de humo.
#
# /.ado/pipelines/scripts/Run-SmokeTests.ps1
#
$header = @{
"X-Azure-FDID" = "$frontdoorHeaderId"
"TEST-DATA" = "true" # Header to indicate that posted comments and ratings are for tests and can be deleted again by the app.
}
Implementaciones seguras
Para seguir los principios de referencia bien diseñados para la excelencia operativa, automatice completamente todas las implementaciones. No deben requerir pasos manuales, excepto para desencadenar la ejecución o aprobar una puerta.
Debe evitar intentos malintencionados o configuraciones incorrectas accidentales que puedan deshabilitar las medidas de seguridad. La implementación de referencia usa la misma canalización para la implementación de la infraestructura y la aplicación, lo que fuerza una reversión automatizada de cualquier posible desfase de configuración. Esta reversión ayuda a mantener la integridad de la infraestructura y la alineación con el código de la aplicación. Cualquier cambio se descarta en la siguiente implementación.
Terraform genera valores confidenciales para la implementación durante la ejecución de la canalización o Azure DevOps los proporciona como secretos. Estos valores están protegidos con restricciones de acceso basadas en roles.
Nota:
Los flujos de trabajo de GitHub proporcionan un concepto similar de almacenes independientes para los valores secretos. Los secretos se cifran y las variables de entorno que pueden usar Acciones de GitHub.
Es importante prestar atención a los artefactos que genera la canalización, ya que esos artefactos pueden contener valores secretos o información sobre el funcionamiento interno de la aplicación. La implementación de Azure DevOps de la implementación de referencia genera dos archivos con salidas de Terraform. Un archivo es para stamps y un archivo es para la infraestructura global. Estos archivos no contienen contraseñas que podrían poner en peligro la infraestructura. Sin embargo, debe considerar que estos archivos son confidenciales porque revelan información sobre la infraestructura, incluidos identificadores de clúster, direcciones IP, nombres de cuenta de almacenamiento, nombres de key Vault, nombres de base de datos de Azure Cosmos DB y identificadores de encabezado de Azure Front Door.
En el caso de las cargas de trabajo que usan Terraform, debe dedicar más esfuerzo a proteger el archivo de estado porque contiene el contexto de implementación completo, incluidos los secretos. El archivo de estado se almacena normalmente en una cuenta de almacenamiento que debe tener un ciclo de vida independiente de la carga de trabajo y solo debe ser accesible desde una canalización de implementación. Debe registrar cualquier otro acceso a este archivo y enviar alertas al grupo de seguridad adecuado.
Actualizaciones de dependencias
Las bibliotecas, marcos y herramientas que usa la aplicación se actualizan con el tiempo. Es importante completar estas actualizaciones periódicamente porque a menudo contienen correcciones para problemas de seguridad que podrían proporcionar a los atacantes acceso no autorizado al sistema.
La implementación de referencia usa dependabot de GitHub para las actualizaciones de dependencias de NuGet, Docker, npm, Terraform y Acciones de GitHub. El dependabot.yml
archivo de configuración se genera automáticamente con un script de PowerShell debido a la complejidad de las distintas partes de la aplicación. Por ejemplo, cada módulo de Terraform necesita una entrada independiente.
#
# /.github/dependabot.yml
#
version: 2
updates:
- package-ecosystem: "nuget"
directory: "/src/app/AlwaysOn.HealthService"
schedule:
interval: "monthly"
target-branch: "component-updates"
- package-ecosystem: "docker"
directory: "/src/app/AlwaysOn.HealthService"
schedule:
interval: "monthly"
target-branch: "component-updates"
# ... the rest of the file...
- Las actualizaciones se desencadenan mensualmente buscando el equilibrio entre tener las bibliotecas más actualizadas y conservar la sobrecarga mantenida. Además, las herramientas clave como Terraform se supervisan continuamente y las actualizaciones importantes se ejecutan manualmente.
- Las solicitudes de incorporación de cambios (PR) tienen como destino la
component-updates
rama en lugar demain
. - Las bibliotecas de Npm están configuradas para comprobar solo las dependencias que van a la aplicación compilada en lugar de admitir herramientas como
@vue-cli
.
Dependabot crea una solicitud de incorporación de cambios independiente para cada actualización, lo que puede sobrecargar al equipo de operaciones. La implementación de referencia recopila primero un lote de actualizaciones en la component-updates
rama y, a continuación, ejecuta pruebas en el e2e
entorno. Si esas pruebas se realizan correctamente, crea otra solicitud de incorporación de cambios destinada a la main
rama.
Codificación defensiva
Las llamadas API pueden producir errores debido a varios motivos, incluidos los errores de código, las implementaciones con mal funcionamiento y los errores de infraestructura. Si se produce un error en una llamada API, el autor de la llamada o la aplicación cliente, no debe recibir información de depuración extensa porque esa información podría proporcionar a los adversarios puntos de datos útiles sobre la aplicación.
La implementación de referencia muestra este principio devolviendo solo el identificador de correlación en la respuesta con error. No comparte el motivo del error, como el mensaje de excepción o el seguimiento de la pila. Mediante este identificador y con la ayuda del Server-Location
encabezado, un operador puede investigar el incidente mediante Application Insights.
//
// Example ASP.NET Core middleware, which adds the Correlation ID to every API response.
//
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ...
app.Use(async (context, next) =>
{
context.Response.OnStarting(o =>
{
if (o is HttpContext ctx)
{
context.Response.Headers.Add("Server-Name", Environment.MachineName);
context.Response.Headers.Add("Server-Location", sysConfig.AzureRegion);
context.Response.Headers.Add("Correlation-ID", Activity.Current?.RootId);
context.Response.Headers.Add("Requested-Api-Version", ctx.GetRequestedApiVersion()?.ToString());
}
return Task.CompletedTask;
}, context);
await next();
});
// ...
}
Paso siguiente
Implemente la implementación de referencia para comprender completamente los recursos y su configuración.