Analyser vos script IaC avec Checkov dans des pipelines Azure Devops

Un script de déploiement c’est du code. Il est donc utile de se demander s’il existe des stratégies pour en améliorer la qualité avant de le déployer.

Checkov est un de ces outils. Il est capable d’analyser vos scripts et de signaler certains antipatterns. Il s’agit d’un outil d’analyse statique, Checkov n’exécute donc pas le script. Il analyse juste son code.

Actuellement Checkov peut analyser des scripts:

  • Terraform ressources ou plan
  • ARM Template
  • Bicep
  • Pipeline Yaml
  • Help
  • Kubernetes

Et bien d’autres.

Je propose dans ce premier article de voir comment l’intégrer dans un pipeline Azure Devops. Je traiterai le cas de l’utilisation en local dans le suivant.

Au menu:

  • Lancer Checkov avec Docker
  • Lancer Checkov sans Docker

Une dernière chose, dès que vous trouvez un lien vers un site Microsoft dans cet article, cela m’arrangerait que vous cliquiez dessus, merci d’avance!

La documentation

La documentation officielle est ici:

https://www.checkov.io/

 

2022-11-15_23-15-23

La page de téléchargement en (1) et la doc en (2). Une fois n’est pas coutume, les exemple d’intégration et d’utilisation sont multi-plateforme:

2022-11-15_23-18-18

On ne voit pas Azure Devops dans la liste, mais c’est possible et nous allons le faire.

Lancer Checkov dans Docker

Il existe une image Checkov dans Docker, l’intérêt est que l’on a pas besoin d’installer Checkov soit nativement dans l’agent, soit dynamiquement.

 

Je vais m’inspirer de celui trouvé dans cet article, mais le commenter différemment, d’autant plus qu’il contient pas mal d’erreur:

https://thecloudnative.blog/2022/02/02/azure-devops-and-chechov/

Tout d’abord j’ai besoin d’un projet dans Azure Devops:

2022-11-15_23-45-47

Il est composé de deux fichiers:

  1. Deploy.tf
    Un script Terraform
  2. pipeline.yaml
    Le pipeline Yaml

Dans la vraie vie vous le structurerez sans doute différemment. Par exemple dans deux repositories.

Commençons par le script Terraform:

resource "azurerm_resource_group" "rg" {
  name     = "rg-demoapp-dev-001"
  location = "west europe"
}
 
resource "azurerm_virtual_network" "vnet" {
  name                = "vnet"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  address_space       = ["10.0.0.0/16"]
}
 
resource "azurerm_subnet" "integrationsubnet" {
  name                 = "integrationsubnet"
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.0.1.0/24"]
  delegation {
    name = "delegation"
    service_delegation {
      name = "Microsoft.Web/serverFarms"
    }
  }
}


resource "azurerm_storage_account" "test" {
  name                = "storamethyste"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location


  account_tier              = "Standard"
  account_kind              = "StorageV2"
  account_replication_type  = "LRS"
  enable_https_traffic_only = true
}

On déploie donc:

  • un groupe de ressources
  • un VNET
  • un compte de stockage

Voyons maintenant le script Yaml:

trigger: none

variables:
  # There must be an Azure Service Connection with that name defined in your Azure DevOps settings. See https://docs.microsoft.com/en-us/azure/devops/pipelines/library/connect-to-azure?view=azure-devops
  # serviceConnection: 'terraform-basic-testing-azure-connection'
  # azureLocation: 'westeurope'
  # Terraform settings
  terraformWorkingDirectory: '$(System.DefaultWorkingDirectory)'
  terraformVersion: '1.3.4'
  pwd: "/user"
 
pool:
    vmImage: ubuntu-20.04

