Aller au contenu
English

Auditer le contrôle d'accès d'une application multilocataire libre

  • Étude de cas 1
  • 120 min
  • Facultative · 1 étude de cas requise pour le certificat

Ce que vous saurez faire

Prenez une vraie application multilocataire libre que vous n’avez pas écrite. Exécutez-la sur votre ordinateur et vérifiez qu’elle sépare bien deux organisations. Vous relierez ses utilisateurs, organisations et rôles au modèle du cours. Vous écrirez sa matrice d’accès et testerez chaque cellule avec une petite matrice de requêtes HTTP. Vous utiliserez le même raisonnement que dans le reste du cours. Le résultat sera un rapport court et honnête, et un projet que vous pourrez présenter.

Étude de casPièce de portfolio

Licence
MIT
Durée
Environ 2 h
S’exécute dans
VS Code et Docker, Python 3.12+
Compétences utilisées
  • Correspondance avec un modèle de locataires
  • Rédaction d'une matrice d'accès
  • Tests d'accès au niveau des objets (BOLA)
  • Tests des rôles et des portées
  • Lecture d'un journal d'audit
  • Un dispositif de test paramétré (pytest + httpx)

Décompressez le projet de départ, ouvrez le dossier dans VS Code et suivez les étapes ci-dessous. Elles commencent par un nouvel environnement virtuel.

Le projet de départ fournit la base technique. Il comprend un fichier Docker qui exécute Gitea, un script qui ajoute des personnes et des enregistrements fictifs, et la structure du dispositif de test. Vous écrivez la matrice d’accès et les tests. Le projet terminé fournit le résultat complet, si vous êtes bloqué ou voulez comparer. Vous pouvez utiliser et publier son code.

Avant de commencerCe qu’il faut savoir et avoir sous la mainEnviron 120 minutes

Ce que vous devez savoir

Cette étude de cas applique tout le cours au code de quelqu’un d’autre :

Vous n’avez pas besoin de connaître le langage Go. Vous observez le comportement de l’application par son API HTTP, pas par son code source.

Ce qu’il vous faut sur votre ordinateur

  • Docker Desktop (en cours d’exécution), VS Code et Python 3.12 ou une version plus récente.
  • Environ 1 Go d’espace disque et de mémoire libres : l’image Gitea fait environ 70 Mo, et le conteneur utilise environ 120 Mo.

Testé sur macOS (puce Apple) avec Docker Engine 29.6.1. L’empreinte de l’image fixe Gitea à la version 28.1.0. Vous testez donc exactement le code que nous avons testé. Les commandes Windows (PowerShell) suivent le modèle du cours, mais nous ne les avons pas exécutées. Vos durées seront différentes.

Priya Shah dirige une petite équipe de plateforme. Son entreprise utilise déjà le service de soutien du cours, créé dans les modules précédents. L’équipe veut maintenant un endroit où conserver son code et suivre son travail. Quelqu’un propose Gitea, un service gratuit et libre qui ressemble à un petit GitHub. Il est multilocataire au même sens que le service de soutien. Il contient des organisations, chacune avec ses propres dépôts privés, membres et rôles.

Avant de confier à Gitea le code de deux clients dans deux organisations, Priya pose la question récurrente du cours. Est-ce qu’il les sépare vraiment? Elle ne veut pas d’une promesse sur une page de vente. Elle veut voir les vraies réponses.

Elle demande donc à Amira d’en faire l’audit. Pas de pénétrer dans le serveur de quelqu’un : ce serait à la fois mal et inutile. Elle doit plutôt exécuter Gitea sur son portable et créer deux organisations fictives avec des personnes fictives. Elle essaiera chaque franchissement de frontière signalé dans le cours : un identifiant deviné de l’autre organisation, un rôle limité tentant une action privilégiée, le navigateur indiquant lui-même son organisation, un lien de fichier, une exportation et le journal d’audit. Quoi que fasse Gitea, elle consigne la vraie réponse.

Kwame décrit le travail au tableau : « Un audit, c’est trois questions posées à chaque porte. Qui frappe? Dans quel bâtiment se trouve cette personne? Qu’a-t-elle le droit d’y faire? Nous ne sommes pas là pour admirer la serrure. Nous sommes là pour essayer la poignée. »

Les personnes et les deux organisations sont fictives. Gitea est réel et libre.

