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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user