feat: refactoring complet de la verification DNS avec services separes

Architecture:
- Les domaines (siteconseil.fr, esy-web.dev) sont definis en constante
  dans la commande uniquement, pas dans les services
- 3 services independants reutilisables:

src/Service/DnsCheckService.php (nouveau):
- Methodes publiques checkSpf(), checkDmarc(), checkDkim(), checkMx(),
  checkBounce() qui prennent le domaine en parametre
- Verification SPF: presence des includes amazonses.com et mail.esy-web.dev
- Verification DMARC: politique, presence de rua
- Verification DKIM: test de 10 selecteurs en CNAME et TXT
- Verification MX: le MX attendu est passe en parametre par la commande
- Verification Bounce: MX/CNAME/TXT sur bounce.*

src/Service/AwsSesService.php (nouveau):
- Authentification AWS Signature V4 via HTTP direct (pas de SDK)
- isDomainVerified(): verification du statut du domaine dans SES
- getDkimStatus(): statut DKIM (enabled, verified, tokens)
- getNotificationStatus(): bounce_topic, complaint_topic, forwarding
- listVerifiedIdentities(): liste des domaines verifies
- isAvailable(): test de connectivite API

src/Service/CloudflareService.php (nouveau):
- Authentification Bearer token via HTTP direct (pas de SDK)
- getZoneId(): recupere le zone ID dynamiquement par nom de domaine
  (plus besoin de CLOUDFLARE_ZONE_ID en dur)
- getDnsRecords(): tous les enregistrements d'une zone
- getDnsRecordsByType(): filtrage par type (TXT, MX, CNAME...)
- getZone(): informations d'une zone
- isAvailable(): verification du token API

src/Command/CheckDnsCommand.php (reecrit):
- Utilise les 3 services pour orchestrer les verifications
- Affichage console colore avec icones OK/ERREUR/ATTENTION
- Envoie un rapport email via le template Twig dns_report.html.twig

templates/emails/dns_report.html.twig (nouveau):
- Template email compatible tous clients (table-based, CSS inline,
  margin/padding longhand, mso-line-height-rule, pas de rgba/border-radius)
- Bandeau colore vert/jaune/rouge selon le statut global
- Section succes avec checkmarks verts dans un tableau alterne
- Section erreurs en rouge avec croix dans un tableau fond #fef2f2
- Section avertissements en jaune avec triangles fond #fffbeb
- Detail par domaine avec tableau type/verification/statut
- Utilise le template email/base.html.twig (header gold, footer dark)

Variables d'environnement ajoutees:
- .env: AWS_PK, AWS_SECRET, AWS_REGION (eu-west-3), CLOUDFLARE_KEY (vides)
- .env.local: valeurs reelles des cles AWS et Cloudflare
- ansible/vault.yml: aws_pk, aws_secret, cloudflare_key
- ansible/env.local.j2: AWS_PK, AWS_SECRET, AWS_REGION, CLOUDFLARE_KEY
  avec references au vault