Vous allez créer gitea-access-audit : un petit projet qui

  1. exécute Gitea dans Docker, avec une version fixe;
  2. le remplit, par son API, avec les deux organisations du cours (Larkfield et Bramble Books), sept personnes ayant les rôles du cours, un dépôt privé par organisation, et quelques billets, commentaires et fichiers;
  3. définit la matrice d’accès de Gitea dans le code : qui peut faire quoi, dans quelle organisation, et le statut attendu pour chaque requête;
  4. teste chaque cellule avec un dispositif paramétré (pytest + httpx), puis produit un court rapport contenant les vraies réponses.

Le livrable comprend le rapport, les tests réussis et une liste honnête à la fin. Cette liste décrit ce que Gitea fait bien et ce que ce cours changerait.

C’est un audit du contrôle d’accès, par l’API HTTP, sur les routes indiquées. Ce n’est pas un audit de sécurité complet de Gitea. Un audit complet examinerait aussi les références croisées entre dépôts, la recherche, les webhooks (rappels Web), les notifications, les paquets, l’interface Web et le protocole Git. Nous le précisons dans le rapport et n’affirmons jamais que « Gitea a réussi tous les tests ».

L’application est Gitea, un service Git libre. Nous l’avons préféré à d’autres candidats : un outil d’analyse, une plateforme à faible code et un service de clavardage. Il y a trois raisons à ce choix. Sa licence est clairement permissive, il tient dans un seul conteneur, et son modèle correspond presque exactement à celui du cours.

  • Licence : MIT. Le fichier LICENSE indique MIT, et chaque fichier source contient un en-tête SPDX-License-Identifier: MIT. MIT vous permet d’exécuter, de modifier et de publier le code. Le cœur de l’application n’a ni clause non commerciale ni licence « entreprise » distincte. (Deux autres candidats affichaient une licence peu claire sur leur page de code. C’est une raison de s’arrêter : un audit qu’on ne peut pas partager librement a moins de valeur.)
  • Version : 28.1.0, publiée quelques jours avant la rédaction de cette leçon. Le fichier compose.yaml du projet fixe l’image Docker par son empreinte (sha256:f505a0d3…a21a). Une nouvelle image téléversée ne peut donc pas changer en silence ce que vous testez. Une version plus récente de Gitea peut répondre autrement. Fixez la nouvelle version et relancez les tests avant de vous fier à un ancien résultat.

La fiche dataset.md du projet consigne la source, la licence, la version et l’empreinte de l’image.

Un audit commence par les mots de l’application : comment nomme-t-elle les idées du cours? Les mots de Gitea sont proches :

Deux colonnes. À gauche, les termes du cours; à droite, ceux de Gitea. locataire / organisation correspond à une organisation (larkfield, bramble). adhésion correspond à une adhésion par une équipe (Camille en a deux). le rôle owner correspond à l'équipe Owners (Grace, Ines). le rôle staff correspond à une équipe avec l'autorisation d'écriture (Sam, Camille). le rôle read_only correspond à une équipe avec l'autorisation de lecture (Omar, Camille). administrateur de plateforme correspond à un administrateur de site (Kwame). un enregistrement limité au locataire correspond à un billet, un commentaire ou un fichier dans un dépôt privé.
Le modèle du cours (à gauche) correspond bien à Gitea (à droite). Le rôle appartient à une équipe, pas à une personne. Camille peut donc être membre du personnel dans une organisation et en lecture seule dans une autre.
Description textuelle du diagramme

À gauche se trouvent les termes du cours : locataire, adhésion, les rôles owner, staff et read_only, administrateur de plateforme et enregistrement limité au locataire. À droite se trouvent ceux de Gitea : organisation, adhésion par une équipe, équipe Owners, équipe avec autorisation d’écriture, équipe avec autorisation de lecture, administrateur de site et billet, commentaire ou fichier dans un dépôt privé. Chaque terme du cours pointe vers son équivalent dans Gitea. Les personnes de l’exemple sont Grace et Ines (propriétaires), Sam et Camille (personnel), Omar (lecture), Kwame (administrateur de site). Camille apparaît aussi une deuxième fois : elle appartient à deux organisations avec des rôles différents.

Un rôle dans Gitea appartient à une équipe, et une équipe appartient à une organisation. Une personne rejoint une équipe. Ses pouvoirs viennent donc de l’équipe, pas de la personne. C’est la règle du cours, sous la forme propre à Gitea : un rôle appartient à une adhésion, pas à une personne. Voilà pourquoi Camille peut être membre du personnel chez Larkfield et en lecture seule chez Bramble Books en même temps.

Un mot demande de l’attention. L’équipe read (lecture) de Gitea n’est pas le rôle read_only (lecture seule) du cours. Comme vous le verrez, le rôle de lecture de Gitea peut quand même ouvrir des billets et ajouter des commentaires. « Lecture » signifie ici « lire le code », pas « ne rien modifier ». Un audit consigne le comportement réel de l’application, pas celui que ses étiquettes suggèrent.

  1. Téléchargez le projet de départ dans l’encadré du haut, décompressez-le et ouvrez le dossier gitea-access-audit dans VS Code. Ouvrez un terminal (Terminal > New Terminal (Terminal : nouveau terminal)).

  2. Démarrez Gitea. Un nom de projet et un port uniques évitent les conflits avec ce qui fonctionne déjà :

    Fenêtre de terminal
    export COMPOSE_PROJECT_NAME=gitea-audit GITEA_PORT=3300
    docker compose up -d --wait

    Ouvrez http://localhost:3300 dans votre navigateur. Vous voyez la page de connexion de Gitea. Il n’y a aucun lien d’inscription. Les réglages de compose.yaml verrouillent l’installation, désactivent l’inscription et exigent une connexion pour voir quoi que ce soit.

  3. Créez un environnement virtuel et installez les deux paquets (httpx pour envoyer les requêtes, pytest pour exécuter les tests) :

    Fenêtre de terminal
    python3 -m venv .venv
    source .venv/bin/activate
    python -m pip install -r requirements.txt
  4. Remplissez Gitea avec les deux organisations et leurs personnes :

    Fenêtre de terminal
    python -m audit.seed
    Sortie
    Made 7 people (1 site administrator).
    Larkfield: organization 'larkfield', teams ['Owners', 'read-only', 'staff'], repository larkfield/help-desk, 2 issues, 1 comment, 1 file.
    Bramble Books: organization 'bramble', teams ['Owners', 'read-only', 'staff'], repository bramble/help-desk, 2 issues, 1 comment, 1 file.
    Wrote .audit-state.json (8 tokens; local and made-up). API: http://127.0.0.1:3300/api/v1

Ce que cela nous apprend. Le premier compte ne peut être créé que sur le serveur lui-même. Le script exécute donc la commande admin user create de Gitea dans le conteneur pour Kwame, l’administrateur de site. Il crée toutes les autres personnes par l’API, au nom de Kwame. Chaque personne reçoit ensuite un jeton d’accès personnel. Le script enregistre les jetons dans .audit-state.json, qui reste sur votre ordinateur et ne va pas dans Git. Vous pouvez maintenant vous connecter au site, par exemple avec sam et le mot de passe practice-sam-2026. Vous verrez Larkfield, mais pas Bramble Books.

Ici, le jeton est propre à Gitea : une longue chaîne aléatoire, envoyée sous la forme Authorization: token …. Ce n’est pas un JWT signé comme celui du service de soutien. C’est la première note d’audit : les jetons de Gitea n’ont aucune expiration. Ils restent valides jusqu’à ce que quelqu’un les supprime. Les jetons d’accès du service de soutien expirent en dix minutes. Gitea échange cette protection contre de la commodité, et l’audit consigne ce compromis.

Avant de tester, indiquez ce qui devrait se produire. Le fichier audit/matrix.py du projet liste les routes testées et, pour chacune, les rôles qui peuvent l’utiliser. Le statut attendu découle ensuite des trois questions du cours :

  • Aucune adhésion à l’organisation → 404. L’appelant n’apprend rien, pas même que l’enregistrement existe.
  • Un membre dont le rôle est trop limité → 403.
  • Sinon → le statut de réussite (200 pour une lecture, 201 pour une création).
audit/matrix.py (the rule)
def expected_status(role, route):
_method, _path, _opts, ok = ROUTES[route]
if role is None:
return 404 # not a member: 404 for everything
if role in ALLOWED[route]:
return ok # 200 or 201
return 403 # a member whose role is too small

Les routes sont limitées à une organisation (/orgs/{org}/…) ou à un dépôt (/repos/{org}/{repo}/…). La requête d’une personne externe nomme une organisation à laquelle elle n’appartient pas. Gitea peut donc répondre 404. Écrire ALLOWED est l’étape de jugement de l’audit. Vous décidez chaque cellule selon le modèle du cours et ce que vous apprenez en essayant Gitea. Par exemple, vous constaterez que le rôle de lecture de Gitea peut ouvrir un billet. Ainsi, "open an issue" (ouvrir un billet) autorise les trois rôles. Par contre, "edit an issue" (modifier un billet) et "upload a file" (téléverser un fichier) autorisent seulement owner (propriétaire) et staff (personnel).

Le dispositif est petit, car la matrice porte le raisonnement. Pour chaque personne, organisation et route, tests/test_access_matrix.py vérifie si le statut réel correspond à expected_status :