stages:
  - stage: Validate
    displayName: Terraform Validate
    jobs:
    - job: Validate
      steps:
       # Step 1: installe Terraform sur l'agent de release
      - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-installer.TerraformInstaller@0
        displayName: 'Installer Terraform'
        inputs:
          terraformVersion: $(terraformVersion)

      # Step 2: lance Terraform init pour initialiser l'espace de travail
      - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
        displayName: 'Terraform init'
        inputs:
          command: init
          workingDirectory: $(terraformWorkingDirectory)

       # Step 3 lance Terraform validate 
      - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
        displayName: 'Terraform validate'
        inputs:
          command: validate
          workingDirectory: $(terraformWorkingDirectory)

  - stage: Compliance
    displayName: Analyse par Checkov
    jobs:
    - job: Compliance
      displayName: Analyse par Checkov
      steps:

      - bash: docker pull bridgecrew/checkov
        workingDirectory: $(System.DefaultWorkingDirectory)
        displayName: "Pull le conteneur bridgecrew/checkov"

      - bash: | 
            docker run \
            -t \
            --volume $(System.DefaultWorkingDirectory):/tf \
            bridgecrew/checkov \
            --directory /tf \
            --output junitxml \
            --soft-fail \
            > $(System.DefaultWorkingDirectory)/Checkov-Report.xml 
        workingDirectory: $(System.DefaultWorkingDirectory)
        displayName: "Lance Checkov"

      - task: PublishTestResults@2
        displayName: "Publie les résultats de test"
        condition: succeededOrFailed()
        inputs:
          testRunTitle: "Résultats Checkov"
          publishRunAttachments: true
          failTaskOnFailedTests: false
          mergeTestResults: false
          testResultsFormat: "JUnit"
          testResultsFiles: "**/*Checkov-Report.xml"
          searchFolder: "$(System.DefaultWorkingDirectory)"

Disons tout de suite que je l’ai simplifié, j’ai pas besoin dans cette démo de lancer le déploiement, juste d’analyser avec Checkov le Terraform. Il manque donc une partie du script.

Je commence par définir des variables, au moment où vous lirez ce blog il est possible que le numéro de version de Terraform ne soit plus à jour.

Puis on déclare deux Stages:

  • Validate
    Installation de Terraform et initialisation
  • Compliance
    On lance Checkov

A vous de voir si vous faites 1 ou plusieurs stages.

Pour manipuler Terraform on a besoin d’une task. Elle sont nombreuses dans la Marketplace. Je pense que la plus complète est celle de Charles Zip:

https://marketplace.visualstudio.com/items?itemName=charleszipp.azure-pipelines-tasks-terraform

Je vous la recommande.

2022-11-15_23-57-04

Il suffit de cliquer sur Get it Free

Le stage Validate:

  • Installe Terraform dans l’agent
  • Initialise l’espace de travail
  • Lance Terraform Validate

Checkov est lancé dans un conteneur Docker.

Notez le paramètre soft-fail de Checkov.

Il est documenté ici:

https://www.checkov.io/2.Basics/Hard%20and%20soft%20fail.html

Il demande à Checkov de générer un exit 0 au cas où des erreurs se produisent. Autrement le stage échoue immédiatement, ce n’est pas ce que l’on souhaite car on veut récupérer le résultat des tests.

Directory est le répertoire qui a été monté dans Docker.

A l’exécution un fichier de sortie est généré, le paramètre output sélectionne le format de sortie. On a le choix dans 3 formats:

  1. CSV
  2. JUNIT XML
    Un format XML donc!
  3. CycloneDX
    Je ne sais même pas ce que c’est

On souhaite publier le compte rendu Checkov dans le portail avec la task PublishTestResult:

https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/publish-test-results-v2?WT.mc_id=DT-MVP-5004831

On doit donc choisir JUNIT XML.

NOTE: comme je l’indiquais plus haut, je n’ai pas repris la suite du script dans laquelle on fait le plan, puis on lance le script Terraform. Je n’en ai pas besoin pour cette démo.

Testons!

Il n’y a pas 50 façons, on lance le script en créant une instance de pipeline:

2022-11-19_18-50-46

On ne voit pas apparaître l’onglet Test, mais on trouve un warning fort mystérieux.

Après quelques recherches, j’ai appris que l’image Checkov est une version boguée qui ajoute d’étranges caractères hexa à la fin du fichier. Il faut les enlever en attendant que le bogue soit corrigé:

Voici la nouvelle version du pipeline:

trigger: none

variables:
  # There must be an Azure Service Connection with that name defined in your Azure DevOps settings. See https://docs.microsoft.com/en-us/azure/devops/pipelines/library/connect-to-azure?view=azure-devops
  # serviceConnection: 'terraform-basic-testing-azure-connection'
  # azureLocation: 'westeurope'
  # Terraform settings
  terraformWorkingDirectory: '$(System.DefaultWorkingDirectory)'
  terraformVersion: '1.3.4'
  pwd: "/user"
 
pool:
    vmImage: ubuntu-20.04

