Partager via


Considérations relatives à la sécurité pour les charges de travail stratégiques

Les charges de travail critiques doivent être sécurisées par nature. Si une application ou son infrastructure est compromise, la disponibilité est à risque. L’objectif de cette architecture est d’optimiser la fiabilité afin que l’application reste performante et disponible en toutes circonstances. Les contrôles de sécurité sont appliqués principalement dans le but d’atténuer les menaces qui affectent la disponibilité et la fiabilité.

Remarque

Vos besoins métier peuvent nécessiter davantage de mesures de sécurité. Nous vous recommandons vivement d’étendre les contrôles dans votre implémentation en fonction des instructions de la sécurité d’Azure Well-Architected Framework pour les charges de travail stratégiques.

Gestion de l’identité et de l’accès

Au niveau de l’application, cette architecture utilise un schéma d’authentification simple basé sur des clés API pour certaines opérations restreintes, telles que la création d’éléments de catalogue ou la suppression de commentaires. Les scénarios avancés tels que l’authentification utilisateur et les rôles d’utilisateur dépassent l’étendue de l’architecture de base.

Si votre application nécessite l’authentification utilisateur et la gestion des comptes, suivez les recommandations relatives à la gestion des identités et des accès. Certaines stratégies incluent l’utilisation de fournisseurs d’identité managés, l’évitement de la gestion des identités personnalisées et l’utilisation de l’authentification sans mot de passe si possible.

Accès avec le privilège minimum

Configurez les stratégies d’accès afin que les utilisateurs et les applications obtiennent le niveau minimal d’accès dont ils ont besoin pour remplir leur fonction. Les développeurs n’ont généralement pas besoin d’accéder à l’infrastructure de production, mais le pipeline de déploiement a besoin d’un accès complet. Les clusters Kubernetes n’envoient pas d’images conteneur dans un registre, mais les workflows GitHub peuvent le faire. Les API frontales n’obtiennent généralement pas de messages du répartiteur de messages, et les workers principaux n’envoient pas nécessairement de nouveaux messages au répartiteur. Ces décisions dépendent de la charge de travail, et le niveau d’accès que vous affectez doit refléter la fonctionnalité de chaque composant.

Voici quelques exemples de l’implémentation de référence stratégique Azure :

  • Chaque composant d’application qui fonctionne avec Azure Event Hubs utilise un chaîne de connexion avec des autorisations d’écoute (BackgroundProcessor) ou d’envoi (CatalogService). Ce niveau d’accès garantit que chaque pod dispose uniquement du minimum d’accès nécessaire pour remplir sa fonction.
  • Le principal de service pour le pool d’agents Azure Kubernetes Service (AKS) dispose uniquement des autorisations Get et List pour les secrets dans Azure Key Vault.
  • L’identité AKS Kubelet dispose uniquement de l’autorisation AcrPull d’accéder au registre de conteneurs global.

Identités managées

Pour améliorer la sécurité d’une charge de travail stratégique, évitez d’utiliser des secrets basés sur le service, tels que des chaîne de connexion s ou des clés API, lorsque cela est possible. Nous vous recommandons d’utiliser des identités managées si le service Azure prend en charge cette fonctionnalité.

L’implémentation de référence utilise une identité managée affectée par le service dans le pool d’agents AKS (« Identité Kubelet ») pour accéder au coffre de clés global d’Azure Container Registry et au coffre de clés d’un tampon. Des rôles intégrés appropriés sont utilisés pour restreindre l’accès. Par exemple, ce code Terraform affecte uniquement le AcrPull rôle à l’identité 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
}

Secrets

Si possible, utilisez l’authentification Microsoft Entra au lieu de clés lorsque vous accédez aux ressources Azure. De nombreux services Azure, comme Azure Cosmos DB et Stockage Azure, prennent en charge l’option de désactiver complètement l’authentification par clé. AKS prend en charge ID de charge de travail Microsoft Entra.

Pour les scénarios dans lesquels vous ne pouvez pas utiliser l’authentification Microsoft Entra, chaque tampon de déploiement a une instance dédiée de Key Vault pour stocker des clés. Ces clés sont créées automatiquement pendant le déploiement et sont stockées dans Key Vault avec Terraform. Aucun opérateur humain, à l’exception des développeurs dans des environnements de bout en bout, peut interagir avec des secrets. De plus, les stratégies d’accès Key Vault sont configurées afin qu’aucun compte d’utilisateur ne soit autorisé à accéder aux secrets.

Remarque

Cette charge de travail n’utilise pas de certificats personnalisés, mais les mêmes principes s’appliquent.

Sur le cluster AKS, le fournisseur Key Vault pour le magasin de secrets permet à l’application d’utiliser des secrets. Le pilote CSI charge les clés à partir de Key Vault et les monte en tant que fichiers dans des pods individuels.

