Notes techniques VMArm

Isoler les pull requests externes dans une CI Mac cloud

Isoler les pull requests externes dans une CI Mac cloud

Dès qu’une équipe autorise des contributeurs externes à soumettre des pull requests, la CI Mac dans le cloud ne doit plus seulement déterminer si « le code compile ». Les scripts de build constituent eux aussi des entrées à contrôler : ils peuvent lire les variables d’environnement, parcourir les répertoires utilisateur, modifier des caches partagés, voire remplacer des outils qui seront utilisés par des tâches ultérieures. L’objectif de sécurité doit donc être explicite : une PR externe peut servir à valider la compilation et les tests, mais elle ne doit ni accéder aux identifiants de publication ni envoyer directement ses artefacts vers la version de production.

Séparer d’abord les deux circuits de confiance

Répartissez les tâches entre un circuit de « validation sans secret » et un circuit de « publication de confiance ». Le premier accepte les PR externes et se limite à la résolution des dépendances, à la compilation, à l’analyse statique et aux tests ne nécessitant aucun identifiant réel. Le second n’accepte que des commits immuables déjà relus et fusionnés, puis prend en charge la signature, l’archivage et la livraison.

Sur les nœuds physiques dédiés de VMArm, ces deux circuits peuvent utiliser des comptes système macOS distincts. Le compte de validation ne doit pas appartenir au groupe des administrateurs, conserver des certificats de publication ni pouvoir lire le répertoire personnel du compte de publication. De son côté, le compte de publication ne doit réutiliser aucun script, cache ou répertoire d’outils accessible en écriture au compte de validation.

L’isolation par comptes système n’offre pas la même frontière qu’une machine virtuelle. Elle réduit fortement le risque qu’un script ordinaire lise accidentellement des identifiants ou altère des fichiers, mais elle ne remplace ni les mises à jour du système, ni le principe du moindre privilège, ni la revue manuelle du code non fiable.

Voici les frontières recommandées :

Élément Validation sans secret Publication de confiance
Origine du code Commit immuable d’une PR externe Commit immuable d’une branche relue
Signature du code Désactivée Activée selon le processus de publication
Espace de travail Recréé à chaque exécution Répertoire indépendant
Cache Jetable, jamais partagé entre domaines de confiance Partagé uniquement entre tâches de confiance
Artefacts de build Réservés aux contrôles Livrés après une nouvelle compilation
Identifiants Non injectés Injectés brièvement et uniquement si nécessaire

Préparer un espace de travail au moindre privilège

Ne laissez pas le runner compiler durablement à la racine du répertoire personnel d’un utilisateur. Créez un répertoire racine réservé aux tâches de validation et interdisez sa lecture aux autres utilisateurs locaux. Au début de chaque tâche, utilisez un répertoire temporaire, puis supprimez-le à la fin, que l’exécution réussisse ou échoue.

set -euo pipefail

umask 077
RUN_ROOT="$HOME/ci-untrusted"
mkdir -p "$RUN_ROOT"
chmod 700 "$RUN_ROOT"

WORK_DIR="$(mktemp -d "$RUN_ROOT/job.XXXXXX")"
cleanup() {
  chmod -R u+rwX "$WORK_DIR" 2>/dev/null || true
  rm -rf "$WORK_DIR"
}
trap cleanup EXIT INT TERM

cd "$WORK_DIR"
printf 'user=%s\nworkspace=%s\n' "$(id -un)" "$WORK_DIR"

Avant d’exécuter rm -rf, vérifiez impérativement que le chemin provient du répertoire temporaire créé pour la tâche en cours. Il ne faut jamais construire directement ce chemin à partir du nom de la branche. Celui-ci peut contenir des barres obliques, des espaces ou des caractères spécialement conçus à cet effet : il convient à l’affichage, mais pas à une utilisation non filtrée dans un chemin de fichier.

Après avoir récupéré le code source, consignez le hash du commit et affichez-le dans les journaux. Le circuit de confiance devra ensuite récupérer à nouveau le code à partir de cet identifiant immuable, sans dépendre uniquement d’un nom de branche susceptible de continuer à évoluer.

Prouver l’absence de secrets avant le build

« Ne pas utiliser activement de secrets » ne signifie pas que la tâche est incapable de les lire. Le service du runner peut hériter de son environnement de démarrage, tandis que les fichiers d’initialisation du Shell peuvent ajouter des jetons. Avant d’appeler les scripts du projet, contrôlez le nom des variables et ne transmettez à la commande de build que celles qui sont indispensables.

blocked='TOKEN|SECRET|PASSWORD|PRIVATE|SIGNING|AUTH|CREDENTIAL'
if env | cut -d= -f1 | grep -Eiq "$blocked"; then
  printf '%s\n' "blocked environment variable detected" >&2
  exit 70
fi

