Files
innotexBoard/TECHNICAL_EXPLANATION.md
2026-01-16 18:40:39 +01:00

15 KiB

🔧 Explication technique détaillée - InnotexBoard

Table des matières

  1. Architecture système
  2. Authentification PAM + JWT
  3. Gestion Docker
  4. Monitoring système
  5. Frontend et communication API
  6. 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 :

  1. Groupe docker (moins sécurisé)

    sudo usermod -aG docker www-data
    
  2. Sudo sans mot de passe (recommandé)

    echo 'www-data ALL=(ALL) NOPASSWD: /usr/bin/docker' | sudo tee /etc/sudoers.d/docker
    
  3. 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

  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