首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >企业内网场景下 Python 自定义 CA 证书信任链的正确配置方法

企业内网场景下 Python 自定义 CA 证书信任链的正确配置方法

原创
作者头像
小白学大数据
发布2026-07-17 16:37:03
发布2026-07-17 16:37:03
390
举报

Python 在内网访问 HTTPS 接口时,常遇到 SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate。多数人用 verify=False 绕过验证,但这等于关闭了 TLS 的身份校验。正确做法是将企业 CA 根证书加入 Python 的信任链,同时保留对公网证书的正常校验。内网自定义 CA 的来源企业内网的自定义 CA 证书来自两类场景。内部服务 (API 网关、GitLab、Harbor、监控平台等) 使用内部域名或 IP 地址,公共 CA 无法签发此类证书,企业自建 CA 签发后要求客户端信任其根证书。网络安全设备 (防火墙、IDS、DLP) 对 HTTPS 流量做 MITM 检查,设备持有自签根证书,经其转发的连接会被重新签名。浏览器通过组策略信任该根证书,Python 不会。两类场景的本质相同:程序需要信任一个不在公共 CA 信任链里的根证书。Python 的证书验证机制Python 的 ssl 模块加载 CA bundle (PEM 格式文件) 作为信任锚。默认路径可通过 ssl.get_default_verify_paths() 查看,各操作系统不同。requests 库在此基础上使用 certifi 包提供的证书包 (certifi.where()),其中只包含 Mozilla 维护的公共根证书。遇到企业 CA 签名的证书时,在 certifi 证书包中找不到对应的根证书,验证失败。配置方法方法一: 环境变量指定合并证书包requests 读取 REQUESTS_CA_BUNDLE 环境变量, ssl 模块读取 SSL_CERT_FILE。直接指定企业 CA 文件会丢失所有公共根证书,必须先合并:

优点:不改现有代码。缺点:影响进程内所有 requests 调用;合并文件需随 certifi 更新手动重新生成。方法二: verify 参数按请求指定verify 参数只影响当前请求,不影响全局:

缺点:每个内网请求都要传参;合并文件维护问题不变。方法三: 自定义 SSLContext + HTTPAdapter通过 ssl.SSLContext 精细控制 TLS 配置,挂载到 HTTPAdapter 上按域名匹配:

session.mount 按 URL 前缀匹配,内网请求走自定义 Adapter,公网请求走默认 SSLContext。此方法支持 TLS 版本和密码套件控制,适合等保合规场景。亿牛云代理场景下的证书处理亿牛云 (16yun.cn) 是企业级代理 IP 服务商,代理地址如 t.16yun.cn:31111,需用户名密码认证。HTTPS 请求经过正向代理时使用 CONNECT 方法建立加密隧道,代理仅转发流量不做 MITM。目标服务器使用公共 CA 证书时,走代理与直连行为一致,校验正常通过。若目标在内网且使用企业 CA 证书,证书校验仍会失败,需将企业 CA 加入信任链。三种方法均可适用,以 Session + SSLContext 为例:

若企业网络设备对代理流量做 MITM 审查,客户端收到的证书由防火墙签发而非目标服务器,需将防火墙 CA 加入信任链。排查原则:亿牛云正向代理不做 MITM,SSL 错误的根源在客户端信任链配置或服务器端证书链配置,不在代理参数。多级 CA 证书链企业 CA 的典型结构为:根 CA 签发中间 CA,中间 CA 签发服务器证书。客户端加载根 CA 即可完成整条链的验证,无需单独加载中间 CA。但若服务器端未发送完整证书链 (缺少中间证书),客户端需手动加载:

若输出仅含服务器证书而无中间证书,说明服务器配置不完整。客户端可临时补丁:

根本解决方案是让运维修复服务器端配置,使其发送完整证书链。容器与 CI 环境Python 官方 Docker 镜像基于 Debian,不含企业 CA。两种注入方式:构建时注入:

运行时挂载:

CI/CD 环境中,将企业 CA 证书存入 secrets 管理系统,流水线启动时动态合并:

常见错误

错误

原因

解决

DER 文件静默加载失败

load_verify_locations 只接受 PEM 格式,DER 文件不报错但不生效

openssl x509 -inform DER -in ca.cer -out ca.pem

证书链断裂

中间 CA 过期,根 CA 有效时整条链仍断

openssl x509 -in ca.pem -noout -dates 检查各层有效期

合合文件解析失败

两个 PEM 证书之间缺少换行分隔

合并时 f.write("\n") 确保分隔

Windows 环境变量路径异常

路径含空格或反斜杠

用正斜杠或 os.path.abspath() 规范化

生产环境配置模板

代码语言:txt
复制
import ssl, certifi, requests
from requests.adapters import HTTPAdapter
from pathlib import Path
def create_ssl_context(ca_paths, min_tls=ssl.TLSVersion.TLSv1_2):
 ctx = ssl.create_default_context()
 ctx.minimum_version = min_tls
 ctx.set_ciphers("ECDHE+AESGCM:DHE+AESGCM:!aNULL:!MD5:!DSS")
 ctx.load_verify_locations(certifi.where())
 for ca_path in ca_paths:
 if not Path(ca_path).exists():
 raise FileNotFoundError(f"CA certificate not found: {ca_path}")
 ctx.load_verify_locations(ca_path)
 return ctx
def create_internal_session(ca_paths, domain_prefix, proxy_config=None):
 ctx = create_ssl_context(ca_paths)
 adapter = HTTPAdapter()
 adapter.poolmanager.connection_pool_kw["ssl_context"] = ctx
 session = requests.Session()
 session.mount(domain_prefix, adapter)
 if proxy_config:
 session.proxies = proxy_config
 return session
session = create_internal_session(
 ca_paths=["/etc/ssl/company-root-ca.pem"],
 domain_prefix="https://internal-api.company.com",
 proxy_config={
 "http": "http://user:pass@t.16yun.cn:31111",
 "https": "http://user:pass@t.16yun.cn:31111",
 },
)
r = session.get("https://internal-api.company.com/v1/users")

模板包含:TLS 1.2 强制最低版本、弱密码套件禁用、公共证书与企业 CA 合并加载、域名级 Adapter 匹配、可选代理配置、CA 文件路径校验。总结核心原则:将企业 CA 加入信任链,而非绕过验证。环境变量方式适合快速验证;verify 参数适合少量内网请求;自定义 SSLContext + HTTPAdapter 适合生产环境精细控制。无论哪种方法,合并证书包而非替换、确认 PEM 格式编码、定期检查 CA 有效期。代理场景下,正向代理不做 MITM,SSL 错误排查方向在客户端信任链或服务器证书链,不在代理参数。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档