579 lines
15 KiB
Markdown
579 lines
15 KiB
Markdown
# 🔧 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)
|