tests/test_access_matrix.py (the matrix test)
MATRIX = [(p, o, r) for p in PERSONS for o in ORGS for r in ROUTES]
@pytest.mark.parametrize("person, org, route", MATRIX, ids=...)
def test_matrix(client, state, person, org, route):
role = role_of(org, person)
want = ROUTES[route][3] if person == ADMIN else expected_status(role, route)
response = send(client, person, org, route, state)
assert response.status_code == want, response.text

Exécutez-le :

Fenêtre de terminal
python -m pytest
Sortie (fin)
151 passed in 1.2s

Ce que cela nous apprend. 151 vérifications ont réussi. Parmi elles, 140 viennent de la matrice : 7 personnes × 2 organisations × 10 routes. Les autres couvrent les quatre franchissements ci-dessous. Ce sont des tests distincts pour qu’un échec indique la tentative exacte. Regardons ce que chaque franchissement a réellement renvoyé. Le but d’un audit est d’obtenir la vraie réponse, pas une coche verte.

Les trois questions du cours sous forme de quatre barrières, chacune avec la vraie réponse de Gitea. 1. Qui est l'appelant? Un jeton vérifié par Gitea : sans jeton, 403; avec un jeton invalide, 401. 2. Est-il membre de cette organisation? Une adhésion à une équipe : Tomás, ou Sam qui interroge bramble, reçoit 404 et n'apprend rien. 3. Le rôle autorise-t-il l'action? read / write / admin : Omar, avec le rôle de lecture, reçoit 403 en modifiant un billet. Sam, membre du personnel, reçoit 403 en tentant de rejoindre Owners. 4. L'enregistrement est-il dans cette organisation? Un identifiant global reste limité par l'organisation : Sam reçoit 404 en ouvrant le dépôt de Bramble par son identifiant global. L'identifiant d'un commentaire de l'autre organisation renvoie 404. Un membre autorisé à faire l'action reçoit 200 ou 201.
Les trois questions du cours posées à Gitea, avec les vraies réponses. Chaque « non » a son propre statut, et les vérifications s'arrêtent là.
Description textuelle du diagramme

Une requête nomme une organisation et contient un jeton. Barrière 1, qui est l’appelant : sans jeton, Gitea répond 403; avec un jeton invalide, 401. Barrière 2, est-il membre de cette organisation : Tomás, ou Sam qui interroge Bramble, reçoit 404 et n’apprend rien. Barrière 3, le rôle autorise-t-il l’action : Omar, qui a le rôle de lecture, reçoit 403 en modifiant un billet. Sam, membre du personnel, reçoit 403 en tentant de rejoindre Owners. Barrière 4, l’enregistrement est-il dans cette organisation : un identifiant global d’enregistrement de l’autre organisation reste limité par l’organisation. Sam reçoit donc 404 en ouvrant le dépôt de Bramble par son identifiant numérique. L’identifiant d’un commentaire de l’autre organisation renvoie aussi 404. Un membre autorisé à faire l’action reçoit 200 ou 201.

Franchissement 1 : un identifiant deviné de l’autre organisation (BOLA)

Section intitulée « Franchissement 1 : un identifiant deviné de l’autre organisation (BOLA) »

C’est le premier risque du cours : une défaillance d’accès au niveau des objets. Sam est membre du personnel chez Larkfield. Il n’a jamais vu les données de Bramble, mais les identifiants sont souvent faciles à deviner. Gitea donne à chaque dépôt, commentaire et pièce jointe un identifiant numérique global, partagé sur tout le serveur. Le dépôt de Bramble pourrait donc porter le numéro 2, et son commentaire aussi. Sam essaie d’y accéder par ses propres chemins légitimes :

The attempt (from audit/report.py)
# Sam opens Bramble's repository by its global numeric ID
GET /repositories/2
# Sam reads Bramble's comment by its global ID, through his own repository path
GET /repos/larkfield/help-desk/issues/comments/2
# Sam downloads Bramble's attachment ID, through his own issue
GET /repos/larkfield/help-desk/issues/1/assets/2

Chaque tentative renvoie 404 « not found » (introuvable). Gitea limite la recherche selon les accès de l’appelant. Ainsi, l’identifiant correct d’un dépôt invisible pour Sam donne « not found ». Le service de soutien du cours répond exactement ainsi pour un billet de l’autre entreprise. Le bon identifiant n’aide pas, car le serveur vérifie l’adhésion avant de chercher.