#
# /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 }}

L’implémentation de référence utilise Helm avec Azure Pipelines pour déployer le pilote CSI qui contient tous les noms de clés de Key Vault. Le pilote est également responsable de l’actualisation des secrets montés s’ils changent dans Key Vault.

Côté consommateur, les deux applications .NET utilisent la fonctionnalité intégrée pour lire la configuration à partir de fichiers (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 combinaison du rechargement automatique du pilote CSI et reloadOnChange: true permet de s’assurer que lorsque les clés changent dans Key Vault, les nouvelles valeurs sont montées sur le cluster. Ce processus ne garantit pas la rotation des secrets dans l’application. L’implémentation utilise une instance cliente Azure Cosmos DB singleton qui exige que le pod redémarre pour appliquer la modification.

Domaines personnalisés et TLS

Les charges de travail basées sur le web doivent utiliser HTTPS pour empêcher les attaques man-in-the-middle sur tous les niveaux d’interaction, comme la communication du client vers l’API ou de l’API à l’API. Veillez à automatiser la rotation des certificats, car les certificats arrivés à expiration sont toujours une cause courante de pannes et d’expériences détériorées.

L’implémentation de référence prend entièrement en charge HTTPS avec des noms de domaine personnalisés, tels que contoso.com. Il applique également la configuration appropriée aux environnements et prod aux int environnements. Vous pouvez également ajouter des domaines personnalisés pour e2e les environnements. Toutefois, cette implémentation de référence n’utilise pas de noms de domaine personnalisés en raison de la nature courte et de l’augmentation du temps de e2e déploiement lorsque vous utilisez des domaines personnalisés avec des certificats SSL dans Azure Front Door.

Pour activer l’automatisation complète du déploiement, vous devez gérer le domaine personnalisé via une zone Azure DNS. Le pipeline de déploiement d’infrastructure crée de façon dynamique des enregistrements CNAME dans la zone Azure DNS et mappe automatiquement ces enregistrements vers une instance Azure Front Door.

Les certificats SSL gérés par Azure Front Door sont activés, ce qui supprime les conditions requises pour les renouvellements manuels de certificats SSL. TLS 1.2 est configuré comme version minimale.

#
# /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"
  }
}

Les environnements qui ne sont pas provisionnés avec des domaines personnalisés sont accessibles via le point de terminaison Azure Front Door par défaut. Par exemple, vous pouvez les contacter à une adresse telle que env123.azurefd.net.

Remarque

Sur le contrôleur d’entrée de cluster, les domaines personnalisés ne sont pas utilisés dans les deux cas. Au lieu de cela, un nom DNS fourni par Azure, tel qu’il [prefix]-cluster.[region].cloudapp.azure.com est utilisé avec Let’s Encrypt, peut émettre des certificats SSL gratuits pour ces points de terminaison.

L’implémentation de référence utilise Jetstack cert-manager pour approvisionner automatiquement des certificats SSL/TLS à partir de Let’s Encrypt pour les règles d’entrée. D’autres paramètres de configuration, comme ceux ClusterIssuerqui demandent des certificats de Let’s Encrypt, sont déployés via un graphique Helm distinct cert-manager-config stocké dans src/config/cert-manager/chart.

Cette implémentation utilise ClusterIssuer au lieu d’éviter d’avoir Issuer des émetteurs pour chaque espace de noms. Pour plus d’informations, consultez la documentation du gestionnaire de certificats et les notes de publication du gestionnaire de certificats.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:

Configuration

Toutes les configurations du runtime d’application sont stockées dans Key Vault, y compris les secrets et les paramètres non sensibles. Vous pouvez utiliser un magasin de configuration, par exemple Azure App Configuration, pour stocker les paramètres. Toutefois, avoir un magasin unique réduit le nombre de points de défaillance potentiels pour les applications stratégiques. Utilisez Key Vault pour la configuration du runtime pour simplifier l’implémentation globale.

Les coffres de clés doivent être remplis par le pipeline de déploiement. Dans l’implémentation, les valeurs requises proviennent directement de Terraform (comme les chaînes de connexion de base de données) ou sont transmises en tant que variables Terraform à partir du pipeline de déploiement.

La configuration de l’infrastructure et du déploiement d’environnements individuels, tels que e2e, intet prod, est stockée dans des fichiers variables qui font partie du référentiel de code source. Cette approche présente deux avantages principaux :

  • Toutes les modifications d’un environnement sont suivies et passent par des pipelines de déploiement avant d’être appliquées à l’environnement.
  • Les environnements individuels e2e peuvent être configurés différemment, car le déploiement est basé sur du code dans une branche.

Une exception est le stockage de valeurs sensibles pour des pipelines. Ces valeurs sont stockées en tant que secrets dans des groupes de variables Azure DevOps.

