Isolation K8S : Le CoCo

Heeeello, aujourd’hui, on va deep dive sur un nouveau sujet qui est en relation avec mon précédent article sur les micro vm; les confidential containers ou le CoCo pour les intimes. De manière générale, le confidential computing est un sujet particulièrement hype en ce moment notamment chez les CSP. Les acronymes s’enchainent BYOK (Bring Your Own Key), HYOK (Hold Your Own Key), etc… pour répondre aux enjeux de sécurité liés à la protection des données dans un environnement non maitrisé notamment dans un contexte de multi tenancy.
Les confidential containers sont donc la réponse de la communauté à ces enjeux pour apporter des garanties dans les environnements cloud-native.

Mais une question se pose, est-ce que je peux vraiment me protéger contre mon administrateur et par extension de cluster-admin ?

Et pour répondre à cette question, je vous propose de mettre directement les mains dans le cambouis. Je n’ai pas de machine supportant Intel TDX ou AMD SEV sous la main (mes vieux HP gen 8 là…). L’objectif ne sera donc pas de s’intéresser directement aux mécanismes propres offerts par ces vendors mais surtout regarder l’implémentation autour de ces technologies et voir jusqu’où va réellement la frontière de confiance.
PS : J’ai arrêté de faire le fainéant car ça fait plus d’un an que je dois sortir l’article et je dois dire qu’il n’y a pas grand chose qui a bougé.

Définitions

Les confidential containers ne sont rien d’autre que des confidential VM. Dans Kubernetes, c’est une micro VM qui est exécutée à l’intérieur d’un TEE dans lequel les données sont protégés de l’hyperviseur et de l’hôte. (Zero Trust on t’a dit. . . )

Les confidential containers reposent sur plusieurs technos :

  • Kata Containers
  • AMD SEV ou Intel TDX

“Confidential Containers mainly relies on VM enclaves, where the guest does not trust the
host. Confidential computing, and by extension Confidential Containers, provides technical
assurances that the untrusted host cannot access guest data or manipulate guest control flow.”

Modèle de confiance

Tout ce qui est au sein de la VM est trusted :

  • le noyau du SE invité
  • le système de fichiers de l’invité
  • etc. . .

Tout ce qui est en dehors de la VM est untrusted :

  • le shim
  • l’hyperviseur
  • le kubelet
  • le control plane Kubernetes
  • etc. . .

Un « Trusted Execution Environment » (TEE) aide à protéger le code et les données exécutés par l’utilisateur contre toute modification par des logiciels, du matériel et des composants système non approuvés en dehors des limites du TEE. C’est une région ou une enclave protégé par le processeur.

https://confidentialcontainers.org/docs/architecture/trust-model/trust-model

Architecture

Et bien, vous avez l’oeil, le CoCo se base sur le projet Kata donc l’architecture initiale ne change que très peu sauf que la machine virtuelle s’exécutera dans un TEE et sa mémoire ne sera plus accessible par le VM.
Par exemple, un dump mémoire du processus QEMU n’aboutira pas.

Qu’est ce qui pourrait mal se passer ?

Confiance implicite

Lorsqu’une micro VM est créé, le kata-agent reçoit des informations du shim pour exécuter des conteneurs et gérer leur configuration.
De plus, l’administrateur peut aussi pousser des configurations particulières au kata-agent. Dans le modèle de menace, on constate que le kata-agent fait implicitement confiance au shim.

Exécution de commandes dans les pods

Dans le contexte du CoCo, le privilège exec dans les pods est risqué car il permet d’exécuter des commandes arbitraires au sein des pods et par défaut cluster-admin en est capable. Depuis, le control plane, on peut appeler :

kubectl exec -it <microvm> -- bash

La surprise du chef (la ref incroyable)

Après un petit tour dans le repo de Kata, je suis tombé sur un guide développeur qui fournissait des éléments pour passer le kata-agent en mode debug : Set up a debug console

Voici ce qu’il y a écrit :

Kata Containers 2.0 supports a shell simulated console for quick debug purpose. This approach uses VSOCK to connect to the shell running inside the guest which the agent starts. This method only requires the guest image to contain either /bin/sh or /bin/bash.

3 points important à savoir :

  • Le paramètre debug_console_enabled permet à l’utilisateur de se connecter au guest s’exécutant sur l’hyperviseur
  • S’échapper en tant que root d’un pod vers le noeud permet de manipuler la configuration du kata-agent dans /opt/kata/share/defaults/kata-containers/${hypervisor}.toml
  • /bin/bash en exécution est aussi le processus fils du kata-agent

Pour l’activer, il faut donc modifier le fichier configuration-qemu-tdx.toml, de mon côté j’ai modifié le fichier configuration-qemu-coco-dev.toml avec le paramètre suivant :

[agent.kata]
debug_console_enabled = true

Si un pod basé sur la runtimeClassName kata-qemu-coco-dev, il est nécessaire de le redéployer pour que le kata-agent puisse démarrer un shell dans le guest. Il est possible de retrouver le socket dans /var/run/vc/vm/<sandbox id>/console.sock.

Pour maintenant se connecter à ce socket, il est possible d’utiliser le binaire kata-runtime présent dans /opt/kata/bin en lui passant en argument le sandbox id.

./kata-runtime exec 85cf9b8c1ab373551c45ad0fa08662adcb82a86ed96cf2a38f161e557e76912d

Cette commande nous fait tomber directement dans le mount namespace et le PID namespace de la machine virtuelle dont le rootfs est en RO et non dans le conteneur accédé par le kube-apiserver.
Pour écrire dans le filesystem du conteneur, il faut trouver son overlay avec la commande mount pour trouver le rootfs du conteneur qui est en RW.

mount | grep overlay

Après avoir identifié l’overlay, on peut écrire dans le rootfs du conteneur et voilà.

La commande nsenter est disponible dans l’image par défaut offert par Kata, et bien, on ne va pas se gêner pour exécuter des commandes dans les namespaces du processus de l’environnement d’exécution (conteneur).
On identifie le processus avec un ps auxf et on tombe sur /usr/bin/kata qui a un processus fils qui exécute /pause, je ne sais pas pour vous mais pour moi ça sent le conteneur.

Le PID 194 a l’air d’être un bon candidat donc on va entrer dans tous les namespaces du processus avec l’option -a. Si vous ne connaissez pas nsenter, voici la doc : https://man7.org/linux/man-pages/man1/nsenter.1.html

On retombe bien dans le conteneur ubuntu-kata vu du kube-apiserver avec la commande kubectl exec et voilà.

Scénario d’attaque

Ton explication est longue là, on en fait quoi ?
En effet, ça permet de construire un sérieux scénario d’attaque vis à vis du modèle notamment en mélangeant deux workloads d’exposition et sensibilité différentes sur le même noeud.
Dans le cas où un attaquant escape un conteneur privilégié en tant que root, il aurait les privilèges de modifier le fichier configuration-qemu-tdx.toml et initier un redéploiement du conteneur pour accéder à la console de debug en passant par le socket VSOCK. Une fois dans le guest, l’attaquant peut réaliser des actions au sein du confidential container notamment accéder à sa mémoire.

J’en ai pas encore parlé mais il existe justement un moyen de limiter certaines actions avec le kata-agent.
Le kata-agent peut recevoir des stratégies de sécurité OPA basées sur Rego qui permettent notamment de limiter l’exec dans les pods indépendamment des privilèges de l’admin kube ou du SA. Et ils sont très sympas car ils fournissent un jeu de stratégies déjà construits pour tester. Je mets une partie du code du fichier rego avec la fonction ExecProcessRequest() qui nous permettra de bloquer ces accès shell.

allow-all-except-exec-process.rego
package agent_policy
default PullImageRequest := true
...
default SignalProcessRequest := true
default StartTracingRequest := true
default ExecProcessRequest := false

Pour embarquer la policy rego dans un objet Pod, il faut l’encoder en base64 puis la passer en valeur à une annotation io.katacontainers.config.agent.policy.

