CI/CD avec GitHub et signatures avec cosign

Objectifs

  • Construire une image de conteneur automatiquement avec les GitHub Workflows / Actions
  • Signer une image de conteneur avec cosign
  • Configurer podman pour n’accepter que des images de conteneur signées

CI/CD avec les GitHub Workflows / Actions

  • Créer un compte GitHub si vous n’en avez pas déjà un
  • Ajouter un clé SSH à votre compte
  • Créer un nouveau dépot public sur GitHub
  • Cloner le dépôt en local sur votre machine
  • Ajouter un Containerfile pour créer une image de conteneur :

    conteneur/Containerfile :

    FROM registry.fedoraproject.org/fedora:39
    
    RUN touch /insa-cvl
    
  • Ajouter le workflow GitHub suivant :

    .github/workflows/conteneur.yml:

    name: "Build container image"
    
    env:
      NAME: "conteneur"
      # Si votre identifiant de compte GitHub comporte des majuscules, remplacez
      # '${{ github.repository_owner }}' par votre identifiant en miniscule uniquement
      REGISTRY: ghcr.io/${{ github.repository_owner }}
    
    # Ce workflow ne sera exécuté que lors d'un 'push' sur la branche 'main'
    on:
      push:
        branches:
          - main
    
    # Authorise :
    # - Accès en lecture seule au contenu du dépôt Git
    # - Accès en lecture/écriture au "packages" pour avoir l'accès au registre de
    #   conteneurs de GitHub
    permissions:
      contents: read
      packages: write
    
    jobs:
      build-push-image:
        runs-on: ubuntu-latest
        steps:
          # Récupération du dépôt Git
          - name: Checkout repo
            uses: actions/checkout@v4
    
          # Construction de l'image de conteneur avec buildah
          - name: Build container image
            uses: redhat-actions/buildah-build@v2
            with:
              context: ${{ env.NAME }}
              image: ${{ env.NAME }}
              tags: latest
              containerfiles: ${{ env.NAME }}/Containerfile
              layers: false
              oci: true
    
          # Publication de l'image de conteneur dans le regsitre de GitHub avec podman
          - name: Push to Container Registry
            uses: redhat-actions/push-to-registry@v2
            id: push
            with:
              registry: ${{ env.REGISTRY }}
              username: ${{ github.actor }}
              password: ${{ secrets.GITHUB_TOKEN }}
              image: ${{ env.NAME }}
              tags: latest
    
  • Vous devez obtenir une organisation comme suit :

    $ tree -a
    .
    ├── conteneur
    │   └── Containerfile
    ├── .git
    │   └── ...
    └── .github
        └── workflows
            └── conteneur.yml
    
  • Commiter et poussez vos changements dans votre dépôt distant.

  • Observer la construction de votre image dans l’onglet Actions.

  • Vérifier que votre image est bien construite et disponible dans le registre de conteneur de GitHub à l’aide de l’interface Web.

  • Tester votre image :

    $ podman run --rm -ti ghcr.io/<utilisateur GitHub>/conteneur
    # ls /
    ...
    insa-cvl
    ...
    

Sigstore et Cosign

  • Installer cosign. Suivre la documentation en ligne: https://docs.sigstore.dev/system_config/installation/

  • Générer une clé pivée et publique pour signer les conteneurs avec cosign

    $ cosign generate-key-pair
    
  • Connectez vous au registre de conteneurs GitHub de votre dépôt :

    $ podman login ghcr.io/<utilisateur GitHub>
    

    Si vous utilisez l’authentification multi-facteurs, suivez la documentation GitHub pour créer un access token à utiliser comme mot de passe dans la commande précédente.

    Si nécessaire, copiez les token d’authentification de podman à l’emplacement de ceux de Docker :

    $ cp ${XDG_RUNTIME_DIR}/containers/auth.json ~/.docker/config.json
    
  • Signer votre image avec cosign :

    $ cosign sign --key cosign.key ghcr.io/<utilisateur GitHub>/conteneur
    
  • Vérifier la signature ajoutée à l’image avec cosign :

    $ cosign verify --key cosign.pub ghcr.io/<utilisateur GitHub>/conteneur
    

