Sécurité via des modèles

Azure DevOps Services | Azure DevOps Server 2022 | Azure DevOps Server 2020

Les vérifications sur les ressources protégées constituent le bloc de base de la sécurité pour Azure Pipelines. Les vérifications fonctionnent, quelle que soit la structure (les index et les travaux) de votre pipeline. Si plusieurs pipelines de votre équipe ou organisation ont la même structure, vous pouvez simplifier davantage la sécurité à l’aide de modèles.

Azure Pipelines propose deux types de modèles : includes et extends. Les modèles includes se comportent comme #include en C++ : c’est comme si vous colliez le code du modèle directement dans le fichier externe, qui y fait référence. Par exemple, ici, un modèle includes (include-npm-steps.yml) est inséré dans steps.

  steps:
  - template: templates/include-npm-steps.yml 

Pour continuer la métaphore C++, les modèles extends ressemblent davantage à l’héritage : le modèle fournit la structure externe du pipeline et un ensemble d’emplacements où le consommateur de modèles peut apporter des modifications ciblées.

Utiliser des modèles extends

Pour les pipelines les plus sécurisés, nous vous recommandons de commencer par des modèles extends. En fournissant la structure externe, un modèle peut empêcher le code malveillant d’entrer dans votre pipeline. Vous pouvez toujours utiliser includes, dans le modèle et dans le pipeline final, pour prendre en compte les éléments de configuration courants. Pour utiliser un modèle extends, votre pipeline peut ressembler à l’exemple ci-dessous.

# template.yml
parameters:
- name: usersteps
  type: stepList
  default: []
steps:
- ${{ each step in parameters.usersteps }}:
  - ${{ step }}
# azure-pipelines.yml
resources:
  repositories:
  - repository: templates
    type: git
    name: MyProject/MyTemplates
    ref: refs/tags/v1

extends:
  template: template.yml@templates
  parameters:
    usersteps:
    - script: echo This is my first step
    - script: echo This is my second step

Lorsque vous configurez des modèles extends, envisagez de les ancrer sur une balise ou une branche GIT particulière. Ainsi, si des changements cassants doivent être apportés, les pipelines existants ne seront pas affectés. Les exemples ci-dessus utilisent cette fonctionnalité.

Fonctionnalités de sécurité appliquées via YAML

Il existe plusieurs protections intégrées à la syntaxe YAML, et un modèle d’extension peut appliquer l’utilisation de tout ou partie d’entre elles.

Cibles d’étape

Limitez certaines étapes à exécuter dans un conteneur au lieu de l’hôte. Sans accès à l’hôte de l’agent, les étapes utilisateur ne peuvent pas modifier la configuration de l’agent ou laisser du code malveillant pour une exécution ultérieure. Exécutez d’abord du code sur l’hôte pour sécuriser le conteneur. Par exemple, nous vous recommandons de limiter l’accès au réseau. Sans accès ouvert au réseau, les étapes utilisateur ne peuvent pas accéder aux packages à partir de sources non autorisées, ni charger le code et les secrets vers un emplacement réseau.

resources:
  containers:
  - container: builder
    image: mysecurebuildcontainer:latest
steps:
- script: echo This step runs on the agent host, and it could use docker commands to tear down or limit the container's network
- script: echo This step runs inside the builder container
  target: builder

Restrictions de la commande de journalisation de l’agent

Limitez les services que l’agent Azure Pipelines fournira aux étapes utilisateur. Les étapes demandent des services à l’aide de « commandes de journalisation » (chaînes spécialement mises en forme imprimées dans stdout). En mode restreint, la plupart des services de l’agent, tels que le chargement d’artefacts et l’attachement des résultats de test, ne sont pas disponibles.

# this task will fail because its `target` property instructs the agent not to allow publishing artifacts
- task: PublishBuildArtifacts@1
  inputs:
    artifactName: myartifacts
  target:
    commands: restricted

L’une des commandes toujours autorisées en mode restreint est la commande setvariable. Étant donné que les variables de pipeline sont exportées en tant que variables d’environnement vers les tâches suivantes, les tâches qui génèrent des données fournies par l’utilisateur (par exemple, le contenu des problèmes ouverts récupérés à partir d’une API REST) peuvent être vulnérables aux attaques par injection. Ce contenu utilisateur peut définir des variables d’environnement qui peuvent à leur tour être utilisées pour exploiter l’hôte de l’agent. Pour interdire cela, les auteurs de pipelines peuvent déclarer explicitement les variables pouvant être définies via la commande de journalisation setvariable. La spécification d’une liste vide interdit la définition de toutes les variables.

# this task will fail because the task is only allowed to set the 'expectedVar' variable, or a variable prefixed with "ok"
- task: PowerShell@2
  target:
    commands: restricted
    settableVariables:
    - expectedVar
    - ok*
  inputs:
    targetType: 'inline'
    script: |
      Write-Host "##vso[task.setvariable variable=BadVar]myValue"

Insertion conditionnelle d’index ou de travaux

Limitez l’exécution des index et des travaux dans des conditions spécifiques. Les conditions peuvent vous aider, par exemple, à vous assurer que vous ne créez que certaines branches.

jobs:
- job: buildNormal
  steps:
  - script: echo Building the normal, unsensitive part
- ${{ if eq(variables['Build.SourceBranchName'], 'refs/heads/main') }}:
  - job: buildMainOnly
    steps:
    - script: echo Building the restricted part that only builds for main branch

Exiger une certaine syntaxe avec les modèles d’extension