rego.sh
$ wget https://raw.githubusercontent.com/kata-containers/kata-containers/refs/heads/main/src/kata-opa/allow-all-except-exec-process.rego
$ base64 -w0 allow-all-except-exec-process.rego
IyBDb3B5cmlnaHQgKGMpIDIwMjMgTWljcm9zb2Z0IENvcnBvcmF0aW9uCiMKIyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMAojCgpwYWNrYWdlIGFnZW50X3BvbGljeQoKZGVmYXVsdCBBZGRBUlBOZWlnaGJvcnNSZXF1ZXN0IDo9IHRydWUKZGVmYXVsdCBBZGRTd2FwUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgQ2xvc2VTdGRpblJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IENvcHlGaWxlUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgQ3JlYXRlQ29udGFpbmVyUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgQ3JlYXRlU2FuZGJveFJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IERlc3Ryb3lTYW5kYm94UmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgR2V0TWV0cmljc1JlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IEdldE9PTUV2ZW50UmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgR3Vlc3REZXRhaWxzUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgTGlzdEludGVyZmFjZXNSZXF1ZXN0IDo9IHRydWUKZGVmYXVsdCBMaXN0Um91dGVzUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgTWVtQWdlbnRDb21wYWN0Q29uZmlnIDo9IHRydWUKZGVmYXVsdCBNZW1BZ2VudE1lbWNnQ29uZmlnIDo9IHRydWUKZGVmYXVsdCBNZW1Ib3RwbHVnQnlQcm9iZVJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IE9ubGluZUNQVU1lbVJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IFBhdXNlQ29udGFpbmVyUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgUHVsbEltYWdlUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgUmVhZFN0cmVhbVJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IFJlbW92ZUNvbnRhaW5lclJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IFJlbW92ZVN0YWxlVmlydGlvZnNTaGFyZU1vdW50c1JlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IFJlc2VlZFJhbmRvbURldlJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IFJlc3VtZUNvbnRhaW5lclJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IFNldEd1ZXN0RGF0ZVRpbWVSZXF1ZXN0IDo9IHRydWUKZGVmYXVsdCBTZXRQb2xpY3lSZXF1ZXN0IDo9IHRydWUKZGVmYXVsdCBTaWduYWxQcm9jZXNzUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgU3RhcnRDb250YWluZXJSZXF1ZXN0IDo9IHRydWUKZGVmYXVsdCBTdGFydFRyYWNpbmdSZXF1ZXN0IDo9IHRydWUKZGVmYXVsdCBTdGF0c0NvbnRhaW5lclJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IFN0b3BUcmFjaW5nUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgVHR5V2luUmVzaXplUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgVXBkYXRlQ29udGFpbmVyUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgVXBkYXRlRXBoZW1lcmFsTW91bnRzUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgVXBkYXRlSW50ZXJmYWNlUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgVXBkYXRlUm91dGVzUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgV2FpdFByb2Nlc3NSZXF1ZXN0IDo9IHRydWUKZGVmYXVsdCBXcml0ZVN0cmVhbVJlcXVlc3QgOj0gdHJ1ZQoKZGVmYXVsdCBFeGVjUHJvY2Vzc1JlcXVlc3QgOj0gZmFsc2UK

Il faut ensuite créer notre fichier yaml avec cette policy encodé en base64.