stages:
  - stage: Validate
    displayName: Terraform Validate
    jobs:
    - job: Validate
      steps:
       # Step 1: installe Terraform sur l'agent de release
      - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-installer.TerraformInstaller@0
        displayName: 'Installer Terraform'
        inputs:
          terraformVersion: $(terraformVersion)

      # Step 2: lance Terraform init pour initialiser l'espace de travail
      - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
        displayName: 'Terraform init'
        inputs:
          command: init
          workingDirectory: $(terraformWorkingDirectory)

       # Step 3 lance Terraform validate 
      - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
        displayName: 'Terraform validate'
        inputs:
          command: validate
          workingDirectory: $(terraformWorkingDirectory)

  - stage: Compliance
    displayName: Analyse par Checkov
    jobs:
    - job: Compliance
      displayName: Analyse par Checkov
      steps:

      - bash: docker pull bridgecrew/checkov
        workingDirectory: $(System.DefaultWorkingDirectory)
        displayName: "Pull le conteneur bridgecrew/checkov"

      - bash: | 
            docker run \
            -t \
            --volume $(System.DefaultWorkingDirectory):/tf \
            bridgecrew/checkov \
            --directory /tf \
            --output junitxml \
            --soft-fail \
            > $(System.DefaultWorkingDirectory)/Checkov-Report.xml 
        workingDirectory: $(System.DefaultWorkingDirectory)
        displayName: "Lance Checkov"

      - bash: |
                  sed -i '$d' $(System.DefaultWorkingDirectory)/Checkov-Report.xml
                  sed -i '$d' $(System.DefaultWorkingDirectory)/Checkov-Report.xml
        displayName: "Supprime les 2 dernières lignes" 
        workingDirectory: $(System.DefaultWorkingDirectory)

      - task: PublishTestResults@2
        displayName: "Publie les résultats de test"
        condition: succeededOrFailed()
        inputs:
          testRunTitle: "Résultats Checkov"
          publishRunAttachments: true
          failTaskOnFailedTests: false
          mergeTestResults: false
          testResultsFormat: "JUnit"
          testResultsFiles: "**/*Checkov-Report.xml"
          searchFolder: "$(System.DefaultWorkingDirectory)"
   

Et cette fois c’est mieux:

2022-11-19_18-57-15

Et on a le résultat des tests:

2022-11-19_18-58-27

Lancer Checkov directement

Il n’est pas obligatoire d’utiliser Docker. On peut lancer Checkov directement.

trigger: none

variables:
  # There must be an Azure Service Connection with that name defined in your Azure DevOps settings. See https://docs.microsoft.com/en-us/azure/devops/pipelines/library/connect-to-azure?view=azure-devops
  # serviceConnection: 'terraform-basic-testing-azure-connection'
  # azureLocation: 'westeurope'
  # Terraform settings
  terraformWorkingDirectory: '$(System.DefaultWorkingDirectory)'
  terraformVersion: '1.3.4'
  pwd: "/user"
 
pool:
    vmImage: ubuntu-20.04

stages:
  - stage: Validate
    displayName: Terraform Validate
    jobs:
    - job: Validate
      steps:
       # Step 1: installe Terraform sur l'agent de release
      - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-installer.TerraformInstaller@0
        displayName: 'Installer Terraform'
        inputs:
          terraformVersion: $(terraformVersion)

      # Step 2: lance Terraform init pour initialiser l'espace de travail
      - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
        displayName: 'Terraform init'
        inputs:
          command: init
          workingDirectory: $(terraformWorkingDirectory)

       # Step 3 lance Terraform validate 
      - task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
        displayName: 'Terraform validate'
        inputs:
          command: validate
          workingDirectory: $(terraformWorkingDirectory)

  - stage: Compliance
    displayName: Analyse par Checkov
    jobs:
    - job: Compliance
      displayName: Analyse par Checkov
      steps:

      # Installe Checkov et le lance
      - bash: |
          pip3 install -U checkov
          checkov -d . \
          --skip-path ./*.json \
          --soft-fail \
          --output junitxml > $(System.DefaultWorkingDirectory)/Checkov-Report.xml
        workingDirectory: $(System.DefaultWorkingDirectory)
        failOnStderr: false
        displayName: "Checkov > Install & Run"
        name: CheckovScan

      # Récupère les résultats dans le portail
      - task: PublishTestResults@2
        inputs:
          testResultsFormat: "JUnit"
          testResultsFiles: "Checkov-Report.xml"
          searchFolder: "$(System.DefaultWorkingDirectory)"
          testRunTitle: "Checkov"
          mergeTestResults: false
          failTaskOnFailedTests: false
          publishRunAttachments: true
        displayName: "Checkov > Publish"

  

Je vous laisse le soin de vérifier que le résultat est le même.

Bibliographie

Publicité

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Image Twitter

Vous commentez à l’aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s