Sécurité du conteneur

Il est nécessaire de sécuriser les images conteneur pour toutes les charges de travail conteneurisées.

Cette implémentation de référence utilise des conteneurs Docker de charge de travail basés sur des images d’exécution, et non sur le SDK, pour réduire l’empreinte et la surface d’attaque potentielle. Il n’existe aucun autre outil, tel que ping, wgetou curl, installé.

L’application s’exécute sous un utilisateur workload non privilégié qui a été créé dans le cadre du processus de génération d’image :

RUN groupadd -r workload && useradd --no-log-init -r -g workload workload
USER workload

L’implémentation de référence utilise Helm pour empaqueter les manifestes YAML dont il a besoin pour déployer des composants individuels. Ce processus inclut leur déploiement, services Kubernetes, configuration de la mise à l’échelle automatique des pods horizontaux et contexte de sécurité. Tous les graphiques Helm contiennent des mesures de sécurité fondamentales qui suivent les meilleures pratiques Kubernetes.

Ces mesures de sécurité sont les suivantes :

  • readOnlyFilesystem: la système de fichiers racine / de chaque conteneur est définie en lecture seule pour empêcher l’écriture du conteneur dans le système de fichiers hôte. Cette restriction empêche les attaquants de télécharger plus d’outils et d’assurer la persistance du code dans le conteneur. Les répertoires qui nécessitent un accès en lecture-écriture sont montés en tant que volumes.
  • privileged: tous les conteneurs sont définis pour s’exécuter en tant que non privilégiés. L’exécution d’un conteneur comme privilégié offre toutes les fonctionnalités au conteneur, et elle supprime également toutes les limitations appliquées par le contrôleur de groupe de contrôle d’appareil.
  • allowPrivilegeEscalation : empêche l’intérieur d’un conteneur d’obtenir plus de privilèges que son processus parent.

Ces mesures de sécurité sont également configurées pour les conteneurs non-Microsoft et les graphiques Helm, comme cert-manager dans la mesure du possible. Vous pouvez utiliser Azure Policy pour auditer ces mesures de sécurité.

#
# Example:
# /src/app/charts/backgroundprocessor/values.yaml
#
containerSecurityContext:
  privileged: false
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false

Chaque environnement, y compris prod, intet chaque e2e environnement, a une instance dédiée de Container Registry qui a une réplication globale dans chacune des régions où les empreintes sont déployées.

Remarque

Cette implémentation de référence n’utilise pas l’analyse des vulnérabilités des images Docker. Nous vous recommandons d’utiliser Microsoft Defender pour les registres de conteneurs, potentiellement avec GitHub Actions.

Entrée du trafic

Azure Front Door est l’équilibreur de charge global dans cette architecture. Toutes les requêtes web sont routées via Azure Front Door, qui sélectionne le back-end approprié. Les applications stratégiques doivent tirer parti d’autres fonctionnalités Azure Front Door, telles que les pare-feu d’applications web (WAF).

Pare-feu d’application web

Une fonctionnalité Azure Front Door importante est le WAF, car il permet à Azure Front Door d’inspecter le trafic qui transite. En mode Prévention, toutes les demandes suspectes sont bloquées. Dans l’implémentation, deux ensembles de règles sont configurés. Ces ensembles de règles sont Microsoft_DefaultRuleSet et Microsoft_BotManagerRuleSet.

Conseil

Lorsque vous déployez Azure Front Door avec WAF, nous vous recommandons de commencer par le mode détection . Surveillez étroitement son comportement avec le trafic naturel des clients et ajustez les règles de détection. Après avoir éliminé les faux positifs ou si les faux positifs sont rares, basculez vers le mode prévention . Ce processus est nécessaire, car chaque application est différente et certaines charges utiles peuvent être considérées comme malveillantes, même si elles sont légitimes pour cette charge de travail spécifique.

Routage

Seules les requêtes qui proviennent d’Azure Front Door sont acheminées vers les conteneurs d’API, comme CatalogService et HealthService. Utilisez une configuration d’entrée Nginx pour vous aider à appliquer ce comportement. Il vérifie la présence d’un X-Azure-FDID en-tête et indique s’il s’agit de l’instance Azure Front Door globale d’un environnement spécifique.

#
# /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\'\"
  # ...

Les pipelines de déploiement permettent de s’assurer que cet en-tête est correctement rempli, mais il doit également contourner cette restriction pour les tests de fumée, car ils sondent chaque cluster directement au lieu d’Azure Front Door. L’implémentation de référence utilise le fait que les tests de fumée sont exécutés dans le cadre du déploiement. Cette conception permet à la valeur d’en-tête d’être connue et ajoutée aux requêtes HTTP de test de fumée.

#
# /.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.
}

Déploiements sécurisés