rego-ubuntu.yaml
apiVersion: v1
kind: Pod
metadata:
name: rego-ubuntu
annotations:
io.katacontainers.config.agent.policy: IyBDb3B5cmlnaHQgKGMpIDIwMjMgTWljcm9zb2Z0IENvcnBvcmF0aW9uCiMKIyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMAojCgpwYWNrYWdlIGFnZW50X3BvbGljeQoKZGVmYXVsdCBBZGRBUlBOZWlnaGJvcnNSZXF1ZXN0IDo9IHRydWUKZGVmYXVsdCBBZGRTd2FwUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgQ2xvc2VTdGRpblJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IENvcHlGaWxlUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgQ3JlYXRlQ29udGFpbmVyUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgQ3JlYXRlU2FuZGJveFJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IERlc3Ryb3lTYW5kYm94UmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgR2V0TWV0cmljc1JlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IEdldE9PTUV2ZW50UmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgR3Vlc3REZXRhaWxzUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgTGlzdEludGVyZmFjZXNSZXF1ZXN0IDo9IHRydWUKZGVmYXVsdCBMaXN0Um91dGVzUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgTWVtQWdlbnRDb21wYWN0Q29uZmlnIDo9IHRydWUKZGVmYXVsdCBNZW1BZ2VudE1lbWNnQ29uZmlnIDo9IHRydWUKZGVmYXVsdCBNZW1Ib3RwbHVnQnlQcm9iZVJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IE9ubGluZUNQVU1lbVJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IFBhdXNlQ29udGFpbmVyUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgUHVsbEltYWdlUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgUmVhZFN0cmVhbVJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IFJlbW92ZUNvbnRhaW5lclJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IFJlbW92ZVN0YWxlVmlydGlvZnNTaGFyZU1vdW50c1JlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IFJlc2VlZFJhbmRvbURldlJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IFJlc3VtZUNvbnRhaW5lclJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IFNldEd1ZXN0RGF0ZVRpbWVSZXF1ZXN0IDo9IHRydWUKZGVmYXVsdCBTZXRQb2xpY3lSZXF1ZXN0IDo9IHRydWUKZGVmYXVsdCBTaWduYWxQcm9jZXNzUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgU3RhcnRDb250YWluZXJSZXF1ZXN0IDo9IHRydWUKZGVmYXVsdCBTdGFydFRyYWNpbmdSZXF1ZXN0IDo9IHRydWUKZGVmYXVsdCBTdGF0c0NvbnRhaW5lclJlcXVlc3QgOj0gdHJ1ZQpkZWZhdWx0IFN0b3BUcmFjaW5nUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgVHR5V2luUmVzaXplUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgVXBkYXRlQ29udGFpbmVyUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgVXBkYXRlRXBoZW1lcmFsTW91bnRzUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgVXBkYXRlSW50ZXJmYWNlUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgVXBkYXRlUm91dGVzUmVxdWVzdCA6PSB0cnVlCmRlZmF1bHQgV2FpdFByb2Nlc3NSZXF1ZXN0IDo9IHRydWUKZGVmYXVsdCBXcml0ZVN0cmVhbVJlcXVlc3QgOj0gdHJ1ZQoKZGVmYXVsdCBFeGVjUHJvY2Vzc1JlcXVlc3QgOj0gZmFsc2UK
spec:
runtimeClassName: kata-qemu-coco-dev
containers:
- name: ubuntu
image: ubuntu

Après déploiement du pod rego-ubuntu il est impossible d’y accéder en réalisant un ExecProcess.

Cette feature est bien mais cela peut provoquer un faux sentiment de sécurité car le modèle de menace reste l’administrateur et par extension cluster-admin. S’il n’y a pas de remote attestation cette valeur peut être modifié en utilisant les verbes PATCH ou UPDATE voire une possibilité de complètement supprimer l’annotation tout en forçant le redémarrage du pod. Par ailleurs, c’est regrettable que la policy par défaut soit aussi permissive et permette l’exécution de requêtes ExecProcess.
Aussi, ça ne bloque pas le mode debug de kata, car oui, je me suis amusé à me reconnecter sur la console de debug en utilisant le binaire kata-runtime pour m’assurer que ça bloquait eh bah non… Si à un moment vous étiez comme moi, bienvenue au club, je leur ai même demandé directement à un moment comme j’étais dans une impasse après m’être fait tout le code de la fonction de debug: https://github.com/confidential-containers/confidentialcontainers.org/issues/122

Comment fonctionne le mode debug ?

Vous trouverez que ce n’est pas important mais j’ai passé énormément de temps à essayer de comprendre le fonctionnement car il y a 2 points d’entrées dont un (mode debug) que je n’arrivais pas à bloquer. Et bien, il s’avèrait que les stratégies Rego ne bloquent que les messages ttRPC présents dans le code de Containerd : agent.proto
Les requêtes ttRPC commence par rpc suivi de la fonction, exemple rpc CreateContainer().

