Securing your AWS SES SMTP credentials is a critical step in protecting your domain’s reputation and avoiding unexpected costs. If your credentials leak, anyone can send emails from your verified domain—unless you restrict access to your specific infrastructure.
The following guide details how to implement IP-based security for AWS SES using IAM policies, ensuring your credentials only work from your production environment (like a Kubernetes cluster).
The Problem: “Working from Anywhere” is a Risk
By default, AWS SES SMTP credentials are valid from any location. If these credentials are leaked—whether through a GitHub commit, an exposed log, or a compromised server—an attacker can use them from their own laptop to send spam or phishing emails in your name.
This can lead to your account being suspended and your domain’s reputation being destroyed.
The Solution: IP-Based Access Control
The most effective way to secure these credentials without changing your code is to update your IAM Policy. By adding a “Condition” to the policy, you can tell AWS to only allow email-sending requests that come from a specific IP address—the NAT Gateway of your Kubernetes cluster.
Step 1: Find Your Kubernetes NAT Gateway IP
Your cluster uses a NAT Gateway to send traffic to the internet. This gateway has a static Elastic IP address that you must whitelist.
- Log in to the AWS Console.
- Navigate to VPC → NAT Gateways.
- Identify the NAT Gateway used by your EKS/Kubernetes VPC.
- Copy the Primary public IPv4 address (e.g.,
1.2.3.4).- Note: If you have multiple NAT Gateways for high availability, copy all of them.
Step 2: Update Your IAM Policy
Next, you need to modify the permissions attached to your SES SMTP user to include the IP restriction.
- Go to IAM → Users and select your SES SMTP user.
- In the Permissions tab, edit the existing policy in the JSON editor.
- Replace the policy with the following secure template:
JSON
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "ses:SendRawEmail",
"Resource": [
"arn:aws:ses:YOUR_REGION:YOUR_ACCOUNT_ID:identity/*",
"arn:aws:ses:YOUR_REGION:YOUR_ACCOUNT_ID:configuration-set/*"
],
"Condition": {
"IpAddress": {
"aws:SourceIp": "YOUR_NAT_GATEWAY_IP/32"
}
}
}]
}
- YOUR_REGION: e.g.,
us-east-1. - YOUR_ACCOUNT_ID: Your 12-digit AWS ID.
- YOUR_NAT_GATEWAY_IP: The IP you found in Step 1.
Step 3: Verify with Node.js
To ensure the security is active, you should test it from two different environments.
Example Node.js Sending Code
JavaScript
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
host: "email-smtp.us-east-1.amazonaws.com",
port: 587,
secure: false, // use TLS
auth: {
user: "YOUR_SMTP_USERNAME",
pass: "YOUR_SMTP_PASSWORD",
},
});
async function sendMail() {
try {
const info = await transporter.sendMail({
from: '"Security Test" <sender@yourdomain.com>',
to: "recipient@example.com",
subject: "SES Security Test",
text: "Testing IP restrictions.",
});
console.log("Message sent: %s", info.messageId);
} catch (error) {
console.error("Error sending email:", error.message);
}
}
sendMail();
Test Results
- From Your Local Machine: Running
node index.jsshould now return an Access Denied error. This proves the restriction is working. - From a Kubernetes Pod: Running the same code inside your cluster should result in Success.
Summary of Changes
| Feature | Before (Unsecure) | After (Secure) |
| Access | Works from anywhere | Restricted to your Cluster IP |
| Resources | Wildcard (*) | Specific Verified Identities |
| Security | Single-layer (Password only) | Multi-layer (Password + IP) |