Pour suivre les principes de base bien conçus pour l’excellence opérationnelle, automatisez entièrement tous les déploiements. Ils ne doivent pas nécessiter d’étapes manuelles, sauf pour déclencher l’exécution ou approuver une porte.

Vous devez empêcher les tentatives malveillantes ou les configurations incorrectes accidentelles qui peuvent désactiver les mesures de sécurité. L’implémentation de référence utilise le même pipeline pour le déploiement d’infrastructure et d’application, ce qui force une restauration automatisée de toute dérive de configuration potentielle. Cette restauration permet de maintenir l’intégrité de l’infrastructure et l’alignement avec le code de l’application. Toute modification est ignorée lors du déploiement suivant.

Terraform génère des valeurs sensibles pour le déploiement pendant l’exécution du pipeline, ou Azure DevOps les fournit en tant que secrets. Ces valeurs sont protégées avec des restrictions d’accès en fonction du rôle.

Remarque

Les flux de travail GitHub fournissent un concept similaire de magasins distincts pour les valeurs secrètes. Les secrets sont chiffrés, variables environnementales que GitHub Actions peut utiliser.

Il est important de prêter attention aux artefacts générés par le pipeline, car ces artefacts peuvent potentiellement contenir des valeurs secrètes ou des informations sur les fonctionnements internes de l’application. Le déploiement Azure DevOps de l’implémentation de référence génère deux fichiers avec des sorties Terraform. Un fichier concerne les tampons et un fichier est destiné à l’infrastructure globale. Ces fichiers ne contiennent pas de mots de passe susceptibles de compromettre l’infrastructure. Toutefois, vous devez considérer ces fichiers comme sensibles, car ils révèlent des informations sur l’infrastructure, notamment les ID de cluster, les adresses IP, les noms de compte de stockage, les noms de coffre de clés, les noms de base de données Azure Cosmos DB et les ID d’en-tête Azure Front Door.

Pour les charges de travail qui utilisent Terraform, vous devez déployer des efforts supplémentaires pour protéger le fichier d’état, car il contient un contexte de déploiement complet, y compris des secrets. Le fichier d’état est généralement stocké dans un compte de stockage qui doit avoir un cycle de vie distinct de la charge de travail et ne doit être accessible qu’à partir d’un pipeline de déploiement. Vous devez enregistrer tout autre accès à ce fichier et envoyer des alertes au groupe de sécurité approprié.

Mises à jour des dépendances

Les bibliothèques, les frameworks et les outils utilisés par l’application sont mis à jour au fil du temps. Il est important de terminer ces mises à jour régulièrement, car elles contiennent souvent des correctifs pour les problèmes de sécurité susceptibles de donner aux attaquants un accès non autorisé au système.

L’implémentation de référence utilise Les mises à jour des dépendances De Dependabot de GitHub pour NuGet, Docker, npm, Terraform et GitHub Actions. Le dependabot.yml fichier de configuration est généré automatiquement avec un script PowerShell en raison de la complexité des différentes parties de l’application. Par exemple, chaque module Terraform a besoin d’une entrée distincte.

#
# /.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...
  • Des mises à jour sont déclenchées mensuellement comme un compromis entre le fait d'avoir les bibliothèques les plus à jour et de garder la surcharge maintenable. En outre, les outils clés tels que Terraform sont surveillés en continu et les mises à jour importantes sont exécutées manuellement.
  • Les demandes de tirage ciblent la component-updates branche au lieu de main.
  • Les bibliothèques Npm sont configurées pour vérifier uniquement les dépendances qui vont à l’application compilée au lieu de prendre en charge des outils comme @vue-cli.

Dependabot crée une demande de tirage distincte pour chaque mise à jour, ce qui peut submerger l’équipe des opérations. L’implémentation de référence collecte d’abord un lot de mises à jour dans la component-updates branche, puis exécute des tests dans l’environnement e2e . Si ces tests réussissent, il crée une autre demande de tirage ciblant la main branche.

Codage défensif

Les appels d’API peuvent échouer en raison de diverses raisons, notamment les erreurs de code, les déploiements défectueux et les échecs d’infrastructure. Si un appel d’API échoue, l’appelant ou l’application cliente ne doit pas recevoir d’informations de débogage approfondies, car ces informations peuvent donner des points de données utiles à l’application.

L’implémentation de référence illustre ce principe en retournant uniquement l’ID de corrélation dans la réponse ayant échoué. Il ne partage pas la raison de l’échec, comme le message d’exception ou la trace de pile. En utilisant cet ID et avec l’aide de l’en-tête, un opérateur peut examiner l’incident à l’aide d’Application Server-Location 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();
    });
    
    // ...
}

Étape suivante

Déployez l’implémentation de référence pour comprendre pleinement les ressources, ainsi que leur configuration.