requests请求自签名证书IP不匹配问题的修复方案问询
CI流水线中的一项测试近3个月来开始失败,已反复确认证书有效,执行openssl x509 -text -noout -in the-cert.crt的结果如下:
Certificate: Data: Version: 3 (0x2) Serial Number: <redacted> Signature Algorithm: ecdsa-with-SHA256 Issuer: CN = 172.17.0.2 Validity Not Before: Jul 19 16:51:57 2023 GMT Not After : Jul 18 16:51:57 2024 GMT Subject: CN = 172.17.0.2 Subject Public Key Info: Public Key Algorithm: id-ecPublicKey Public-Key: (256 bit) pub: <redacted> ASN1 OID: prime256v1 NIST CURVE: P-256 X509v3 extensions: X509v3 Subject Key Identifier: <redacted> X509v3 Authority Key Identifier: <redacted> X509v3 Basic Constraints: critical CA:TRUE Signature Algorithm: ecdsa-with-SHA256 Signature Value: <redacted>
需要让以下Python代码执行通过:
res2 = requests.get('http://172.17.0.2/index.html', timeout=15, verify="/home/mrx/appx/the-cert.crt")
但实际触发SSL错误:
requests.exceptions.SSLError: HTTPSConnectionPool(host='172.17.0.2', port=443): Max retries exceeded with url: /index.html (Caused by SSLError(SSLCertVerificationError(1, "[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: IP address mismatch, certificate is not valid for '172.17.0.2'. (_ssl.c:997)")))
服务器配置中将80端口请求转发到443,curl https://172.17.0.2/index.html --cacert the-cert.crt可正常执行;纯HTTP请求返回301重定向;注释掉nginx服务器块中的listen 443后所有请求正常,但会破坏原有测试逻辑。
环境信息:requests 2.31.0,Python 3.10.6,Ubuntu 22.04;nginx版本覆盖Debian bullseye-slim到bookworm(1.22.1)。
求简洁修复方案(排除try-catch分支)。
直接使用HTTPS URL:将请求URL改为
https://172.17.0.2/index.html,跳过301重定向环节,直接发起HTTPS请求并验证证书,修改后的代码:res2 = requests.get('https://172.17.0.2/index.html', timeout=15, verify="/home/mrx/appx/the-cert.crt")这是最直接的解决方案,避免重定向带来的证书验证问题。
为证书添加SAN扩展:生成证书时添加**Subject Alternative Name(SAN)**扩展,明确指定
IP:172.17.0.2。部分Python/OpenSSL版本对CN字段中的IP地址验证严格,SAN是标准的IP绑定方式。示例openssl配置:[req] req_extensions = v3_req [v3_req] subjectAltName = IP:172.17.0.2重新生成证书后,原有HTTP请求的重定向验证即可通过。
禁用自动重定向并手动处理:若必须保留HTTP URL,可关闭requests的自动重定向,手动处理301跳转并在HTTPS请求中指定证书:
res = requests.get('http://172.17.0.2/index.html', timeout=15, allow_redirects=False) if res.status_code == 301: res2 = requests.get(res.headers['Location'], timeout=15, verify="/home/mrx/appx/the-cert.crt")
内容的提问来源于stack exchange,提问作者John

