Skip to content

Instantly share code, notes, and snippets.

@sonnykt
Last active December 29, 2025 08:46
Show Gist options
  • Select an option

  • Save sonnykt/d9a62a410e19cb879322b268eb60456a to your computer and use it in GitHub Desktop.

Select an option

Save sonnykt/d9a62a410e19cb879322b268eb60456a to your computer and use it in GitHub Desktop.
AWS SES Identity audit

PHP Script

<?php

function aws(string $command) : array|false {
  $aws_command = 'aws --no-cli-pager --no-paginate --output json ' . $command;
  $output = [];
  $result_code = NULL;

  if (exec($aws_command, $output, $result_code)) {
    if ($result_code !== 0) {
      return FALSE;
    }
    $json_output = implode(PHP_EOL, $output);
    try {
      $result = json_decode($json_output, TRUE, JSON_THROW_ON_ERROR);
      return $result;
    }
    catch (\JsonException) {
      return FALSE;
    }
  }

  return FALSE;
}

function ses_list_identities() : array {
  $results = aws('ses list-identities');
  if (empty($results['Identities']) || !is_array($results['Identities'])) {
    return [];
  }

  return $results['Identities'];
}

function ses_get_identity_verification_attributes(array $identities) : array {
  $verification_attributes = [];
  $chunks = array_chunk($identities, 50);
  foreach ($chunks as $chunk_identities) {
    $command = 'ses get-identity-verification-attributes --identities ' . implode(' ', $chunk_identities);
    $results = aws($command);
    if (!empty($results['VerificationAttributes']) && is_array($results['VerificationAttributes'])) {
      foreach ($results['VerificationAttributes'] as $identity => $attributes) {
        $verification_attributes[$identity]['Verified'] = get_success_attribute('VerificationStatus', $attributes);
      }
    }
  }
  return $verification_attributes;
}

function ses_get_identity_dkim_attributes(array $identities) : array {
  $dkim_attributes = [];
  $chunks = array_chunk($identities, 50);
  foreach ($chunks as $chunk_identities) {
    $command = 'ses get-identity-dkim-attributes --identities ' . implode(' ', $chunk_identities);
    $results = aws($command);
    if (!empty($results['DkimAttributes']) && is_array($results['DkimAttributes'])) {
      foreach ($results['DkimAttributes'] as $identity => $attributes) {
        $dkim_attributes[$identity]['DKIM On'] = get_bool_attribute('DkimEnabled', $attributes);
        $dkim_attributes[$identity]['DKIM State'] = get_success_attribute('DkimVerificationStatus', $attributes);
      }
    }
  }
  return $dkim_attributes;
}

function ses_get_identity_notification_attributes(array $identities) : array {
  $notification_attributes = [];
  $chunks = array_chunk($identities, 50);
  foreach ($chunks as $chunk_identities) {
    $command = 'ses get-identity-notification-attributes --identities ' . implode(' ', $chunk_identities);
    $results = aws($command);
    if (!empty($results['NotificationAttributes']) && is_array($results['NotificationAttributes'])) {
      foreach ($results['NotificationAttributes'] as $identity => $attributes) {
        $notification_attributes[$identity]['Bounce Notify'] = get_sns_topic('BounceTopic', $attributes, ' -');
        $notification_attributes[$identity]['Complaint Notify'] = get_sns_topic('ComplaintTopic', $attributes, ' -');
        $notification_attributes[$identity]['Delivery Notify'] = get_sns_topic('DeliveryTopic', $attributes, ' -');
        $notification_attributes[$identity]['Forwarding'] = get_bool_attribute('ForwardingEnabled', $attributes);
        $notification_attributes[$identity]['Headers in Bounce'] = get_bool_attribute('HeadersInBounceNotificationsEnabled', $attributes);
        $notification_attributes[$identity]['Headers in Complaint'] = get_bool_attribute('HeadersInComplaintNotificationsEnabled', $attributes);
        $notification_attributes[$identity]['Headers in Delivery'] = get_bool_attribute('HeadersInDeliveryNotificationsEnabled', $attributes);
      }
    }
  }
  return $notification_attributes;
}