Les modèles peuvent itérer sur et modifier/interdire toute syntaxe YAML. L’itération peut forcer l’utilisation d’une syntaxe YAML particulière, y compris les fonctionnalités ci-dessus.

Un modèle peut réécrire les étapes utilisateur et autoriser l’exécution de certaines tâches approuvées uniquement. Vous pouvez, par exemple, empêcher l’exécution de scripts inclus.

Avertissement

Dans l’exemple ci-dessous, les étapes de type « bash », « powershell », « pwsh » et « script » ne peuvent pas s’exécuter. Pour un verrouillage complet des scripts ad hoc, vous devez également bloquer « BatchScript » et « ShellScript ».

# template.yml
parameters:
- name: usersteps
  type: stepList
  default: []
steps:
- ${{ each step in parameters.usersteps }}:
  - ${{ if not(or(startsWith(step.task, 'Bash'),startsWith(step.task, 'CmdLine'),startsWith(step.task, 'PowerShell'))) }}:  
    - ${{ step }}
  # The lines below will replace tasks like Bash@3, CmdLine@2, PowerShell@2
  - ${{ else }}:  
    - ${{ each pair in step }}:
        ${{ if eq(pair.key, 'inputs') }}:
          inputs:
            ${{ each attribute in pair.value }}:
              ${{ if eq(attribute.key, 'script') }}:
                script: echo "Script removed by template"
              ${{ else }}:
                ${{ attribute.key }}: ${{ attribute.value }}
        ${{ elseif ne(pair.key, 'displayName') }}:
          ${{ pair.key }}: ${{ pair.value }}

          displayName: 'Disabled by template: ${{ step.displayName }}'
# azure-pipelines.yml
extends:
  template: template.yml
  parameters:
    usersteps:
    - task: MyTask@1
    - script: echo This step will be stripped out and not run!
    - bash: echo This step will be stripped out and not run!
    - powershell: echo "This step will be stripped out and not run!"
    - pwsh: echo "This step will be stripped out and not run!"
    - script: echo This step will be stripped out and not run!
    - task: CmdLine@2
      displayName: Test - Will be stripped out
      inputs:
        script: echo This step will be stripped out and not run!
    - task: MyOtherTask@2

Paramètres de type sécurisé

Les modèles et leurs paramètres sont transformés en constantes avant l’exécution du pipeline. Les paramètres de modèle fournissent une sécurité de type aux paramètres d’entrée. Par exemple, elle peut restreindre les pools qui peuvent être utilisés dans un pipeline en offrant une énumération des options possibles plutôt qu’une chaîne de forme libre.

# template.yml
parameters:
- name: userpool
  type: string
  default: Azure Pipelines
  values:
  - Azure Pipelines
  - private-pool-1
  - private-pool-2

pool: ${{ parameters.userpool }}
steps:
- script: # ... removed for clarity
# azure-pipelines.yml
extends:
  template: template.yml
  parameters:
    userpool: private-pool-1

Définir les modèles requis

Pour exiger l’utilisation d’un modèle spécifique, vous pouvez définir la vérification du modèle requis pour une ressource ou un environnement. La vérification du modèle requis peut être utilisée lors de l’extension à partir d’un modèle.

Vous pouvez vérifier l’état d’une vérification lors de l’affichage d’un travail de pipeline. Lorsqu’un pipeline ne s’étend pas à partir du modèle requis, la vérification échoue et l’exécution s’arrête. Vous verrez que votre vérification a échoué.

échec de la vérification d’approbation

Lorsque le modèle requis est utilisé, vous verrez que votre vérification est réussie.

réussite de la vérification d’approbation

Ici, le modèle params.yml est requis avec une approbation sur la ressource. Pour déclencher l’échec du pipeline, commentez la référence à params.yml.

# params.yml
parameters:
- name: yesNo 
  type: boolean
  default: false
- name: image
  displayName: Pool Image
  type: string
  default: ubuntu-latest
  values:
  - windows-latest
  - ubuntu-latest
  - macOS-latest

steps:
- script: echo ${{ parameters.yesNo }}
- script: echo ${{ parameters.image }}
# azure-pipeline.yml

resources:
 containers:
     - container: my-container
       endpoint: my-service-connection
       image: mycontainerimages

extends:
    template: params.yml
    parameters:
        yesNo: true
        image: 'windows-latest'

Étapes supplémentaires

Un modèle peut ajouter des étapes sans que l’auteur du pipeline ait à les inclure. Ces étapes peuvent être utilisées pour exécuter une analyse des informations d’identification ou des vérifications de code statiques.

# template to insert a step before and after user steps in every job
parameters:
  jobs: []

jobs:
- ${{ each job in parameters.jobs }}: # Each job
  - ${{ each pair in job }}:  # Insert all properties other than "steps"
      ${{ if ne(pair.key, 'steps') }}:
        ${{ pair.key }}: ${{ pair.value }}
    steps:                            # Wrap the steps
    - task: CredScan@1                # Pre steps
    - ${{ job.steps }}                # Users steps
    - task: PublishMyTelemetry@1      # Post steps
      condition: always()

Application des modèles

Un modèle n’est un mécanisme de sécurité que si vous pouvez l’appliquer. Le point de contrôle permettant d’appliquer l’utilisation des modèles est une ressource protégée. Vous pouvez configurer des approbations et des vérifications sur votre pool d’agents ou d’autres ressources protégées telles que les référentiels. Pour obtenir un exemple, consultez Ajouter une vérification de ressource de référentiel.

Étapes suivantes

Ensuite, découvrez comment prendre des entrées en toute sécurité via des variables et des paramètres.