- CLOUDFLARE_ZONE_ID supprime (recupere dynamiquement via l'API)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-04-02 21:28:24 +02:00
parent b6696df087
commit fea7dbfb61
8 changed files with 714 additions and 277 deletions

View File

@@ -0,0 +1,99 @@
{% extends 'email/base.html.twig' %}
{% block title %}Rapport DNS - CRM SITECONSEIL{% endblock %}
{% block content %}
{# ─── Bandeau statut ─── #}
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center" style="background-color: {{ statusColor }}; color: #ffffff; padding-top: 10px; padding-bottom: 10px; padding-left: 16px; padding-right: 16px; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px;">
{{ statusText }}
</td>
</tr>
</table>
<h1 style="font-size: 18px; font-weight: 700; margin-top: 16px; margin-right: 0; margin-bottom: 8px; margin-left: 0;">Rapport DNS</h1>
<p style="font-size: 12px; color: #888888; margin-top: 0; margin-right: 0; margin-bottom: 16px; margin-left: 0;">
{{ date|date('d/m/Y H:i:s') }} &#8226; Domaines : <strong>{{ domains|join(', ') }}</strong>
</p>
{# ─── Succes ─── #}
{% if successes|length > 0 %}
<h2 style="font-size: 14px; font-weight: 700; color: #16a34a; margin-top: 16px; margin-right: 0; margin-bottom: 8px; margin-left: 0;">
Verifications reussies ({{ successes|length }})
</h2>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border: 1px solid #e5e5e5; margin-top: 0; margin-right: 0; margin-bottom: 16px; margin-left: 0;">
{% for success in successes %}
<tr>
<td style="padding-top: 6px; padding-bottom: 6px; padding-left: 12px; padding-right: 12px; font-size: 12px; mso-line-height-rule: exactly; line-height: 18px; background-color: {{ loop.index is odd ? '#ffffff' : '#f9fafb' }}; border-bottom: 1px solid #eeeeee; color: #16a34a;">
&#10003; {{ success }}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{# ─── Erreurs ─── #}
{% if errors|length > 0 %}
<h2 style="font-size: 14px; font-weight: 700; color: #dc2626; margin-top: 16px; margin-right: 0; margin-bottom: 8px; margin-left: 0;">
Erreurs ({{ errors|length }})
</h2>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border: 1px solid #fca5a5; margin-top: 0; margin-right: 0; margin-bottom: 16px; margin-left: 0;">
{% for error in errors %}
<tr>
<td style="padding-top: 6px; padding-bottom: 6px; padding-left: 12px; padding-right: 12px; font-size: 12px; mso-line-height-rule: exactly; line-height: 18px; background-color: #fef2f2; color: #991b1b; border-bottom: 1px solid #fca5a5;">
&#10007; {{ error }}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{# ─── Avertissements ─── #}
{% if warnings|length > 0 %}
<h2 style="font-size: 14px; font-weight: 700; color: #f59e0b; margin-top: 16px; margin-right: 0; margin-bottom: 8px; margin-left: 0;">
Avertissements ({{ warnings|length }})
</h2>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border: 1px solid #fde68a; margin-top: 0; margin-right: 0; margin-bottom: 16px; margin-left: 0;">
{% for warning in warnings %}
<tr>
<td style="padding-top: 6px; padding-bottom: 6px; padding-left: 12px; padding-right: 12px; font-size: 12px; mso-line-height-rule: exactly; line-height: 18px; background-color: #fffbeb; color: #92400e; border-bottom: 1px solid #fde68a;">
&#9888; {{ warning }}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{# ─── Detail par domaine ─── #}
{% for domainData in domainResults %}
<h2 style="font-size: 14px; font-weight: 700; margin-top: 20px; margin-right: 0; margin-bottom: 8px; margin-left: 0;">
{{ domainData.domain }}
</h2>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border: 1px solid #e5e5e5; margin-top: 0; margin-right: 0; margin-bottom: 16px; margin-left: 0;">
<tr>
<td style="background-color: #111827; color: #ffffff; padding-top: 8px; padding-bottom: 8px; padding-left: 12px; padding-right: 12px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; width: 80px;">Type</td>
<td style="background-color: #111827; color: #ffffff; padding-top: 8px; padding-bottom: 8px; padding-left: 12px; padding-right: 12px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;">Verification</td>
<td style="background-color: #111827; color: #ffffff; padding-top: 8px; padding-bottom: 8px; padding-left: 12px; padding-right: 12px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; text-align: center; width: 60px;">Statut</td>
</tr>
{% for check in domainData.checks %}
<tr>
<td style="padding-top: 6px; padding-bottom: 6px; padding-left: 12px; padding-right: 12px; font-size: 11px; font-weight: 700; border-bottom: 1px solid #eeeeee; color: #666666;">{{ check.type }}</td>
<td style="padding-top: 6px; padding-bottom: 6px; padding-left: 12px; padding-right: 12px; font-size: 11px; border-bottom: 1px solid #eeeeee;">
<strong>{{ check.label }}</strong>
{% if check.detail %}<br><span style="color: #888888; font-size: 10px;">{{ check.detail }}</span>{% endif %}
</td>
<td style="padding-top: 6px; padding-bottom: 6px; padding-left: 12px; padding-right: 12px; font-size: 12px; text-align: center; border-bottom: 1px solid #eeeeee; font-weight: 700; color: {{ check.status == 'ok' ? '#16a34a' : (check.status == 'error' ? '#dc2626' : '#f59e0b') }};">
{{ check.status == 'ok' ? '&#10003;' : (check.status == 'error' ? '&#10007;' : '&#9888;') }}
</td>
</tr>
{% endfor %}
</table>
{% endfor %}
<p style="font-size: 11px; color: #888888; mso-line-height-rule: exactly; line-height: 16px; margin-top: 16px; margin-right: 0; margin-bottom: 0; margin-left: 0;">
Rapport genere par la commande <strong>app:dns:check</strong>. Verifiez la configuration dans Cloudflare et AWS SES si necessaire.
</p>
{% endblock %}