Signatures avec cosign dans le workflow GitHub

Nous allons ensuite automatiser cette procédure à l’aide du workflow GitHub :

  • Ajouter la clé privée cosign.key comme secret dans GitHub :
    • Aller dans les préférences du dépôt, puis “Secrets et variables”, puis “Actions”
    • Ajouter la clé privée dans les secrets associés au dépôt
    • Nommer le secret COSIGN_PRIVATE_KEY
  • Ajouter la signature du conteneur à l’aide de cosign dans le workflow GitHub, à la suite du contenu existant :

    ...
          # Authentification au registre de conteneur de GitHub
          - name: Login to Container Registry
            uses: redhat-actions/podman-login@v1
            with:
              registry: ${{ env.REGISTRY }}
              username: ${{ github.actor }}
              password: ${{ secrets.GITHUB_TOKEN }}
    
          # Installation de cosign
          - uses: sigstore/cosign-installer@v3.3.0
    
          # Signature de l'image de conteneur
          - name: Sign container image
            run: |
              cosign sign -y --key env://COSIGN_PRIVATE_KEY ${{ env.REGISTRY }}/${{ env.NAME }}@${{ steps.push.outputs.digest }}
            env:
              COSIGN_EXPERIMENTAL: false
              COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
    
  • Attendre qu’une nouvelle version de l’image soit publiée dans le registre de conteneur

  • Vérifier la signature de la nouvelle image à l’aide de cosign :

    $ cosign verify --key cosign.pub ghcr.io/<utilisateur GitHub>/conteneur
    

Configurer Podman pour vérifier les signatures

Nous allons configurer podman sur notre système pour qu’il vérifie la signature des images de conteneurs et qu’il refuse les images non signées.

  • Installer la clé publique :

    $ sudo mkdir -p /etc/pki/containers
    $ sudo cp cosign.pub /etc/pki/containers/
    $ sudo restorecon -RFv /etc/pki/containers
    
  • Ajouter la configuration du registre pour récupérer les signatures :

    $ cat /etc/containers/registries.d/ghcr.io-<utilisateur GitHub>.yaml
    docker:
      ghcr.io/<utilisateur GitHub>:
        use-sigstore-attachments: true
    $ sudo restorecon -RFv /etc/containers/registries.d/ghcr.io-<utilisateur GitHub>.yaml
    
    
  • Configurer ensuite la politique utilisée par podman. Modifier le fichier /etc/containers/policy.json pour obtenir la configuration suivante. Les ... signifient de laisser les valeurs qui se trouvent déjà dans le fichier à ces emplacements :

    {
        "default": [
            {
                "type": "reject"
            }
        ],
        "transports": {
            "docker": {
                "ghcr.io/<utilisateur GitHub>": [
                    {
                        "type": "sigstoreSigned",
                        "keyPath": "/etc/pki/containers/ghcr.io-<utilisateur GitHub>.pub",
                        "signedIdentity": {
                            "type": "matchRepository"
                        }
                    }
                ],
                ...
                "": [
                    {
                        "type": "insecureAcceptAnything"
                    }
                ]
            },
            ...
        }
    }
    

    La page de man qui explique les options de configuration utilisées : containers-policy.json(5)

  • Vérifier que seul les conteneurs signés sont acceptés :

    $ podman rmi --force ghcr.io/<utilisateur GitHub>/conteneur
    $ podman pull ghcr.io/<utilisateur GitHub>/conteneur
    $ podman run --rm -ti ghcr.io/<utilisateur GitHub>/conteneur
    
  • Ajouter une copie du workflow initial pour créer un autre conteneur non signé dans le même repo mais avec un autre tag et vérifier qu’il n’est pas accepté par podman

Références