feat(security): Ajoute blocage intranet et vérification des services.
```
This commit is contained in:
Serreau Jovann
2026-01-19 11:25:56 +01:00
parent 9599aec7b2
commit d86d6612b5
2 changed files with 80 additions and 135 deletions

View File

@@ -9,28 +9,87 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Twig\Environment;
#[AsEventListener(RequestEvent::class,method: 'onLocked',priority: 10)]
#[AsEventListener(RequestEvent::class,method: 'onControl',priority: 5)]
#[AsEventListener(RequestEvent::class, method: 'onLocked', priority: 10)]
#[AsEventListener(RequestEvent::class, method: 'onControl', priority: 5)]
class IntranetLocked
{
public function __construct(private readonly Environment $environment,
private readonly Client $signatureClient,
private readonly \App\Service\Search\Client $searchClient,
private readonly \App\Service\Stripe\Client $stripeClient,
private readonly Mailer $mailer
private const WHITELISTED_IPS = [
'212.114.31.239', // Bureau
'1.2.3.4', // Autre
'172.19.0.1',
];
){
public function __construct(
private readonly Environment $environment,
private readonly Client $signatureClient,
private readonly \App\Service\Search\Client $searchClient,
private readonly \App\Service\Stripe\Client $stripeClient,
private readonly Mailer $mailer
) {}
}
public function onLocked(RequestEvent $requestEvent)
public function onLocked(RequestEvent $requestEvent): void
{
if($_ENV['INTRANET_LOCK'] == "true" && ! $this->isDebugRoute($requestEvent)) {
// Si l'IP est autorisée ou si c'est une route de debug, on ne bloque pas
if ($this->isWhitelisted($requestEvent) || $this->isDebugRoute($requestEvent)) {
return;
}
if (($_ENV['INTRANET_LOCK'] ?? 'false') === "true") {
$response = new Response($this->environment->render('security/locked.twig'));
$response->setStatusCode(Response::HTTP_FORBIDDEN);
$requestEvent->setResponse($response);
}
}
public function onControl(RequestEvent $requestEvent): void
{
// On ignore également le contrôle des services pour l'IP whitelisted
if ($this->isWhitelisted($requestEvent) || $this->isDebugRoute($requestEvent)) {
return;
}
$isValid = true;
$message = [];
// Check Signature
if (!$this->signatureClient->status()) {
$isValid = false;
$message = ['service' => 'Signature', 'status' => 'Hors Service'];
$this->advertTech($message);
}
// Check Recherche (seulement si le précédent est encore valide ou pour accumuler les erreurs)
if ($isValid && !$this->searchClient->status()) {
$isValid = false;
$message = ['service' => 'Recherche', 'status' => 'Hors Service'];
$this->advertTech($message);
}
// Check Stripe
if ($isValid && !$this->stripeClient->status()) {
$isValid = false;
$message = ['service' => 'Stripe', 'status' => 'Hors Service'];
$this->advertTech($message);
}
if (!$isValid) {
$response = new Response($this->environment->render('security/error.twig', [
'message' => $message,
]));
$response->setStatusCode(Response::HTTP_FORBIDDEN);
$requestEvent->setResponse($response);
}
}
/**
* Vérifie si l'IP du client est l'IP de maintenance
*/
private function isWhitelisted(RequestEvent $event): bool
{
$request = $event->getRequest();
$clientIp = $request->headers->get('cf-connecting-ip') ?? $request->getClientIp();
return in_array($clientIp, self::WHITELISTED_IPS, true);
}
private function isDebugRoute(RequestEvent $event): bool
@@ -38,52 +97,15 @@ class IntranetLocked
$path = $event->getRequest()->getPathInfo();
return str_contains($path, "_wdt") || str_contains($path, "_profiler");
}
public function onControl(RequestEvent $requestEvent)
public function advertTech(array $message): void
{
if ($this->isDebugRoute($requestEvent)) {
return;
}
$isValid = true;
$message = [];
if(!$this->signatureClient->status()) {
$isValid = false;
$message = [
'service' => 'Signature',
'status' => 'Hors Service'
];
$this->advertTech($message);
}
if(!$this->searchClient->status()) {
$isValid = false;
$message = [
'service' => 'Recherche',
'status' => 'Hors Service'
];
$this->advertTech($message);
}
if(!$this->stripeClient->status()) {
$isValid = false;
$message = [
'service' => 'Stripe',
'status' => 'Hors Service'
];
$this->advertTech($message);
}
if(!$isValid) {
$response = new Response($this->environment->render('security/error.twig',[
'message' => $message,
]));
$response->setStatusCode(Response::HTTP_FORBIDDEN);
$requestEvent->setResponse($response);
}
}
public function advertTech(array $message) {
$this->mailer->send('notification@siteconseil.fr',"Notification siteconseil",
"[Intranet Ludikevent] - Accées impossible suite à une erreur de service",
"mails/tech/noaccess.twig",[
'message' => $message
]);
$this->mailer->send(
'notification@siteconseil.fr',
"Notification siteconseil",
"[Intranet Ludikevent] - Accès impossible suite à une erreur de service",
"mails/tech/noaccess.twig",
['message' => $message]
);
}
}

View File

@@ -68,83 +68,6 @@
</p>
</div>
</div>
{# ... après le div du footer ... #}
<div class="mt-10 p-6 backdrop-blur-md bg-black/20 border border-white/5 rounded-3xl overflow-hidden">
<h3 class="text-[10px] font-black text-blue-500 uppercase tracking-[0.2em] mb-4 flex items-center">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
Request Headers (Debug)
</h3>
<div class="mt-10 p-6 backdrop-blur-md bg-black/20 border border-white/5 rounded-3xl overflow-hidden">
<h3 class="text-[10px] font-black text-blue-500 uppercase tracking-[0.2em] mb-4 flex items-center">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
Réseau & Cloudflare (Debug)
</h3>
{# Affichage de l'IP réelle calculée par Symfony #}
<div class="mb-6 grid grid-cols-2 gap-4">
<div class="p-3 bg-blue-600/10 rounded-xl border border-blue-500/20">
<p class="text-[9px] text-slate-500 uppercase font-bold">Client IP (Symfony)</p>
<p class="text-sm font-mono text-white">{{ app.request.clientIp }}</p>
</div>
<div class="p-3 bg-purple-600/10 rounded-xl border border-purple-500/20">
<p class="text-[9px] text-slate-500 uppercase font-bold">Origine (CF-Country)</p>
<p class="text-sm font-mono text-white">{{ app.request.headers.get('cf-ipcountry')|default('N/A') }}</p>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-white/10">
<th class="py-2 text-[9px] font-bold text-slate-500 uppercase tracking-wider">Header</th>
<th class="py-2 text-[9px] font-bold text-slate-500 uppercase tracking-wider pl-4">Valeur</th>
</tr>
</thead>
<tbody class="divide-y divide-white/5">
{# On filtre pour afficher les headers Cloudflare en priorité #}
{% for key, values in app.request.headers.all %}
{% if "cf-" in key or "x-forwarded" in key %}
<tr class="group hover:bg-white/5 transition-colors">
<td class="py-3 text-[11px] font-mono text-blue-400 align-top">
<span class="bg-blue-500/10 px-1.5 py-0.5 rounded text-[9px]">{{ key }}</span>
</td>
<td class="py-3 pl-4 text-[11px] text-slate-300 align-top break-all">
{{ values|join(', ') }}
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-white/10">
<th class="py-2 text-[9px] font-bold text-slate-500 uppercase tracking-wider">Clé</th>
<th class="py-2 text-[9px] font-bold text-slate-500 uppercase tracking-wider pl-4">Valeur</th>
</tr>
</thead>
<tbody class="divide-y divide-white/5">
{% for key, values in app.request.headers.all %}
<tr class="group hover:bg-white/5 transition-colors">
<td class="py-3 text-[11px] font-mono text-blue-400 align-top break-all">{{ key }}</td>
<td class="py-3 pl-4 text-[11px] text-slate-300 align-top break-all">
{# Les headers peuvent être des tableaux, on les joint proprement #}
{{ values|join(', ') }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</body>