使用PyMongo建立自签名SSL连接

12

我正在尝试使用PyMongo创建安全的SSL连接到MongoDB。 目标是在EC2上运行的Mongo实例上使用此配置,以便我可以使用Python客户端连接。 为了测试,我首先尝试将配置在本地运行。 我的尝试未能成功,可以在这里找到。

我认为问题的简短版本是:我的客户端证书机构文件ca.pem不正确。 我现在的做法是,这个文件与我在服务器端使用的文件完全相同。 这两个文件都是使用x509openssl创建的,我怀疑我的客户端文件需要某种不同的内容,但我不确定如何生成该内容或者这个怀疑是否正确。

下面是我为创建必要的证书和密钥所做的详细信息(在Mac El Capitan上):

首先,生成证书机构:

$ mkdir ~/ssl
$ cd ~/ssl
$ openssl req -out ca.pem -new -x509 -days 3650    
# enter info

生成服务器 .pem 文件:

$ openssl genrsa -out server.key 2048
$ openssl req -key server.key -new -out server.req
# enter info
$ openssl x509 -req -in server.req -CA ca.pem -CAkey privkey.pem -CAserial file.srl -out server.crt -days 3650
$ cat server.key server.crt > server.pem

现在对于客户端也做同样的操作:

$ openssl genrsa -out client.key 2048
$ openssl req -key client.key -new -out client.req
$ openssl x509 -req -in client.req -CA ca.pem -CAkey privkey.pem -CAserial file.srl -out client.crt -days 3650
$ cat client.key client.crt > client.pem

接下来我在etc/mongod.conf中进行以下配置:

net:
  ...
  ssl:
    mode: requireSSL
    PEMKeyFile: ~/ssl/server.pem
    CAFile: ~/ssl/ca.pem

现在使用以下命令在Mac上启动Mongo:

$ mongod --config /etc/mongod.conf

如预期一样工作。进程运行并似乎正在接受配置。

然后在Python端(也在本地调试中运行),我在调用mongo时执行以下操作:

import ssl
from pymongo import MongoClient

client = MongoClient(
        '127.0.0.1',
        27017,
        ssl=True,
        ssl_certfile='~/ssl/client.pem',
        ssl_cert_reqs=ssl.CERT_REQUIRED,
        ssl_ca_certs='~/ssl/ca.pem'
        )
# try a simple insert
当我运行这段代码时,在创建MongoClient后,Python代码块被阻塞,并且我在mongo日志中看到以下内容:
2016-03-03T22:11:30.331-0800 E NETWORK  [conn21] SSL: error:14094418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca

基于下面Wan的评论,我重新做了整件事情,并确保'Distinguished Name'中的信息与那些规范相一致。旧版本明显针对服务器和客户端都使用同样的通用名称。连接仍未成功,但是消息有所不同:

2016-03-11T12:29:40.380-0800 I NETWORK  [initandlisten] connection accepted from 127.0.0.1:57363 #3 (1 connection now open)
2016-03-11T12:29:40.386-0800 I NETWORK  [conn3] end connection 127.0.0.1:57363 (0 connections now open)

这两条消息会不断重复,直到我停止Python进程,在尝试打开连接时会被阻塞。现在end connection部分已经放置在mongo日志之前alert unknown ca行的位置。

调试附加信息:

$ openssl verify -CAfile ca.pem client.pem
client.pem: OK
$ openssl verify -CAfile ca.pem server.pem
server.pem: OK
$ openssl x509 -noout -subject -in server.pem
subject= /C=US/ST=Washington/L=Seattle/O=codeMelon/OU=Engineering/CN=server.com/emailAddress=<my-email-address>@gmail.com
$ openssl x509 -noout -subject -in client.pem
subject= /C=US/ST=Washington/L=Seattle/O=codeMelon/OU=Engineering/CN=client.com/emailAddress=<my-email-address>@gmail.com

在正确形成主题行之后,最终结论是mongo接受了连接,然后结束了连接,Python进程在没有进行预期插入的情况下阻塞。

非常感谢任何帮助!


