Message d’erreur
Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
Également affiché sous la forme
- requests.exceptions.SSLError: HTTPSConnectionPool(host='api.example.com', port=8443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
L’hôte, le port et le chemin de l’URL proviennent de votre requête. La raison la plus imbriquée varie (« self-signed certificate in certificate chain », « Hostname mismatch… », « certificate has expired »), et la ligne se termine par un emplacement dans le code source, comme « (_ssl.c:1082)'))) ».
requests / urllib3 (Python) — requests.exceptions.SSLError wrapping urllib3 MaxRetryError
Ce que cela signifie
requests a demandé une connexion à urllib3, la vérification du certificat a échoué pendant le handshake TLS, et urllib3 a abandonné (dès la première tentative, puisque requests ne fait par défaut aucune nouvelle tentative). La partie utile se trouve entre les parenthèses : ici, OpenSSL n’a pas trouvé l’émetteur dans le fichier d’AC qu’utilise requests, c’est-à-dire par défaut la copie des racines Mozilla fournie par certifi, et non le magasin du système d’exploitation.
Pourquoi cela se produit
requests vérifie avec certifi, pas avec le magasin du système
fréquenteUne AC d’entreprise ou privée installée avec update-ca-certificates, dans le magasin Windows ou dans le trousseau macOS est ignorée : requests.certs.where() pointe vers le cacert.pem de certifi. C’est exactement ce que montre la reproduction.
Un proxy d’inspection TLS re-signe le trafic
fréquenteLes requêtes vers des API publiques n’échouent que sur le réseau de l’entreprise, car la racine du proxy ne figure pas dans certifi.
Le serveur omet son certificat intermédiaire
occasionnelleSi le serveur de l’API n’envoie que son certificat feuille, OpenSSL ne peut atteindre une racine dans aucun fichier d’AC ; les navigateurs masquent le problème en récupérant les certificats intermédiaires.
REQUESTS_CA_BUNDLE ou CURL_CA_BUNDLE pointe vers un fichier d’AC partiel
occasionnelleUn fichier d’AC qui ne contient que la racine de l’entreprise fait échouer tous les hôtes publics (et inversement) ; ces variables remplacent certifi au lieu de le compléter.
Comment corriger l’erreur
- 1.
Passer le bon fichier d’AC avec verify=
verify accepte le chemin d’un fichier d’AC ou d’un répertoire d’AC haché ; les contrôles du nom d’hôte et des dates restent actifs.
python import requests # A CA file (or a directory prepared with c_rehash) for this call requests.get("https://api.example.com/", verify="/etc/ssl/certs/ca-certificates.crt") - 2.
Définir REQUESTS_CA_BUNDLE pour le processus
Si elle pointe vers le fichier d’AC du système (après que vous y avez installé votre AC), chaque appel requests de ce processus fait confiance aux mêmes racines que curl et les outils du système d’exploitation.
bash # For every requests call in the process (also honoured: CURL_CA_BUNDLE) export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt # Debian/Ubuntu system bundle python3 app.py - 3.
Utiliser le magasin de confiance du système d’exploitation
Appelé au démarrage, truststore.inject_into_ssl() permet à requests de vérifier via le système d’exploitation : les racines déployées par le service informatique fonctionnent donc sans avoir à maintenir de fichiers d’AC.
python # pip install truststore==0.10.5 — requests then verifies with the OS trust store import truststore truststore.inject_into_ssl() import requests requests.get("https://api.example.com/") - 4.
Configurer une Session une seule fois
Dans les applications, faites pointer Session.verify vers un fichier d’AC qui contient les racines publiques et la racine de l’entreprise, puis réutilisez la session.
python import requests session = requests.Session() session.verify = "/etc/ssl/private-ca/corp-ca-bundle.pem" # public roots + company root session.get("https://api.example.com/")