Mensagem de erro
Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
Também aparece como
- 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
O host, a porta e o caminho da URL vêm da sua requisição. O motivo mais interno varia (“self-signed certificate in certificate chain”, “Hostname mismatch…”, “certificate has expired”), e a linha termina com uma localização no código-fonte, como “(_ssl.c:1082)')))”.
requests / urllib3 (Python) — requests.exceptions.SSLError wrapping urllib3 MaxRetryError
O que significa
O requests pediu uma conexão ao urllib3, o handshake TLS falhou na verificação do certificado e o urllib3 desistiu (com o padrão do requests, que não faz novas tentativas, já depois da primeira tentativa). A parte útil está entre parênteses: aqui, o OpenSSL não encontrou o emissor no bundle de CAs que o requests usa, que por padrão é a cópia das raízes da Mozilla distribuída pelo certifi, e não o repositório de certificados do sistema operacional.
Por que acontece
O requests verifica com o certifi, e não com o repositório do sistema
comumUma CA da empresa ou privada instalada com update-ca-certificates, no repositório do Windows ou no chaveiro do macOS (Keychain) é ignorada: requests.certs.where() aponta para o cacert.pem do certifi. É exatamente isso que a reprodução mostra.
Um proxy com inspeção de TLS reassina o tráfego
comumRequisições a APIs públicas falham só na rede da empresa porque a raiz do proxy não está no certifi.
O servidor não envia o certificado intermediário
ocasionalSe o servidor da API envia apenas o certificado folha, o OpenSSL não consegue chegar a uma raiz em nenhum bundle; os navegadores escondem isso buscando os intermediários.
REQUESTS_CA_BUNDLE ou CURL_CA_BUNDLE aponta para um bundle parcial
ocasionalUm bundle que contém só a raiz da empresa faz todos os hosts públicos falharem (e vice-versa); as variáveis substituem o certifi em vez de complementá-lo.
Como corrigir
- 1.
Passar o bundle certo com verify=
O verify aceita o caminho de um arquivo de bundle de CAs ou de um diretório de CAs indexado por hash; as verificações de nome de host e de datas continuam ativas.
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.
Definir REQUESTS_CA_BUNDLE para o processo
Apontar essa variável para o bundle do sistema (depois de instalar sua CA nele) faz toda chamada do requests nesse processo confiar nas mesmas raízes que o curl e as ferramentas do sistema operacional.
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.
Usar o trust store do sistema operacional
truststore.inject_into_ssl() na inicialização faz o requests verificar pelo sistema operacional, de modo que as raízes implantadas pela TI funcionam sem que seja preciso manter bundles.
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.
Configurar uma sessão (Session) uma única vez
Em aplicativos, defina Session.verify como um bundle que contenha as raízes públicas mais a raiz da empresa e reutilize a sessão.
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/")