Environnements waza

Environnements waza : isoler des agents avec Go et Linux

Retour d'expérience GoAvancé

Environnements waza : isoler des agents avec Go et Linux

Un script Python malveillant a accédé à nos variables d’environnement de production. L’incident s’est produit dans l’un de nos Environnements waza lors d’un test automatisé.

Nous utilisions Go 1.22 pour orchestrer des agents autonomes. Le processus enfant, bien qu’isolé par un simple dossier, partageait encore le même espace de noms réseau et les mêmes descripteurs de fichiers que le parent. Une fuite de jetons AWS a été détectée 14 minutes après le lancement du conteneur de test.

Cet article détaille comment nous avons reconstruit notre système d’isolation. Vous apprendrez à manipuler les primitives Linux via Go pour créer de véritables Environnements waza hermétiques.

Environnements waza

🛠️ Prérequis

Pour reproduire ces mécanismes, une machine Linux est indispensable.

  • Linux Kernel 5.15 ou supérieur (pour le support complet de Cgroups v2).
  • Go 1.22+.
  • Privilèges root ou capacités CAP_SYS_ADMIN pour l’utilisation de unshare.
  • Installation des outils de debug : strace, bpftrace.

📚 Comprendre Environnements waza

L’isolation de processus repose sur deux piliers du noyau Linux : les Namespaces et les Cgroups. Les Namespaces (espaces de noms) permettent de masquer les ressources du système (PID, Network, Mount, UTS, IPC, User). Les Cgroups (Control Groups) limitent l’utilisation des ressources physiques (CPU, RAM, I/O).

Dans nos Environnements waza, nous ne nous contentons pas de chroot. Le chroot est une illusion de sécurité insuffisante. Un processus root dans un chroot peut s’échapper en manipissant les descripendant de fichiers. Nous utilisons donc la primitive unshare(2).

  Structure simplifiée de l'isolation :
  [ Host Process (Root) ]
         |
         |-- [ syscall.Unshare(CLONE_NEWNS | CLONE_NEWPID | ...) ]
         |           |
         |           |-- [ Child Process (Sandboxed) ]
         |               |-- [ Isolated Mount Namespace ]
         |               |-- [ Isolated PID Namespace ]
         |               |-- [ Limited Cgroups ]

En Go, l’utilisation de ces primitives est complexe. Le scheduler de Go déplace les goroutines entre les threads système (M). Or, les appels unshare affectent le thread appelant. Si une goroutine effectue un unshare, seule cette thread est isolée. Les autres threads du même processus partagent toujours l’espace de noms original. Cela nécessite l’utilisation de runtime.LockOSThread() pour garantir la stabilité de l’isolation.

🐹 Le code — Environnements waza

Go
package main

import (
	"fmt"
	"os"
	"os/exec"
	"syscall"
	"runtime"
)

// IsolerProcess lance une commande dans un espace de noms restreint.
func IsolerProcess(command string, args []string) error {
	// On verrouille la goroutine sur le thread OS actuel.
	// Indispensable car l'unshare modifie l'état du thread.
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	cmd := exec.Command(command, args...)

	// Configuration des flags de clonage pour les <strong>Environnements waza</strong>.
	// CLONE_NEWUTS : Isolation du nom d'hôte.
	// CLONE_NEWPID : Le processus devient le PID 1 du nouveau namespace.
	// CLONE_NEWNS : Isolation des points de montage.
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUTS | 
				syscall.CLONE_NEWPID | 
				syscall.CLONE_NEWNS,
	}

	// On vide l'environnement pour éviter les fuites de secrets.
	cmd.Env = []string{"PATH=/usr/bin:/bin", "USER=sandbox"}

	// On redirige la sortie pour le debug.
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	return cmd.Run()
}

📖 Explication

Dans le premier snippet, l’utilisation de runtime.LockOSThread() est la clé. Sans cela, le Go scheduler peut déplacer l’exécution de la fonction IsolerProcess sur un autre thread M après l’appel à unshare, rendant l’isolation inopérante pour les opérations suivantes.

Le champ Cloneflags utilise des constantes du package syscall. CLONE_NEWNS est crucial pour isoler les points de montage. Si vous oubliez ce flag, l’agent peut monter de nouveaux systèmes de fichiers sur des répertoires sensibles de l’hôte.

L’assignation de cmd.Env à une liste restreinte est une défense en profondeur. Ne jamais faire cmd.Env = os.Environ(). C’est l’erreur qui a causé notre fuite de données. La doc officielle de Go précise que l’environnement est hérité par défaut.

Documentation officielle Go

🔄 Second exemple

Go
package main

import (
	"fmt"
	"syscall"
)

// VerifierIsolation vérifie si le PID actuel est bien dans un namespace isolé.
func VerifierIsolation() error {
	// On lit le contenu de /proc/self/status pour vérifier le namespace PID.
	data, err := os.ReadFile("/proc/self/status")
	if err != nil {
		return err
	}

	// On cherche la ligne 'NSpid' qui liste les namespaces PID.
	// Dans un namespace isolé, il ne devrait y avoir qu'une seule ligne.
	fmt.Println(string(data))
	return nil
}

▶️ Exemple d’utilisation

Pour tester notre isolation, lancez le code suivant. Il tente de lancer un shell dans un namespace restreint.

package main

import (
	"fmt"
	"os"
	"os/exec"
	"syscall"
	"runtime"
)

func main() {
	// On simule la création d'un environnement waza
	runtime.LockOSThread()

	cmd := exec.Command("/bin/sh")
	cmd.SysProcAttr = &syscall.Sysoptr{
		Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID,
	}
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	fmt.Println("Lancement de l'agent dans un Environnement waza...")
	err := cmd.Run()
	if err != nil {
		fmt.Printf("Erreur: %v\n", err)
	}
}\

Sortie attendue (si vous vérifiez le hostname dans le shell lancé) :

🚀 Cas d'usage avancés

1. Exécution de plugins tiers : Utiliser les Environnements waza pour charger des modules .so ou .py sans risquer l'accès au filesystem hôte. cmd.SysProcAttr = &syscall.SysProcAttr{Cloneflags: syscall.CLONE_NEWNS}.

2. Analyse de malware : Isoler un processus réseau pour observer ses communications sans qu'il puisse scanner le réseau local. Cloneflags: syscall.CLONE_NEWNET.

3. Limitation de ressources (Quotas) : Combiner l'isolation avec l'écriture dans /sys/fs/cgroup/ pour limiter la consommation CPU. Cela empêche un agent de monopoliser les cycles processeur de l'orchestrateur.

✅ Bonnes pratiques

Pour garantir la sécurité des Environnements waza, suivez ces règles :

  • Principe du moindre privilège : Ne lancez jamais l'agent avec les privilèges root. Utilisez setuid pour descendre les privilèges dans le SysProcAttr.
  • Immuabilité : Le système de fichiers de l'agent doit être en lecture seule. Utilisez MS_RDONLY lors du montage des volumes.
  • Surveillance des ressources : Implémentez toujours une limite de mémoire via Cgroups. Un agent sans limite est une bombe à retardement pour l'OOM Killer.
  • Audit des syscalls: Utilisez seccomp pour interdire les appels système dangereux comme mount ou ptrace.
  • Nettoyage: Assurez-vous que les descripteurs de fichiers (FD) sont fermés avant le fork pour éviter que l'agent n'hérite d'un socket de base de données.
Points clés

  • L'isolation par namespace est indispensable pour les agents tiers.
  • Le Go scheduler nécessite runtime.LockOSThread pour les syscalls d'isolation.
  • Le CLONE_NEWPID empêche la visibilité des processus hôtes.
  • L'héritage de l'environnement est le vecteur de fuite de secrets n°1.
  • Le Cgroups v2 est nécessaire pour prévenir l'épuisement de la RAM.
  • L'utilisation de chroot seul est une faille de sécurité majeure.
  • Le nettoyage des variables d'environnement doit être explicite.
  • L'isolation réseau via CLONE_NEWNET est vitale pour les agents suspects.

❓ Questions fréquentes

Est-ce que l'isolation est suffisante pour du code malveillant ?

Non, seuls les Environnements waza avec Seccomp et Cgroups offrent une protection sérieuse. L'isolation de namespace ne bloque pas l'exploitation de failles kernel.

Pourquoi utiliser Go plutôt que Python pour l'orchestrateur ?

Go offre un contrôle précis sur les primitives système et une gestion efficace de la concurrence pour gérer des milliers d'agents simultanément.

Comment limiter l'accès au disque ?

Utilisez le namespace Mount (CLONE_NEWNS) et montez un système de fichiers temporaire (tmpfs) en lecture seule.

L'utilisation de containers Docker est-elle une alternative ?

Docker utilise les mêmes primitives. Créer vos propres Environnements waza est utile pour des besoins de très faible latence ou des architectures ultra-légères.

📚 Sur le même blog

🔗 Le même sujet sur nos autres blogs

📝 Conclusion

La création d'Environnements waza demande une compréhension profonde du noyau Linux et du runtime Go. L'isolation simple ne suffit pas ; il faut une stratégie de défense en profondeur combinant Namespaces, Cgroups et Seccomp. Pour approfondir la gestion des processus en Go, consultez la documentation Go officielle. Un processus mal configuré est une porte ouverte sur votre infrastructure.

Publications similaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *