Initial commit
This commit is contained in:
578
TECHNICAL_EXPLANATION.md
Normal file
578
TECHNICAL_EXPLANATION.md
Normal file
@@ -0,0 +1,578 @@
|
||||
# 🔧 Explication technique détaillée - InnotexBoard
|
||||
|
||||
## Table des matières
|
||||
|
||||
1. [Architecture système](#architecture)
|
||||
2. [Authentification PAM + JWT](#authentification)
|
||||
3. [Gestion Docker](#docker)
|
||||
4. [Monitoring système](#monitoring)
|
||||
5. [Frontend et communication API](#frontend)
|
||||
6. [Sécurité](#sécurité)
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture système {#architecture}
|
||||
|
||||
### Diagramme de flux
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Navigateur (Frontend) │
|
||||
│ http://localhost:3000 │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Vue.js 3 Application │ │
|
||||
│ │ - Pages: Login, Dashboard, Containers │ │
|
||||
│ │ - Store Pinia pour l'état │ │
|
||||
│ │ - Axios pour les requêtes HTTP │ │
|
||||
│ └────────────────┬────────────────────────────────────┘ │
|
||||
└─────────────────┬───────────────────────────────────────────┘
|
||||
│
|
||||
│ JWT Bearer Token
|
||||
│ CORS allowlist
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Backend FastAPI │
|
||||
│ http://localhost:8000 │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ /api/v1/auth - Authentification PAM + JWT │ │
|
||||
│ │ /api/v1/system - psutil (CPU/RAM/Processus) │ │
|
||||
│ │ /api/v1/docker - Docker SDK Python │ │
|
||||
│ └────┬─────────────────────┬──────────────────┬────────┘ │
|
||||
└───────┼─────────────────────┼──────────────────┼────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌────────┐ ┌──────────┐ ┌─────────────┐
|
||||
│ PAM │ │ psutil │ │ Docker │
|
||||
│ /etc/ │ │ /proc │ │ Socket │
|
||||
│shadow │ │ /sys │ │ /var/run/ │
|
||||
└────────┘ └──────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
### Communication API
|
||||
|
||||
```
|
||||
Client → POST /api/v1/auth/login
|
||||
(username, password)
|
||||
↓
|
||||
PAM Authentication
|
||||
↓
|
||||
JWT Token Generated
|
||||
↓
|
||||
Server → JSON {token, username}
|
||||
|
||||
Client → GET /api/v1/system/stats
|
||||
Authorization: Bearer {token}
|
||||
↓
|
||||
Token validation
|
||||
↓
|
||||
psutil data gathering
|
||||
↓
|
||||
Server → JSON {cpu, memory, processes}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Authentification PAM + JWT {#authentification}
|
||||
|
||||
### Fichier principal : [backend/app/core/security.py](backend/app/core/security.py)
|
||||
|
||||
### Flux d'authentification
|
||||
|
||||
```python
|
||||
# 1. L'utilisateur envoie ses identifiants
|
||||
POST /auth/login
|
||||
{
|
||||
"username": "john",
|
||||
"password": "secret123"
|
||||
}
|
||||
|
||||
# 2. Backend appelle PAM
|
||||
pam_auth = pam.pam()
|
||||
if pam_auth.authenticate(username, password):
|
||||
# ✅ Identifiants valides
|
||||
# ✅ L'utilisateur existe sur le système
|
||||
|
||||
# 3. Générer un JWT token
|
||||
token = jwt.encode({
|
||||
"sub": username,
|
||||
"exp": datetime.utcnow() + timedelta(hours=8)
|
||||
}, SECRET_KEY, algorithm="HS256")
|
||||
|
||||
# 4. Retourner le token
|
||||
return {
|
||||
"access_token": token,
|
||||
"token_type": "bearer",
|
||||
"username": username
|
||||
}
|
||||
else:
|
||||
# ❌ Identifiants invalides
|
||||
raise HTTP 401 Unauthorized
|
||||
```
|
||||
|
||||
### Validation des requêtes protégées
|
||||
|
||||
```python
|
||||
# Chaque endpoint protégé utilise get_current_user
|
||||
@router.get("/stats")
|
||||
async def get_system_stats(current_user: User = Depends(get_current_user)):
|
||||
# 1. Extraire le token du header Authorization
|
||||
# 2. Décoder et valider le JWT
|
||||
# 3. Vérifier l'expiration
|
||||
# 4. Retourner l'utilisateur ou lever 401
|
||||
return system_stats
|
||||
```
|
||||
|
||||
### Structure du JWT
|
||||
|
||||
```
|
||||
Header:
|
||||
{
|
||||
"alg": "HS256",
|
||||
"typ": "JWT"
|
||||
}
|
||||
|
||||
Payload:
|
||||
{
|
||||
"sub": "john", # subject (username)
|
||||
"exp": 1704067200, # expiration time
|
||||
"iat": 1704060000 # issued at
|
||||
}
|
||||
|
||||
Signature: HMAC-SHA256(header.payload, SECRET_KEY)
|
||||
|
||||
Token final: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
### Avantages PAM vs alternatives
|
||||
|
||||
| Aspect | PAM | LDAP | Local DB |
|
||||
|--------|-----|------|----------|
|
||||
| Utilise les utilisateurs système | ✅ | ✅ | ❌ |
|
||||
| Facile à configurer | ✅ | ❌ | ✅ |
|
||||
| SSO support | Limité | ✅ | ❌ |
|
||||
| Groupe système | ✅ | ✅ | ❌ |
|
||||
| Nécessite sudo | ✅ | ❌ | ❌ |
|
||||
|
||||
---
|
||||
|
||||
## 3. Gestion Docker {#docker}
|
||||
|
||||
### Fichier principal : [backend/app/services/docker_service.py](backend/app/services/docker_service.py)
|
||||
|
||||
### Initialisation de la connexion
|
||||
|
||||
```python
|
||||
import docker
|
||||
|
||||
class DockerService:
|
||||
def __init__(self):
|
||||
# Se connecter via le socket Docker
|
||||
self.client = docker.from_env()
|
||||
# Equivalent à: docker.DockerClient(base_url='unix:///var/run/docker.sock')
|
||||
```
|
||||
|
||||
### Récupérer les conteneurs
|
||||
|
||||
```python
|
||||
def get_containers(self, all=True):
|
||||
"""
|
||||
all=True : obtient tous les conteneurs (running + stopped)
|
||||
all=False : obtient seulement les conteneurs en cours d'exécution
|
||||
"""
|
||||
containers = self.client.containers.list(all=all)
|
||||
|
||||
for container in containers:
|
||||
# Stats en temps réel
|
||||
stats = container.stats(stream=False)
|
||||
|
||||
# Extraction CPU
|
||||
cpu_delta = stats['cpu_stats']['cpu_usage']['total_usage'] - \
|
||||
stats['precpu_stats']['cpu_usage']['total_usage']
|
||||
system_cpu = stats['cpu_stats']['system_cpu_usage'] - \
|
||||
stats['precpu_stats']['system_cpu_usage']
|
||||
cpu_percent = (cpu_delta / system_cpu) * 100.0
|
||||
|
||||
# Extraction mémoire
|
||||
memory_mb = stats['memory_stats']['usage'] / (1024*1024)
|
||||
```
|
||||
|
||||
### Opérations sur les conteneurs
|
||||
|
||||
```python
|
||||
# Démarrer
|
||||
container.start()
|
||||
|
||||
# Arrêter (gracieux)
|
||||
container.stop(timeout=10)
|
||||
|
||||
# Redémarrer
|
||||
container.restart(timeout=10)
|
||||
|
||||
# Supprimer
|
||||
container.remove(force=False) # force=True pour les containers running
|
||||
```
|
||||
|
||||
### Permissions Docker requises
|
||||
|
||||
Pour que le serveur web puisse utiliser Docker, il faut soit :
|
||||
|
||||
1. **Groupe docker** (moins sécurisé)
|
||||
```bash
|
||||
sudo usermod -aG docker www-data
|
||||
```
|
||||
|
||||
2. **Sudo sans mot de passe** (recommandé)
|
||||
```bash
|
||||
echo 'www-data ALL=(ALL) NOPASSWD: /usr/bin/docker' | sudo tee /etc/sudoers.d/docker
|
||||
```
|
||||
|
||||
3. **Socket Docker avec permissions** (avancé)
|
||||
```bash
|
||||
sudo setfacl -m u:www-data:rw /var/run/docker.sock
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Monitoring système {#monitoring}
|
||||
|
||||
### Fichier principal : [backend/app/services/system.py](backend/app/services/system.py)
|
||||
|
||||
### Utilisation CPU
|
||||
|
||||
```python
|
||||
import psutil
|
||||
|
||||
# CPU % (moyenne sur 1 seconde)
|
||||
cpu_percent = psutil.cpu_percent(interval=1)
|
||||
# Retourne: 45.3
|
||||
|
||||
# Nombre de cores
|
||||
cpu_count = psutil.cpu_count()
|
||||
# Retourne: 8
|
||||
```
|
||||
|
||||
### Utilisation mémoire
|
||||
|
||||
```python
|
||||
memory = psutil.virtual_memory()
|
||||
# memory.percent → 65.2%
|
||||
# memory.used → 4GB (en bytes)
|
||||
# memory.total → 8GB (en bytes)
|
||||
# memory.available → 2GB (en bytes)
|
||||
```
|
||||
|
||||
### Processus actifs
|
||||
|
||||
```python
|
||||
for proc in psutil.process_iter(['pid', 'name', 'status', 'username']):
|
||||
try:
|
||||
info = proc.info
|
||||
cpu_pct = proc.cpu_percent(interval=0.1)
|
||||
mem_pct = proc.memory_percent()
|
||||
|
||||
# Résultat:
|
||||
# {
|
||||
# "pid": 1234,
|
||||
# "name": "python3",
|
||||
# "status": "running",
|
||||
# "username": "www-data",
|
||||
# "cpu_percent": 5.2,
|
||||
# "memory_percent": 0.8
|
||||
# }
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
continue # Accès denied pour certains processus
|
||||
```
|
||||
|
||||
### Points d'accès système
|
||||
|
||||
- `/proc/cpuinfo` → Info CPU
|
||||
- `/proc/meminfo` → Info mémoire
|
||||
- `/proc/[pid]/` → Info processus
|
||||
- `/sys/class/net/` → Info réseau
|
||||
|
||||
### Permissions nécessaires
|
||||
|
||||
```bash
|
||||
# psutil lit /proc et /sys, accessible par défaut
|
||||
ls -la /proc | head
|
||||
# dr-xr-xr-x root root /proc
|
||||
|
||||
# Certains processus peuvent être inaccessibles
|
||||
# (/proc/[pid]/stat pour processus d'autres utilisateurs)
|
||||
|
||||
# Solution: lancer avec sudo
|
||||
sudo python main.py
|
||||
|
||||
# Ou relâcher les permissions (attention!)
|
||||
sudo chmod 755 /proc
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Frontend et communication API {#frontend}
|
||||
|
||||
### Architecture Vue 3
|
||||
|
||||
```
|
||||
App.vue (composant racine)
|
||||
├── Navigation (header)
|
||||
├── Sidebar (menu)
|
||||
└── <RouterView>
|
||||
├── LoginView.vue (unauthenticated)
|
||||
├── DashboardView.vue (authenticated)
|
||||
└── ContainersView.vue (authenticated)
|
||||
```
|
||||
|
||||
### Store Pinia
|
||||
|
||||
```javascript
|
||||
// stores/auth.js
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
// État réactif
|
||||
const token = ref(null)
|
||||
const username = ref(null)
|
||||
|
||||
// Computed
|
||||
const isAuthenticated = computed(() => !!token.value)
|
||||
|
||||
// Actions
|
||||
async function login(username, password) {
|
||||
const response = await api.post('/auth/login', formData)
|
||||
token.value = response.data.access_token
|
||||
localStorage.setItem('token', token.value)
|
||||
}
|
||||
|
||||
function logout() {
|
||||
token.value = null
|
||||
localStorage.removeItem('token')
|
||||
}
|
||||
|
||||
return { token, username, isAuthenticated, login, logout }
|
||||
})
|
||||
```
|
||||
|
||||
### Client HTTP avec Axios
|
||||
|
||||
```javascript
|
||||
// api/index.js
|
||||
const api = axios.create({
|
||||
baseURL: 'http://localhost:8000/api/v1',
|
||||
})
|
||||
|
||||
// Interceptor de requête: ajouter le JWT token
|
||||
api.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
})
|
||||
|
||||
// Interceptor de réponse: gérer les 401
|
||||
api.interceptors.response.use(
|
||||
response => response,
|
||||
error => {
|
||||
if (error.response?.status === 401) {
|
||||
// Token expiré, rediriger vers login
|
||||
window.location.href = '/login'
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Affichage des données
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<!-- Barre de progression CPU -->
|
||||
<div class="w-full bg-gray-700 rounded-full h-2">
|
||||
<div
|
||||
class="bg-blue-600 h-2 rounded-full"
|
||||
:style="{ width: stats.cpu.percent + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- Tableau des processus avec tri -->
|
||||
<table>
|
||||
<tr v-for="proc in stats.processes" :key="proc.pid">
|
||||
<td>{{ proc.name }}</td>
|
||||
<td :class="proc.cpu_percent > 50 ? 'text-red-400' : 'text-green-400'">
|
||||
{{ proc.cpu_percent.toFixed(1) }}%
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import api from '../api'
|
||||
|
||||
const stats = ref({})
|
||||
|
||||
const fetchStats = async () => {
|
||||
const response = await api.get('/system/stats')
|
||||
stats.value = response.data
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchStats()
|
||||
// Rafraîchir chaque 5 secondes
|
||||
setInterval(fetchStats, 5000)
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
### Styling Tailwind CSS
|
||||
|
||||
```css
|
||||
/* Classes utilitaires utilisées */
|
||||
.card {
|
||||
@apply bg-gray-800 rounded-lg p-6 shadow-lg border border-gray-700;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg;
|
||||
}
|
||||
|
||||
/* Design "dark mode" */
|
||||
body {
|
||||
@apply bg-gray-900 text-gray-100;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Sécurité {#sécurité}
|
||||
|
||||
### CORS (Cross-Origin Resource Sharing)
|
||||
|
||||
```python
|
||||
# backend/app/core/config.py
|
||||
ALLOWED_ORIGINS: list = [
|
||||
"http://localhost:3000", # Dev frontend
|
||||
"https://admin.example.com", # Production frontend
|
||||
]
|
||||
|
||||
# Appliqué via middleware
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=ALLOWED_ORIGINS,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
```
|
||||
|
||||
**Importance**: Évite que des sites malveillants fassent des requêtes à votre API
|
||||
|
||||
### TrustedHost Middleware
|
||||
|
||||
```python
|
||||
app.add_middleware(
|
||||
TrustedHostMiddleware,
|
||||
allowed_hosts=["localhost", "127.0.0.1"],
|
||||
)
|
||||
```
|
||||
|
||||
**Importance**: Valide l'en-tête Host des requêtes
|
||||
|
||||
### Validation Pydantic
|
||||
|
||||
```python
|
||||
class LoginRequest(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
|
||||
@validator('username')
|
||||
def username_alphanumeric(cls, v):
|
||||
assert v.isalnum() # Seulement alphanumérique
|
||||
return v
|
||||
```
|
||||
|
||||
**Importance**: Valide et parse automatiquement les données
|
||||
|
||||
### JWT Secrets
|
||||
|
||||
```bash
|
||||
# Générer une clé sécurisée
|
||||
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
|
||||
|
||||
# Ajouter au .env
|
||||
SECRET_KEY=your-generated-key-here
|
||||
```
|
||||
|
||||
**Importance**:
|
||||
- Doit être complexe (32+ caractères)
|
||||
- Jamais en clair dans le code
|
||||
- Unique par environnement (dev/prod)
|
||||
- Jamais partager
|
||||
|
||||
### Protection des endpoints
|
||||
|
||||
```python
|
||||
@router.get("/sensitive-data")
|
||||
async def sensitive_endpoint(current_user: User = Depends(get_current_user)):
|
||||
# Cette dépendance valide le token
|
||||
# Lever HTTPException 401 si invalid
|
||||
return data
|
||||
```
|
||||
|
||||
### Vérification des permissions
|
||||
|
||||
```bash
|
||||
# Avant de lancer l'app
|
||||
ls -la /var/run/docker.sock
|
||||
# crw-rw---- root docker /var/run/docker.sock
|
||||
|
||||
# L'utilisateur du serveur doit être dans le groupe docker
|
||||
id www-data
|
||||
# uid=33(www-data) gid=33(www-data) groups=33(www-data),999(docker)
|
||||
```
|
||||
|
||||
### Best practices en production
|
||||
|
||||
1. ✅ Utiliser HTTPS (TLS/SSL)
|
||||
2. ✅ Ajouter un reverse proxy (Nginx)
|
||||
3. ✅ Rate limiting sur les endpoints
|
||||
4. ✅ Logging des accès
|
||||
5. ✅ Rotation des secrets
|
||||
6. ✅ Backup de la config
|
||||
7. ✅ Monitoring de l'uptime
|
||||
8. ✅ Firewall IP whitelist
|
||||
|
||||
---
|
||||
|
||||
## 📊 Résumé flux de données
|
||||
|
||||
```
|
||||
1. USER LOGIN
|
||||
Client → Browser → POST /auth/login → Backend
|
||||
Backend: PAM auth → JWT encode → JSON response
|
||||
Browser: Store token in localStorage
|
||||
|
||||
2. API CALL
|
||||
Browser → GET /system/stats (Header: Authorization: Bearer {token})
|
||||
Backend: Validate JWT → Call psutil → JSON response
|
||||
Browser: Update UI with new data
|
||||
|
||||
3. DOCKER ACTION
|
||||
Browser → POST /docker/containers/{id}/start
|
||||
Backend: Docker.container.start() → JSON response
|
||||
Browser: Refresh container list
|
||||
|
||||
4. AUTO-REFRESH
|
||||
JS Timer every 5 seconds:
|
||||
GET /system/stats → Update charts
|
||||
GET /docker/containers → Update list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Ressources
|
||||
|
||||
- [PAM Documentation](http://www.linux-pam.org/)
|
||||
- [JWT Introduction](https://jwt.io)
|
||||
- [Docker Python API](https://docker-py.readthedocs.io)
|
||||
- [psutil Documentation](https://psutil.readthedocs.io)
|
||||
- [FastAPI Security](https://fastapi.tiangolo.com/tutorial/security/)
|
||||
- [Vue 3 Composition API](https://vuejs.org/guide/extras/composition-api-faq.html)
|
||||
Reference in New Issue
Block a user