L’audit nous a appris une subtilité : un numéro de billet est propre au dépôt, pas global. Le billet numéro 1 sous larkfield est celui de Larkfield, pas celui de Bramble. Donc, « demander le billet de l’autre organisation par son propre chemin » lit son propre billet. Ce n’est pas du tout un franchissement. Le vrai franchissement utilise un identifiant global : celui d’un dépôt, d’un commentaire ou d’une pièce jointe. Ce sont ces identifiants qu’il faut tester. Cette découverte fait partie de l’audit : vous apprenez quels identifiants sont partagés avant de pouvoir les tester.

Franchissement 2 : un rôle limité tente une action privilégiée (élévation)

Section intitulée « Franchissement 2 : un rôle limité tente une action privilégiée (élévation) »

Le deuxième risque est l’élévation de privilèges. Un membre tente d’augmenter ses propres pouvoirs :

Tentative Réponse
Sam (personnel) s’ajoute à l’équipe Owners 403 « Must be an organization owner » (il faut être propriétaire de l’organisation)
Sam (personnel) crée une équipe avec l’autorisation d’administration 403 « Must be an organization owner »
Omar (lecture) augmente l’autorisation de l’équipe de lecture à l’administration 403 « Must be an organization owner »
Camille (lecture seule chez Bramble) rejoint l’équipe Owners de Bramble 403 « Must be an organization owner »

Gitea refuse chaque tentative. Seul un propriétaire peut modifier les équipes. Un membre du personnel ou en lecture ne peut donc pas se promouvoir lui-même. C’est la règle du cours : le pouvoir d’accorder des pouvoirs est lui-même l’action la plus protégée.

Il existe un rôle dans Gitea qui traverse effectivement les organisations : l’administrateur de site. Kwame, comme administrateur de site, lit toutes les organisations. Il peut même agir au nom d’un autre utilisateur par l’API. Gitea appelle cela sudo : GET /user?sudo=grace renvoie Grace. Une personne qui n’est pas administratrice et tente la même chose reçoit 403 « Only administrators allowed to sudo. » (seuls les administrateurs peuvent utiliser sudo). C’est un vrai pouvoir, plus large que celui de l’administrateur de plateforme du cours. Celui-ci pouvait seulement demander une autorisation limitée, de courte durée. L’audit le consigne clairement : sur ce serveur, l’administrateur de site peut tout voir et tout faire. La seule limite est le choix des personnes que vous nommez administratrices.

Passage 3 : l’organisation vient de la requête, pas du jeton

Section intitulée « Passage 3 : l’organisation vient de la requête, pas du jeton »

La troisième idée fausse du cours est « envoyer l’identifiant du locataire depuis le navigateur ». Gitea ne permet pas à l’appelant de nommer son organisation dans un en-tête ou un corps de requête. L’organisation fait toujours partie du chemin de l’URL (/orgs/{org}/…, /repos/{org}/{repo}/…). Il n’y a donc aucun en-tête « X-Organization » à falsifier. Gitea vérifie l’adhésion dans ses propres tables pour l’organisation du chemin, et une personne externe reçoit 404. Cette conception élimine l’idée fausse plutôt que de s’en protéger. C’est un bon modèle, que le centre de soutien du cours suit aussi.

Passage 4 : les fichiers, les liens et l’exportation

Section intitulée « Passage 4 : les fichiers, les liens et l’exportation »

Un fichier n’est privé que si son lien l’est aussi. Gitea fournit une pièce jointe à /attachments/{uuid}, avec un long identifiant aléatoire :

Qui demande le fichier du reçu de Bramble Réponse
Ines (propriétaire chez Bramble) ou Camille (membre) 200, le fichier
Sam (non membre de Bramble) 404
Aucun jeton 303, une redirection vers la page de connexion

Un membre reçoit donc le fichier, une personne externe reçoit 404, et une requête anonyme mène à la connexion. Le système vérifie le lien à chaque requête. Ce n’est pas un secret qui fonctionne pour toute personne qui le possède. Cela respecte la règle du cours : vérifier d’abord, puis fournir le fichier.

L’équivalent le plus proche de l’exportation du centre de soutien est l’archive du dépôt. C’est un fichier zip du code à /repos/{org}/{repo}/archive/main.zip. Un membre de l’organisation reçoit 200 et le fichier zip; une personne externe reçoit 404. Le serveur crée l’archive à partir du dépôt que l’appelant peut déjà lire. Elle n’ajoute donc aucun nouvel accès.

Le journal d’audit est le « registre des visiteurs » du cours. Gitea en a un, mais deux faits comptent, et le rapport doit inclure les deux.

D’abord, il est désactivé par défaut. Gitea est livré avec [audit] RECORD_OUTPUT = disabled (enregistrement désactivé). Le projet l’active dans compose.yaml (RECORD_OUTPUT=database, enregistrement dans la base de données). Un auditeur doit vérifier ce réglage, et non supposer que le registre existe.

