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 étapes 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 offre deux types de modèles : inclut et étend. Les modèles inclus se comportent comme #include en C++: c’est comme si vous colliez le code du modèle directement dans le fichier externe, qui le référence. Par exemple, ici, un modèle inclut (include-npm-steps.yml) est inséré dans steps.

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

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

Utiliser des modèles étendus

Pour les pipelines les plus sécurisés, nous vous recommandons de commencer par des extends modèles. En fournissant la structure externe, un modèle peut empêcher le code malveillant d’entrer dans votre pipeline. Vous pouvez toujours utiliser includes, à la fois dans le modèle et dans le pipeline final, pour prendre en compte les éléments de configuration courants. Pour utiliser un modèle d’extension, 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 extends modèles, envisagez de les ancrer sur une branche ou une balise Git particulière. Ainsi, si des modifications cassants doivent être apportées, 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 ni laisser de code malveillant pour une exécution ultérieure. Exécutez d’abord le 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 pourront 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 au format spécial imprimées sur 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 setvariable commande . É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 l’interdire, les auteurs de pipeline peuvent déclarer explicitement quelles variables peuvent être définies via la setvariable commande de journalisation. 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’étapes ou de travaux

Limitez l’exécution des étapes et des travaux dans des conditions spécifiques. Les conditions peuvent vous aider, par exemple, à vous assurer que vous créez uniquement 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 des 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 script inline.

Avertissement

Dans l’exemple ci-dessous, les étapes de type « bash », « powershell », « pwsh » et « script » ne peuvent pas s’exécuter. Pour le verrouillage total 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és

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, il peut restreindre les pools qui peuvent être utilisés dans un pipeline en proposant une énumération d’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 de modèle requise pour une ressource ou un environnement. La vérification de modèle requise 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 exiger, 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 voyez que votre vérification a réussi.

validation de la vérification des passes

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 pour appliquer l’utilisation de 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 dépôts. Pour obtenir un exemple, consultez Ajouter une vérification de ressources 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.