Permissions de base de donnees

Les gardes Eloquent boot d'AuditChain empechent les mises a jour et suppressions au niveau applicatif, mais une application compromise peut contourner Eloquent et executer du SQL brut. Un utilisateur base de donnees dedie avec des privileges restreints comble cette lacune en imposant l'immutabilite au niveau de la base de donnees.

Pourquoi c'est important

Le model AuditLog lance une RuntimeException si vous tentez de mettre a jour ou de supprimer un enregistrement via Eloquent. C'est efficace contre les modifications accidentelles et les bugs applicatifs, mais cela ne protege pas contre :

  • Les requetes SQL directes (DB::statement('DELETE FROM audit_logs ...'))
  • Une application compromise executant des commandes base de donnees arbitraires
  • Un attaquant ayant acces aux identifiants base de donnees utilises par l'application principale
  • Les outils de gestion de base de donnees (phpMyAdmin, TablePlus, etc.) connectes avec l'utilisateur applicatif

En donnant a la connexion d'audit un utilisateur base de donnees qui ne peut qu'inserer et lire (INSERT et SELECT), vous garantissez que les journaux d'audit ne peuvent etre ni modifies ni supprimes, meme si l'application est entierement compromise.

Mise en place d'une connexion dediee

1. Creer un utilisateur base de donnees restreint

MySQL

-- Create the user
CREATE USER 'audit_writer'@'%' IDENTIFIED BY 'a-strong-random-password';

-- Grant only INSERT and SELECT on the audit table
GRANT INSERT, SELECT ON your_database.audit_logs TO 'audit_writer'@'%';

-- Apply changes
FLUSH PRIVILEGES;

PostgreSQL

-- Create the user
CREATE USER audit_writer WITH PASSWORD 'a-strong-random-password';

-- Grant only INSERT and SELECT on the audit table
GRANT INSERT, SELECT ON audit_logs TO audit_writer;

-- Grant USAGE on the schema (required for PostgreSQL)
GRANT USAGE ON SCHEMA public TO audit_writer;

Si votre table d'audit utilise une sequence pour une colonne (peu courant avec les ULID, mais possible), vous devrez egalement executer GRANT USAGE, SELECT ON SEQUENCE audit_logs_id_seq TO audit_writer; dans PostgreSQL.

2. Ajouter la connexion dans config/database.php

Definissez une nouvelle connexion utilisant l'utilisateur restreint :

// config/database.php
'connections' => [
    // Your main application connection
    'mysql' => [
        'driver' => 'mysql',
        'host' => env('DB_HOST', '127.0.0.1'),
        'database' => env('DB_DATABASE', 'your_database'),
        'username' => env('DB_USERNAME', 'app_user'),
        'password' => env('DB_PASSWORD', ''),
        // ...
    ],

    // Dedicated audit connection (INSERT + SELECT only)
    'audit' => [
        'driver' => 'mysql',
        'host' => env('DB_HOST', '127.0.0.1'),
        'database' => env('DB_DATABASE', 'your_database'),
        'username' => env('AUDIT_DB_USERNAME', 'audit_writer'),
        'password' => env('AUDIT_DB_PASSWORD', ''),
        // ...remaining options same as your main connection
    ],
],

3. Pointer AuditChain vers la connexion restreinte

Definissez la connexion dans config/audit-chain.php :

'connection' => 'audit',

Ou via votre fichier .env si vous preferez :

AUDIT_DB_USERNAME=audit_writer
AUDIT_DB_PASSWORD=a-strong-random-password

Execution des migrations

L'utilisateur restreint audit_writer ne peut pas creer ou modifier des tables. Executez les migrations en utilisant votre connexion applicative principale (celle par defaut). La migration d'AuditChain ne specifie pas de connexion, elle utilise donc automatiquement la connexion base de donnees par defaut.

Purge des journaux d'audit

La commande audit:prune supprime les anciens journaux d'audit. Comme l'utilisateur restreint ne dispose pas du privilege DELETE, la purge echouera si la connexion d'audit est la seule disponible. Deux approches :

Option A : Executez la commande de purge avec une connexion differente disposant des privileges DELETE. Vous pouvez surcharger temporairement la connexion dans votre tache planifiee :

Schedule::command('audit:prune --days=365')
    ->daily()
    ->before(function () {
        config(['audit-chain.connection' => 'mysql']);
    })
    ->after(function () {
        config(['audit-chain.connection' => 'audit']);
    });

Option B : Executez la requete de purge manuellement en utilisant votre connexion base de donnees principale en dehors d'AuditChain.

Verification des permissions

Confirmez que l'utilisateur restreint ne peut pas modifier ni supprimer les enregistrements d'audit :

-- These should succeed
INSERT INTO audit_logs (id, auditable_type, auditable_id, event, created_at)
VALUES ('test', 'App\\Models\\User', '1', 'test', NOW());

SELECT * FROM audit_logs WHERE id = 'test';

-- These should fail with a permission error
UPDATE audit_logs SET event = 'tampered' WHERE id = 'test';
DELETE FROM audit_logs WHERE id = 'test';

Nettoyez la ligne de test en utilisant votre utilisateur applicatif principal apres la verification.

Defense en profondeur

Les permissions au niveau base de donnees constituent une couche de la strategie d'immutabilite d'AuditChain :

Couche Protege contre
Gardes Eloquent boot Mises a jour/suppressions au niveau applicatif via Eloquent
$fillable strict Assignation de masse d'attributs non autorises
Permissions utilisateur BD SQL brut, application compromise, acces direct a la BD
Verification de la chaine de hash Toute modification, quelle que soit la methode utilisee

Aucune couche n'est suffisante a elle seule. Ensemble, elles fournissent une assurance solide que votre journal d'audit n'a pas ete falsifie.