Amazon SESのSMTPインターフェースを用いた送信で送信元を制限する
初めに
Amaozon SESをしたメール送信はAWS SDK等によるAPIを用いた送信のほか、SMTP認証情報を発行して従来のSMTPサーバのように直接SMTPインターフェースを利用してのメール送信が可能です。
APIを用いた呼び出しについてはAWS APIと同様ですので権限周りはIAMポリシー制御だなと意識できる方も多いかと思います。
そのためユーザのポリシーを適切に設定すればAPI経由でのメール送信ではIP制限や利用できる検証済みドメインやアドレスなどの制限といったことが可能です。
一方でSMTPインターフェースを利用する場合は当然通信はHTTP(S)ではなくSMTPによる送信となるためプロトコルレベル自体の仕様が異なります。
全く異なるプロトコルとなりますがこちらにこちらを利用する場合に送信元IP制限といった制約は実現可能なのでしょうか。
SMTPインターフェースを利用しても認可認証の大元はIAMから
結論としては送信に関する権限の制御はIAMの権限に基づくため上記記事のようなポリシーを利用することで送信制限を実現することが可能です。
SMTPインターフェースを利用する際に利用するSMTP認証情報はIAMユーザのアクセスキー・シークレットキーが元となっておりSMTPインターフェースで送信する場合も元となっているIAMユーザ影響を受けます。
現在ではマネジメントコンソールかなりUIが改善されていますが、以前はIAMユーザに依存していることが微妙にわかりづらく、SESのSMTPユーザはIAMユーザが元になっていることを知らない方もいらっしゃるのではないでしょうか。
※ 2022/02ごろの旧UIでのSMTPユーザ作成記事。雑に読み飛ばしてラベルだけ見て入れてると意外と実態がIAMであることを見落とす可能性がある
一応AWSのドキュメントでも設定するIAMポリシーの情報が一応載っていますが、細かい言及はなくやりたい頃から逆引きしようと思うと引っかからない印象です。
ポリシーにIP制限をもつユーザの認証情報でSMTPインターフェースを利用して送信してみる
実際にses:SendRawEmail
に対してIP制限を割り当てるようなポリシーのみを持つユーザを作成し、そのユーザのSMTP認証情報を利用してメール送信してみましょう。
SMTP認証情報及びその元となるユーザはAmazon SESのマネジメントコンソールの左側の「SMTP設定」を開き、SMTP認証情報作成を押します。
次の画面でIAMユーザ名を入力します。SMTPユーザ名はアクセスキーの値となりこのユーザ名が使われるわけではありません。
ポリシー部分はJSONのブロックの開閉等もできいかにも編集できそうですがリードオンリーみたいなのでここで書き換えることはできません。
余談ですが、このUIについて実は今回の検証で初めてアップデート入ってるのを知りました。
SMTPユーザの実態がIAMユーザであることを意識しやすくなった意味ですごく良いアップデートだとおもいます(少なくとも去年の秋まではこのUIではなかった)。
次の画面でSMTP認証情報が発行されるので保管します。
現在では既存のユーザに対して直接SMTP認証情報を追加することはできないのですが、パンくずリストをよくみるとIAM配下なのでもしかしたら作れるようになる未来があるかもしれません。
もしSMTPのパスワードを紛失し同一のユーザに認証情報を作りたい場合、直接マネジメントコンソールから生成はできませんが通常のアクセスキーを発行の上ドキュメントに記載のスクリプトを用いて算出することで生成は可能です。
作成されたユーザにはAmazonSesSendingAccess
という名前で先ほどIAMユーザ名指定の時に表示されたポリシーが割り当てられます。
今回は自宅からの送信を失敗させたいので127.0.0.1/32
のみからのアクセスを許可するように変更します。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "ses:SendRawEmail", "Resource": "*", "Condition": { "IpAddress": { "aws:SourceIp": [ "127.0.0.1/32" ] } } } ] }
上記で作成したユーザの認証情報をThunderbirdに設定して送信すると利用するAWS CLIやSDK等でAPIを実行した際によく見かける権限不足のようなメッセージが確認できます。
どうやら専用のエラーが用意されているようです。
エラーの出るステップを確認する
なんとなく推定はできますが具体的にエラーとなるタイミングや生のエラーメッセージを確認してみます。
STARTTLSで通信を行うのでopenssl
コマンド上で対話します。
成功ケース
とりあえず参考として認証情報やポリシー自体に問題がなく(デフォルトのポリシーを利用)メールが送信できるパターンを試しておきます。
#example.com部分について実際には自分が保持しているドメインに置き換えて実行しています % openssl s_client -crlf -quiet -connect email-smtp.ap-northeast-1.amazonaws.com:587 -starttls smtp depth=2 C = US, O = Amazon, CN = Amazon Root CA 1 verify return:1 depth=1 C = US, O = Amazon, CN = Amazon RSA 2048 M01 verify return:1 depth=0 CN = email-smtp.ap-northeast-1.amazonaws.com verify return:1 250 Ok EHLO mail.example.com 250-email-smtp.amazonaws.com 250-8BITMIME 250-STARTTLS 250-AUTH PLAIN LOGIN 250 Ok AUTH PLAIN xxxxxxxxxx 235 Authentication successful. MAIL FROM: auth-fail@example.com 250 Ok RCPT TO: xxxxx@example.com 250 Ok DATA 354 End data with <CR><LF>.<CR><LF> From: auth-fail@example.com To: xxxxx@example.com Subject: from ses Im from SES . 250 Ok 01060189d4b021bf-350814d7-97db-4a4d-824f-782d7760f018-000000 QUIT 221 Bye
ハイライト部分がこちらで入力しているコマンドです。
一部割愛しますがざっくりとemail-smtp.ap-northeast-1.amazonaws.com
のポート587にSTARTTLSで接続し、クライアント側がmail.example.com
であることを名乗った上で、SMTPの認証情報xxxxxxxxxx
を送信して成功したのでsuccess@example.com
をfrom、xxxxx@example.com
をtoとしてIm from SES
というメールの内容を送ってコネクションを切断してるという感じです。
なお認証情報に指定する値(xxxxxxxxxx
)は実際にはSMTPの仕様に則りユーザ名
とユーザ名
とパスワード
をヌルバイト文字で結合してbase64でエンコードした文字列になります(ユーザ名が2回出てくるのは誤りではありません)。
認証情報誤り(認証失敗)
認証情報(AUTH PLAIN
コマンド)にabcdefg
という誤った値を入れてみます。
% openssl s_client -crlf -quiet -starttls smtp -connect email-smtp.ap-northeast-1.amazonaws.com:587 depth=2 C = US, O = Amazon, CN = Amazon Root CA 1 verify return:1 depth=1 C = US, O = Amazon, CN = Amazon RSA 2048 M01 verify return:1 depth=0 CN = email-smtp.ap-northeast-1.amazonaws.com verify return:1 250 Ok EHLO mail.example.com 250-email-smtp.amazonaws.com 250-8BITMIME 250-STARTTLS 250-AUTH PLAIN LOGIN 250 Ok AUTH PLAIN abcdefg 535 Authentication failed MAIL FROM: auth-fail@example.com 530 Authentication required
認証(Authentication)が失敗している為、操作のためには認証が必要となるMAIL FROM
コマンドに対して認証必須のエラーが返却されていることが確認できます。
ポリシーによる制限(権限なし)
先ほどのようにIP制限をかけた上でその範囲外からアクセスしまします。認証情報は正しいものを使います。
(根本的にはメールの送信権限ses:SendRawEmail
が不足している状態)
% openssl s_client -crlf -quiet -starttls smtp -connect email-smtp.ap-northeast-1.amazonaws.com:587 depth=2 C = US, O = Amazon, CN = Amazon Root CA 1 verify return:1 depth=1 C = US, O = Amazon, CN = Amazon RSA 2048 M01 verify return:1 depth=0 CN = email-smtp.ap-northeast-1.amazonaws.com verify return:1 250 Ok EHLO mail.example.com 250-email-smtp.amazonaws.com 250-8BITMIME 250-STARTTLS 250-AUTH PLAIN LOGIN 250 Ok AUTH PLAIN xxxxxxxxxx 235 Authentication successful. MAIL FROM: policy-fail@example.com 250 Ok RCPT TO: xxxxx@example.com 250 Ok DATA 354 End data with <CR><LF>.<CR><LF> From: policy-fail@example.com To: xxxxx@example.com Subject: from ses Im from SES . 554 Access denied: User `arn:aws:iam::xxxxx:user/ses-smtp-user.20230808-xxxxx' is not authorized to perform `ses:SendRawEmail' on resource `arn:aws:ses:ap-northeast-1:xxxxx:identity/example.com'
さて先ほど同様エラーとはなるのですがメッセージだけではなく、エラーの出るフェーズが異なるのがわかりますでしょうか。
これがよく言われる認可(Authorization)と認証(Authentication)の違いです。
認証情報(ユーザ名/パスワード)はあっている為認証は成功するが、その後メールを送る瞬間(DATA
コマンド送信後に.
のみの行を送信したタイミング)にそのユーザが送信するための権限(ses:SendRawEmail
)を持ち合わせないためこのタイミングでの失敗となります。
1つ興味深い点としては少なくともユーザ側の見える範囲でIAMポリシーに対して許可(Allow)となっている権限は割り当てていないはずなのですが、MAIL FROM
やRCPT TO
、DATA
といった送られるコマンド自体は受け付けられており、あくまで最後の送信のみが権限なしになるという点です。
終わりに
本当はIAMで制限かけられる+例としてIP制限を、と思っていたのですがやっていくうちに色々気になってしまい実際の対話部分まで見てしまいました。
大きく脱線してしまいましたが、Amazon SESで直接SMTPインターフェースを利用する際に利用する認証情報はIAMユーザが元となっており、AWS APIのようにユーザに割り当てられたIAMポリシーをもとに送信の権限が決定する。ということを意識していただけるとできることの選択肢が広がるので頭の片隅においていただければ幸いです。
ただ注意点すべ起点としては、IAMポリシーによる操作の制限は認可の部分の制御となる関係で認証の段階自体を防御できるものではないという点です。メールを送信するという操作に対してIP制限をかけることはできますが、SMTP認証という行為に対しては保護がかけらないという点はご留意いただければと思います(操作自体はできないのでそれ問題になるかは別として)。