这个操作应该是保护公共可访问的MongoDB的一个相当常见的方式。如果我只能得到他们的注意,这里肯定有人已经做过了 :) 如果有人能够提供能够工作的指示,我很乐意从头开始而不是调试我已经拥有的内容。 - Marshall Farrier
1
еҰӮжһңжҲ‘жҢүз…§жӮЁзҡ„жӯҘйӘӨеӨҚеҲ¶пјҲз”ҡиҮійҖҡиҝҮжҢүз…§жӮЁзҡ„й“ҫжҺҘеҲӣе»әfile.srlпјүпјҢе®ғеҸҜд»Ҙе·ҘдҪңгҖӮйңҖиҰҒжЈҖжҹҘзҡ„еҮ д»¶дәӢжғ…пјҡ1пјүиҝҗиЎҢopenssl verify -CAfile ca.pem client.pemжқҘйӘҢиҜҒе®ўжҲ·з«Ҝе’ҢжңҚеҠЎеҷЁpemдёҺCAзҡ„еҢ№й…ҚжҖ§гҖӮ2пјүзЎ®дҝқжӮЁиҫ“е…Ҙзҡ„subjжҲ–дҝЎжҒҜе…·жңүеҢ№й…Қзҡ„дҝЎжҒҜпјҢеҚі/C=<Country Name>/ST=<State>/L=<Locality Name>/O=<Organisation Name>/emailAddress=<email>пјҢдҪҶеҜ№дәҺCN=<Common Name> CA/server/clientжңүдёҚеҗҢзҡ„еҖјгҖӮеҰӮжһңжӮЁд»Қжңүй—®йўҳпјҢиҜ·еҸ‘еёғжӮЁзҡ„subjеҖјзӨәдҫӢгҖӮ - Wan B.
通过您的修复重新运行后,现在看起来更像是我的客户端没有对数据库具有写入权限。在我的玩具客户端测试代码中,我添加了一个“print”语句,显示一切都按预期进行,直到客户端实际尝试写入数据库。 - Marshall Farrier
1
一些事情:1)在您的服务器上运行hostname命令(假设您使用的是*nix系统)。将主机名为服务器的subj添加一个“CN”附加项。将您的MongoClient 127.0.0.1值更改为与主机名匹配(在1之前也应该是一个句点而不是引号),或查看sslAllowInvalidHostnames标志。2)尝试使用mongo shell连接,而不是您的Python脚本,先解决SSL问题,然后再添加额外的脚本。告诉我进展如何。 - Wan B.
2
通过在MongoClient()中传递参数ssl_match_hostname=False,我已经成功地在本地使用PyMongo工作了。如果您想将结果总结为答案,我很乐意给您一个“正确的答案”。非常感谢您的帮助! - Marshall Farrier
显示剩余5条评论
1个回答

27

有其他的方法可以使用证书颁发机构(Certificate Authority)生成服务器/客户端pem文件,而不涉及到file.srl,但本答案是为了回答这个问题。

值得一提的是,大多数MongoDB v3.0+的版本现在都包括SSL支持,请确保选择一个支持SSL的软件包。下面的示例经过了在Ubuntu Linux 14.04上使用MongoDB v3.2PyMongo v3.2.1进行测试,其中单台机器生成了CA、服务器和客户端pem文件以进行演示。

让我们生成ca.pemprivkey.pem。主体结构为/C=<Country Name>/ST=<State>/L=<Locality Name>/O=<Organisation Name>/emailAddress=<email>/CN=<Common Name>

mkdir ~/ssl
cd ~/ssl
openssl req -out ca.pem -new -x509 -days 3650 -subj "/C=AU/ST=NSW/O=Organisation/CN=root/emailAddress=user@domain.com"

生成 server .pem 文件:

hostname  # note down the value
echo "00" > file.srl # two random digits number
openssl genrsa -out server.key 2048
openssl req -key server.key -new -out server.req -subj  "/C=AU/ST=NSW/O=Organisation/CN=server1/CN=<hostname value>/emailAddress=user@domain.com"
openssl x509 -req -in server.req -CA ca.pem -CAkey privkey.pem -CAserial file.srl -out server.crt -days 3650
cat server.key server.crt > server.pem
openssl verify -CAfile ca.pem server.pem

尽管您也可以将IP地址用作 CN 值,但不建议这样做。请参见 RFC-6125

现在让我们生成 client.pem 文件:

openssl genrsa -out client.key 2048
openssl req -key client.key -new -out client.req -subj "/C=AU/ST=NSW/O=Organisation/CN=client1/emailAddress=user@domain.com"
openssl x509 -req -in client.req -CA ca.pem -CAkey privkey.pem -CAserial file.srl -out client.crt -days 3650
cat client.key client.crt > client.pem
openssl verify -CAfile ca.pem client.pem

生成.pem文件后,现在可以运行mongod。例如:

mongod --sslMode requireSSL --sslPEMKeyFile ~/server.pem --sslCAFile ~/ca.pem

您可以使用Mongo shell进行连接测试,例如:

mongo --ssl --sslPEMKeyFile ~/client.pem --sslCAFile ~/ca.pem --host <server hostname>

如果您能成功连接,您可以尝试使用PyMongo。例如:

import ssl 
from pymongo import MongoClient
client = MongoClient(
    '<server hostname>',
    27017,
    ssl=True,
    ssl_certfile='~/client.pem',
    ssl_cert_reqs=ssl.CERT_REQUIRED,
    ssl_ca_certs='~/ca.pem'
 )

另外,您还可以使用 mongod 标志 --sslAllowInvalidHostnames 来指定 localhost 等。

对于生产环境使用,您的 MongoDB 部署应该使用由单个证书机构生成和签名的有效证书。如果您使用自签名证书,尽管通信渠道将会加密,但无法验证服务器身份。使用由受信任的证书机构签名的证书将允许 MongoDB 驱动程序验证服务器的身份。一般来说,除非网络是受信任的,否则应避免使用自签名证书。

您可能会发现以下其他相关链接有用:


1
服务器和客户端的O或OU字段必须不同,否则证书会被视为集群中的另一个服务器,而不是用户。如果需要区分,请添加OU字段。 - Mattis Asp
如果有帮助的话,我必须执行的第一步是 cd ~/; openssl rand -writerand .rnd 以确保 .rnd 文件存在于预期路径中,否则我无法继续。 - openCivilisation
这是我找到的最好的例子,可以帮助我学习如何做到这一点。如果还有一个中间CA,它会是什么样子? - openCivilisation

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接