feat: afficher les vraies valeurs AWS SES attendues dans le rapport DNS

src/Service/AwsSesService.php:
- Ajout methode getMailFromStatus() qui recupere via l'API SES:
  - mail_from_domain: le sous-domaine MAIL FROM (ex: bounce.siteconseil.fr)
  - mail_from_status: statut de verification (Success/Pending/Failed)
  - mx_expected: le MX attendu (feedback-smtp.{region}.amazonses.com)
  - txt_expected: le SPF attendu (v=spf1 include:amazonses.com ~all)
  Les valeurs sont specifiques a chaque domaine et region AWS

src/Command/CheckDnsCommand.php - methode checkAwsSes() reecrite:
- Verification domaine: attendu="Success", dig=statut reel
- DKIM statut global: attendu="Enabled=oui, Verified=oui", dig=statut reel
- 3 DKIM CNAME individuels: pour chaque token retourne par SES,
  verifie que {token}._domainkey.{domain} CNAME {token}.dkim.amazonses.com
  existe dans le DNS. Attendu=CNAME cible, Dig=valeur trouvee ou "Non trouve"
- MAIL FROM: attendu=sous-domaine configure dans SES, dig=statut
- MAIL FROM MX: attendu="{bounce.domain} MX feedback-smtp.{region}.amazonses.com",
  dig=MX reel trouve
- MAIL FROM TXT: attendu="{bounce.domain} TXT v=spf1 include:amazonses.com ~all",
  dig=enregistrement SPF reel trouve
- Bounce notifications: attendu="Forwarding ou SNS topic", dig=config reelle
- Ajout methodes getMxValues() et getTxtSpfValue() pour recuperer les
  valeurs reelles du DNS a afficher dans la colonne Dig

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-04-02 21:41:46 +02:00
parent d1fdb5ab52
commit b9261f2946
3 changed files with 150 additions and 6 deletions

View File

@@ -15,7 +15,7 @@ sonarqube_badge_token: sqb_dc1d0f73af1016295f49d1c56bf60e115e43bf48
keycloak_admin_client_secret: QqYnQc6p9aio4sBJYKNhpPsdrvpUtt2z
aws_pk: AKIAWTT2T22CWBRBBDYN
aws_secret: BBdgb6KxRQ8mNcpWFJsZCJxbSGNdgLhKFiITMErfBlQP
cloudflare_key: cfat_hOADTEIl9naAUW2PUf0CfnNiToUHZtB21D3jH3f310d0b8ba
cloudflare_key: cfut_xqEEvg5LDezheCI9rWsd4JdfflvLH5vjmeMp7QHO442dd83b
mailcow_api_key: DF0E7E-0FD059-16226F-8ECFF1-E558B3
docuseal_api: pgAU116mCFmeF7WQSezHqxtZW8V1fgo31u5d2FXoaKe
docuseal_webhooks_secret: CRM_COSLAY

View File