Ensuite, il enregistre les changements, pas les refus. Après une initialisation propre des données, le journal contient 33 événements : le démarrage du serveur, six utilisateurs créés, huit jetons ajoutés, deux organisations, quatre équipes, quatre adhésions aux équipes, deux dépôts, quatre tickets et deux commentaires. Chaque changement que nous avons fait y figure, avec son acteur, sa cible et son origine : API, interface Web ou ligne de commande. L’administrateur du site peut exporter tout le journal, avec une ligne par événement :

Two lines of the exported audit log (reports/audit-export.jsonl)
{"action":"organization:create","actor":{"type":"user","name":"ines"},"scope":{"type":"organization","name":"bramble"},"origin":"api", ...}
{"action":"user:impersonation:start","actor":{"type":"user","name":"kwame"},"scope":{"type":"user","name":"grace"},"origin":"api", ...}

La deuxième ligne mérite votre attention : l’action de Kwame au nom de Grace est enregistrée. Le pouvoir le plus étendu laisse donc une trace. Mais exécutez toutes les requêtes refusées des passages précédents, et le journal n’ajoute rien. Les tentatives échouées — identifiants devinés, élévations de privilèges — ne sont pas enregistrées. Le centre de soutien du cours enregistre les refus comme les réussites. Le propriétaire peut ainsi voir que quelqu’un a essayé. Le journal de Gitea ne montrerait pas à Priya que Sam a exploré les accès de Bramble. C’est une véritable lacune, et le changement le plus évident que ce cours proposerait.

Proof, from the report (reports/responses.json)
# after a clean seed: 33 audit events
# after four refused requests (two escalations, two cross-organization reads): still 33
Fenêtre de terminal
python -m audit.report
Sortie
Matrix: 140/140 as expected.
Extra checks: 11/11 as expected.
Audit events: 33 (actions: ['issue:comment:create', 'issue:create', 'organization:create', ...]).
Wrote reports/REPORT.md, reports/responses.json, reports/audit-export.jsonl.

reports/REPORT.md est le résumé court et honnête que remet un auditeur. Il contient le résultat de la matrice, le tableau de chaque passage et le contenu du journal d’audit. responses.json conserve chaque code d’état pour que le lecteur puisse vérifier chaque affirmation.

Ce qu’il fait bien.

  • L’organisation est toujours dans le chemin, jamais dans un en-tête contrôlé par l’appelant. Toute la catégorie d’attaques « j’ai envoyé mon propre identifiant de locataire » n’a aucun point d’entrée.
  • Les personnes externes reçoivent 404, pour les dépôts, les tickets, les membres et les fichiers. Un identifiant correct, deviné pour un enregistrement d’une autre organisation, donne « introuvable ». Les identifiants ne révèlent donc rien.
  • Seuls les propriétaires modifient les équipes. Un membre du personnel ou avec le rôle de lecture ne peut pas se promouvoir. Il ne peut ni créer une équipe d’administration ni augmenter les autorisations d’une équipe.
  • Les portées des jetons sont appliquées, en plus des rôles. Un jeton limité à la lecture peut lire, mais pas écrire, même pour un propriétaire. (Vous le testez avec le deuxième jeton de Grace, destiné aux rapports.)
  • Le système vérifie les liens de fichiers à chaque requête. Un lien divulgué ne fonctionne donc pas pour une personne externe.

Ce que ce cours changerait.

  • Enregistrer les refus, pas seulement les changements. Le journal d’audit devrait montrer que Sam a essayé de lire Bramble. Un propriétaire pourrait ainsi repérer une tentative d’exploration. Actuellement, seuls les changements réussis sont enregistrés.
  • Activer le journal d’audit par défaut, ou avertir clairement qu’il est désactivé. Un registre d’audit vide parce que personne ne l’a ouvert cumule les inconvénients.
  • Donner une expiration aux jetons. Un jeton valide jusqu’à ce que quelqu’un pense à le supprimer est un secret de longue durée. Le jeton d’accès de dix minutes du centre de soutien limite les dommages d’une fuite.
  • « Lecture » devrait signifier lecture. Un rôle de lecture qui permet d’ouvrir des tickets et de commenter surprend un administrateur attentif. Le cours limite strictement read_only (lecture seule) à la lecture, pour que l’étiquette ne trompe personne.
  • Limiter l’administrateur du site. Un rôle qui voit toutes les organisations et agit au nom de chaque utilisateur peut causer des dommages étendus. Le cours préfère une autorisation de soutien limitée, temporaire et auditée à une clé maîtresse permanente.

