Python 抛出 SSL 证书验证错误 w/o 咨询系统 CA 包?
Python throwing an SSL certificate validation error w/o consulting system CA bundles?
对于两个基本相同的系统(都是 运行ning Fedora 25,都安装了相似的软件包版本),一个系统因 SSL 证书验证错误而失败,而另一个则没有。也就是说,如果我 运行:
import requests
r = requests.get('https://insidehost.corp.example.com')
一个系统运行正常,而另一个系统失败:
requests.exceptions.SSLError: ("bad handshake: Error([('SSL routines', 'ssl3_get_server_certificate', 'certificate verify failed')],)",)
起初我认为我只是缺少必要的 CA 证书,但是 strace
下的 运行ning python 显示在故障系统上,python 是从不尝试打开 ca 包。也就是说,在运行的系统上:
strace -e trace=open,stat python testscript.py |& grep /etc/pki
仅产量:
open("/etc/pki/tls/legacy-settings", O_RDONLY) = -1 ENOENT (No such file or directory)
stat("/etc/pki/tls/certs/ca-bundle.crt", {st_mode=S_IFREG|0444, st_size=257079, ...}) = 0
open("/etc/pki/tls/certs/ca-bundle.crt", O_RDONLY) = 4
但是在失败的系统上产生:
open("/etc/pki/tls/legacy-settings", O_RDONLY) = -1 ENOENT (No such file or directory)
此外,运行在故障系统上使用 python3
使用相同的测试脚本...有效!
在这两种情况下,python
都是 /usr/bin/python
来自 python-2.7.13-1.fc25.x86_64
。两个系统都没有设置任何 *_CA_BUNDLE
环境变量。
经过一些额外的调查,我已经弄清楚了,我想我会 post 这里的解决方案,因为它不一定很明显。
requests
模块包含自己的证书包,如果找不到另一个要使用的证书包,它将退回到那个。它查找证书包的方式是这样的,在 requests/certs.py:
try:
from certifi import where
except ImportError:
def where():
"""Return the preferred certificate bundle."""
# vendored bundle inside Requests
return os.path.join(os.path.dirname(__file__), 'cacert.pem')
你可以通过运行看到这个结果:
$ python -m requests.certs
/etc/pki/tls/certs/ca-bundle.crt
从上面的代码可以看出,requests
使用了certifi
模块来定位一个合适的bundle。在故障系统上,certifi
模块是通过 pip
安装的,而不是使用系统包,这意味着它缺少适当的配置。
解决方案是 yum install python2-certifi
。
对于两个基本相同的系统(都是 运行ning Fedora 25,都安装了相似的软件包版本),一个系统因 SSL 证书验证错误而失败,而另一个则没有。也就是说,如果我 运行:
import requests
r = requests.get('https://insidehost.corp.example.com')
一个系统运行正常,而另一个系统失败:
requests.exceptions.SSLError: ("bad handshake: Error([('SSL routines', 'ssl3_get_server_certificate', 'certificate verify failed')],)",)
起初我认为我只是缺少必要的 CA 证书,但是 strace
下的 运行ning python 显示在故障系统上,python 是从不尝试打开 ca 包。也就是说,在运行的系统上:
strace -e trace=open,stat python testscript.py |& grep /etc/pki
仅产量:
open("/etc/pki/tls/legacy-settings", O_RDONLY) = -1 ENOENT (No such file or directory)
stat("/etc/pki/tls/certs/ca-bundle.crt", {st_mode=S_IFREG|0444, st_size=257079, ...}) = 0
open("/etc/pki/tls/certs/ca-bundle.crt", O_RDONLY) = 4
但是在失败的系统上产生:
open("/etc/pki/tls/legacy-settings", O_RDONLY) = -1 ENOENT (No such file or directory)
此外,运行在故障系统上使用 python3
使用相同的测试脚本...有效!
在这两种情况下,python
都是 /usr/bin/python
来自 python-2.7.13-1.fc25.x86_64
。两个系统都没有设置任何 *_CA_BUNDLE
环境变量。
经过一些额外的调查,我已经弄清楚了,我想我会 post 这里的解决方案,因为它不一定很明显。
requests
模块包含自己的证书包,如果找不到另一个要使用的证书包,它将退回到那个。它查找证书包的方式是这样的,在 requests/certs.py:
try:
from certifi import where
except ImportError:
def where():
"""Return the preferred certificate bundle."""
# vendored bundle inside Requests
return os.path.join(os.path.dirname(__file__), 'cacert.pem')
你可以通过运行看到这个结果:
$ python -m requests.certs
/etc/pki/tls/certs/ca-bundle.crt
从上面的代码可以看出,requests
使用了certifi
模块来定位一个合适的bundle。在故障系统上,certifi
模块是通过 pip
安装的,而不是使用系统包,这意味着它缺少适当的配置。
解决方案是 yum install python2-certifi
。