{"id":543,"date":"2026-02-22T12:16:54","date_gmt":"2026-02-22T11:16:54","guid":{"rendered":"https:\/\/secure-pipelines.com\/?p=543"},"modified":"2026-07-06T20:40:32","modified_gmt":"2026-07-06T19:40:32","slug":"lab-ephemeral-self-hosted-runners-actions-runner-controller","status":"publish","type":"post","link":"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/lab-ephemeral-self-hosted-runners-actions-runner-controller\/","title":{"rendered":"Lab : Runners \u00c9ph\u00e9m\u00e8res Self-Hosted avec Actions Runner Controller"},"content":{"rendered":"<h2>Aper\u00e7u<\/h2>\n<p>Les runners h\u00e9berg\u00e9s par GitHub sont partag\u00e9s et \u00e9ph\u00e9m\u00e8res par d\u00e9faut \u2014 chaque job obtient une machine virtuelle neuve qui est d\u00e9truite une fois le job termin\u00e9. Les runners self-hosted, en revanche, sont persistants et partag\u00e9s entre les ex\u00e9cutions de workflows. Cela cr\u00e9e un risque de s\u00e9curit\u00e9 important : les secrets, les tokens et les artefacts de build d&rsquo;un job peuvent fuiter vers le suivant. Un workflow compromis peut empoisonner l&rsquo;environnement du runner pour tous les jobs futurs.<\/p>\n<p><strong>Actions Runner Controller (ARC)<\/strong> r\u00e9sout ce probl\u00e8me. ARC est un op\u00e9rateur natif Kubernetes qui vous fournit des runners self-hosted \u00e9ph\u00e9m\u00e8res, auto-scalables et bas\u00e9s sur des conteneurs. Chaque job obtient un pod neuf qui est d\u00e9truit une fois le job termin\u00e9 \u2014 exactement comme les runners h\u00e9berg\u00e9s par GitHub, mais s&rsquo;ex\u00e9cutant sur votre propre infrastructure avec vos propres outils et politiques r\u00e9seau.<\/p>\n<p>Dans ce lab pratique, vous allez :<\/p>\n<ul>\n<li>D\u00e9ployer ARC sur un cluster Kubernetes local<\/li>\n<li>Configurer des scale sets de runners \u00e9ph\u00e9m\u00e8res<\/li>\n<li>D\u00e9montrer l&rsquo;isolation entre jobs (le principal b\u00e9n\u00e9fice de s\u00e9curit\u00e9)<\/li>\n<li>Construire des images de runner personnalis\u00e9es<\/li>\n<li>Impl\u00e9menter l&rsquo;isolation par groupe de runners pour la s\u00e9paration des responsabilit\u00e9s<\/li>\n<li>Configurer l&rsquo;autoscaling<\/li>\n<li>Appliquer des politiques r\u00e9seau pour restreindre l&rsquo;acc\u00e8s r\u00e9seau des runners<\/li>\n<\/ul>\n<h2>Pr\u00e9requis<\/h2>\n<p>Avant de commencer ce lab, assurez-vous de disposer des \u00e9l\u00e9ments suivants :<\/p>\n<ul>\n<li><strong>Cluster Kubernetes<\/strong> \u2014 <a href=\"https:\/\/kind.sigs.k8s.io\/\" target=\"_blank\" rel=\"noopener\">kind<\/a>, <a href=\"https:\/\/minikube.sigs.k8s.io\/\" target=\"_blank\" rel=\"noopener\">minikube<\/a>, ou un cluster g\u00e9r\u00e9 dans le cloud (EKS, GKE, AKS)<\/li>\n<li><strong>Helm 3<\/strong> \u2014 Installez-le depuis <a href=\"https:\/\/helm.sh\/docs\/intro\/install\/\" target=\"_blank\" rel=\"noopener\">helm.sh<\/a><\/li>\n<li><strong>kubectl<\/strong> \u2014 Configur\u00e9 pour communiquer avec votre cluster<\/li>\n<li><strong>Compte GitHub<\/strong> \u2014 Avec un acc\u00e8s administrateur \u00e0 un d\u00e9p\u00f4t ou une organisation<\/li>\n<li><strong>GitHub App ou Personal Access Token (PAT)<\/strong> \u2014 Avec les scopes <code>repo<\/code> et <code>admin:org<\/code> (PAT) ou les permissions GitHub App appropri\u00e9es<\/li>\n<li><strong>Docker<\/strong> \u2014 Pour construire des images de runner personnalis\u00e9es (Exercice 4)<\/li>\n<\/ul>\n<h2>Configuration de l&rsquo;environnement<\/h2>\n<p>Nous utiliserons <strong>kind<\/strong> (Kubernetes in Docker) pour cr\u00e9er un cluster local. Cela garde le lab autonome et facile \u00e0 nettoyer.<\/p>\n<h3>Cr\u00e9er un cluster kind<\/h3>\n<pre><code>kind create cluster --name arc-lab<\/code><\/pre>\n<p>V\u00e9rifiez que le cluster fonctionne :<\/p>\n<pre><code>kubectl cluster-info --context kind-arc-lab<\/code><\/pre>\n<h3>Cr\u00e9er un d\u00e9p\u00f4t GitHub de test<\/h3>\n<p>Cr\u00e9ez un nouveau d\u00e9p\u00f4t (par exemple, <code>arc-lab-test<\/code>) dans votre compte GitHub. Ajoutez un fichier de workflow simple dans <code>.github\/workflows\/test.yml<\/code> :<\/p>\n<pre><code>name: ARC Test Workflow\non:\n  push:\n    branches: [main]\n  workflow_dispatch:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Hello from GitHub-hosted runner\n        run: echo \"This runs on a GitHub-hosted runner\"<\/code><\/pre>\n<p>Poussez ceci vers votre d\u00e9p\u00f4t. Nous le modifierons plus tard pour cibler les runners ARC.<\/p>\n<h2>Exercice 1 : Installer ARC avec Helm<\/h2>\n<p>Actions Runner Controller v2 utilise des charts Helm pour d\u00e9ployer deux composants : un <strong>controller<\/strong> qui g\u00e8re le cycle de vie des pods de runner, et un ou plusieurs <strong>runner scale sets<\/strong> qui s&rsquo;enregistrent aupr\u00e8s de GitHub et acceptent des jobs.<\/p>\n<h3>\u00c9tape 1 : Ajouter le d\u00e9p\u00f4t Helm<\/h3>\n<pre><code>helm repo add actions-runner-controller \\\n  https:\/\/actions-runner-controller.github.io\/actions-runner-controller\nhelm repo update<\/code><\/pre>\n<h3>\u00c9tape 2 : Configurer l&rsquo;authentification<\/h3>\n<p>ARC doit s&rsquo;authentifier aupr\u00e8s de l&rsquo;API GitHub. Vous avez deux options :<\/p>\n<p><strong>Option A : GitHub App (recommand\u00e9e pour la production)<\/strong><\/p>\n<p>Cr\u00e9ez une GitHub App dans les param\u00e8tres de votre organisation ou de votre compte :<\/p>\n<ol>\n<li>Allez dans <strong>Settings \u2192 Developer settings \u2192 GitHub Apps \u2192 New GitHub App<\/strong><\/li>\n<li>D\u00e9finissez les permissions suivantes :\n<ul>\n<li>Repository : <code>Actions<\/code> (lecture), <code>Administration<\/code> (lecture\/\u00e9criture), <code>Metadata<\/code> (lecture)<\/li>\n<li>Organization : <code>Self-hosted runners<\/code> (lecture\/\u00e9criture)<\/li>\n<\/ul>\n<\/li>\n<li>G\u00e9n\u00e9rez une cl\u00e9 priv\u00e9e et t\u00e9l\u00e9chargez-la<\/li>\n<li>Installez l&rsquo;App sur votre organisation ou votre d\u00e9p\u00f4t<\/li>\n<li>Notez l&rsquo;App ID et l&rsquo;Installation ID<\/li>\n<\/ol>\n<p><strong>Option B : Personal Access Token (plus simple pour les labs)<\/strong><\/p>\n<p>Cr\u00e9ez un PAT (classique) avec les scopes <code>repo<\/code> et <code>admin:org<\/code>, ou un PAT fine-grained avec les permissions Actions et Administration. Pour ce lab, nous utiliserons un PAT par souci de simplicit\u00e9.<\/p>\n<h3>\u00c9tape 3 : Installer le controller ARC<\/h3>\n<pre><code>helm install arc \\\n  actions-runner-controller\/gha-runner-scale-set-controller \\\n  --namespace arc-systems \\\n  --create-namespace<\/code><\/pre>\n<p>V\u00e9rifiez que le controller fonctionne :<\/p>\n<pre><code>kubectl get pods -n arc-systems<\/code><\/pre>\n<p>Vous devriez voir une sortie similaire \u00e0 :<\/p>\n<pre><code>NAME                                     READY   STATUS    RESTARTS   AGE\narc-gha-runner-scale-set-controller-xxx  1\/1     Running   0          30s<\/code><\/pre>\n<h3>\u00c9tape 4 : Installer un runner scale set<\/h3>\n<p>D\u00e9ployez maintenant un runner scale set qui s&rsquo;enregistre aupr\u00e8s de votre d\u00e9p\u00f4t GitHub :<\/p>\n<pre><code>helm install arc-runner-set \\\n  actions-runner-controller\/gha-runner-scale-set \\\n  --namespace arc-runners \\\n  --create-namespace \\\n  --set githubConfigUrl=\"https:\/\/github.com\/&lt;org&gt;\/&lt;repo&gt;\" \\\n  --set githubConfigSecret.github_token=\"&lt;PAT&gt;\"<\/code><\/pre>\n<p>Remplacez <code>&lt;org&gt;\/&lt;repo&gt;<\/code> par le chemin de votre d\u00e9p\u00f4t et <code>&lt;PAT&gt;<\/code> par votre personal access token.<\/p>\n<p>V\u00e9rifiez le runner scale set :<\/p>\n<pre><code>kubectl get pods -n arc-runners<\/code><\/pre>\n<p>\u00c0 ce stade, il se peut qu&rsquo;il n&rsquo;y ait encore aucun pod de runner \u2014 ARC utilise un mod\u00e8le scale-to-zero. Les pods ne sont cr\u00e9\u00e9s que lorsque des jobs sont mis en file d&rsquo;attente.<\/p>\n<h3>\u00c9tape 5 : V\u00e9rifier dans GitHub<\/h3>\n<p>Naviguez vers votre d\u00e9p\u00f4t sur GitHub : <strong>Settings \u2192 Actions \u2192 Runners<\/strong>. Vous devriez voir le runner scale set list\u00e9 avec le nom <code>arc-runner-set<\/code>. Le statut indique qu&rsquo;il est pr\u00eat \u00e0 accepter des jobs.<\/p>\n<h2>Exercice 2 : Ex\u00e9cuter un workflow sur des runners ARC<\/h2>\n<p>Mettez maintenant \u00e0 jour le workflow de test pour cibler le runner scale set ARC au lieu des runners h\u00e9berg\u00e9s par GitHub.<\/p>\n<h3>\u00c9tape 1 : Mettre \u00e0 jour le workflow<\/h3>\n<p>Modifiez <code>.github\/workflows\/test.yml<\/code> pour utiliser le label du runner ARC :<\/p>\n<pre><code>name: ARC Test Workflow\non:\n  push:\n    branches: [main]\n  workflow_dispatch:\n\njobs:\n  test:\n    runs-on: arc-runner-set\n    steps:\n      - name: Hello from ARC runner\n        run: |\n          echo \"This runs on an ephemeral ARC runner!\"\n          echo \"Hostname: $(hostname)\"\n          echo \"Runner OS: $(uname -a)\"\n      - name: Show environment\n        run: env | sort<\/code><\/pre>\n<p>Le changement cl\u00e9 est <code>runs-on: arc-runner-set<\/code> \u2014 cela correspond au nom de la release Helm du runner scale set.<\/p>\n<h3>\u00c9tape 2 : D\u00e9clencher le workflow<\/h3>\n<p>Poussez le fichier de workflow mis \u00e0 jour ou utilisez le bouton \u00ab Run workflow \u00bb (workflow_dispatch) dans l&rsquo;interface GitHub Actions.<\/p>\n<h3>\u00c9tape 3 : Observer le pod de runner<\/h3>\n<p>Surveillez le namespace <code>arc-runners<\/code> pendant l&rsquo;ex\u00e9cution du workflow :<\/p>\n<pre><code>kubectl get pods -n arc-runners -w<\/code><\/pre>\n<p>Vous verrez un pod cr\u00e9\u00e9 pour le job :<\/p>\n<pre><code>NAME                          READY   STATUS    RESTARTS   AGE\narc-runner-set-xxxxx-runner   1\/1     Running   0          5s<\/code><\/pre>\n<p>Une fois le job termin\u00e9, le pod est arr\u00eat\u00e9 et supprim\u00e9 :<\/p>\n<pre><code>NAME                          READY   STATUS      RESTARTS   AGE\narc-runner-set-xxxxx-runner   0\/1     Completed   0          45s<\/code><\/pre>\n<p>Ex\u00e9cutez \u00e0 nouveau <code>kubectl get pods -n arc-runners<\/code> \u2014 le pod a disparu. C&rsquo;est le mod\u00e8le \u00e9ph\u00e9m\u00e8re : chaque job obtient un conteneur neuf, et le conteneur est d\u00e9truit une fois le job termin\u00e9. Il n&rsquo;y a aucune persistance d&rsquo;\u00e9tat entre les jobs.<\/p>\n<h2>Exercice 3 : D\u00e9montrer la s\u00e9curit\u00e9 \u00e9ph\u00e9m\u00e8re<\/h2>\n<p>Cet exercice d\u00e9montre le principal b\u00e9n\u00e9fice de s\u00e9curit\u00e9 des runners \u00e9ph\u00e9m\u00e8res : <strong>aucune contamination entre jobs<\/strong>.<\/p>\n<h3>\u00c9tape 1 : Cr\u00e9er un workflow qui \u00e9crit des donn\u00e9es sensibles<\/h3>\n<p>Cr\u00e9ez <code>.github\/workflows\/ephemeral-test.yml<\/code> :<\/p>\n<pre><code>name: Ephemeral Security Test\non: workflow_dispatch\n\njobs:\n  write-secret:\n    runs-on: arc-runner-set\n    steps:\n      - name: Write sensitive data\n        run: |\n          echo \"SECRET_API_KEY=sk-prod-abc123xyz\" &gt; \/tmp\/secret-data\n          echo \"DB_PASSWORD=super-secret-password\" &gt;&gt; \/tmp\/secret-data\n          echo \"Written sensitive data to \/tmp\/secret-data\"\n          cat \/tmp\/secret-data\n\n  read-secret:\n    runs-on: arc-runner-set\n    needs: write-secret\n    steps:\n      - name: Attempt to read previous job data\n        run: |\n          echo \"Checking if \/tmp\/secret-data exists from previous job...\"\n          if [ -f \/tmp\/secret-data ]; then\n            echo \"SECURITY RISK: Found data from previous job!\"\n            cat \/tmp\/secret-data\n          else\n            echo \"SECURE: \/tmp\/secret-data does not exist.\"\n            echo \"Each job gets a fresh container \u2014 no cross-job contamination.\"\n          fi<\/code><\/pre>\n<h3>\u00c9tape 2 : Ex\u00e9cuter le workflow<\/h3>\n<p>D\u00e9clenchez le workflow via <code>workflow_dispatch<\/code>. Le premier job (<code>write-secret<\/code>) \u00e9crit des donn\u00e9es sensibles dans <code>\/tmp\/secret-data<\/code>. Le second job (<code>read-secret<\/code>) s&rsquo;ex\u00e9cute dans un nouveau pod et tente de lire ce fichier.<\/p>\n<h3>\u00c9tape 3 : V\u00e9rifier les r\u00e9sultats<\/h3>\n<p>Dans les logs de GitHub Actions, vous verrez :<\/p>\n<ul>\n<li><strong>Job write-secret :<\/strong> \u00c9crit le fichier avec succ\u00e8s et affiche son contenu<\/li>\n<li><strong>Job read-secret :<\/strong> Le fichier n&rsquo;existe pas \u2014 la sortie affiche <code>SECURE: \/tmp\/secret-data does not exist.<\/code><\/li>\n<\/ul>\n<p>Chaque job s&rsquo;est ex\u00e9cut\u00e9 dans un pod distinct, fra\u00eechement cr\u00e9\u00e9. Lorsque le pod <code>write-secret<\/code> a \u00e9t\u00e9 d\u00e9truit, toutes les donn\u00e9es \u2014 y compris le fichier sensible \u2014 ont \u00e9t\u00e9 d\u00e9truites avec lui.<\/p>\n<h3>Pourquoi c&rsquo;est important<\/h3>\n<p>Sur un <strong>runner self-hosted persistant<\/strong>, le fichier <code>\/tmp\/secret-data<\/code> serait toujours sur le disque lors de l&rsquo;ex\u00e9cution du second job. Un workflow malveillant dans une pull request pourrait lire des secrets, des tokens ou des identifiants laiss\u00e9s par des jobs pr\u00e9c\u00e9dents. Avec des runners \u00e9ph\u00e9m\u00e8res, ce vecteur d&rsquo;attaque est \u00e9limin\u00e9.<\/p>\n<h2>Exercice 4 : Images de runner personnalis\u00e9es<\/h2>\n<p>Les runners ARC utilisent une image de conteneur de base. Pour une utilisation en conditions r\u00e9elles, vous devez personnaliser cette image afin d&rsquo;y inclure vos outils de build.<\/p>\n<h3>\u00c9tape 1 : Cr\u00e9er un Dockerfile personnalis\u00e9<\/h3>\n<p>Cr\u00e9ez un <code>Dockerfile<\/code> pour votre runner personnalis\u00e9 :<\/p>\n<pre><code>FROM ghcr.io\/actions\/actions-runner:latest\n\nUSER root\n\n# Install build tools\nRUN apt-get update &amp;&amp; apt-get install -y \\\n    curl \\\n    wget \\\n    git \\\n    jq \\\n    unzip \\\n    build-essential \\\n    &amp;&amp; rm -rf \/var\/lib\/apt\/lists\/*\n\n# Install Go\nRUN wget -q https:\/\/go.dev\/dl\/go1.22.4.linux-amd64.tar.gz \\\n    &amp;&amp; tar -C \/usr\/local -xzf go1.22.4.linux-amd64.tar.gz \\\n    &amp;&amp; rm go1.22.4.linux-amd64.tar.gz\nENV PATH=\"$PATH:\/usr\/local\/go\/bin\"\n\n# Install cosign\nRUN curl -sSL -o \/usr\/local\/bin\/cosign \\\n    https:\/\/github.com\/sigstore\/cosign\/releases\/latest\/download\/cosign-linux-amd64 \\\n    &amp;&amp; chmod +x \/usr\/local\/bin\/cosign\n\n# Install Docker CLI (for Docker-in-Docker workflows)\nRUN curl -fsSL https:\/\/get.docker.com | sh\n\nUSER runner<\/code><\/pre>\n<h3>\u00c9tape 2 : Construire et pousser l&rsquo;image<\/h3>\n<pre><code># Build the image\ndocker build -t ghcr.io\/&lt;org&gt;\/custom-runner:latest .\n\n# Authenticate to GitHub Container Registry\necho \"&lt;PAT&gt;\" | docker login ghcr.io -u &lt;username&gt; --password-stdin\n\n# Push the image\ndocker push ghcr.io\/&lt;org&gt;\/custom-runner:latest<\/code><\/pre>\n<h3>\u00c9tape 3 : Configurer ARC pour utiliser l&rsquo;image personnalis\u00e9e<\/h3>\n<p>Cr\u00e9ez un fichier de values <code>custom-runner-values.yaml<\/code> :<\/p>\n<pre><code>githubConfigUrl: \"https:\/\/github.com\/&lt;org&gt;\/&lt;repo&gt;\"\ngithubConfigSecret:\n  github_token: \"&lt;PAT&gt;\"\n\ntemplate:\n  spec:\n    containers:\n      - name: runner\n        image: ghcr.io\/&lt;org&gt;\/custom-runner:latest\n        command: [\"\/home\/runner\/run.sh\"]\n        resources:\n          requests:\n            cpu: \"500m\"\n            memory: \"512Mi\"\n          limits:\n            cpu: \"2\"\n            memory: \"2Gi\"<\/code><\/pre>\n<p>Mettez \u00e0 jour le runner scale set avec l&rsquo;image personnalis\u00e9e :<\/p>\n<pre><code>helm upgrade arc-runner-set \\\n  actions-runner-controller\/gha-runner-scale-set \\\n  --namespace arc-runners \\\n  -f custom-runner-values.yaml<\/code><\/pre>\n<h3>\u00c9tape 4 : V\u00e9rifier les outils personnalis\u00e9s<\/h3>\n<p>Cr\u00e9ez un workflow qui utilise les outils personnalis\u00e9s :<\/p>\n<pre><code>name: Custom Runner Tools Test\non: workflow_dispatch\n\njobs:\n  verify-tools:\n    runs-on: arc-runner-set\n    steps:\n      - name: Verify Go\n        run: go version\n      - name: Verify cosign\n        run: cosign version\n      - name: Verify Docker CLI\n        run: docker --version<\/code><\/pre>\n<p><strong>B\u00e9n\u00e9fice de s\u00e9curit\u00e9 :<\/strong> En construisant votre propre image de runner, vous contr\u00f4lez exactement quels outils et d\u00e9pendances sont pr\u00e9sents dans l&rsquo;environnement de build. Il n&rsquo;y a aucun binaire inattendu, aucun logiciel pr\u00e9install\u00e9 que vous n&rsquo;avez pas approuv\u00e9, et vous pouvez \u00e9pingler chaque outil \u00e0 une version sp\u00e9cifique. Vous pouvez \u00e9galement analyser l&rsquo;image \u00e0 la recherche de vuln\u00e9rabilit\u00e9s avant de la d\u00e9ployer.<\/p>\n<h2>Exercice 5 : Isolation par groupe de runners<\/h2>\n<p>Diff\u00e9rents workflows ont diff\u00e9rents niveaux de confiance. La validation des pull requests ne devrait pas avoir acc\u00e8s aux secrets de production. Les workflows de d\u00e9ploiement ont besoin de secrets mais ne devraient s&rsquo;ex\u00e9cuter que depuis la branche main. ARC vous permet d&rsquo;impl\u00e9menter cette s\u00e9paration en cr\u00e9ant des runner scale sets distincts avec des labels et des configurations diff\u00e9rents.<\/p>\n<h3>\u00c9tape 1 : Cr\u00e9er un runner scale set de validation des PR<\/h3>\n<p>Cr\u00e9ez <code>pr-runner-values.yaml<\/code> :<\/p>\n<pre><code>githubConfigUrl: \"https:\/\/github.com\/&lt;org&gt;\/&lt;repo&gt;\"\ngithubConfigSecret:\n  github_token: \"&lt;PAT&gt;\"\n\ntemplate:\n  spec:\n    containers:\n      - name: runner\n        image: ghcr.io\/&lt;org&gt;\/custom-runner:latest\n        command: [\"\/home\/runner\/run.sh\"]\n        env:\n          - name: RUNNER_GROUP\n            value: \"pr-validation\"\n        resources:\n          requests:\n            cpu: \"250m\"\n            memory: \"256Mi\"\n          limits:\n            cpu: \"1\"\n            memory: \"1Gi\"<\/code><\/pre>\n<pre><code>helm install arc-runner-pr \\\n  actions-runner-controller\/gha-runner-scale-set \\\n  --namespace arc-runners \\\n  -f pr-runner-values.yaml<\/code><\/pre>\n<h3>\u00c9tape 2 : Cr\u00e9er un runner scale set de d\u00e9ploiement<\/h3>\n<p>Cr\u00e9ez <code>deploy-runner-values.yaml<\/code> :<\/p>\n<pre><code>githubConfigUrl: \"https:\/\/github.com\/&lt;org&gt;\/&lt;repo&gt;\"\ngithubConfigSecret:\n  github_token: \"&lt;PAT&gt;\"\n\ntemplate:\n  spec:\n    containers:\n      - name: runner\n        image: ghcr.io\/&lt;org&gt;\/custom-runner:latest\n        command: [\"\/home\/runner\/run.sh\"]\n        env:\n          - name: RUNNER_GROUP\n            value: \"deployment\"\n        resources:\n          requests:\n            cpu: \"500m\"\n            memory: \"512Mi\"\n          limits:\n            cpu: \"2\"\n            memory: \"2Gi\"\n    serviceAccountName: deploy-runner-sa\n    nodeSelector:\n      runner-type: deployment<\/code><\/pre>\n<pre><code>helm install arc-runner-deploy \\\n  actions-runner-controller\/gha-runner-scale-set \\\n  --namespace arc-runners \\\n  -f deploy-runner-values.yaml<\/code><\/pre>\n<h3>\u00c9tape 3 : Configurer les workflows pour l&rsquo;isolation<\/h3>\n<p>Utilisez des labels de runner diff\u00e9rents selon le d\u00e9clencheur du workflow :<\/p>\n<pre><code>name: CI\/CD Pipeline\non:\n  pull_request:\n    branches: [main]\n  push:\n    branches: [main]\n\njobs:\n  validate:\n    if: github.event_name == 'pull_request'\n    runs-on: arc-runner-pr\n    steps:\n      - uses: actions\/checkout@v4\n      - name: Run tests\n        run: make test\n      - name: Run linter\n        run: make lint\n\n  deploy:\n    if: github.ref == 'refs\/heads\/main' &amp;&amp; github.event_name == 'push'\n    runs-on: arc-runner-deploy\n    steps:\n      - uses: actions\/checkout@v4\n      - name: Deploy to production\n        run: make deploy\n        env:\n          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}<\/code><\/pre>\n<p>Cela impl\u00e9mente la <strong>s\u00e9paration des responsabilit\u00e9s au niveau du runner<\/strong>. Les jobs de validation des PR s&rsquo;ex\u00e9cutent sur des runners qui n&rsquo;ont aucun acc\u00e8s aux secrets de d\u00e9ploiement ni aux segments r\u00e9seau privil\u00e9gi\u00e9s. Les jobs de d\u00e9ploiement s&rsquo;ex\u00e9cutent sur un ensemble distinct de runners qui disposent des identifiants et de l&rsquo;acc\u00e8s r\u00e9seau n\u00e9cessaires, mais ne se d\u00e9clenchent que sur les push vers main.<\/p>\n<h2>Exercice 6 : Mise \u00e0 l&rsquo;\u00e9chelle automatique<\/h2>\n<p>ARC prend en charge nativement l&rsquo;autoscaling. Les pods de runner sont cr\u00e9\u00e9s \u00e0 la demande et d\u00e9truits lorsqu&rsquo;ils sont inactifs. Vous pouvez configurer un nombre minimal et maximal de r\u00e9plicas pour ma\u00eetriser le co\u00fbt et la r\u00e9activit\u00e9.<\/p>\n<h3>\u00c9tape 1 : Configurer les param\u00e8tres d&rsquo;autoscaling<\/h3>\n<p>Mettez \u00e0 jour le fichier de values de votre runner scale set pour y inclure les param\u00e8tres de mise \u00e0 l&rsquo;\u00e9chelle :<\/p>\n<pre><code>githubConfigUrl: \"https:\/\/github.com\/&lt;org&gt;\/&lt;repo&gt;\"\ngithubConfigSecret:\n  github_token: \"&lt;PAT&gt;\"\n\nminRunners: 0\nmaxRunners: 10\n\ntemplate:\n  spec:\n    containers:\n      - name: runner\n        image: ghcr.io\/actions\/actions-runner:latest\n        command: [\"\/home\/runner\/run.sh\"]<\/code><\/pre>\n<pre><code>helm upgrade arc-runner-set \\\n  actions-runner-controller\/gha-runner-scale-set \\\n  --namespace arc-runners \\\n  -f autoscale-values.yaml<\/code><\/pre>\n<h3>\u00c9tape 2 : G\u00e9n\u00e9rer de la charge<\/h3>\n<p>Cr\u00e9ez un workflow qui d\u00e9clenche plusieurs jobs en parall\u00e8le :<\/p>\n<pre><code>name: Autoscale Test\non: workflow_dispatch\n\njobs:\n  parallel-job:\n    runs-on: arc-runner-set\n    strategy:\n      matrix:\n        id: [1, 2, 3, 4, 5]\n    steps:\n      - name: Simulate work\n        run: |\n          echo \"Job ${{ matrix.id }} running on $(hostname)\"\n          sleep 60<\/code><\/pre>\n<p>D\u00e9clenchez ce workflow et observez la mont\u00e9e en charge des pods :<\/p>\n<pre><code>kubectl get pods -n arc-runners -w<\/code><\/pre>\n<p>Vous verrez cinq pods cr\u00e9\u00e9s \u2014 un pour chaque job de la matrice :<\/p>\n<pre><code>NAME                              READY   STATUS    RESTARTS   AGE\narc-runner-set-abcde-runner       1\/1     Running   0          5s\narc-runner-set-fghij-runner       1\/1     Running   0          5s\narc-runner-set-klmno-runner       1\/1     Running   0          5s\narc-runner-set-pqrst-runner       1\/1     Running   0          5s\narc-runner-set-uvwxy-runner       1\/1     Running   0          5s<\/code><\/pre>\n<p>Une fois les jobs termin\u00e9s (60 secondes), tous les pods sont arr\u00eat\u00e9s. Le namespace revient \u00e0 z\u00e9ro pod.<\/p>\n<h3>\u00c9tape 3 : Configurer le d\u00e9lai de scale-down<\/h3>\n<p>Pour optimiser les co\u00fbts, vous pouvez souhaiter que les pods restent chauds pendant une courte p\u00e9riode apr\u00e8s la fin d&rsquo;un job. Cela \u00e9vite la latence de d\u00e9marrage \u00e0 froid pour les charges de travail en rafale. Le comportement scale-to-zero d&rsquo;ARC est l&rsquo;option par d\u00e9faut et la plus s\u00e9curis\u00e9e. Si vous avez besoin de runners chauds, gardez la fen\u00eatre courte (moins de 5 minutes) et assurez-vous que le mode \u00e9ph\u00e9m\u00e8re reste appliqu\u00e9.<\/p>\n<h2>Exercice 7 : Politiques r\u00e9seau pour les runners<\/h2>\n<p>Les NetworkPolicies de Kubernetes vous permettent de restreindre l&rsquo;acc\u00e8s r\u00e9seau des pods de runner. C&rsquo;est une d\u00e9fense essentielle contre l&rsquo;exfiltration de donn\u00e9es depuis des builds compromis.<\/p>\n<h3>\u00c9tape 1 : Cr\u00e9er une NetworkPolicy<\/h3>\n<p>Appliquez la NetworkPolicy suivante au namespace <code>arc-runners<\/code> :<\/p>\n<pre><code>apiVersion: networking.k8s.io\/v1\nkind: NetworkPolicy\nmetadata:\n  name: runner-egress-policy\n  namespace: arc-runners\nspec:\n  podSelector: {}\n  policyTypes:\n    - Egress\n  egress:\n    # Allow DNS resolution\n    - to:\n        - namespaceSelector: {}\n      ports:\n        - protocol: UDP\n          port: 53\n        - protocol: TCP\n          port: 53\n    # Allow GitHub API and Actions services\n    - to:\n        - ipBlock:\n            cidr: 140.82.112.0\/20\n        - ipBlock:\n            cidr: 143.55.64.0\/20\n        - ipBlock:\n            cidr: 185.199.108.0\/22\n        - ipBlock:\n            cidr: 4.0.0.0\/8\n      ports:\n        - protocol: TCP\n          port: 443\n    # Allow your container registry (example: ghcr.io)\n    - to:\n        - ipBlock:\n            cidr: 140.82.112.0\/20\n      ports:\n        - protocol: TCP\n          port: 443\n    # Allow your artifact storage (replace with your CIDR)\n    # - to:\n    #     - ipBlock:\n    #         cidr: 10.0.0.0\/8\n    #   ports:\n    #     - protocol: TCP\n    #       port: 443<\/code><\/pre>\n<pre><code>kubectl apply -f runner-network-policy.yaml<\/code><\/pre>\n<p><strong>Note :<\/strong> GitHub publie ses plages d&rsquo;adresses IP \u00e0 l&rsquo;adresse <a href=\"https:\/\/api.github.com\/meta\" target=\"_blank\" rel=\"noopener\">https:\/\/api.github.com\/meta<\/a>. Utilisez les plages <code>actions<\/code> et <code>api<\/code>. Les CIDR ci-dessus sont des exemples \u2014 v\u00e9rifiez les plages actuelles et mettez-les \u00e0 jour en cons\u00e9quence.<\/p>\n<h3>\u00c9tape 2 : Tester la NetworkPolicy<\/h3>\n<p>Cr\u00e9ez un workflow qui tente d&rsquo;atteindre une URL externe :<\/p>\n<pre><code>name: Network Policy Test\non: workflow_dispatch\n\njobs:\n  test-network:\n    runs-on: arc-runner-set\n    steps:\n      - name: Test GitHub API (should work)\n        run: curl -s -o \/dev\/null -w \"%{http_code}\" https:\/\/api.github.com\n\n      - name: Test external URL (should be blocked)\n        run: |\n          if curl -s --connect-timeout 5 https:\/\/evil-exfiltration-server.example.com; then\n            echo \"FAIL: External access was allowed\"\n            exit 1\n          else\n            echo \"PASS: External access was blocked by NetworkPolicy\"\n          fi<\/code><\/pre>\n<p>Lorsque vous ex\u00e9cutez ce workflow :<\/p>\n<ul>\n<li>La requ\u00eate vers l&rsquo;API GitHub r\u00e9ussit (HTTP 200) parce que la NetworkPolicy autorise le trafic vers les plages d&rsquo;adresses IP de GitHub.<\/li>\n<li>La requ\u00eate vers l&rsquo;URL externe expire et \u00e9choue parce qu&rsquo;elle ne figure pas dans la liste d&rsquo;egress autoris\u00e9e.<\/li>\n<\/ul>\n<p>Cela emp\u00eache un build compromis d&rsquo;exfiltrer du code source, des secrets ou des artefacts de build vers un serveur contr\u00f4l\u00e9 par un attaquant. M\u00eame si une d\u00e9pendance malveillante ex\u00e9cute du code arbitraire pendant le build, elle ne peut pas communiquer avec l&rsquo;ext\u00e9rieur.<\/p>\n<h2>Nettoyage<\/h2>\n<p>Supprimez toutes les ressources cr\u00e9\u00e9es au cours de ce lab :<\/p>\n<pre><code># Delete Helm releases\nhelm uninstall arc-runner-set -n arc-runners\nhelm uninstall arc-runner-pr -n arc-runners\nhelm uninstall arc-runner-deploy -n arc-runners\nhelm uninstall arc -n arc-systems\n\n# Delete namespaces\nkubectl delete namespace arc-runners\nkubectl delete namespace arc-systems\n\n# Delete the kind cluster\nkind delete cluster --name arc-lab<\/code><\/pre>\n<p>Si vous avez cr\u00e9\u00e9 une GitHub App pour ce lab, vous pouvez la supprimer depuis <strong>Settings \u2192 Developer settings \u2192 GitHub Apps<\/strong>. R\u00e9voquez tous les PAT que vous avez cr\u00e9\u00e9s.<\/p>\n<h2>Points cl\u00e9s \u00e0 retenir<\/h2>\n<ul>\n<li><strong>Les runners \u00e9ph\u00e9m\u00e8res \u00e9liminent la contamination entre jobs.<\/strong> Chaque job obtient un conteneur neuf \u2014 les secrets, les tokens et les artefacts de build sont d\u00e9truits une fois le job termin\u00e9.<\/li>\n<li><strong>ARC apporte les b\u00e9n\u00e9fices des runners self-hosted sans les risques de s\u00e9curit\u00e9.<\/strong> Vous b\u00e9n\u00e9ficiez d&rsquo;outils personnalis\u00e9s, d&rsquo;un acc\u00e8s \u00e0 un r\u00e9seau priv\u00e9 et de la ma\u00eetrise des co\u00fbts tout en conservant le mod\u00e8le de s\u00e9curit\u00e9 \u00e9ph\u00e9m\u00e8re.<\/li>\n<li><strong>Les images de runner personnalis\u00e9es vous donnent un contr\u00f4le total sur l&rsquo;environnement de build.<\/strong> \u00c9pinglez les versions des outils, analysez-les \u00e0 la recherche de vuln\u00e9rabilit\u00e9s et \u00e9liminez le risque li\u00e9 \u00e0 la cha\u00eene d&rsquo;approvisionnement des logiciels pr\u00e9install\u00e9s.<\/li>\n<li><strong>L&rsquo;isolation par groupe de runners impl\u00e9mente la s\u00e9paration des responsabilit\u00e9s.<\/strong> Les workflows de validation de PR et de d\u00e9ploiement s&rsquo;ex\u00e9cutent sur des ensembles de runners distincts, avec des privil\u00e8ges et des acc\u00e8s r\u00e9seau diff\u00e9rents.<\/li>\n<li><strong>Les politiques r\u00e9seau constituent une couche de d\u00e9fense essentielle.<\/strong> Restreindre l&rsquo;egress des runners emp\u00eache l&rsquo;exfiltration de donn\u00e9es m\u00eame si une \u00e9tape de build est compromise.<\/li>\n<li><strong>L&rsquo;autoscaling scale-to-zero r\u00e9duit le co\u00fbt et la surface d&rsquo;attaque.<\/strong> Les pods de runner n&rsquo;existent que pour la dur\u00e9e d&rsquo;un job \u2014 il n&rsquo;y a aucune infrastructure persistante \u00e0 maintenir ou \u00e0 s\u00e9curiser.<\/li>\n<\/ul>\n<h2>Prochaines \u00e9tapes<\/h2>\n<p>Continuez \u00e0 renforcer la posture de s\u00e9curit\u00e9 de votre CI\/CD avec ces guides connexes :<\/p>\n<ul>\n<li><a href=\"https:\/\/secure-pipelines.com\/fr\/github-actions\/securing-github-actions-runners\/\">S\u00e9curiser les runners GitHub Actions<\/a> \u2014 Plong\u00e9e approfondie dans les bonnes pratiques de s\u00e9curit\u00e9 des runners, la gestion des tokens et la surveillance, pour les runners h\u00e9berg\u00e9s par GitHub comme pour les runners self-hosted.<\/li>\n<li><a href=\"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/separation-of-duties-least-privilege-ci-cd-pipelines\/\">S\u00e9paration des responsabilit\u00e9s et moindre privil\u00e8ge dans les pipelines CI\/CD<\/a> \u2014 Guide complet pour appliquer les principes de moindre privil\u00e8ge \u00e0 l&rsquo;ensemble de votre pipeline CI\/CD, du contr\u00f4le de source au d\u00e9ploiement en production.<\/li>\n<\/ul>\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>Aper\u00e7u Les runners h\u00e9berg\u00e9s par GitHub sont partag\u00e9s et \u00e9ph\u00e9m\u00e8res par d\u00e9faut \u2014 chaque job obtient une machine virtuelle neuve qui est d\u00e9truite une fois le job termin\u00e9. Les runners self-hosted, en revanche, sont persistants et partag\u00e9s entre les ex\u00e9cutions de workflows. Cela cr\u00e9e un risque de s\u00e9curit\u00e9 important : les secrets, les tokens et &#8230; <a title=\"Lab : Runners \u00c9ph\u00e9m\u00e8res Self-Hosted avec Actions Runner Controller\" class=\"read-more\" href=\"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/lab-ephemeral-self-hosted-runners-actions-runner-controller\/\" aria-label=\"En savoir plus sur Lab : Runners \u00c9ph\u00e9m\u00e8res Self-Hosted avec Actions Runner Controller\">Lire la suite<\/a><\/p>\n","protected":false},"author":1,"featured_media":1234,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[49,52],"tags":[],"post_folder":[],"class_list":["post-543","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ci-cd-security","category-github-actions"],"_links":{"self":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts\/543","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/comments?post=543"}],"version-history":[{"count":4,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts\/543\/revisions"}],"predecessor-version":[{"id":1235,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts\/543\/revisions\/1235"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/media\/1234"}],"wp:attachment":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/media?parent=543"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/categories?post=543"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/tags?post=543"},{"taxonomy":"post_folder","embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/post_folder?post=543"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}