昨天在调试Jenkins自动化脚本时,遇到了一个典型的SSL证书验证错误。错误信息显示为requests.exceptions.SSLError: HTTPSConnectionPool(host='rsda-sds.sd.dch.com', port=443),并明确指出证书验证失败。这个问题在Python开发中相当常见,特别是当你的脚本需要与内部或自签名证书的服务器交互时。下面我将详细拆解这个问题的成因和多种解决方案。
这个SSL错误的完整堆栈信息表明,Python的requests库在尝试与Jenkins服务器建立HTTPS连接时,无法验证服务器证书的有效性。关键错误信息CERTIFICATE_VERIFY_FAILED和unable to get local issuer certificate指出了问题的核心:
提示:现代Python环境(特别是Anaconda发行版)会使用自己的证书存储,而不是系统默认的存储位置,这常常是问题的来源。
Python的SSL验证流程大致如下:
在Windows环境下,Python(特别是Anaconda发行版)通常会从以下位置查找证书:
C:\Anaconda3\Library\ssl\cacert.pem - Anaconda的基础SSL证书存储C:\Anaconda3\Lib\site-packages\certifi\cacert.pem - certifi包提供的证书存储如果你需要快速让脚本运行起来,可以考虑以下临时方案:
python复制import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
# 禁用SSL验证警告
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
# 创建不验证SSL的会话
session = requests.Session()
session.verify = False
# 使用这个session进行请求
response = session.get('https://rsda-sds.sd.dch.com')
警告:这种方法完全禁用了SSL验证,会使你的连接面临中间人攻击的风险。仅限在开发环境或绝对信任的内部网络中使用。
Anaconda环境的证书存储可能过时,可以尝试更新:
bash复制conda update --all
conda install -c anaconda certifi
更新后,检查证书文件是否更新:
bash复制ls -l C:\Anaconda3\Lib\site-packages\certifi\cacert.pem
如果服务器使用内部CA颁发的证书,你需要将根证书添加到Python的信任存储中:
python复制# 追加证书的Python脚本
cert_path = 'C:/Anaconda3/Lib/site-packages/certifi/cacert.pem'
with open('internal_ca.crt', 'r') as f:
new_cert = f.read()
with open(cert_path, 'a') as f:
f.write('\n' + new_cert)
你可以在代码中指定自定义的CA包路径:
python复制import requests
session = requests.Session()
session.verify = 'path/to/custom/cacert.pem' # 你的自定义CA包路径
response = session.get('https://rsda-sds.sd.dch.com')
对于python-jenkins库,可以通过以下方式配置SSL验证:
python复制import jenkins
server = jenkins.Jenkins(
'https://rsda-sds.sd.dch.com',
username='your_username',
password='your_password',
ssl_verify=False # 禁用验证(不推荐)
# 或
# ssl_verify='/path/to/cacert.pem' # 使用自定义CA包
)
使用openssl工具诊断证书链问题:
bash复制openssl s_client -connect rsda-sds.sd.dch.com:443 -showcerts
这会显示服务器返回的所有证书。检查输出中是否包含完整的证书链。
你可以用Python提取服务器证书并保存到本地:
python复制import ssl
import socket
hostname = 'rsda-sds.sd.dch.com'
context = ssl.create_default_context()
with socket.create_connection((hostname, 443)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
cert = ssock.getpeercert(True) # 获取DER格式证书
with open('server_cert.der', 'wb') as f:
f.write(cert)
然后可以用openssl转换格式:
bash复制openssl x509 -inform der -in server_cert.der -out server_cert.pem
对于高级用例,可以创建自定义SSL上下文:
python复制import ssl
import requests
from requests.adapters import HTTPAdapter
from urllib3.poolmanager import PoolManager
class CustomSSLAdapter(HTTPAdapter):
def init_poolmanager(self, connections, maxsize, block=False):
ctx = ssl.create_default_context()
ctx.load_verify_locations(cafile='path/to/custom/cacert.pem')
self.poolmanager = PoolManager(
num_pools=connections,
maxsize=maxsize,
block=block,
ssl_context=ctx
)
session = requests.Session()
session.mount('https://', CustomSSLAdapter())
时间不同步:系统时间不正确会导致证书验证失败
代理干扰:企业网络中的SSL拦截代理可能导致问题
证书链不完整:服务器配置问题
python复制import logging
import http.client
http.client.HTTPConnection.debuglevel = 1
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
使用Postman或curl测试相同端点,确认是否是Python特有的问题
检查Python和OpenSSL版本:
python复制import ssl
import requests
print(ssl.OPENSSL_VERSION)
print(requests.__version__)
在企业环境中,你可能需要:
python复制import os
os.environ['HTTP_PROXY'] = 'http://proxy.example.com:8080'
os.environ['HTTPS_PROXY'] = 'http://proxy.example.com:8080'
python复制session = requests.Session()
session.verify = 'path/to/company/root/cert.pem'
python复制from requests_ntlm import HttpNtlmAuth
session.auth = HttpNtlmAuth('domain\\username', 'password')
我在处理这类问题时发现,大多数SSL验证错误都可以通过系统地检查证书链、更新本地证书存储或正确配置SSL上下文来解决。关键是要理解错误背后的原因,而不是简单地禁用验证。对于内部系统,建议建立完善的证书颁发和管理流程,从根本上减少这类问题的发生。