env -i \
  HOME="$HOME" \
  PATH="/usr/bin:/bin:/usr/sbin:/sbin" \
  TMPDIR="$WORK_DIR/tmp" \
  xcodebuild \
    -workspace App.xcworkspace \
    -scheme App \
    -configuration Debug \
    -derivedDataPath "$WORK_DIR/DerivedData" \
    CODE_SIGNING_ALLOWED=NO \
    build

Si le projet dépend d’un gestionnaire de paquets ou d’outils personnalisés, ajoutez explicitement à PATH les répertoires d’outils préalablement vérifiés, au lieu d’hériter de l’intégralité de l’environnement de connexion. Le contrôle des noms de variables n’est qu’un premier filtre. Des valeurs sensibles peuvent également se trouver dans des fichiers de configuration, le trousseau, les paramètres de proxy et les gestionnaires d’identifiants Git. Le compte de validation doit donc être vierge dès sa création, plutôt que de supprimer temporairement quelques variables avant chaque build.

Gérer les dépendances et les caches

Du code externe peut écrire du contenu dans un cache, qui risque ensuite de contaminer une tâche de confiance utilisant ce même cache. La solution la plus sûre consiste à séparer les caches par domaine de confiance. Pour les PR externes, segmentez-les en plus par dépôt ou par numéro de tâche. Le circuit de validation ne doit jamais écrire dans les caches SwiftPM, les caches de modules compilés ou les caches d’outils binaires personnalisés du compte de publication.

Après la restauration d’un cache, contrôlez son propriétaire et ses permissions, et refusez les liens symboliques pointant hors de l’espace de travail. Les outils exécutables doivent de préférence provenir d’un répertoire de référence en lecture seule. Les scripts générés par le projet pendant le build ne doivent s’exécuter que dans l’espace de travail temporaire de la tâche en cours.

Empêcher les artefacts de franchir la frontière de confiance

La compilation réussie d’une PR externe prouve seulement que le commit satisfait aux conditions de validation. Elle ne garantit pas que l’archive produite peut être publiée en toute confiance. Une tâche de validation peut remplacer des étapes du build, modifier les sorties ou ajouter des fichiers à l’archive. Les paquets d’application, archives et répertoires d’exportation créés dans le circuit sans secret ne doivent donc jamais être transmis à la tâche de publication de confiance.

Le circuit de confiance doit suivre les étapes suivantes :

  1. Recevoir le hash du commit confirmé par le système de revue.
  2. Récupérer à nouveau ce commit dans un espace de travail neuf appartenant au compte de publication.
  3. Vérifier que le commit courant correspond exactement à la valeur approuvée.
  4. Résoudre à nouveau les dépendances depuis un cache de confiance ou un cache vide.
  5. Exécuter les tests, puis recréer l’archive à partir du code source.
  6. Fournir les identifiants uniquement pendant la courte période où la signature les exige.
  7. Consigner le hash du commit, l’empreinte du fichier de verrouillage des dépendances et l’empreinte de l’artefact final.

S’il est indispensable de transmettre des rapports de test depuis le circuit de validation, ne transférez que des données non exécutables et analysez-les selon un format strict du côté de confiance. Ne transmettez ni scripts Shell, ni plugins, ni caches de compilation, ni fichiers pouvant être directement intégrés au paquet de l’application.

Intégrer le nettoyage après échec et l’audit aux critères de validation

Les mécanismes d’isolation échouent le plus souvent dans les scénarios d’erreur. Lorsqu’un build est annulé, qu’un processus dépasse son délai ou que la machine redémarre, des répertoires temporaires, des processus en arrière-plan et des journaux peuvent subsister. Avant chaque tâche, le runner doit rechercher les résidus de l’exécution précédente et, lors de la phase finale, arrêter les processus enfants appartenant à la tâche en cours.

Avant la mise en production, utilisez la liste suivante pour valider la configuration :

Terminez par un exercice actif : soumettez une PR de test dont le script de build tente de lire des variables sensibles courantes, d’accéder au répertoire de publication et d’écrire un fichier dans un cache partagé. Le résultat attendu n’est pas que le script « ne trouve rien », mais que ces emplacements soient inaccessibles par conception, tant au niveau des permissions que du processus. Ce n’est qu’après cette vérification que la validation des contributions externes est réellement découplée de la publication en production.

Questions fréquentes

Désactiver la signature suffit-il pour une pull request externe ?

Non. Il faut aussi supprimer les variables secrètes, séparer les comptes et les caches inscriptibles, limiter les droits et interdire l’accès au circuit de publication.

Peut-on publier l’archive produite par la tâche non fiable ?

Non. Le circuit de confiance doit récupérer l’identifiant immuable du commit approuvé et reconstruire l’application depuis les sources.

Nœud Mac physique dédié

Choisissez un Mac dans le cloud toujours disponible pour votre prochaine compilation Xcode

Vérifiez la puce, la mémoire, le stockage, la période de facturation et la région du nœud, puis lancez la configuration. La disponibilité réelle est indiquée en temps réel par la console.

Choisir une configuration de Mac dans le cloud