function ses_get_identity_main_from_domain_attributes(array $identities) : array {
  $mail_attributes = [];
  $chunks = array_chunk($identities, 50);
  foreach ($chunks as $chunk_identities) {
    $command = 'ses get-identity-mail-from-domain-attributes --identities ' . implode(' ', $chunk_identities);
    $results = aws($command);
    if (!empty($results['MailFromDomainAttributes']) && is_array($results['MailFromDomainAttributes'])) {
      foreach ($results['MailFromDomainAttributes'] as $identity => $attributes) {
        $mail_attributes[$identity]['Mail From Status'] = get_success_attribute('MailFromDomainStatus', $attributes);
        $mail_attributes[$identity]['Main From Domain'] = get_attribute('MailFromDomain', $attributes);
      }
    }
  }
  return $mail_attributes;
}

function ses_get_identity_policies(array $identities) : array {
  $policies_count = [];
  foreach ($identities as $identity) {
    $command = 'ses list-identity-policies --identity ' . $identity;
    $results = aws($command);
    if (!empty($results['PolicyNames']) && is_array($results['PolicyNames'])) {
      $policies_count[$identity]['Policies'] = count($results['PolicyNames']);
    }
    else {
      $policies_count[$identity]['Policies'] = ' ';
    }
  }
  return $policies_count;
}

function get_attribute(string $attribute, array $attributes, string $default = ' ') : mixed {
  return $attributes[$attribute] ?? $default;
}

function get_bool_attribute(string $attribute, array $attributes) : string {
  $value = get_attribute($attribute, $attributes);
  return !empty($value) ? 'βœ…' : ' ';
}

function get_success_attribute(string $attribute, array $attributes, string $empty = ' ') : string {
  $value = get_attribute($attribute, $attributes, '');
  if (is_bool($value)) {
    return $value ? '🟒' : 'πŸ”΄';
  }
  if (empty($value)) {
    return $empty;
  }
  return $value === 'Success' ? '🟒' : 'πŸ”΄';
}

function get_sns_topic(string $attribute, array $attributes, string $default = ' ') : string {
  $value = get_attribute($attribute, $attributes, $default);
  if (is_string($value) && !empty($value)) {
    $arn = explode(':', $value);
    return end($arn);
  }
  return $default;
}

function merge_table(array &$table, array $data) : void {
  foreach ($data as $identity => $attributes) {
    if (empty($table[$identity])) {
      $table[$identity] = [];
    }
    $table[$identity] = array_merge($table[$identity], $attributes);
  }
}

$header = [
  'Domain / Email',
  'Verified',
  'DKIM On',
  'DKIM State',
  'Bounce Notify',
  'Complaint Notify',
  'DeliveryNotify',
  'Forwarding',
  'Headers in Bounce',
  'Headers in Complaint',
  'Headers in Delivery',
  'Mail From Status',
  'Mail From Domain',
  'Policies',
];
$audit_table = [];

$identities = ses_list_identities();

$verification_attributes = ses_get_identity_verification_attributes($identities);
merge_table($audit_table, $verification_attributes);

$dkim_attributes = ses_get_identity_dkim_attributes($identities);
merge_table($audit_table, $dkim_attributes);

$notif_attributes = ses_get_identity_notification_attributes($identities);
merge_table($audit_table, $notif_attributes);

$email_attributes = ses_get_identity_main_from_domain_attributes($identities);
merge_table($audit_table, $email_attributes);

$policies = ses_get_identity_policies($identities);
merge_table($audit_table, $policies);

uksort($audit_table, 'strnatcasecmp');
fwrite(STDOUT, '| ' . implode(' | ', $header) . ' |' . PHP_EOL);
fwrite(STDOUT, '| ' . implode(' | ', array_fill(0, count($header), '---')) . ' |' . PHP_EOL);
foreach ($audit_table as $identity => $row) {
  array_unshift($row, $identity);
  fwrite(STDOUT, '| ' . implode(' | ', $row) . ' |' . PHP_EOL);
}

Example:

AWS_PROFILE=my-aws php -f audit-aws-ses.php > /tmp/aws-ses-audit.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment