美文网首页
AWS SES与Sendmail集成碰见的问题

AWS SES与Sendmail集成碰见的问题

作者: 我的写作练习 | 来源:发表于2019-07-07 14:05 被阅读0次

引子

业务部门使用的一个内部系统,邮件功能怕是坏了有一两年了吧,之前都是业务团队的管理员帮着添加用户找回密码之类的,倒也相安无事。最近人员扩充比较厉害,忙不过来了,就提工单过来了。

分析

现状

公司的应用都是运行在AWS上,这个出问题的内部系统也不例外。阅读了相关代码和cloudformation后,搞清了之前邮件功能的实现逻辑。

old.jpg

正如上图所示,应用调用邮件API将邮件发送至本机的Postfix服务,再由Postfix转发至AWS SES. 而Postfix投递AWS SES时使用了postman(此postman非彼Postman,这个是python的包,提供了6条调用AWS SES的命令,底层用是boto)。

问题

先查看了一下程序日志,邮件发送成功。那么问题就出在Postfix到AWS SES的阶段,于是查看了一下邮件日志(/var/log/maillog), 报了认证方面的错误,转头看看ec2的IAM Role发现是有SES相关权限的,相关配置代码四五年没有修改过了。
问题应该出在AWS权限获取上面了。而负责这一部分的是postman. 于是pip list看了下安装的postman版本:v0.6,查了一下已经是最后一个版本了,发布时间是2011年…… 然后看了下boto的版本:v2.0,也是非常老旧。
看了下boto3关于AWS credentials的部分,boto3对于credentials的获取顺序如下:

  1. Passing credentials as parameters in the boto.client() method
  2. Passing credentials as parameters when creating a Session object
  3. Environment variables
  4. Shared credential file (~/.aws/credentials)
  5. AWS config file (~/.aws/config)
  6. Assume Role provider
  7. Boto2 config file (/etc/boto.cfg and ~/.boto)
  8. Instance metadata service on an Amazon EC2 instance that has an IAM role configured.

更多的请参考boto3文档
上面说的环境变量和配置文件我这个ec2实例里面当然没有了……
又看了boto2.49(2.0的没找到哇)的文档,只说了传参,环境变量,各种配置文件的部分,没有提到instance metadata service. 看了一下2.0的源码,也是有从此获取credentials的相关代码的。
于是打算测试一下相关代码,SES的API还是很简单的,创建connection然后发邮件就好。

>>> import boto.ses
>>> conn = boto.ses.connection.SESConnection(
        aws_access_key_id='<YOUR_AWS_KEY_ID>',
        aws_secret_access_key='<YOUR_AWS_SECRET_KEY>')
>>> conn
SESConnection:email.us-east-1.amazonaws.com
>>> conn.send_email(
        'from@address.com',
        'subject',
        'body',
        ['to@address.com'])
  • 填写了从metadata service获取的id和key, 失败
  • 看了下API发现id和key是非必填的,再次尝试,还是失败

印象中其它系统调用非python语言的AWS SDK时也是没有配置过ID和Key,大抵也是用的无参构造函数,由SDK自己获取的吧。于是又看了下源码,大致逻辑是如果构造函数没有传,provider按照上面提到过的顺序,挨着尝试获取credentials. 感觉好像并没什么问题。
始终觉得包太老了,可能会有什么东西过时,于是卸载了boto2.0, 安装了最新的boto2.49, 再次尝试。

  • 填写了从metadata service获取的id和key, 失败
  • 使用无参构造函数,成功

难道是获取的id和key不正确,打印了对比了一下两种方式构造的id和key是一样的,困惑...于是又打开2.49的源码浏览下,发现参数有个security_token,而metadata service返回的json里面也有个叫Token的字段。于是尝试使用id, key, token三个参数的构造函数,成功…… 印象中2.0的构造函数好像并没有这个参数,查看源码果然如此。

推测应该是AWS更新了metadata service,之前很有可能返回的一个永久的key pair,而现在返回的是一个有效期为几小时的左右的临时key pair,而且需要结合token使用。这里就不做更细节的考证了。

方案

那么只剩下自行更新postman的依赖库版本(大概是下载源码后修改依赖,调试,然后打个包自己用吧),不使用postman换一种方式集成AWS SES,修改代码使用AWS SDK发送email这三个办法了。
权衡了一下,我选择了第二种,并且没用Postfix,因为发现我们的AMI中本来就已经安装了Sendmail作为自启动服务,Postfix还需要初始化ec2时下载安装,有些麻烦。

new.jpg

执行

AWS 已经提供了非常详细的指导AWS SES集成Sendmail或者Postfix的说明文档,所以还有什么写的呢?一开始我也以为这样……

Sendmail与AWS SES的集成

按照AWS文档配置了一下,命令行调用了一下sendmail,邮件经AWS SES成功发送至目的邮箱。于是就将配置过程脚本化,写在了userData中用于初始化ec2时执行,这个过程没什么特别需要说的。

新的问题

上面也说了,通过Sendmail的命令行工具已经可以成功发送邮件了,但是应用系统给Sendmail发邮件却出了问题……又是Authentication failed.
要知道在做Sendmail与AWS SES集成前我是测试过应用系统与Sendmail之前的连通性的,并不需要用户名和密码。
所以应该就是在集成过程中对于Sendmail配置文件的修改,给我本机的Sendmail服务添加了认证。为了验证这个结论,做了个实验。
在未配置集成时

$ telnet localhost 25
...
$ EHLO localhost
...
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-8BITMIME
250-SIZE
250-DSN
250-ETRN
250-DELIVERBY
250 HELP

在集成完后

$ telnet localhost 25
...
$ EHLO localhost
...
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-8BITMIME
250-SIZE
250-DSN
250-ETRN
250-AUTH LOGIN PLAIN
250-DELIVERBY
250 HELP

多了一行250-AUTH LOGIN PLAIN, 这就是Sendmail服务器添加了认证标志,而PLAIN方式显而易见的就是指明文方式,也就是用户名和密码。
这就是要登录了?问题是我也没有用户名和密码呀……
我尝试了一下SES的用户名密码,失败;又创建了一个系统用户然后登录(为什么要新建用户,因为AMI自带的用户是没密码的,ssh时使用密钥进行登录),还是失败。这下不知道该怎么办了……
稍微提一下,如果尝试在telnet后的交互中执行AUTH LOGIN命令登录时,系统会提示334 VXNlcm5hbWU6334 UGFzc3dvcmQ6,分别是Base64编码的用户名和Base64编码的密码。

解决——去掉认证

期间查阅了一些讲给Sendmail添加认证配置的文章,但都没讲用户名密码是哪来的,于是乎停止了搜索,又仔细看了两遍与AWS SES的集成时修改的配置,结合Sendmail文档,删掉了这行: define(`confAUTH_MECHANISMS', `LOGIN PLAIN')dnl. 在telnet看看发现已经没有250-AUTH LOGIN PLAIN这一行了,去掉认证了,再试试应用程序,成功发送邮件。

解决——保留认证

SASL和Cyrus SASL

Sendmail认证的相关文章基本都提到了cyrus-sasl,查阅了一下相关文档。说cyrus-sasl之前不得不提一下SASL.
SASL即Simple Authentication and Security Layer.

简单认证与安全层 (SASL) 是一个在网络协议中用来认证和数据加密的构架。它把认证机制从程序协议中分离开, 理论上使用SASL的程序协议都可以使用SASL所支持的全部认证机制。

简单来说就是SASL定义了一个中间抽象层,类似胶水或者粘合剂一样,使程序协议和认证机制能够集成在一起。

sasl.jpg

Cyrus SASL

Simple Authentication and Security Layer (SASL) is a specification that describes how authentication mechanisms can be plugged into an application protocol on the wire. Cyrus SASL is an implementation of SASL that makes it easy for application developers to integrate authentication mechanisms into their application in a generic way.

SASL是标准, Cyrus SASL是它的实现之一。

Cyrus SASL的配置

  1. 利用应用程序自己的配置文件,通过SASL_CB_GETOPT回调,详见于此。没有细看这种方式,因为看到Sendmail用的是第二种。
  2. Cyrus SASL会去这个路径查找/usr/lib/sasl/Appname.conf, Appname是应用设置的名称。比如Sendmail,文件名即为Sendmail.conf. 如果使用的是SASLv2,那么目录可能是sasl2而不是sasl, 同时可能也会有lib, lib64的差异。具体还是要看操作系统吧,比如我的这个ec2实例,配置文件位于/etc/sasl2/Sendmail.conf.

查看本机的配置

$ cat /etc/sasl2/Sendmail.conf

pwcheck_method:saslauthd

pwcheck_method指定密码验证工具(Password verifiers)。

saslauthd This calls out to the saslauthd daemon, which also ships with the distribution. The saslauthd daemon has a number of modules of its own, which allow it to do verification of passwords in a variety of ways, including PAM, LDAP, against a Kerberos database, and so on. This is how you would want to, for example, use the data contained in /etc/shadow to authenticate users.

简单来说就是调用saslauthd这个守护进程来验证密码。
下面看看saslauthd的配置。

$ cat /etc/sysconfig/saslauthd

# Directory in which to place saslauthd's listening socket, pid file, and so
# on.  This directory must already exist.
SOCKETDIR=/var/run/saslauthd

# Mechanism to use when checking passwords.  Run "saslauthd -v" to get a list
# of which mechanism your installation was compiled with the ablity to use.
MECH=pam

# Options sent to the saslauthd. If the MECH is other than "pam" uncomment the next line.
# DAEMONOPTS=--user saslauth

# Additional flags to pass to saslauthd on the command line.  See saslauthd(8)
# for the list of accepted flags.
FLAGS=

这里的认证机制使用的pam. Linux PAM 为Linux操作系统上面应用程序与服务提供了动态的认证支持。它将程序开发与认证方式进行分离,程序在运行时调用附加的“认证”模块完成自己的工作。系统管理员可以通过配置选择要使用哪些认证模块。

$ cat /etc/pam.d/smtp.sendmail

#%PAM-1.0
auth       include  password-auth
account    include  password-auth

好吧,还是用户密码,又回到了最初胡乱尝试的时候,当时使用用户名密码尝试认证失败了呀。再试一次吧,通过文档知道了testsaslauthd这个方便的测试工具,就用这个试试吧。

$ testsaslauthd -u user -p passwod -s smtp.sendmail

connect() : No such file or directory

没见过的错误,搜了一下基本都是Postfix认证时出现的。突然想起来saslauthd是个守护进程,于是去查看了下状态,果然没开,启动服务后再次尝试,已经成功了。

$ testsaslauthd -u user -p passwod -s smtp.sendmail

0: OK "Success."

又先后测试了telnet登陆和应用程序代码指定用户名密码的登陆,均获成功。
回想刚集成完时,若知道敲一句service saslauthd start,哪还有后面这大半天的折腾呢……

相关文章

网友评论

      本文标题:AWS SES与Sendmail集成碰见的问题

      本文链接:https://www.haomeiwen.com/subject/txmbhctx.html