Nomenclature logicielle et sécurité logistique
Contexte
Le monde constate de plus en plus d’attaques de logiciels rançons, qui ciblent notamment la chaîne logistique, e.g. le cas de Kaseya ou SolarWinds par exemple.
Néanmoins, un arbre de dépendance classique dans un projet Node.js pour un projet populaire: Ghost, 1188 dépendances, 2314 inter-dépendances entre les paquets. Notez l’ironie que Ghost soit (effectivement) une alternative minimaliste à WordPress :).
De plus, cet arbre ne prend en compte que les dépendances « Node.js » et non pas la clôture transitive1 des dépendances, il manque NGINX, systemd, Linux, etc.
Avec Nix, on peut la calculer et l’afficher, voici un exemple2 du projet Mangaki:
[insérer la photo]
Ceci étant dit, on comprend qu’il y a un enjeu de maîtrise des dépendances dans un projet informatique. La plupart des solutions mise en œuvre se concentrent sur la sécurité à travers le système CVE, imparfait pour beaucoup trop de raisons qui dépassent le cadre de cet article3.
Pire, ces solutions sont incapables de suivre proprement la sécurité des systèmes en général en raison de ce que je nomme « la perte d’information induite par la containerization aveugle », beaucoup de projets recourent à Docker par simplicité car ça offre une solution « universel »4, or, pour « packager » un projet, on écrit souvent un Dockerfile
5.
C’est quoi le problème de ce Dockerfile
? Il contient une image de départ sous la forme de FROM <image de départ>
, cette image de départ est par exemple postgres:9
ou postgres:latest
. Derrière l’intention naïve de choisir une image avec une version plus ou moins récente, plus ou moins la dernière, plus ou moins une version majeure, il y a un oubli que ces images se basent sur des distributions (souvent Alpine Linux dira-on, parfois Debian ou Ubuntu.) et les utilisateurs recourent à des apt-get update && apt-get install <paquets systèmes>
avant de recourir à pip install -r requirements.txt
ou npm install
ou peu importe. Sauf, que, la plupart du temps, afin d’avoir des images « petites », ces informations sont éliminées. De plus, les gestionnaires de paquets systèmes ou de projets sont rarement utilisés à des fins d’exploitation de données, bien que cela commence à changer.
Ainsi, ces solutions ne permettent pas d’assurer l’intégrité d’un système en production, fsverity
est rarement utilisé dans ces contextes par ailleurs, alors qu’il pourrait l’être.
Terminologie
Nous allons parcourir quelques éléments de terminologie (notamment en anglais) sur ces concepts qui deviennent de plus en plus important.
BOM
Bill Of Materials
En français, nomenclature.
Un dispositif utilisé dans tous les autres projets impliquant des inventaires, du matériel, visant à concevoir ou à construire des choses, e.g. un pont, etc.
Cela permet de suivre et de savoir d’où viennent les pièces, qui sont les fournisseurs, etc.
Par ailleurs, un exemple de (Configurable) BOM c’est les configurateurs de PCs qu’on retrouve dans certains e-commerce qui permettent de monter des PC sur-mesure.
SBOM
L’idée ici c’est d’avoir une version logicielle de ce dispositif afin de suivre les dépendances.
Un SBOM peut contenir beaucoup de choses :
- Des empreintes (e.g. SHA256) de la représentation binaire de la dépendance ;
- Des métadonnées: licenses, page d’accueil, révision du logiciel en usage ;
- Des patchs appliqués par dessus: dans le cadre de patch downstream pour CVE ;
- Un niveau de confiance qu’on peut mettre en relation avec le framework SLSA par exemple dont on parlera plus tard
Supply chain security
La sécurité de la chaîne logistique est un sujet assez mal renseigné, au delà des questions simplistes que SonarQube ou Snyk fournit à travers ses mécanismes de scan.
Pour rappeler quelques principes :
- Quelle est la provenance d’une dépendance ?
- Quel est le canal de distribution ? e.g. PyPI pour Python, un satellite Red Hat, un serveur privé, NPM, etc. ;
- Est-ce qu’on télécharge des binaires ? e.g. wheels vs sdist pour Python ;
- Est-ce qu’on dispose de signatures ? e.g. sigstore, etc.
CycloneDX
Exemple avec Nix
git clone https://github.com/RaitoBezarius/nix-cyclonedx-example /tmp/cyclonedx-example
pushd /tmp/cycleonedx-example
nix-build -A sbom -o bom
nix-shell -p fx --run "fx ./bom"
popd
SLSA
Exemple avec Nix
Expériences
```nix rec { collectThroughDeps = f: package: let deps = (package.buildInputs or []) ++ (package.nativeBuildInputs or []); in [ (f package) ] ++ lib.filter (x: x != []) (lib.concatMap (collectThroughDeps f) deps);
writeLicenseClosure = package: pkgs.writeTextFile “license-closure-${package.name}” (builtins.toJSON (collectThroughDeps (dep: if (dep ? “meta” && dep.meta ? “license”) then [ dep.name ] ++ map (l: l.spdxId or l.shortName) (if builtins.isList dep.meta.license then dep.meta.license else [ dep.meta.license ]) else [ ]) package)) }
Au sens de la relation d’ordre : « x ≤ y si x dépend d’y au niveau logiciel »↩︎
Imparfait, car Mangaki utilise des fichiers non identifiés comme dépendances qu’il gère à part comme des assets, c’est mal.↩︎
Mais si le sujet vous intéresse, n’hésitez pas à le mentionner dans un commentaire à cet article :).↩︎
C’est assez faux en raison des hypothèses implicites de ces outils, ça mérite un article aussi à part.↩︎
Plus rarement, on génère une tarball de conteneur à l’aide d’un outil ou à la main.↩︎