urllib's CERTIFICATE_VERIFY_FAILED after installing newer OpenSSL in Ubuntu 20.04
Requests to our authorization providers started failing with urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1129)>
. At the same time, when testing https calls with the requests
library, everything worked. It turns out the library Flask-Oauthlib uses urllib.request.urlopen
to talk a provider, and it looked like urllib
couldn’t get a hold of certificates, which made the request fail.
We recently updated from OpenSSL 1.1.1f to 1.1.1l, so it was natural to start there. After some digging through cpython
and openssl
, I found the problem.
When openssl
is being installed, a value called OPENSSLDIR
can be configured. It specifies the default location for configuration and CA certificates. Ubuntu 20.04 comes preinstalled with OpenSSL 1.1.1f with OPENSSLDIR
set to /usr/lib/ssl
. This is the version we used until recently. /usr/lib/ssl/certs
is filled with symlinks for certificates residing in /usr/share/ca-certificates/mozilla
. However, after moving to OpenSSL 1.1.1l, we install it from source in our Dockerfile
, where we use the default OPENSSLDIR
which is /usr/local/ssl
. The folder /usr/local/ssl/certs
is empty. Which version of OpenSSL we are installing is not important, the difference is in the OPENSSLDIR
set in the pre-installed OpenSSL vs the default one when installing it yourself.
urllib
expects certs to be under OPENSSLDIR/certs
so when it’s empty, using HTTPS fails.
Ways to solve this:
- Set the environment variable
SSL_CERT_DIR
to/usr/lib/ssl/certs
or other directory with the CA certificates. This overrides the defaultOPENSSLDIR/certs
value. - Add symlinks to the empty
/usr/local/ssl/certs
folder that point to/usr/share/ca-certificates/mozilla
.
I initially thought a solution could be to use usr/lib/ssl
as the --openssldir
option when installing, however the docs do not recommend overwriting the preinstalled version location. The best solution might therefore be to use the SSL_CERT_DIR
variable.
The reason requests
isn’t affected is because it uses the certifi
package which comes with it’s own certificate, which resides in its directory under Python’s site-packages
. You could therefore also point the environment variable SSL_CERT_FILE
(which also overrides OpenSSL’s default) to this certificate to make urllib
work.