agent.proto
package grpc;
import "oci.proto";
import "csi.proto";
import "types.proto";
service AgentService {
rpc CreateContainer(CreateContainerRequest) returns (google.protobuf.Empty);
rpc StartContainer(StartContainerRequest) returns (google.protobuf.Empty);
rpc RemoveContainer(RemoveContainerRequest) returns (google.protobuf.Empty);
rpc ExecProcess(ExecProcessRequest) returns (google.protobuf.Empty);
rpc SignalProcess(SignalProcessRequest) returns (google.protobuf.Empty);
rpc WaitProcess(WaitProcessRequest) returns (WaitProcessResponse); // wait & reap like waitpid(2)
rpc UpdateContainer(UpdateContainerRequest) returns (google.protobuf.Empty);
rpc UpdateEphemeralMounts(UpdateEphemeralMountsRequest) returns (google.protobuf.Empty);
rpc StatsContainer(StatsContainerRequest) returns (StatsContainerResponse);
rpc PauseContainer(PauseContainerRequest) returns (google.protobuf.Empty);
rpc ResumeContainer(ResumeContainerRequest) returns (google.protobuf.Empty);
}

De manière générale, la fonction de debug a été écrit en Rust et se lit assez bien je trouve, elle se trouve ici : console.rs.
La fonction run_debug_console_vsock() ne se base pas jamais sur ttRPC mais uniquement sur un socket VSOCK et appelle ensuite un pseudo-terminal.

async fn run_debug_console_vsock<T: AsyncRead + AsyncWrite>(
logger: Logger,
shell: String,
stream: T,
) -> Result<()> {
let logger = logger.new(o!("subsystem" => "debug-console-shell"));
let pseudo = openpty(None, None)?;
let slave_fd = pseudo.slave.as_fd().as_raw_fd();
let _ = fcntl::fcntl(pseudo.master.as_fd(), FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC));
let _ = fcntl::fcntl(pseudo.slave.as_fd(), FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC));

La fonction debug_console_handler() se base sur deux interpréteurs de commandes qui sont définis dans la structure SHELLS.

lazy_static! {
static ref SHELLS: Arc<SyncMutex<Vec<String>>> = {
let mut v = Vec::new();
if !cfg!(test) {
// Ahead of the built-ins: some bases are shell-less, and where a
// shell does exist the devkit one is the richer choice.
v.push(DEVKIT_DEBUG_CONSOLE_SHELL.to_string());
v.push("/bin/bash".to_string());
v.push("/bin/sh".to_string());
}
Arc::new(SyncMutex::new(v))
};
}

La fonction debug_console_handler() ouvre un socket stream dans la machine virtuelle créée par QEMU via socket(AF_VSOCK, SOCK_STREAM, 0) et récupère le CID (on peut le retrouver avec la commande ps par exemple, id=vsock-3358306640,guest-cid=3358306640).
La fonction recherche ensuite si l’un des shells est présent et se met en écoute.