Cela ne signifie pas que Gitea n’est pas sûr. Un audit transforme plutôt les promesses en une liste qu’une équipe peut évaluer. Priya a maintenant cette liste.

Erreur courante

L'audit « réussit », mais ne prouve rien

Une erreur fréquente consiste à tester seulement le cas où tout fonctionne. Un membre lit sa propre organisation, le test réussit, et l’auditeur déclare victoire. Cela montre que la serrure fonctionne pour la personne qui possède la clé. Cela ne dit rien sur la personne externe. Un audit du contrôle d’accès repose surtout sur des tests négatifs : les requêtes qui doivent échouer. Une matrice permet de vérifier que vous les avez toutes exécutées. Si votre rapport ne contient aucun 403 ni aucun 404, vous n’avez pas encore audité le contrôle d’accès.

  • Données locales et synthétiques seulement. Chaque requête du projet vise un conteneur Gitea sur votre ordinateur, avec des personnes et des enregistrements inventés. Exécuter ces tests contre un serveur qui ne vous appartient pas est une attaque, pas un audit. Le nom que vous lui donnez n’y change rien.
  • Ce n’est pas un audit de sécurité complet. Il couvre le contrôle d’accès des routes d’API dans audit/matrix.py. Un audit complet testerait aussi les références entre dépôts, la recherche, les webhooks (notifications automatiques), les notifications, les paquets, l’interface Web et le protocole Git. Le rapport le précise et ne prétend jamais que Gitea a « réussi tous les tests ».
  • Une seule version fixée. Les résultats concernent Gitea 28.1.0, fixé par l’empreinte de l’image. Une version plus récente pourrait se comporter autrement. Fixez la nouvelle version, réinitialisez les données et relancez les tests avant de vous fier à une ancienne conclusion.
  • Si vous trouvez une véritable faille, signalez-la de façon responsable. Les projets établis publient une politique de sécurité et un moyen privé de signalement. Ne publiez pas d’attaque fonctionnelle et ne la testez pas contre le serveur en service de quelqu’un d’autre. La démarche responsable est un rapport privé au projet, pas un article de blogue.

Ce projet montre à un employeur que vous pouvez analyser le contrôle d’accès d’un code réel que vous ne connaissez pas. Vous raisonnez comme un ingénieur soucieux de sécurité : cartographier le modèle, définir la matrice, tester chaque cellule et rendre compte honnêtement.

Quoi mettre dans le dépôt :

gitea-access-audit/
README.md what it is, how to run it, the model map, the limits
compose.yaml Gitea pinned by digest, with the audit log turned on
dataset.md the app, its source, licence and version
audit/ people.py, client.py, seed.py, matrix.py, report.py
tests/ the parametrised matrix, BOLA, escalation and bad-identity tests
reports/REPORT.md the audit's output (regenerated by `python -m audit.report`)
pyproject.toml, requirements.txt

Pour le publier :

  1. Créez un dépôt Git à partir du dossier du projet et poussez-le vers le dépôt distant. .gitignore exclut l’environnement virtuel et le fichier de jetons.
  2. Conservez dataset.md : l’application, sa licence MIT, ainsi que la version exacte et l’empreinte que vous avez testées.
  3. Vérifiez que python -m pytest réussit depuis un nouveau clone, avec des données Gitea fraîchement initialisées. Précisez-le dans le README.
  4. Rédigez le README pour une personne qui ne connaît pas le projet : la carte du modèle et les étapes d’exécution. Surtout, incluez la liste « ce que ce cours changerait ». Elle montre le raisonnement qu’un employeur veut voir.

Comment le décrire en entrevue, en deux phrases : « J’ai audité le contrôle d’accès de Gitea, un service Git multilocataire libre, exécuté localement avec deux organisations synthétiques, en testant une matrice d’accès complète — chaque personne, organisation et route — ainsi que des cas d’accès aux objets, d’élévation de rôle et d’identité incorrecte, avec un dispositif de test paramétré utilisant pytest et httpx. J’ai rapporté ses points forts, comme les recherches limitées aux accès autorisés pour qu’un identifiant deviné d’une autre organisation renvoie 404, et les changements que je proposerais, comme enregistrer les tentatives refusées dans le journal d’audit et donner une expiration aux jetons. »

Aller plus loin :

  • Auditez une deuxième application avec le même dispositif de test, puis comparez. La matrice est la partie réutilisable; seules la carte du modèle et la liste des routes changent.
  • Ajoutez l’interface Web, pas seulement l’API, avec un outil qui pilote un navigateur. Vérifiez que les pages cachent ce que l’API cache.
  • Testez le protocole Git, pas seulement l’API HTTP : un non-membre peut-il cloner le dépôt privé par HTTP?
  • Transformez le rapport en fiche d’évaluation d’une page pour un lecteur non technique, avec une ligne par passage et un verdict clair.

