With two largely identical systems (both running Fedora 25, both with similar package versions installed), one system is failing with an SSL certicate verification error while another is not. That is, if I run:
import requests
r = requests.get('https://insidehost.corp.example.com')
One one system it works, while on the other it fails:
requests.exceptions.SSLError: ("bad handshake: Error([('SSL routines', 'ssl3_get_server_certificate', 'certificate verify failed')],)",)
At first I figure that I was simply missing the necessary CA certificates, but running python under strace
reveals that on the failing system, python is never attempting to open a ca bundle. That is, on the system that works:
strace -e trace=open,stat python testscript.py |& grep /etc/pki
Yields only:
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
But on the failing system yields:
open("/etc/pki/tls/legacy-settings", O_RDONLY) = -1 ENOENT (No such file or directory)
Furthermore, running the same test script with python3
on the failing system...works!
In both cases, python
is /usr/bin/python
from python-2.7.13-1.fc25.x86_64
. Neither system is setting any *_CA_BUNDLE
environment variable.
After some additional investigation I've figured it out, and I thought I would post the solution here because it's not necessarily obvious.
The requests
module includes its own certificate bundle, and it will fall back on that if it can't find another one to use. The way it looks for a certificate bundle is like this, in 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')
You can see the result of this by running:
$ python -m requests.certs
/etc/pki/tls/certs/ca-bundle.crt
As you can see from the above code, requests
uses the certifi
module to locate an appropriate bundle. On the failing system, the certifi
module had been installed via pip
rather than using the system package, which meant it was lacking the appropriate configuration.
The solution was yum install python2-certifi
.