pub async fn debug_console_handler(
logger: Logg
er,
port: u32,
mut shutdown: Receiver<bool>,
) -> Result<()> {
let logger = logger.new(o!("subsystem" => "debug-console"));
let shells = SHELLS.lock().unwrap().to_vec();
let shell = shells
.into_iter()
.find(|sh| PathBuf::from(sh).exists())
.ok_or_else(|| anyhow!("no shell found to launch debug console"))?;
if port > 0 {
let listenfd = socket::socket(
AddressFamily::Vsock,
SockType::Stream,
SockFlag::SOCK_CLOEXEC,
None,
)?;
let addr = VsockAddr::new(libc::VMADDR_CID_ANY, port);
socket::bind(listenfd.as_raw_fd(), &addr)?;
socket::listen(&listenfd, nix::sys::socket::Backlog::new(1).unwrap())?;

À chaque nouvelle connexion sur le socket, la fonction debug_console_handler() appelle run_debug_console_vsock() pour ouvrir un pseudo terminal.

let mut incoming = util::get_vsock_incoming(listenfd.into_raw_fd());
loop {
select! {
_ = shutdown.changed() => {
info!(logger, "debug console got shutdown request");
break;
}
conn = incoming.next() => {
if let Some(conn) = conn {
// Accept a new connection
match conn {
Ok(stream) => {
let logger = logger.clone();
let shell = shell.clone();
// Do not block(await) here, or we'll never receive the shutdown signal
tokio::spawn(async move {
let _ = run_debug_console_vsock(logger, shell, stream).await;
});
}

Après avoir décortiqué ce code, j’ai enfin pu comprendre que je ne pouvais pas le bloquer avec une stratégie Rego. Les devs du projet m’ont aussi spécifié que l’activation du debug console modifie la ligne de commande du noyau du guest qui est mesuré. On en revient donc à l’attestation qui est un mécanisme très important pour s’assurer qu’on démarre bien dans un environnement de confiance.
En tant qu’administrateur, je dois comprendre tous ces mécanismes pour mettre en oeuvre un envrironnement sécurisé, c’est assez costaud. Il faut utiliser Trustee pour réaliser l’attestation distante avec des valeurs de références : https://confidentialcontainers.org/docs/attestation/.

Chemins pour contourner la frontière de confiance

Système (pas d’attestation):

  1. S’échapper d’un pod en tant que root vers le noeud
  2. Activer le mode debug du kata-agent
  3. Forcer le redémarrage du pod
  4. Exécution de commandes au sein de la VM et usage de nsenter pour exécuter un programme dans les namespaces du processus

K8S (erreur dans la configuration de la stratégie rego ou pas d’attestation):

  1. Patcher la configuration du pod pour supprimer la stratégie de sécurité du kata-agent
  2. Forcer le redemérrage du pod
  3. Exécution de commandes au sein du conteneur de la VM avec “kubectl exec”

Le CoCo est-il la solution aux enjeux de multi tenancy ?

Mon opinion est que le kata-agent devrait introduire par défaut des stratégies de sécurité non dépendantes de l’administrateur quand la solution passe en mode CoCo. Ça évite de faire confiance implicitement au shim et plus globalement à l’administrateur mais aussi parce que les stratégies peuvent être source d’erreur humaine.
Aussi, le projet CoCo devrait recompiler kata sans la fonction debug console du kata-agent pour de la production.

Pour finir, juste utiliser TDX ou SEV n’est pas suffisant comme on a pu le voir tout au long de l’article, il faut aussi mettre en place de l’attestation et des processus organisationnels. La sécurité dépend du maillon le plus faible, si on blinde le workload mais qu’au final c’est l’ingress qui est la terminaison TLS, celui-ci pourra déchiffrer le trafic des confidential containers, il en va de même pour le stockage. Je suis aussi parti du principe que SEV et TDX étaient des forteresses mais ce n’est pas toujours le cas, ce qui peut fortement impacter un contexte multi-tenant si toute
le cloisonnement repose exclusivement sur cette brique. Il est donc très important de garder à jour le microcode de son processeur.

Les deux ont été mis à l’honneur lors de BlackHat US 2024, comme ça, pas de jaloux.

SEV: https://i.blackhat.com/BH-US-24/Presentations/US-24-Dohrmann-All-Your-Secrets-
Belong-To-Us-Leveraging.pdf


TDX: https://i.blackhat.com/BH-US-24/Presentations/US24-Villard-Compromising-
Confidential-Compute-One-Bug-Wednesday.pdf

Conclusion

Pour faire du CoCo correctement, il faut réaliser des attestations mais aussi enforce le kata-agent avec des stratégies indépendantes des RBAC kube. C’est un très bon mécanisme de défense en profondeur mais le CoCo n’est pas à lui seul une garantie suffisante dans un contexte de multi tenancy.
De plus, la complexité opérationnelle est forte, il faut une expertise pointue pour gérer toute la chaine de confiance. Je vous laisse aussi lire ce document de l’ANSSI qui remet l’église au milieu du village : https://messervices.cyber.gouv.fr/documents-guides/anssi-technical-position-paper-coco-v1.0.pdf

Laisser un commentaire

En savoir plus sur p0sql

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Poursuivre la lecture