TP : Protéger une API locale avec limitation de débit
Prérequis
Environnement technique
- Docker installé et fonctionnel.
- Docker Compose disponible (
docker compose version). - curl installé.
- Accès à un terminal Bash ou Zsh.
- Accès Internet pour télécharger les images Docker.
Installer jq
Sur Debian / Ubuntu :
sudo apt updatesudo apt install -y jqSur macOS avec Homebrew :
brew install jqVérifier les outils
docker --versiondocker compose versioncurl --versionOutils utilisés
python:3.12-alpinekong:3.9Précaution
Ce TP crée uniquement un environnement local.
Aucune ressource cloud n’est créée.
Les conteneurs doivent être arrêtés à la fin du TP.
Objectifs
À la fin de ce TP, l’apprenant doit être capable de :
- Déployer une API locale dans un conteneur.
- Déployer Kong OSS comme API Gateway.
- Configurer une route Kong vers une API locale.
- Appliquer un plugin de limitation de débit par adresse IP.
- Déclencher et observer un blocage HTTP 429.
- Modifier un quota à chaud et observer l’effet.
- Configurer une authentification par clé API.
- Appliquer un quota par consommateur authentifié.
- Comparer le comportement de deux clients avec des quotas indépendants.
- Produire un rapport local des tests réalisés.
Commandes
1. Créer l’arborescence du TP
mkdir -p tp-api-rate-limitcd tp-api-rate-limit
mkdir -p configs reports scripts2. Vérifier l’arborescence
find . -maxdepth 2 -type d3. Définir les variables du TP
cat > scripts/env.sh <<'EOF'export TP_NAME="tp-api-rate-limit"export API_DIRECT_URL="http://localhost:9000/hello"export API_GATEWAY_URL="http://localhost:8000/api/hello"export KONG_ADMIN_URL="http://localhost:8001"export CLIENT_A_KEY="client-a-secret"export CLIENT_B_KEY="client-b-secret"EOFsource scripts/env.sh4. Créer l’API locale
cat > api.py <<'EOF'from http.server import BaseHTTPRequestHandler, HTTPServerimport jsonimport time
class Handler(BaseHTTPRequestHandler): def do_GET(self): response = { "status": "ok", "message": "Réponse de l'API locale", "path": self.path, "timestamp": time.time() }
body = json.dumps(response, indent=2).encode("utf-8")
self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body)
def log_message(self, format, *args): return
if __name__ == "__main__": server = HTTPServer(("0.0.0.0", 8080), Handler) print("API locale disponible sur le port 8080") server.serve_forever()EOF5. Créer la configuration Kong initiale
cat > configs/kong.yml <<'EOF'_format_version: "3.0"_transform: true
services: - name: local-api-service url: http://api:8080 routes: - name: local-api-route paths: - /api strip_path: true plugins: - name: rate-limiting config: second: 5 minute: 20 limit_by: ip policy: local fault_tolerant: true hide_client_headers: false error_code: 429 error_message: "Trop de requêtes vers cette API"EOFCette configuration déclare :
local-api-service: service Kong représentant l’API locale.url: http://api:8080: adresse interne de l’API dans Docker./api: chemin public exposé par Kong.strip_path: true: supprime/apiavant d’appeler l’API locale.rate-limiting: plugin de limitation de débit.second: 5: limite de 5 requêtes par seconde.minute: 20: limite de 20 requêtes par minute.limit_by: ip: limitation par adresse IP.error_code: 429: réponse HTTP en cas de dépassement.
6. Créer le fichier Docker Compose
cat > docker-compose.yml <<'EOF'services: api: image: python:3.12-alpine container_name: tp-api-locale working_dir: /app volumes: - ./api.py:/app/api.py:ro command: python /app/api.py ports: - "9000:8080"
kong: image: kong:3.9 container_name: tp-kong-gateway depends_on: - api environment: KONG_DATABASE: "off" KONG_DECLARATIVE_CONFIG: /kong/declarative/kong.yml KONG_PLUGINS: bundled KONG_PROXY_LISTEN: 0.0.0.0:8000 KONG_ADMIN_LISTEN: 0.0.0.0:8001 KONG_PROXY_ACCESS_LOG: /dev/stdout KONG_ADMIN_ACCESS_LOG: /dev/stdout KONG_PROXY_ERROR_LOG: /dev/stderr KONG_ADMIN_ERROR_LOG: /dev/stderr volumes: - ./configs/kong.yml:/kong/declarative/kong.yml:ro ports: - "8000:8000" - "127.0.0.1:8001:8001"EOFL’Admin API de Kong est exposée uniquement sur 127.0.0.1:8001 pour éviter toute exposition sur le réseau local.
7. Démarrer l’environnement
docker compose up -ddocker compose ps8. Vérifier les logs
docker compose logs apidocker compose logs kongdocker compose logs api > reports/01-api-logs.txtdocker compose logs kong > reports/02-kong-logs.txt9. Vérifier l’Admin API de Kong
curl -s "$KONG_ADMIN_URL" | tee reports/03-kong-admin-api.jsoncurl -s "$KONG_ADMIN_URL/services" | tee reports/04-kong-services.jsoncurl -s "$KONG_ADMIN_URL/routes" | tee reports/05-kong-routes.jsoncurl -s "$KONG_ADMIN_URL/plugins" | tee reports/06-kong-plugins.jsonVérification de l’API locale
10. Tester l’API directement
curl -i "$API_DIRECT_URL" | tee reports/07-api-direct-call.txtCe test montre que l’API fonctionne correctement, mais la requête contourne l’API Gateway.
Dans une architecture réelle, l’API interne ne devrait pas être exposée directement aux utilisateurs finaux.
Exposition via API Gateway
11. Tester l’API via Kong
curl -i "$API_GATEWAY_URL" | tee reports/08-api-gateway-call.txt12. Vérifier la suppression du préfixe /api
La route Kong utilise strip_path: true.
Kong reçoit /api/hello et transmet /hello à l’API locale.
curl -s "$API_GATEWAY_URL" | tee reports/09-strip-path-check.jsonLe champ path de la réponse JSON doit valoir /hello.
Limitation de débit
13. Déclencher le throttling
for i in $(seq 1 10); do echo "Requête $i" curl -s -o /dev/null -w "%{http_code}\n" "$API_GATEWAY_URL"doneDes réponses 429 doivent apparaître lorsque la limite est dépassée.
14. Créer et exécuter un script de test
cat > scripts/test-rate-limit.sh <<'EOF'#!/usr/bin/env sh
URL="${1:-http://localhost:8000/api/hello}"
echo "Test de limitation de débit"echo "URL testée : $URL"echo
for i in $(seq 1 15); do STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$URL") echo "Requête $i : HTTP $STATUS_CODE"doneEOFchmod +x scripts/test-rate-limit.sh./scripts/test-rate-limit.sh "$API_GATEWAY_URL" | tee reports/10-rate-limit-test.txt15. Vérifier la présence de réponses 429
grep -c "HTTP 429" reports/10-rate-limit-test.txt || trueSi aucun 429 n’apparaît, relancer le script immédiatement après la première exécution.
16. Observer les en-têtes HTTP de rate limiting
curl -i "$API_GATEWAY_URL" | tee reports/11-rate-limit-headers.txtgrep -i "ratelimit\|rate-limit\|x-ratelimit" reports/11-rate-limit-headers.txt || trueExemples d’en-têtes retournés par Kong :
X-RateLimit-Limit-SecondX-RateLimit-Remaining-SecondX-RateLimit-Limit-MinuteX-RateLimit-Remaining-MinuteRateLimit-LimitRateLimit-RemainingModification du quota
17. Réduire le quota
python3 - <<'PY'from pathlib import Path
path = Path("configs/kong.yml")content = path.read_text()content = content.replace("second: 5", "second: 2")content = content.replace("minute: 20", "minute: 10")path.write_text(content)print("Configuration mise à jour.")PYgrep -E "second:|minute:" configs/kong.yml18. Redémarrer Kong
docker compose restart kong19. Tester le nouveau quota
./scripts/test-rate-limit.sh "$API_GATEWAY_URL" | tee reports/12-rate-limit-test-reduced-quota.txtLe blocage intervient plus rapidement qu’avec le quota précédent.
Quota par client avec clé API
20. Créer une configuration Kong avec consommateurs
cat > configs/kong.yml <<'EOF'_format_version: "3.0"_transform: true
consumers: - username: client-a keyauth_credentials: - key: client-a-secret
- username: client-b keyauth_credentials: - key: client-b-secret
services: - name: local-api-service url: http://api:8080 routes: - name: local-api-route paths: - /api strip_path: true plugins: - name: key-auth config: key_names: - apikey key_in_header: true key_in_query: false hide_credentials: true
- name: rate-limiting config: minute: 5 limit_by: consumer policy: local fault_tolerant: true hide_client_headers: false error_code: 429 error_message: "Quota dépassé pour ce client"EOFCette configuration ajoute :
consumers: clients connus de Kong avec des clés API distinctes.key-auth: authentification par clé API via le headerapikey.limit_by: consumer: quota séparé par client authentifié.minute: 5: 5 requêtes par minute par client.
21. Redémarrer Kong
docker compose restart kongcurl -s "$KONG_ADMIN_URL/plugins" | tee reports/13-kong-plugins-key-auth.json22. Tester sans clé API
curl -i "$API_GATEWAY_URL" | tee reports/14-call-without-api-key.txtRésultat attendu :
HTTP/1.1 401 Unauthorized23. Tester avec le client A
for i in $(seq 1 7); do echo "Client A - Requête $i" curl -s -o /dev/null -w "%{http_code}\n" \ -H "apikey: $CLIENT_A_KEY" \ "$API_GATEWAY_URL"done | tee reports/15-client-a-rate-limit.txtLe client A dépasse son quota après 5 requêtes et reçoit des 429.
24. Tester avec le client B
for i in $(seq 1 3); do echo "Client B - Requête $i" curl -s -o /dev/null -w "%{http_code}\n" \ -H "apikey: $CLIENT_B_KEY" \ "$API_GATEWAY_URL"done | tee reports/16-client-b-rate-limit.txtLe client B dispose de son propre quota indépendant.
25. Comparer les résultats
cat reports/15-client-a-rate-limit.txtcat reports/16-client-b-rate-limit.txt| Test | Résultat attendu |
|---|---|
| Sans clé API | 401 Unauthorized |
| Client A avec trop de requêtes | 429 Too Many Requests |
| Client B avec peu de requêtes | 200 OK |
Rapport de synthèse
26. Créer un rapport Markdown
{ echo "# Rapport TP : Protection d'une API locale avec limitation de débit" echo echo "## Architecture" echo echo '```text' echo "Client -> Kong OSS -> API locale Python" echo '```' echo echo "## Composants déployés" echo echo "| Composant | Rôle | Port local |" echo "|---|---|---|" echo "| API Python | Service applicatif local | 9000 |" echo "| Kong OSS | API Gateway | 8000 |" echo "| Kong Admin API | Administration locale | 8001 |" echo echo "## Tests réalisés" echo echo "| Test | Résultat attendu |" echo "|---|---|" echo "| Appel direct API | HTTP 200 |" echo "| Appel via Kong | HTTP 200 |" echo "| Dépassement quota par IP | HTTP 429 |" echo "| Appel sans clé API | HTTP 401 |" echo "| Client A avec quota dépassé | HTTP 429 |" echo "| Client B sous le quota | HTTP 200 |" echo echo "## Points pédagogiques" echo echo "- Kong OSS joue le rôle d'API Gateway devant une API locale." echo "- Le plugin rate-limiting protège l'API contre un volume excessif de requêtes." echo "- Le plugin key-auth identifie les clients et applique un quota par consommateur." echo "- La limitation par IP et la limitation par consommateur produisent des comportements différents."} > reports/rapport-tp-api-rate-limit.md27. Afficher le rapport
cat reports/rapport-tp-api-rate-limit.md28. Lister les fichiers générés
find . -maxdepth 3 -type f | sortNettoyage
29. Arrêter les conteneurs
docker compose down30. Vérifier que les conteneurs sont supprimés
docker ps -a | grep "tp-api-locale\|tp-kong-gateway" || true31. Vérifier que les ports sont libérés
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8000 || truecurl -s -o /dev/null -w "%{http_code}\n" http://localhost:9000 || true32. Supprimer le dossier du TP
Se placer dans le dossier parent avant d’exécuter cette commande.
cd ..rm -rf tp-api-rate-limitRésultat attendu
| Élément | Validation |
|---|---|
| API locale | Réponse HTTP 200 sur localhost:9000 |
| API Gateway | Réponse HTTP 200 sur localhost:8000/api |
| Route Kong | Le chemin /api redirige vers l’API locale |
| strip_path | Le champ path retourné par l’API vaut /hello |
| Rate limiting par IP | Les requêtes excessives retournent HTTP 429 |
| Authentification API Key | Les appels sans clé retournent HTTP 401 |
| Rate limiting par consommateur | Les quotas sont indépendants entre client-a et client-b |
| Rapport | Un rapport est généré dans reports/ |
Aucune ressource cloud, aucune clé d’accès AWS et aucun compte externe ne sont utilisés pendant ce TP.