Vérifiez votre compréhension

Sam, membre du personnel chez Larkfield, demande le dépôt de Bramble avec son identifiant global correct et reçoit 404. Un collègue dit : « L'identifiant devait donc être incorrect. » Pourquoi se trompe-t-il, et qu'aurait révélé un 403 ici?

Afficher la réponse

L’identifiant était correct. Gitea renvoie 404 parce que Sam n’est pas membre de Bramble et que la recherche se limite aux accès de l’appelant. « Introuvable » est la même réponse que Sam recevrait pour un identifiant qui n’existe vraiment pas. Il ne peut donc pas distinguer les deux cas, et c’est le but. Un 403 (« vous n’avez pas l’autorisation ») aurait confirmé que l’enregistrement existe et que Sam n’a pas l’autorisation. Cela aurait révélé que Bramble possède un dépôt avec cet identifiant. Le cours utilise justement 404 pour les personnes externes afin qu’un identifiant deviné ne révèle rien.

Vérifiez votre compréhension

Vous relancez toutes les requêtes refusées de l'audit, et le journal d'audit de Gitea ne change pas. Nommez un risque pour le propriétaire d'une organisation et expliquez ce que le centre de soutien du cours fait autrement.

Afficher la réponse

Comme seuls les changements réussis sont enregistrés, une tentative échouée ne laisse aucune trace. Un propriétaire ne peut pas voir que quelqu’un a exploré les accès de son organisation. Cette personne peut avoir essayé un identifiant deviné ou tenté de rejoindre l’équipe Owners. Une tentative de reconnaissance avant une véritable intrusion reste donc invisible. Le centre de soutien du cours enregistre les refus comme les réussites. Il inclut l’acteur, l’organisation et le résultat, mais jamais le jeton. Le propriétaire peut ainsi remarquer une tentative et enquêter. Activer le journal de Gitea est nécessaire, mais insuffisant; les refus n’y figureraient toujours pas.

Vérifiez votre compréhension

Omar, membre de l'équipe « read » de Gitea, peut ouvrir un ticket et ajouter un commentaire. Votre matrice d'accès du centre de soutien du cours ne donne aucun de ces droits à read_only. Votre audit est-il incorrect? Comment le rapport devrait-il traiter cette différence?

Afficher la réponse

L’audit n’est pas incorrect; les deux systèmes définissent le rôle différemment. Un audit décrit le comportement réel de l’application, pas ce qu’une étiquette laisse croire. La matrice de Gitea devrait autoriser « ouvrir un ticket » et « commenter » pour le rôle de lecture. C’est ce que Gitea fait, et les tests devraient réussir en vérifiant ce comportement réel. Le rapport note ensuite cette différence comme constat sous « ce que ce cours changerait ». Un rôle de lecture qui permet d’écrire surprend un administrateur attentif. Le cours limite strictement read_only à la lecture pour que le nom ne trompe personne. La bonne approche combine une description honnête du comportement réel et une recommandation claire.

Récapitulatif

  • Un audit du contrôle d’accès pose les trois questions du cours à chaque porte : qui appelle, quelle organisation, quelles actions permises? Il essaie la poignée plutôt que d’admirer la serrure.
  • Commencez par associer les termes de l’application au modèle du cours. Dans Gitea, une organisation est un locataire, une équipe porte un rôle, et un enregistrement se trouve dans un dépôt privé. Écrivez ces correspondances avant de tester.
  • Définissez la matrice d’accès dans le code, puis testez chaque cellule avec un dispositif paramétré. Les tests négatifs apportent l’essentiel de la valeur : les 403 et les 404.
  • Gitea limite chaque recherche aux accès autorisés. Un identifiant global deviné d’une autre organisation renvoie donc 404. Seuls les propriétaires modifient les équipes; le système vérifie les liens de fichiers à chaque requête. Ce sont de véritables points forts, établis à partir de réponses réelles.
  • Gitea enregistre aussi seulement les changements, pas les refus. Son journal d’audit est désactivé par défaut, et ses jetons n’expirent jamais. Ce sont des constats que ce cours changerait. Un rapport honnête présente les points forts et les lacunes, sans jamais prétendre à une réussite sans réserve.
  • Le projet enrichit votre portfolio : un dispositif de matrice réutilisable, une carte claire du modèle et un court rapport. Ce rapport transforme les promesses en une liste qu’une équipe peut évaluer.

Leçon suivante : Ajouter la connexion et les rôles à une API existante