TT Lab
开始
学习 学习路径 课程

FDE综合实战:仓库收到了三次相同订单

NO_PROXY 并不是标准:三种工具的三种解读

在 TT Lab 中继续学习

一句话总结

代理环境变量只是一种约定而不是标准,所以同样一行 NO_PROXY,curl、Python urllib、requests 会各读各的。在客户现场,比起把配置“写对”,更要紧的是逐个工具实测实际走的路径,并把它作为证据留下来。

为什么需要它

交付第一天,客户安全团队发来三行配置:“这是公司标准,照这个填。”

HTTP_PROXY=http://127.0.0.1:3128
HTTPS_PROXY=http://127.0.0.1:3128
NO_PROXY=corp.example,127.0.0.0/8

填进去之后,集成连接器的健康检查(curl)是绿灯,而用 Python 写的指标采集器却开始在内部 API 上收到 403。本该走向外部 SaaS 的 requests 调用反而绕过了代理,死在名称解析上。读的是同样三行,三个程序却有三种表现。并不是谁错了。http_proxy 只是多个工具共用的一种惯例,下面引用的 curl、Python、requests 文档也各自只说明自己的规则。没有哪里规定了共同的语法,所以各个实现的规则略有不同,这些差异会在客户网络里一下子全部暴露出来。

在这种情况下,FDE 如果先说“把配置改成这样就行”就危险了。客户那边的负责人会用 curl 确认,然后回答“我们这边是正常的”,而这句话也是事实。需要的是能用记录显示哪个请求走了代理的证据。

工作原理

下面是在本实验镜像里启动一个会记录的模拟代理后亲自测得的结果(实测:curl 8.5.0、Python 3.12.3、requests 2.32.3)。

1. 变量名的大小写。curl 手册的 ENVIRONMENT 一节写道,小写和大写都会读取,只有 http_proxy 只接受小写。为什么需要这个例外,urllib.request 文档做了说明——在 CGI 环境中,客户端发送的 Proxy: 头可能被注入成名为 HTTP_PROXY 的环境变量。所以 Python 只在设置了 REQUEST_METHOD 的 CGI 环境中才忽略大写的 HTTP_PROXY,平时大写也会接受。实测也是这样运行的。结果是,只设置大写的 HTTP_PROXY,curl 会直连,两个 Python 工具则走代理。小写和大写都有时,小写优先——这一点 curl 手册和 urllib 文档说法一致。

ALL_PROXY 也有分歧。curl 手册写道,没有按协议设置的变量时使用它,requests 文档也把 all_proxy 列为会读取的变量。实测中两者都走了代理,但 urllib 在只有 ALL_PROXY 时直连。

2. NO_PROXY 的名称匹配。curl 手册说明每个条目按“该主机本身,或属于该域名的名称”来匹配,并举例说 local.com 会匹配 www.local.com,但不匹配 www.notlocal.com。urllib 也遵守点边界。requests 则不同——实测中 NO_PROXY=corp.example 把 saascorp.example 也直接发走了。因为它只看字符串末尾是否相同。前导点(.corp.example)在三个工具中都匹配了 api.corp.example 和 corp.example 本身,而不匹配 saascorp.example。另一方面,像 *.corp.example 这样带星号的条目,三个工具都什么也匹配不了。星号只有在整个列表仅为 * 一个字符时,才表示“全部直连”,像 *,localhost 这样混在一起,星号就失效了(实测)。

3. IP、CIDR 和端口。curl 手册写道,从 7.86.0 起接受 127.0.0.0/8 这样的 CIDR 写法。requests 对 IP 地址主机也应用了 CIDR。urllib 不认识 CIDR——只按字符串比较,把 127.0.0.2 发给了代理。端口则相反。urllib 文档写道允许像 some.host:8080 这样带端口的条目,实际也匹配了,但 curl 匹配不了带端口的条目,requests 对 IP 加端口匹配不了,对名称加端口却匹配了。还有,NO_PROXY=127.0.0.1 并不匹配 localhost。这意味着三个工具都没有把名称解析成地址再比较(实测)。

4. 代码传入的代理。requests 文档的 Proxies 一节建议,放进 session.proxies 的值可能被环境变量中的代理覆盖,想确保生效,就在每次请求时用 proxies 参数传入。然而每次请求传入的 proxies 并没有应用 NO_PROXY(实测)。把配置文件中的代理作为参数传入的集成连接器,无论怎么修改 NO_PROXY,都无法直连内部 API。

相同条件 curl urllib requests
只有大写的 HTTP_PROXY 直连 代理 代理
NO_PROXY=corp.example → saascorp.example 代理 代理 直连
NO_PROXY=127.0.0.0/8 → 127.0.0.2 直连 代理 直连
NO_PROXY=127.0.0.2:8080 → 127.0.0.2:8080 代理 直连 代理

在现场相遇的样子

在客户网络里,原因总是表现为别的症状。代理把内部目标以 403 拒绝,看起来像是“API 认证坏了”;外部调用绕过了代理,看起来像是“DNS 有问题”。健康检查用的是 curl 而显示绿灯,大家就会怀疑应用。这时,代理访问记录中的一行就能终结争论——那个请求有没有到达代理。

第二种常见情形是“安全团队标准”与“工具现实”发生冲突。标准文档中用 CIDR 写的内部网段,基于 urllib 的工具看不懂,但不能因此就要求修改标准。FDE 在遵守标准的同时,另外再写一套三个工具都能理解的公共子集(前导点域名、明确列出的 IP 清单、小写和大写都写),并把理由记录下来。

实际工作中真正重要的事

下一项实验要做什么

启动会记录的模拟代理和内部 API,重现客户配置的故障,用三个工具亲自实测准备好的 18 个用例并留成表格。然后制作让三个工具走同一条路径的配置,修复使用 proxies 参数的集成连接器,最后制作一个探针:无论给哪份配置文件,都通过实测来判定各工具的路径。