@@ -187,6 +187,7 @@ class CheckDnsCommand extends Command
}
try {
// Verification du domaine
$verif = $this->awsSes->isDomainVerified($domain);
$checks[] = DnsCheckService::check(
'AWS SES', 'Domaine', 'Success' === $verif ? 'ok' : 'error',
@@ -198,26 +199,139 @@ class CheckDnsCommand extends Command
$errors[] = "[$domain] AWS SES : domaine non verifie ($verif)";
}
// DKIM - verifier les 3 tokens CNAME
$dkim = $this->awsSes->getDkimStatus($domain);
$dkimOk = $dkim['enabled'] && $dkim['verified'];
$dkimDig = 'Enabled='.($dkim['enabled'] ? 'oui' : 'non').', Verified='.($dkim['verified'] ? 'oui' : 'non');
$checks[] = DnsCheckService::check('AWS SES', 'DKIM', $dkimOk ? 'ok' : 'error', $dkimDig, 'Enabled=oui, Verified=oui', $dkimDig);
$checks[] = DnsCheckService::check(
'AWS SES', 'DKIM statut', $dkimOk ? 'ok' : 'error',
$dkimOk ? 'Active et verifiee' : 'Non active ou non verifiee',
'Enabled=oui, Verified=oui',
'Enabled='.($dkim['enabled'] ? 'oui' : 'non').', Verified='.($dkim['verified'] ? 'oui' : 'non')
);
if ($dkimOk) {
$successes[] = "[$domain] AWS SES DKIM : OK";
$successes[] = "[$domain] AWS SES DKIM : active et verifiee";
} else {
$errors[] = "[$domain] AWS SES DKIM : $dkimDig";
$errors[] = "[$domain] AWS SES DKIM : non active ou non verifiee";
}
// Verifier chaque token DKIM dans le DNS
foreach ($dkim['tokens'] as $token) {
$expectedCname = $token.'.dkim.amazonses.com';
$dkimFqdn = $token.'._domainkey.'.$domain;
$actualCname = $this->getCnameRecord($dkimFqdn);
$found = null !== $actualCname && str_contains($actualCname, 'dkim.amazonses.com');
$checks[] = DnsCheckService::check(
'AWS SES', 'DKIM CNAME '.$token, $found ? 'ok' : 'error',
$found ? 'Present' : 'Absent',
$expectedCname,
$actualCname ?? 'Non trouve'
);
if ($found) {
$successes[] = "[$domain] AWS SES DKIM CNAME $token : OK";
} else {
$errors[] = "[$domain] AWS SES DKIM CNAME $token : absent (attendu: $dkimFqdn CNAME $expectedCname)";
}
}
// MAIL FROM
$mailFrom = $this->awsSes->getMailFromStatus($domain);
$mailFromDomain = $mailFrom['mail_from_domain'];
if (null !== $mailFromDomain) {
$mailFromStatus = $mailFrom['mail_from_status'];
$checks[] = DnsCheckService::check(
'AWS SES', 'MAIL FROM', 'Success' === $mailFromStatus ? 'ok' : 'error',
$mailFromStatus ?? 'Inconnu',
$mailFromDomain.' (statut: Success)',
$mailFromDomain.' (statut: '.($mailFromStatus ?? '?').')'
);
if ('Success' === $mailFromStatus) {
$successes[] = "[$domain] AWS SES MAIL FROM : $mailFromDomain verifie";
} else {
$errors[] = "[$domain] AWS SES MAIL FROM : $mailFromDomain statut $mailFromStatus";
}
// Verifier le MX du MAIL FROM dans le DNS
$mxExpected = $mailFrom['mx_expected'];
if (null !== $mxExpected) {
$mxFound = $this->checkMxExists($mailFromDomain, $mxExpected);
$actualMx = $this->getMxValues($mailFromDomain);
$checks[] = DnsCheckService::check(
'AWS SES', 'MAIL FROM MX', $mxFound ? 'ok' : 'error',
$mxFound ? 'Present' : 'Absent',
"$mailFromDomain MX $mxExpected",
$actualMx ?: 'Non trouve'
);
if ($mxFound) {
$successes[] = "[$domain] AWS SES MAIL FROM MX : OK";
} else {
$errors[] = "[$domain] AWS SES MAIL FROM MX absent (attendu: $mailFromDomain MX $mxExpected)";
}
}
// Verifier le TXT SPF du MAIL FROM dans le DNS
$txtExpected = $mailFrom['txt_expected'];
if (null !== $txtExpected) {
$txtFound = $this->checkTxtContains($mailFromDomain, 'v=spf1');
$actualTxt = $this->getTxtSpfValue($mailFromDomain);
$checks[] = DnsCheckService::check(
'AWS SES', 'MAIL FROM TXT', $txtFound ? 'ok' : 'error',
$txtFound ? 'Present' : 'Absent',
"$mailFromDomain TXT $txtExpected",
$actualTxt ?: 'Non trouve'
);
if ($txtFound) {
$successes[] = "[$domain] AWS SES MAIL FROM SPF : OK";
} else {
$errors[] = "[$domain] AWS SES MAIL FROM SPF absent (attendu: $mailFromDomain TXT $txtExpected)";
}
}
} else {
$checks[] = DnsCheckService::check('AWS SES', 'MAIL FROM', 'warning', 'Non configure', 'bounce.'.$domain, 'N/A');
}
// Notifications bounce
$notif = $this->awsSes->getNotificationStatus($domain);
$bounceOk = $notif['forwarding'] || null !== $notif['bounce_topic'];
$bounceDetail = $notif['forwarding'] ? 'Forwarding actif' : ($notif['bounce_topic'] ?? 'Non configure');
$checks[] = DnsCheckService::check('AWS SES', 'Bounce', $bounceOk ? 'ok' : 'warning', $bounceDetail, 'Forwarding ou SNS topic', $bounceDetail);
$checks[] = DnsCheckService::check('AWS SES', 'Bounce notif', $bounceOk ? 'ok' : 'warning', $bounceDetail, 'Forwarding ou SNS topic', $bounceDetail);
} catch (\Throwable $e) {
$errors[] = "[$domain] AWS SES : ".$e->getMessage();
$checks[] = DnsCheckService::check('AWS SES', 'API', 'error', $e->getMessage());
}
}
private function getMxValues(string $domain): string
{
$records = dns_get_record($domain, \DNS_MX) ?: [];
$values = [];
foreach ($records as $mx) {
$values[] = rtrim($mx['target'] ?? '', '.').' (pri: '.($mx['pri'] ?? '?').')';
}
return implode(', ', $values);
}
private function getTxtSpfValue(string $domain): string
{
$records = dns_get_record($domain, \DNS_TXT) ?: [];
foreach ($records as $r) {
$txt = $r['txt'] ?? '';
if (str_starts_with($txt, 'v=spf1')) {
return $txt;
}
}
return '';
}
/**
* @param list<array> $checks
* @param list<string> $errors

View File

@@ -93,6 +93,36 @@ class AwsSesService
return $identities;
}
/**
* Recuperer la configuration MAIL FROM d'un domaine.
*
* @return array{mail_from_domain: string|null, mail_from_status: string|null, mx_expected: string|null, txt_expected: string|null}
*/
public function getMailFromStatus(string $domain): array
{
$xml = $this->call('GetIdentityMailFromDomainAttributes', ['Identities.member.1' => $domain]);
$mailFromDomain = null;
if (preg_match('/<MailFromDomain>([^<]+)<\/MailFromDomain>/', $xml, $m)) {
$mailFromDomain = $m[1];
}
$status = null;
if (preg_match('/<MailFromDomainStatus>([^<]+)<\/MailFromDomainStatus>/', $xml, $m)) {
$status = $m[1];
}
$mxExpected = null !== $mailFromDomain ? 'feedback-smtp.'.$this->region.'.amazonses.com' : null;
$txtExpected = null !== $mailFromDomain ? 'v=spf1 include:amazonses.com ~all' : null;
return [
'mail_from_domain' => $mailFromDomain,
'mail_from_status' => $status,
'mx_expected' => $mxExpected,
'txt_expected' => $txtExpected,
];
}
/**
* Verifier si l'API est fonctionnelle.
*/