feat: Semaine 13

This commit is contained in:
gauvainboiche
2026-09-04 17:25:11 +02:00
parent d0b00802d7
commit 1a3b11555b
44 changed files with 14807 additions and 31 deletions
+1
View File
@@ -1,4 +1,5 @@
desktop.ini
**/__pycache__/
**/.venv/
**/vendor
**/.vscode/
@@ -0,0 +1,27 @@
# Exercice 01 - Adam GABRIEL, Louis LEULIETTE, Gauvain BOICHÉ
## Quel type de test faut-il écrire ?
### A : La fonction qui calcule la TVA renvoie-t-elle le bon montant ?
> Test unitaire
### B : Le site tient-il si 800 personnes commandent en même temps ?
> Test de charge
### C : Un client peut-il voir la facture d'un autre client en changeant l'URL ?
> Test de pénétration
### D : Le formulaire d'inscription est-il utilisable au clavier seul ?
> Test d'accessibilité
### E : Quand j'enregistre une facture, est-elle vraiment écrite en base ?
> Test d'intégration
### F : Le parcours "je me connecte, je commande, je paie" fonctionne-t-il ?
> Test end-to-end
### G : A partir de combien d'utilisateurs le serveur tombe-t-il ?
> Test de stress
### H : Le comptable valide-t-il que la facture contient les bonnes mentions ?
> Test validation
@@ -0,0 +1,150 @@
# Exercice 02 - Adam GABRIEL, Louis LEULIETTE, Gauvain BOICHÉ
```php
<?php
class Facture
{
private array $lignes = [];
public function ajouterLigne(
string $libelle,
float $prixHT,
int $qte
): void
{
$this->lignes[] = [
'libelle' => $libelle,
'prixHT' => $prixHT,
'qte' => $qte,
];
// 1. Vérifier que la longueur de $ligne soit supérieure à 1
// 2. Vérifier que $ligne["libelle"] soit de type STRING, longueur supérieure à 0 (ou 3 ou plus pour des STRING)
// 3. Vérifier que $ligne["prixHT"] soit de type FLOAT, positif, supérieur à 0
// 4. Vérifier que $ligne["qte"] soit de type INT, positif, supérieur à 0
}
public function totalHT(): float
{
$total = 0;
foreach ($this->lignes as $l) {
// 5. Vérifier qu'un $prixHT à 100 et $qte à 1 multipliés fassent 100
// 6. Vérifier qu'un $prixHT à 150 et $qte à 3 multipliés fassent 450
$total += $l['prixHT'] * $l['qte'];
}
// 7. Vérifier que $total soit supérieur à 0
return $total;
}
}
```
## Tests PHPUnit
Ces tests couvrent les 7 vérifications indiquées par les commentaires du code ci-dessus. Générés par Claude Sonnet 5.
```php
<?php
use PHPUnit\Framework\TestCase;
class FactureTest extends TestCase
{
// 1. Vérifier que la longueur de $ligne soit supérieure à 1
public function testLigneAPlusieursElements(): void
{
$facture = new Facture();
$facture->ajouterLigne('Prestation', 100.0, 1);
$reflection = new ReflectionProperty(Facture::class, 'lignes');
$reflection->setAccessible(true);
$ligne = $reflection->getValue($facture)[0];
$this->assertGreaterThan(1, count($ligne));
}
// 2. Vérifier que $ligne["libelle"] soit de type STRING, longueur >= 3
public function testLibelleEstUneChaineValide(): void
{
$facture = new Facture();
$facture->ajouterLigne('Abc', 100.0, 1);
$reflection = new ReflectionProperty(Facture::class, 'lignes');
$reflection->setAccessible(true);
$ligne = $reflection->getValue($facture)[0];
$this->assertIsString($ligne['libelle']);
$this->assertGreaterThanOrEqual(3, strlen($ligne['libelle']));
}
public function testLibelleTropCourtEstInvalide(): void
{
$facture = new Facture();
$facture->ajouterLigne('Ab', 100.0, 1);
$reflection = new ReflectionProperty(Facture::class, 'lignes');
$reflection->setAccessible(true);
$ligne = $reflection->getValue($facture)[0];
$this->assertLessThan(3, strlen($ligne['libelle']));
}
// 3. Vérifier que $ligne["prixHT"] soit de type FLOAT, positif, > 0
public function testPrixHTEstUnFloatPositif(): void
{
$facture = new Facture();
$facture->ajouterLigne('Prestation', 99.99, 1);
$reflection = new ReflectionProperty(Facture::class, 'lignes');
$reflection->setAccessible(true);
$ligne = $reflection->getValue($facture)[0];
$this->assertIsFloat($ligne['prixHT']);
$this->assertGreaterThan(0, $ligne['prixHT']);
}
// 4. Vérifier que $ligne["qte"] soit de type INT, entier positif, > 0
public function testQteEstUnIntPositif(): void
{
$facture = new Facture();
$facture->ajouterLigne('Prestation', 100.0, 5);
$reflection = new ReflectionProperty(Facture::class, 'lignes');
$reflection->setAccessible(true);
$ligne = $reflection->getValue($facture)[0];
$this->assertIsInt($ligne['qte']);
$this->assertGreaterThan(0, $ligne['qte']);
}
// 5. Vérifier qu'un prixHT à 100 et qte à 1 multipliés fassent 100
public function testTotalHTAvecUneLigneSimple(): void
{
$facture = new Facture();
$facture->ajouterLigne('Prestation', 100.0, 1);
$this->assertEqualsWithDelta(100.0, $facture->totalHT(), 0.0001);
}
// 6. Vérifier qu'un prixHT à 150 et qte à 3 multipliés fassent 450
public function testTotalHTAvecUneLigneMultiple(): void
{
$facture = new Facture();
$facture->ajouterLigne('Prestation', 150.0, 3);
$this->assertEqualsWithDelta(450.0, $facture->totalHT(), 0.0001);
}
// 7. Vérifier que $total soit supérieur à 0
public function testTotalHTEstSuperieurAZero(): void
{
$facture = new Facture();
$facture->ajouterLigne('Prestation', 100.0, 1);
$facture->ajouterLigne('Autre prestation', 150.0, 3);
$this->assertGreaterThan(0, $facture->totalHT());
}
}
```
+48
View File
@@ -0,0 +1,48 @@
<?php
class Facture
{
private array $lignes = [];
public function ajouterLigne(
string $libelle,
float $prixHT,
int $qte
): void
{
$this->lignes[] = [
'libelle' => $libelle,
'prixHT' => $prixHT,
'qte' => $qte,
];
}
public function totalHT(): float
{
$total = 0;
foreach ($this->lignes as $l) {
$total += $l['prixHT'] * $l['qte'];
}
return $total;
}
}
use PHPUnit\Framework\TestCase;
class FactureTest extends TestCase
{
public function testTotalHTDeDeuxLignes()
{
// 1. ARRANGE
$facture = new Facture();
// 2. ACT
$facture->ajouterLigne("Exemple de ligne 1", 150.0, 3);
$facture->ajouterLigne("Exemple de ligne 2", 49.99, 10);
// 3. ASSERT
$this->assertEqualsWithDelta(949.9, $facture->totalHT(), 0.0001);
}
}
?>
@@ -0,0 +1,56 @@
# Exercice 03 - Adam, Louis, Gauvain
## Ecrire son premier test
### Class Facture
```php
<?php
class Facture
{
private array $lignes = [];
public function ajouterLigne(
string $libelle,
float $prixHT,
int $qte
): void
{
$this->lignes[] = [
'libelle' => $libelle,
'prixHT' => $prixHT,
'qte' => $qte,
];
}
public function totalHT(): float
{
$total = 0;
foreach ($this->lignes as $l) {
$total += $l['prixHT'] * $l['qte'];
}
return $total;
}
}
```
```php
<?php
use PHPUnit\Framework\TestCase;
class FactureTest extends TestCase
{
public function testTotalHTDeDeuxLignes()
{
// 1. PREPARER
$facture = new Facture();
// 2. AGIR
$facture->ajouterLigne("Exemple de ligne 1", 150.0, 3);
$facture->ajouterLigne("Exemple de ligne 2", 49.99, 10);
// 3. VERIFIER
$this->assertEqualsWithDelta(949.9, $facture->totalHT(), 0.0001);
}
}
```
@@ -0,0 +1,101 @@
# Exercice 04 - Adam Louis Gauvain
## Chasse au bogue - 3 pièges dans 20 lignes
```php
public function appliquerRemise(float $pourcent): void
{
foreach ($this->lignes as $i => $ligne) {
$this->lignes[$i]["prixHT"] *= (1 - $pourcent / 100);
}
}
public function totalTTC(float $tauxTVA): float
{
return $this->totalHT() * (1 + $tauxTVA);
}
public function estPayee(): bool
{
return $this->montantRegle == $this->totalTTC(0.20);
}
```
## Réponses
### Cas 1
```php
public function appliquerRemise(float $pourcent): void
{
foreach ($this->lignes as $i => $ligne) {
$this->lignes[$i]["prixHT"] *= (1 - $pourcent / 100);
}
}
```
```php
appliquerRemise(15.5) // Cas réaliste
// ligne 1 : prix 100 (remise : 15,5, total 84,5)
// ligne 2 : prix 200 (remise : 31, total 169)
// Renvoi lignes
lignes[i] = $prixHT * (1 - 15.5 / 100) // 100 * 0,845
```
```php
appliquerRemise(150) // Cas irréaliste
// ligne 1 : prix 100 (remise : 150, total -50)
// ligne 2 : prix 200 (remise : 300, total -100)
// Renvoi lignes
lignes[i] = $prixHT * (1 - 150 / 100) // 100 * -0.5, soit -50
```
> Il faut appliquer une limite à 0€ minimum sur le site marchand, pour éviter que le total soit négatif.
> SAUF si le marchand gère les avoirs, auquel cas, il faut étendre la logique métier, et gérer les résultats négatifs comme avoirs sur le compte client, et pas sur la commande.
### Cas 2
> 0.1 + 0.2 en PHP m'ont rendu 0.3, mais on connaît la possibilité d'avoir 0.300000000004 par exemple. Auquel cas, la logique métier voudrait un arrondi à deux décimales :
```php
// https://www.php.net/number_format
number_format($totalTTC, 2)
```
> Sachant qu'un paradigme peu répandu mais loin d'être appliqué veut que l'adaptation à ce problème inhérent au binaire pour les virgules flottantes veut qu'on ne calcule QUE des nombres entiers (INT) en comptant à partir des centimes. Autrement dit, on ne calcule pas 100€ comme 100.00 en FLOAT mais comme 10000 centimes en INT.
### Cas 3
```php
public function totalTTC(float $tauxTVA): float
{
return $this->totalHT() * (1 + $tauxTVA);
}
public function estPayee(): bool
{
return $this->montantRegle == $this->totalTTC(0.20);
}
```
> La TVA peut être de 20% ou de 5,5% en France, et d'un taux différent dans d'autres pays. De fait, la validation de la TVA doit se faire selon un certains nombres de critères à valider dans une fonction, voire une classe dédiée.
> Une logique métier serait un dictionnaire emboîté, avec les pays et les taux déclarés :
```json
{
"France": {
"Particulier": 2.1,
"Réduit": 5.5,
"Intermédiaire": 10.0,
"Normal": 20.0
},
"Belgique": {
"Zéro": 0.0,
"Réduit1": 6.0,
"Réduit2": 12.0,
"Normal": 21.0
}
}
```
@@ -0,0 +1,67 @@
# Exercice 05 - Adam, Louis, Gauvain
## Le test capricieux
```php
public function testDateDeCreation()
{
$f = new Facture()
$this->assertEquals(date("Y-m-d H:i:s"), $f->getDateCreation());
}
public function testListeDesClients()
{
$clients = $this->db->query("SELECT * FROM clients");
$this->assertCount(3, $clients);
}
```
### Test 1
```php
public function testDateDeCreation()
{
// 1.
// Définir un fuseau horaire précis pour éviter la confusion entre des pods d'un déploiement multi-régions
// date_default_timezone_set('Europe/Paris');
$f = new Facture()
// 2.
// Pourquoi comparer la date de création avec le moment T de ce code ?
// 3.
// Il se peut qu'un décalage soit fait entre le temps de getDateCreation() et date("etc etc")
// Il suffit que le processeur rame un peu, ou que l'instruction chevauche une seconde sur le fin et
// on compare 15:03:02 à 15:03:03.
// On pourrait alors mettre un assertEqualsWithDelta() et récupérer les dates en EPOCH pour ne
// pas comparer des strings mais un INT.
$this->assertEquals(date("Y-m-d H:i:s"), $f->getDateCreation());
}
```
### Test 2
```php
public function testListeDesClients()
{
// 1.
// Où est géré la connexion ? Est-on sûrs qu'elle est établie ?
// Y a-t-il un équivalent de 'try: except:' en PHP ?
// 2.
// Est-ce qu'il y a une gestion d'exception dans le cas où la table
// 'clients' n'est pas créée ?
$clients = $this->db->query("SELECT * FROM clients");
// 3.
// Pourquoi 3 ? N'a-t-on que 3 clients ? Il vaut mieux vérifier un "supérieur à 0"
// mais vouloir ne garder que 3 clients, c'est ridicule. Sauf si on veut chercher des
// clients spéciaux (mettons des clients Grands Comptes), auquel cas on va plutôt
// récupérer une valeur dynamique en première phase de l'assertion
$this->assertCount(3, $clients);
}
```
+107
View File
@@ -0,0 +1,107 @@
stages:
- install
- scan
- test
- build&deploy
# Install JavaScript dependencies
frontend_install:
stage: install
image: node:latest
script:
- npm install
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
# Scan project (linter & security)
eslint_1_4:
stage: scan
image: node:lts
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
script:
- npm run lint
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task
sonarcloud-check:
stage: scan
image:
name: sonarsource/sonar-scanner-cli:latest
entrypoint: [""]
cache:
- key: "${CI_JOB_NAME}"
paths:
- .sonar/cache
- key:
files:
- package-lock.json
paths:
- node_modules/
script:
- sonar-scanner
# Test project (functiunality & charge)
jest_3_4:
stage: test
image: node:lts
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
dependencies:
- sonarcloud-check
script:
- npm run test
cypress_4_4:
stage: test
image: cypress/browsers:latest
dependencies:
- jest_3_4
script:
- npm install
- npm run dev &
- npx cypress run --browser firefox --spec "cypress/e2e/experience.cy.js"
- npx cypress run --browser chrome --spec "cypress/e2e/experience.cy.js"
# Build & Deploy project
pages:
stage: build&deploy
image: node:lts
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
script:
- npm run build
- mv build public
artifacts:
paths:
# The folder that contains the files to be exposed at the Page URL
- public
rules:
# This ensures that only pushes to the default branch will trigger
# a pages deploy
- if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "preprod"
+9
View File
@@ -0,0 +1,9 @@
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});
@@ -0,0 +1,44 @@
function makeRandomString(length) {
let string = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
let counter = 0;
while (counter < length) {
string += characters.charAt(Math.floor(Math.random() * charactersLength));
counter += 1;
}
return string;
};
const frontendUrl = process.env.FRONTEND_URL;
describe('Frontend Morning News', () => {
beforeEach("Site frontal", () => {
cy.visit('http://localhost:3001')
});
it("Inscription", () => {
const username = makeRandomString(6)
const password = makeRandomString(10)
cy.contains('Bookmarks');
cy.get('.Header_linkContainer__hNrE8 > :nth-child(2)').click();
cy.wait(500);
cy.get('[class*="Header_userSection"]').click();
cy.wait(500);
cy.contains('Sign-up');
cy.get('#signUpUsername').type(username);
cy.wait(500);
cy.get('#signUpPassword').type(password);
cy.wait(500);
cy.get('#register').click();
cy.wait(500);
cy.contains(username);
});
/*
it("Marque-pages", () => {
cy.contains('Articles');
cy.get(':nth-child(1) > [class*="Article_articleHeader] > fa-eye-slash > path').click();
});
*/
})
@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}
@@ -0,0 +1,25 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
+20
View File
@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/e2e.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
+23
View File
@@ -0,0 +1,23 @@
const globals = require("globals");
const babelParser = require("@babel/eslint-parser");
module.export = [
{files: ["**/*.{js,mjs,cjs,jsx}"]},
{files: ["**/*.js"], languageOptions: {sourceType: "commonjs"}},
{languageOptions: {
globals: globals.browser,
parser: babelParser,
parserOptions: {
"ecmaFeatures": {
"jsx": true,
"modules": true
},
requireConfigFile: false,
babelOptions: {
babelrc: false,
configFile: false,
presets: ["@babel/preset-env"]
}
}
}},
];
+12
View File
@@ -0,0 +1,12 @@
const nextJest = require('next/jest');
const createJestConfig = nextJest({
dir: './',
});
const customJestConfig = {
moduleDirectories: ['node_modules', '<rootDir>/'],
testEnvironment: 'jest-environment-jsdom',
};
module.exports = createJestConfig(customJestConfig);
+14
View File
@@ -0,0 +1,14 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
images: {
domains: ['cdn.vox-cdn.com'],
},
eslint: {
// Warning: This allows production builds to successfully complete even if
// your project has ESLint errors.
ignoreDuringBuilds: true,
},
};
module.exports = nextConfig;
+13187
View File
File diff suppressed because it is too large Load Diff
+39
View File
@@ -0,0 +1,39 @@
{
"name": "morningnews-frontend-part5",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev -p 3001",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "jest --config ./jest.config.js --collectCoverage --coverageDirectory='./coverage' --ci --reporters=default --watchAll=false"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.1",
"@fortawesome/react-fontawesome": "^0.1.18",
"@reduxjs/toolkit": "^1.8.2",
"antd": "^4.21.2",
"moment": "^2.29.3",
"next": "^12.1.6",
"react": "18.1.0",
"react-dom": "18.1.0",
"react-moment": "^1.1.2",
"react-redux": "^8.0.2",
"redux-persist": "^6.0.0"
},
"devDependencies": {
"@next/eslint-plugin-next": "^15.0.2",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.2.0",
"cypress": "^13.15.1",
"eslint": "8.0.0",
"eslint-config-next": "15.0.2",
"eslint-plugin-react": "^7.37.2",
"globals": "^15.11.0",
"jest": "^28.1.0",
"jest-environment-jsdom": "^28.1.0",
"node-fetch": "2"
}
}
@@ -0,0 +1,16 @@
sonar.projectKey=cbolloretti_mediachad-frontend
sonar.organization=cbolloretti
# This is the name and version displayed in the SonarCloud UI.
sonar.projectName=MediaCHAD-FrontEnd
sonar.projectVersion=1.0
# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
sonar.sources=.
# Encoding of the source code. Default is default system encoding
sonar.sourceEncoding=UTF-8
# Option verbeuse pour déboguage
sonar.verbose=true
+5
View File
@@ -0,0 +1,5 @@
let bonjour = "Hello World from Jest !"
test('Hello World', () => {
expect(bonjour).toBe("Hello World from Jest !");
});
+29
View File
@@ -0,0 +1,29 @@
# Exercice 06 - Gauvain
## Chaque action dans le bon environnement
NDR : Il est maintenant de coutume de fusionner les environnements "DEV" et "TEST" dans le seul environnement "DEV", qui sert de test également. Cela allège le déploiement des environnements et les ressources utilisées. Il se peut que les deux concepts se confondent un peu dans les réponses.
### A. Un développeur essaie une idée et casse tout trois fois de suite
> DEV et TEST
### B. La suite des test automatisés tourne après chaque envoi de code
> TEST, STAGING et PROD (avant déploiement)
### C. Le comptable vérifie que la facture PDF est conforme avant la mise en ligne
> TEST, STAGING et PROD (avant déploiement)
### D. Un test de charge à 800 utilisateurs simultanés
> STAGING. Optionnels : TEST et PROD
### E. Un pentest avec autorisation écrite
> Hors scope, il s'agit d'un projet hors pipeline de production pour un client, donc sur application déjà déployée. Sinon il est évident qu'un pentest se fait en STAGING et en PROD, mais pas besoin d'autorisation écrite, on travaille sur SON application
### F. Un vrai client paie une vraie facture
> PROD (après déploiement, hors tests. La vérification se fera par une remontée de journaux sur un moniteur)
@@ -0,0 +1,58 @@
# Exercice 07 - Adam, Louis, Gauvain
## Audit de sécurité d'un environnement de test
Trouver les 5 failles de sécurité dans ce `docker-compose.yml` :
```yaml
services:
app:
build: .
environnement:
DB_PASSWORD: "Sup3rSecret2024!"
APP_DEBUG: "true"
ports:
- "0.0.0.0:8080:8080"
db:
image: mysql:5.7
ports:
- "3306:3306"
volumes:
- ./dump_clients_prod.sql:/docker-entrypoint-initdb.d/init.sql
```
1. BD_PASSWORD en clair
2. APP_DEBUG en true, soit une journalisation totale potentiellement en clair
3. Port 8080 ouvert à 0.0.0.0, donc n'importe quelle machine dans le monde
4. Image mySQL HYPER obsolète, on est à la version 26
5. dump_clients_prod.sql. Il utilise la BDD de production pour faire des tests
## Supplément à l'exercice 01 - Corriger les failles
```yaml
services:
app:
build: . # avec un .dockerignore bien formaté
environnement:
DB_PASSWORD: ${DB_PASSWORD} # dans un .env bien écarté du .dockerignore et du .gitignore
APP_DEBUG: "false" # après on est dans un environnement de test, donc bon...
ports:
- "8080:8080"
db:
image: mysql:latest # ou une version 26 à minima, ça va dépendre de quelle version on a utilisé en DEV
ports:
- "3306:3306"
volumes:
- ./dump_clients_test.sql:/docker-entrypoint-initdb.d/init.sql
```
## Supplément à l'exercice 02 - Trouver des vecteurs d'attaque
(HackTricks sur MySQL)[https://hacktricks.wiki/en/network-services-pentesting/pentesting-mysql.html]
1. Se connecter sur le port 3306 à distance et tenter de lire des journaux en clair
2. Lire un éventuel dépôt de code en public (pour récupérer le mot de passe)
3. Regarder l'arborescence sur le port 8080
4. Vérifier les CVE avec Metasploit après un `nmap -sV`
@@ -0,0 +1,13 @@
```bash
uv init --python 3.14
uv add faker
```
```bash
winget update PHP.PHP.8.5
https://getcomposer.org/Composer-Setup.exe
```
```bash
C:\composer\composer require --dev fakerphp/faker
```
@@ -0,0 +1,5 @@
{
"require-dev": {
"fakerphp/faker": "^1.24"
}
}
+206
View File
@@ -0,0 +1,206 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "3929b7bfae922ea6b3da92aabf968467",
"packages": [],
"packages-dev": [
{
"name": "fakerphp/faker",
"version": "v1.24.1",
"source": {
"type": "git",
"url": "https://github.com/FakerPHP/Faker.git",
"reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5",
"reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0",
"psr/container": "^1.0 || ^2.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
},
"conflict": {
"fzaninotto/faker": "*"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4.1",
"doctrine/persistence": "^1.3 || ^2.0",
"ext-intl": "*",
"phpunit/phpunit": "^9.5.26",
"symfony/phpunit-bridge": "^5.4.16"
},
"suggest": {
"doctrine/orm": "Required to use Faker\\ORM\\Doctrine",
"ext-curl": "Required by Faker\\Provider\\Image to download images.",
"ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.",
"ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.",
"ext-mbstring": "Required for multibyte Unicode string functionality."
},
"type": "library",
"autoload": {
"psr-4": {
"Faker\\": "src/Faker/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "François Zaninotto"
}
],
"description": "Faker is a PHP library that generates fake data for you.",
"keywords": [
"data",
"faker",
"fixtures"
],
"support": {
"issues": "https://github.com/FakerPHP/Faker/issues",
"source": "https://github.com/FakerPHP/Faker/tree/v1.24.1"
},
"time": "2024-11-21T13:46:39+00:00"
},
{
"name": "psr/container",
"version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
"shasum": ""
},
"require": {
"php": ">=7.4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common Container Interface (PHP FIG PSR-11)",
"homepage": "https://github.com/php-fig/container",
"keywords": [
"PSR-11",
"container",
"container-interface",
"container-interop",
"psr"
],
"support": {
"issues": "https://github.com/php-fig/container/issues",
"source": "https://github.com/php-fig/container/tree/2.0.2"
},
"time": "2021-11-05T16:47:00+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d",
"reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/contracts",
"name": "symfony/contracts"
},
"branch-alias": {
"dev-main": "3.7-dev"
}
},
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-06-05T06:23:12+00:00"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {},
"platform-dev": {},
"plugin-api-version": "2.9.0"
}
@@ -0,0 +1,8 @@
from faker import Faker
faker = Faker(["fr_FR", "it_IT", "en_UK"])
Faker.seed(2026)
for _ in range(20):
print(f"{faker.name()} - {faker.city()} - {faker.email()}")
@@ -0,0 +1,79 @@
"""EXERCICE 8 — Fabriquer un jeu de données de test.
python generer_clients.py
Cette version n'utilise AUCUNE dépendance : elle fabrique les données à la
main pour que l'exercice tourne partout. En entreprise, on utiliserait Faker :
pip install faker
from faker import Faker
faker = Faker("fr_FR")
Faker.seed(2026)
faker.last_name() faker.email() faker.city()
TROIS CHOSES À REMARQUER :
1. la graine est figée → même exécution = mêmes données
2. les e-mails sont en @example.org → domaine réservé, qui n'existe pas
3. des cas volontairement pénibles sont injectés → c'est là que ça casse
"""
import csv
import random
from pathlib import Path
GRAINE = 2026
NOMBRE_DE_CLIENTS = 500
random.seed(GRAINE) # 1. reproductibilité
NOMS = ["Martin", "Bernard", "Dubois", "Thomas", "Robert", "Petit", "Durand",
"Leroy", "Moreau", "Simon", "Laurent", "Michel", "Garcia", "David"]
PRENOMS = ["Camille", "Louis", "Léa", "Hugo", "Emma", "Nathan", "Chloé",
"Lucas", "Manon", "Enzo", "Inès", "Théo", "Jade", "Raphaël"]
VILLES = ["Lyon", "Périgueux", "Neuvic", "Bordeaux", "Lille", "Nantes",
"Strasbourg", "Toulouse", "Rennes", "Le Puy-en-Velay"]
# 3. Les cas pénibles : c'est là que les bugs se cachent.
CAS_PENIBLES = [
("O'Connor", "Sean", "Brest"),
("Nguyễn", "Anh", "Paris"),
("Vandenberghe-" * 8, "Jan", "Lille"),
("Müller", "Jörg", "Colmar"),
("Test 🙂", "Emoji", "Nice"),
]
clients = []
for i, (nom, prenom, ville) in enumerate(CAS_PENIBLES, start=1):
clients.append({
"id": i,
"nom": nom,
"prenom": prenom,
"email": f"client{i}@example.org", # 2. domaine réservé
"ville": ville,
"siret": f"{random.randint(0, 99999999):014d}",
})
for i in range(len(clients) + 1, NOMBRE_DE_CLIENTS + 1):
clients.append({
"id": i,
"nom": random.choice(NOMS),
"prenom": random.choice(PRENOMS),
"email": f"client{i}@example.org",
"ville": random.choice(VILLES),
"siret": f"{random.randint(0, 99999999):014d}",
})
sortie = Path(__file__).parent / "clients.csv"
with sortie.open("w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=list(clients[0]))
writer.writeheader()
writer.writerows(clients)
print(f"{len(clients)} clients générés dans {sortie}")
print("Relancez le script : le fichier sera strictement identique (graine figée).\n")
print("Aperçu des 5 cas pénibles :")
for c in clients[:5]:
print(f" {c['nom']:<30} {c['prenom']:<10} {c['email']}")
print()
@@ -1,9 +1,9 @@
[project]
name = "livecampus"
name = "faker-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"git-filter-repo>=2.47.0",
"faker>=40.38.0",
]
+35
View File
@@ -0,0 +1,35 @@
version = 1
revision = 3
requires-python = ">=3.14"
[[package]]
name = "faker"
version = "40.38.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "tzdata", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f2/55/baeae0ecb04ef56c92d6ac1ef7b3b965af0502ac9863fc85db7afc0884fd/faker-40.38.0.tar.gz", hash = "sha256:72e421098664edf38478f4269a5d5a539337de5da18883ce67cb1e6bb96b0b3a", size = 2029190, upload-time = "2026-09-01T19:19:46.958Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f1/b8/48cb8733de8448023983f64e81d7f731ce1459c21e93b9b29657e5c48cab/faker-40.38.0-py3-none-any.whl", hash = "sha256:69927515d61759c8a257540b8a91891cd622ba1c24a386b62a0a777e44ba46a9", size = 2065931, upload-time = "2026-09-01T19:19:45.234Z" },
]
[[package]]
name = "faker-project"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "faker" },
]
[package.metadata]
requires-dist = [{ name = "faker", specifier = ">=40.38.0" }]
[[package]]
name = "tzdata"
version = "2026.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" },
]
@@ -0,0 +1,309 @@
# Atelier - Adam GABRIEL, Louis LEULIETTE, Gauvain BOICHÉ
## ScèneLibre
> ScèneLibre revend des billets de concerts et de spectacles. Cinq salles partenaires aujourd'hui, une quarantaine prévues dans un an.
> Le modèle : la salle publie ses dates, les clients achètent en ligne, ScèneLibre prend une commission de 6 %.
### Côté client
- rechercher un spectacle par ville, date ou artiste
- choisir une catégorie de place et une quantité
- payer par carte bancaire
- recevoir son billet par e-mail, avec un code-barres
- demander un remboursement si le spectacle est annulé
### Côté salle partenaire
- publier une date, définir les catégories et les tarifs
- suivre les ventes en temps réel
- éditer la liste des entrées le soir du spectacle
### Côté ScèneLibre
- valider les salles partenaires
- suivre les commissions
- éditer les factures mensuelles aux salles
### Règles métier
1. Un client ne peut pas acheter plus de 6 places pour un même spectacle.
2. Le tarif réduit (−30 %) s'applique aux moins de 26 ans et aux demandeurs d'emploi, sur justificatif
téléversé.
3. La vente ferme 2 heures avant le début du spectacle.
4. Un billet est nominatif : le nom du porteur est imprimé dessus.
5. En cas d'annulation, le remboursement est intégral, commission comprise, sous 14 jours.
6. Une place vendue ne peut jamais l'être deux fois.
7. La commission de 6 % est calculée sur le prix hors taxes.
8. La TVA sur les spectacles est de 5,5 %.
### Architecture technique
- un site web, consulté à 70 % depuis un téléphone
- une interface d'administration pour les salles
- une base de données
- un service de paiement externe (prestataire bancaire)
- un service d'envoi d'e-mails externe
- un générateur de PDF pour les billets
### Contraintes
- Le pic de vente. Quand une grosse tête d'affiche est mise en vente, tout le monde se connecte à la
même minute. Le record attendu : 40 000 personnes en 10 minutes.
- L'argent. Chaque transaction est un paiement réel.
- Les données personnelles. Noms, adresses, dates de naissance, justificatifs de situation. Et les
coordonnées bancaires transitent par le prestataire.
- La loi. Le site est un service au public : l'accessibilité est une obligation légale.
- L'équipe. 4 développeurs, 1 personne dédiée à la qualité — vous.
- Le budget. Modeste. Une start-up.
### Ce qui existe déjà
- du code, écrit vite, sans aucun test automatisé
- une seule machine, qui sert à la fois de serveur de développement et de démo commerciale
- une base de données, remplie à la main, avec 12 spectacles factices
- les mots de passe du prestataire de paiement sont dans un fichier de configuration, envoyé dans le
dépôt Git
- personne n'a jamais fait de test de charge
## Mission 01 - Analyse de risque
| Risque | Ce qui se passe | Gravité | Propabilité | Priorité |
|-------- |----------------- |--------- |------------- |---------- |
| Clef API presta dans le dépôt GIT | Bah... voilà quoi | 5 | 5 | 25 |
| Aucun test de charge préalable | Aucun moyen de savoir si une tête d'affiche sera absorbée | 1 | 4 | 4 |
| Base de données remplie à la main | Erreurs probables si non compartimentation bdd Dev/Prod | 3 | 3 | 9 |
| Code écrit vite sans aucun test | Le code peut contenir des erreurs s'il n'est pas corrigé et testé en condition réelle | 5 | 3 | 15 |
| Serveur unique de dev et de démo | En cas d'incident, plus rien | 5 | 2 | 10 |
| Risque RGPD | Aucune vérification de la conformité RGPD sur la sécurisation des données en transit | 5 | 4 | 20 |
| Délai de mise en prod trop court | Si le projet est mal géré dans le temps, la production peut ne pas se faire à temps | 3 | 2 | 6 |
| Pas de redondance dans les prestataires | Si le prestataire rencontre un incident de son côté, les paiements sont interrompus | 5 | 1 | 5 |
Par priorité :
1. Clefs API à purger du dépôt APRES les avoir changées
2. Risques RGPD
3. Codé écrit à la va-vite à Linter
4. Dupliquer les serveurs, ou refaire l'infra en compartimentant
5. Dupliquer les BDD : DEV, STG et PRD
6. Délai de prod à tenir (on serre les fesses)
7. Trouver un prestataire alternatif en redondance
8. Définir des tests de charge pour les affluences
> *Si vous n'aviez le temps de tester qu'une seule chose, laquelle serait-ce ?*
> => **Le test de charge**
## Mission 02 - Stratégie de test
### 1. Clefs APIs (et tout système de connexion) à purger
| Type de test | SAST (Pénétration) |
|--------------------- |--- |
| Qui l'exécute | Développeur |
| Quand | A chaque envoi de code |
| Automatisé / Manuel | Automatisé, avec SonarQube |
| Confirmation | SonarQube permet au pipeline de continuer les étapes suivantes |
### 2. Conformité RGPD des données en transit
| Type de test | Validation & Intégration |
|--------------------- |--- |
| Qui l'exécute | Réalisation : Développeur, Approbateur : Qualité |
| Quand | A chque envoi de code sur des faux jeux de données |
| Automatisé / Manuel | Automatisé, avec un outil de journalisation verbeux |
| Confirmation | Aucune donnée en clair (tout chiffré) dans les artefacts du pipeline |
### 3. Base de code incertaine
| Type de test | Unitaire (Statique) |
|--------------------- |--- |
| Qui l'exécute | Développeur |
| Quand | A chaque envoi de code |
| Automatisé / Manuel | Automatisé, avec ESLint ou équivalent selon langage |
| Confirmation | Le code est qualifié et sans erreur majeure, et le pipeline se poursuit |
### 4. Tests généraux de bon fonctionnement (charge, accessibilité, etc)
| Type de test | Charge, Accessibilité |
|--------------------- |--- |
| Qui l'exécute | Développeur |
| Quand | A chaque envoi de code, ou Chaque nuit selon la lourdeur des tests (+ de 30 minutes) |
| Automatisé / Manuel | Automatique, puis Manuel |
| Confirmation | Artefacts résiduels validés le matin |
### Frise chronologique
Développeur écrit son code
V
Test pénétration SonarQube (10 min)
V
Test unitaire *linter* (3 min)
V
Test de validation RGPD (4 min)
V
Test d'accessibilité (2 min)
V
Test de charge (10 min, ou plus, ou moins selon critères)
V
Test d'intégration (3 min)
V
Le site est en ligne
## Mission 03 - Conception des cas de test
### 1. Test "Scénario basique"
> 6% de commission sur le montant HT
> TVA à 5,5%
> Billet nominatif
> Pas de vente 2h avant le spectacle
1. Test d'un prix P HT fixe type 100€ => Calcul final (P * 1,06 * 1,055) soit attendu 111,83€
2. Billet acheté sans prénom-nom => Erreur / Refus
3. Billet acheté 10h avant le spectacle => OK
4. Billet acheté 1h avant le spectacle => Erreur / Refus
5. Billet acheté deux fois => Erreur / Refus (vérification des ID avant)
#### Exceptions
6. Billet acheté 1h avant le spectacle sur un autre fuseau horaire => Horaire fixée à l'heure serveur
7. Billet unique acheté au même centième de seconde => Mise en tampon des achats pour vérification, gestion à la nanoseconde, sinon un random()
### 2. Test "Tarif réduit"
> -30% pour les moins de 26 ans
1. Commande à 12 ans => Réduction (règle pour les -18 ans ? Interdit de commander pour les mineurs ?)
2. Commande à 18 ans => Réduction
3. Commande à 25 ans => Réduction
4. Commande à 26 ans => Réduction ou Tarif normal ? (moins de 26 ans strict ou 26 ans inclus)
5. Commande à 35 ans => Tarif normal
6. Commande à 35 ans, chômeur, justifié => Réduction
7. Commande à 35 ans, chômeur, injustifié => Tarif normal
### 3. Test "6 places maximum"
> 6 places maximum sur un seul spectacle
> Chaque place est vendue une seule fois
1. Commande de 0 place => Erreur / Refus
2. Commande de 1 à 6 places => OK
3. Commande de 7 places => Erreur / Refus
## Mission 04 - Environnements
### 1. L'infrastructure
![Infrastructure](infra.png)
> L'infrastructure présentée compte la Stack principale, mais ne compte pas les artefacts logiciels, les dépôts de code, les interfaces tierces comme des CloudFront ou des WAF, ni les applications déployées dans l'EKS comme celles dédiées à l'authentification des justificatifs téléversés, des tunnels de connexion, etc.
| Nom | DEV | STG | PRD |
|------------------- |------------------------------------------ |-------------------------------------------------------- |--------------------------------------------------------------------------------------- |
| Qui y accède | Les développeurs | Les développeurs | Les développeurs |
| Quelles données | Le code en test + faux jeu de données | Le code testé et stabilisé + faux jeu de données | Le code stabilisé + les données de production |
| Dimensionné comment | Avec un KEDA dans EKS, 15% des ressources | Avec un KEDA dans EKS, 15% des ressources | Avec un KEDA dans EKS, 70% des ressources |
| Durée de vie | Perpétuel | Perpétuel | Perpétuel |
| Ce qu'on y fait | On pousse du test, on casse, c'est la fête | Le test stabilisé en DEV est testé en conditions de prod | Le test stable subit les derniers tests d'intégration et de validation avec déploiement |
### 2. Jeu de données
> Mettons qu'une salle fasse avec une moyenne à 400 sur les 5 salles partenaires. Et que pour 40.000 places dispo, 50.000 tentent d'en acheter. Soit 40.000 places / 5 salles / 400 places = 20 spectacles, pour 50.000 clients.
- 5 salles
- 20 spectacles
- 40.000 places
- 50.000 commandes
Avec l'usage d'un outil de génération de fausses identités (soit librairie type Faker, soit un outil maison qui fonctionne avec des dictionnaires faciles à trouver/peupler et facile à coder en quelques fonctions), on peut aisément peupler 50.000 clients dans une base de données de STG (pas de DEV).
Les test vont croiser les tests de la `Mission 03` pour tester et retourner les résultats (Plus de 7 places, 0 place, chômeur injustifié, 26 ans, billets en double, etc)
- Les tests sur les + de 7 places doivent échouer
- Les billets doivent ne plus être vendus après 40.000 commandes (10.000 commandes échouées sur le volume total, sans compter les tentatives à + de 6 places)
- Un test idempotent doit renvoyer la même masse d'échecs (10.000 commandes échouées sur 50.000), les mêmes retours sur les masses éligibles aux réductions (-26 ans ou chômeurs, avec justificatifs)
### 3. Budget
> Budget excluant masse salariale : 14.000€ / mois
- Pour EC2 Vault : t4g.small (2 vCPU, 2GB ram) = 7,5€ / mois
- Pour EKS : 1 cluster avec 2 noeuds hybrides à 8vCPUs + 1 App ArgoCD : 350€ / mois
- Pour RDS :
- DEV et STG : db.t4g.small = 16€ x 2 (32€) / mois
- PRD : db.t4g.xlarge = 126€ / mois
- Pour S3 : 1,1 € pour 100 Gigas de données stockées en Intelligent-Tiering avec 75% de données de + de 3 mois (Glacier IR) (à cumuler par tranches de 100 GB)
Soit 516,7€ par mois le premier mois, avec 1,1€ supplémentaire par mois chaque mois (estimation).
On peut dimensionner l'EKS et les RDS à 'laise avec un budget de 14.000€ / mois.
## Mission 05 - Sécurité et conformité
### 1. Les trois urgences
#### A. Code écrit vite, sans test
> Failles potentielles, un NMAP + Metasploit pourrait ouvrir des portes d'entrée manifestes. Si le code est rédigé avec l'IA, aucune sécurité par défaut.
La mise en place des tests unitaires, la relecture du code et les patchs applicatifs + l'intégration d'une philosophie de "sécurité par défaut" (usage d'images renforcées / lancées avec des utilisateurs non `root`), d'une instance Vault dédiée aux secrets dans un VPC isolé, d'un Répartiteur de Charge avec un WAF pour bloquer les requêtes anormales ou faire de l'obstruction sur les scans suffisent à sécuriser l'infrastructure contre 99,99% des attaques.
#### B. Une seule machine
> Un attaquant peut lancer une attaque DDOS sur la machine et mettre l'équipe de développement au chômage technique + arrêter toute la mise en vente des billets, et faire perdre l'état des ventes actuelles.
Répartir la stack dans une grappe Kubernetes et compartimentée selon les environnements donne une résilience à l'infrastructure - pour peu que le dimensionnement en cas d'affluence soit bien géré, et que les "fausses" affluences soient bloquées par un WAF - et permet un développement serein de l'application. De plus, le point d'entrée étant protégé par défaut (avec le WAF), rentrer dans la grappe sera autrement plus compliqué.
#### C. Clef API du presta de paeiment en clair sur le dépôt GIT
> Un attaquant peut regarder le dépôt s'il est en public, et se faire passer pour ScèneLibre auprès du prestataire, et se faire payer au nom de ScèneLibre pour "n'importe quoi". Eventuellement ouvrir un site internet illégal (drogues, armes, thaïlandaises, etc) avec une gestion de paiement intitulé "ScèneLibre".
1. Changer les clefs API
2. Demander au prestataire un rapport des usages API (sauf si c'est accessible sur panneau admin client)
3. Pousser du nouveau code avec une clef API en `Secret` du dépôt GIT (en attendant Vault)
##### Option NUCLÉAIRE
1. Changer les clefs API
2. Demander au prestataire un rapport des usages API (sauf si c'est accessible sur panneau admin client)
3. Supprimer le dépôt et en refaire un au propre, et en invisible, sur une instance dédiée (Gitlab, ForgeJo, Gitea, etc) dans le nouveau compte nuagique sur un EC2 dédié
### 2. Le RGPD
> Vous voulez des données réalistes pour tester. Le directeur commercial vous propose une copie de la base de démonstration, qui contient les vrais clients des cinq salles partenaires.
#### Réponse
> Monsieur,
> Vous nous avez proposé une copie de la base de données "de démonstration" contenant les salles de spectacles partenaires actuelles.
> Ces données étant des données réelles, et contenant des informations sensibles et protégées par le RGPD, nous devons refuser.
> Les tests doivent se faire sur des données réalistes mais fictives afin d'éviter, d'une part, des fuites potentiellement néfastes pour ScèneLibre et vos partenaires, et d'autre part, une violation dans l'usage de données sensibles.
> Nous allons prendre le temps de générer de fausses données pour éviter des risques judiciaires et de sécurité.
> Cordialement,
### 3. Le Pentest
1. Le test se fera sur l'EKS entier (le Répartiteur de Charge) pour vérifier la résilience du WAF. Ensuite seront tentés les contournements du VPC et des connexions sur les RDS et les S3 (avec un URI absolu/scanneur d'URIs)
2. L'autorisation écrite doit mentionner le périmètre du test, les outils utilisés (même en restant vague), les méthodes (passives seulement, passives et actives, etc) et les possibilités de test (peux-tu "faire planter" l'infrastructure durant le test, ou juste remettre un rapport de faisabilité hypothétique)
3. Les conséquences d'un arrêt de la production vont dépendre du contrat. S'il est précisé et signé que c'est une possibilité, la couverture légale (contractuelle) sera suffisante. Sinon, le prestataire sera redevable pénalement de l'arrêt provoqué. Pénalités d'interruption de production, intrusion, sabotage... etc
4. Les données accessibles au public - un NSLOOKUP, les informations de registrars, les détails des pages "A propos" - sont visibles soit par obligations légales (hébergeur, siège social, directeur de la publication, SIRET, etc), mais les données de production - le code source, les identifiants, les données clients - ne doivent apparaître NULLE PART
## Restitution
![Prévision du projet](gantt.png)
1. Quel est le risque numéro 1, et comment le couvrir ?
> Une attaque DDOS. Un pare-feu.
2. Qu'est-ce qui bloque la mise en ligne, et qu'est-ce qui ne la bloque pas ?
>
3. Qu'avez-vous décidé de NE PAS tester, et pourquoi ?
>
4. Êtes-vous prêts à signer pour une mise en production dans trois semaines ?
> Non.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

+107
View File
@@ -0,0 +1,107 @@
<mxfile host="app.diagrams.net">
<diagram name="Page-1" id="cmCZJx8tw2y15-M8svDn">
<mxGraphModel dx="979" dy="666" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1000" pageHeight="600" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="q1351BH30wJjeyTeLPon-3" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;labelPosition=left;verticalLabelPosition=top;align=right;verticalAlign=bottom;spacing=2;spacingLeft=0;spacingRight=-130;" value="Infrastructure proposée" vertex="1">
<mxGeometry height="520" width="760" x="200" y="40" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-5" parent="1" style="fillColor=none;strokeColor=light-dark(#ed7100, #ff8575);verticalAlign=top;fontStyle=0;fontColor=#DD3522;whiteSpace=wrap;html=1;" value="" vertex="1">
<mxGeometry height="240" width="520" x="240" y="80" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-1" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;labelPosition=center;verticalLabelPosition=bottom;align=center;verticalAlign=top;spacingRight=0;" value="Infrastructure actuelle" vertex="1">
<mxGeometry height="160" width="120" x="40" y="40" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-2" parent="1" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#ED7100;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.ec2;" value="Serveur&lt;div&gt;Dev&lt;/div&gt;&lt;div&gt;Stg&lt;/div&gt;&lt;div&gt;Prod&lt;/div&gt;" vertex="1">
<mxGeometry height="40" width="40" x="80" y="80" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-4" parent="1" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=light-dark(#ed7100, #bdc7d4);fillColor=#ED7100;strokeColor=#ffffff;dashed=0;verticalLabelPosition=middle;verticalAlign=middle;align=left;html=1;fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.eks;labelPosition=right;spacing=10;" value="eks-scenelibre-ew3" vertex="1">
<mxGeometry height="40" width="40" x="240" y="80" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-6" parent="1" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#7AA116;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=10;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.s3;spacingLeft=40;" value="s3-scenelibre-user-uploads" vertex="1">
<mxGeometry height="40" width="40" x="840" y="280" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-19" edge="1" parent="1" source="q1351BH30wJjeyTeLPon-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;startArrow=classic;startFill=1;" target="q1351BH30wJjeyTeLPon-16">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-7" parent="1" style="fillColor=none;strokeColor=#5A6C86;dashed=1;verticalAlign=top;fontStyle=0;fontColor=#5A6C86;whiteSpace=wrap;html=1;" value="namespace : dev" vertex="1">
<mxGeometry height="120" width="120" x="280" y="160" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-20" edge="1" parent="1" source="q1351BH30wJjeyTeLPon-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;startArrow=classic;startFill=1;" target="q1351BH30wJjeyTeLPon-17">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-8" parent="1" style="fillColor=none;strokeColor=#5A6C86;dashed=1;verticalAlign=top;fontStyle=0;fontColor=#5A6C86;whiteSpace=wrap;html=1;" value="namespace : stg" vertex="1">
<mxGeometry height="120" width="120" x="440" y="160" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-21" edge="1" parent="1" source="q1351BH30wJjeyTeLPon-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;startArrow=classic;startFill=1;" target="q1351BH30wJjeyTeLPon-18">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-9" parent="1" style="fillColor=none;strokeColor=#5A6C86;dashed=1;verticalAlign=top;fontStyle=0;fontColor=#5A6C86;whiteSpace=wrap;html=1;" value="namespace : prd" vertex="1">
<mxGeometry height="120" width="120" x="600" y="160" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-10" parent="1" style="sketch=0;html=1;dashed=0;whitespace=wrap;verticalLabelPosition=bottom;verticalAlign=top;fillColor=#2875E2;strokeColor=#ffffff;points=[[0.005,0.63,0],[0.1,0.2,0],[0.9,0.2,0],[0.5,0,0],[0.995,0.63,0],[0.72,0.99,0],[0.5,1,0],[0.28,0.99,0]];shape=mxgraph.kubernetes.icon2;prIcon=cronjob;aspect=fixed;" value="scenelibre" vertex="1">
<mxGeometry height="40" width="40" x="320" y="200" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-11" parent="1" style="sketch=0;html=1;dashed=0;whitespace=wrap;verticalLabelPosition=bottom;verticalAlign=top;fillColor=#2875E2;strokeColor=#ffffff;points=[[0.005,0.63,0],[0.1,0.2,0],[0.9,0.2,0],[0.5,0,0],[0.995,0.63,0],[0.72,0.99,0],[0.5,1,0],[0.28,0.99,0]];shape=mxgraph.kubernetes.icon2;prIcon=cronjob;aspect=fixed;" value="scenelibre" vertex="1">
<mxGeometry height="40" width="40" x="480" y="200" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-12" parent="1" style="sketch=0;html=1;dashed=0;whitespace=wrap;verticalLabelPosition=bottom;verticalAlign=top;fillColor=#2875E2;strokeColor=#ffffff;points=[[0.005,0.63,0],[0.1,0.2,0],[0.9,0.2,0],[0.5,0,0],[0.995,0.63,0],[0.72,0.99,0],[0.5,1,0],[0.28,0.99,0]];shape=mxgraph.kubernetes.icon2;prIcon=cronjob;aspect=fixed;" value="scenelibre" vertex="1">
<mxGeometry height="40" width="40" x="640" y="200" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-13" parent="1" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#C925D1;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.rds;" value="rds-scenelibre-dev-ew3" vertex="1">
<mxGeometry height="40" width="40" x="320" y="440" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-14" parent="1" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#C925D1;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.rds;" value="rds-scenelibre-stg-ew3" vertex="1">
<mxGeometry height="40" width="40" x="480" y="440" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-15" parent="1" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#C925D1;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.rds;" value="rds-scenelibre-prd-ew3" vertex="1">
<mxGeometry height="40" width="40" x="640" y="440" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-24" edge="1" parent="1" source="q1351BH30wJjeyTeLPon-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;startArrow=classic;startFill=1;" target="q1351BH30wJjeyTeLPon-13">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-16" parent="1" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#DD344C;strokeColor=none;dashed=0;verticalLabelPosition=middle;verticalAlign=middle;align=right;html=1;fontSize=10;fontStyle=0;pointerEvents=1;shape=mxgraph.aws4.role;aspect=fixed;labelPosition=left;" value="eks-scenelibre-dev" vertex="1">
<mxGeometry height="40" width="40" x="320" y="360" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-23" edge="1" parent="1" source="q1351BH30wJjeyTeLPon-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;startArrow=classic;startFill=1;" target="q1351BH30wJjeyTeLPon-14">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-17" parent="1" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#DD344C;strokeColor=none;dashed=0;verticalLabelPosition=middle;verticalAlign=middle;align=right;html=1;fontSize=10;fontStyle=0;pointerEvents=1;shape=mxgraph.aws4.role;aspect=fixed;labelPosition=left;" value="eks-scenelibre-stg" vertex="1">
<mxGeometry height="40" width="40" x="480" y="360" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-22" edge="1" parent="1" source="q1351BH30wJjeyTeLPon-18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;startArrow=classic;startFill=1;" target="q1351BH30wJjeyTeLPon-15">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-27" edge="1" parent="1" source="q1351BH30wJjeyTeLPon-18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" target="q1351BH30wJjeyTeLPon-6">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="800.03" y="380.03" />
<mxPoint x="800.03" y="300.03" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-18" parent="1" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#DD344C;strokeColor=none;dashed=0;verticalLabelPosition=middle;verticalAlign=middle;align=right;html=1;fontSize=10;fontStyle=0;pointerEvents=1;shape=mxgraph.aws4.role;aspect=fixed;labelPosition=left;" value="eks-scenelibre-prd" vertex="1">
<mxGeometry height="40" width="40" x="640" y="360" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-30" edge="1" parent="1" source="q1351BH30wJjeyTeLPon-28" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" target="q1351BH30wJjeyTeLPon-29">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-28" parent="1" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#ED7100;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.ec2;" value="ec2-scenelibre-vault-ew3" vertex="1">
<mxGeometry height="40" width="40" x="840" y="160" as="geometry" />
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-31" edge="1" parent="1" source="q1351BH30wJjeyTeLPon-29" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" target="q1351BH30wJjeyTeLPon-5">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="800" y="100" />
<mxPoint x="800" y="160" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="q1351BH30wJjeyTeLPon-29" parent="1" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#DD344C;strokeColor=none;dashed=0;verticalLabelPosition=top;verticalAlign=bottom;align=center;html=1;fontSize=10;fontStyle=0;pointerEvents=1;shape=mxgraph.aws4.role;aspect=fixed;labelPosition=center;" value="eks-scenelibre-vault" vertex="1">
<mxGeometry height="40" width="40" x="840" y="80" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

-6
View File
@@ -1,6 +0,0 @@
def main():
print("Hello from livecampus!")
if __name__ == "__main__":
main()
Generated
-23
View File
@@ -1,23 +0,0 @@
version = 1
revision = 3
requires-python = ">=3.14"
[[package]]
name = "git-filter-repo"
version = "2.47.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b9/bb/7a283f568af6b0528ade65e8ace84bd6ba46003e429101bcd62c232d01a5/git_filter_repo-2.47.0.tar.gz", hash = "sha256:411b27e68a080c07a69c233cb526dbc2d848b09a72f10477f4444dd0822cf290", size = 275743, upload-time = "2024-12-04T03:10:48.2Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/60/60/d3943f0880ebcb7e0bdf79254d10dddd39c7b656eeecae32b8806ff66dec/git_filter_repo-2.47.0-py3-none-any.whl", hash = "sha256:2cd04929b9024e83e65db571cbe36aec65ead0cb5f9ec5abe42158654af5ad83", size = 76282, upload-time = "2024-12-04T03:10:46.064Z" },
]
[[package]]
name = "livecampus"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "git-filter-repo" },
]
[package.metadata]
requires-dist = [{ name = "git-filter-repo", specifier = ">=2.47.0" }]