15 KiB
15 KiB
🔧 Explication technique détaillée - InnotexBoard
Table des matières
- Architecture système
- Authentification PAM + JWT
- Gestion Docker
- Monitoring système
- Frontend et communication API
- Sécurité
1. Architecture système
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
Fichier principal : backend/app/core/security.py
Flux d'authentification
# 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
# 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
Fichier principal : backend/app/services/docker_service.py
Initialisation de la connexion
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
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
# 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 :
-
Groupe docker (moins sécurisé)
sudo usermod -aG docker www-data -
Sudo sans mot de passe (recommandé)
echo 'www-data ALL=(ALL) NOPASSWD: /usr/bin/docker' | sudo tee /etc/sudoers.d/docker -
Socket Docker avec permissions (avancé)
sudo setfacl -m u:www-data:rw /var/run/docker.sock
4. Monitoring système
Fichier principal : backend/app/services/system.py
Utilisation CPU
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
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
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
# 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
Architecture Vue 3
App.vue (composant racine)
├── Navigation (header)
├── Sidebar (menu)
└── <RouterView>
├── LoginView.vue (unauthenticated)
├── DashboardView.vue (authenticated)
└── ContainersView.vue (authenticated)
Store Pinia
// 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
// 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
<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
/* 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é
CORS (Cross-Origin Resource Sharing)
# 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
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=["localhost", "127.0.0.1"],
)
Importance: Valide l'en-tête Host des requêtes
Validation Pydantic
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
# 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
@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
# 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
- ✅ Utiliser HTTPS (TLS/SSL)
- ✅ Ajouter un reverse proxy (Nginx)
- ✅ Rate limiting sur les endpoints
- ✅ Logging des accès
- ✅ Rotation des secrets
- ✅ Backup de la config
- ✅ Monitoring de l'uptime
- ✅ 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