华为云函数根CA缺失排查

最近几年Serveless的云服务越来越火了,刚好这个周末准备试用一下,写点小工具。测试访问自己的网站时,一直失败,搞一下午最后发现是华为云的一个BUG,没有预置完整的根证书,这也太扯了,记录一下顺便提个单吧。

表现

使用华为云的函数工作流功能,编写python代码,在代码中使用 urlib3 或 requests 访问网站并打印内容,简单测试是正常的,例如访问百度的http和https站点。作为一个有自己博客的人,也经常会把自己的网站作为测试用例的一部分,结果发现,居然报 SSLError [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain (_ssl.c:1123) ,真是瞎了我的狗眼。

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import urllib3

def handler(event, context):
http = urllib3.PoolManager()
try:
r = http.request(url = "https://leadroyal.cn/ok", method="GET", retries=False, decode_content=False)
except urllib3.exceptions.SSLError as e:
print("SSLError", e)
return {
"isBase64Encoded": False,
"statusCode": 200,
"headers": {},
"body": "SSL ERROR"
}
return {
"isBase64Encoded": False,
"statusCode": 200,
"headers": {},
"body": r._body.decode()
}

初步结论

无论使用 urllib3 还是 requests,表现都是一样的,说明问题出在网关那里。我测试的网站绝大部分都是正常的,唯独我自己的博客提示证书不可信,难道是访问冷门站点的https资源就自带劫持?也罢,今天就查个水落石出吧。

仔细排查

查看SSL证书

难度倒是不大,主要是云函数提供的环境比较简陋,不让自己pip安装依赖,允许os.system,path下有什么东西也全靠尝试,最终选择使用环境里的 openssl CLI,网上抄来一段代码:

1
echo | openssl s_client -showcerts -servername www.leadroyal.cn -connect www.leadroyal.cn:443 2>/dev/null | openssl x509 -inform pem -noout -text

在本地执行后,在云函数中 os.system执行,对比结果发现证书一模一样,签发者一模一样。

奇怪了,世界未解之谜,超出了我的认知范围。

查看root CA、Intermediate CA

同样,这里主要难度在于如何使用残破的工具完成证书查看,最后使用了这个命令,成功打印出了证书链。

1
echo | openssl s_client -showcerts -servername www.leadroyal.cn -connect www.leadroyal.cn:443 2>/dev/null

观察网站SSL证书,一模一样;观察中间签发机构,一模一样;观察根证书签发机构,一模一样;只有最后几行输出的校验结果不一样:

1
2
3
4
5
本地:
Verify return code: 0 (ok)
远程:
Verification error: self signed certificate in certificate chain
Verify return code: 19 (self signed certificate in certificate chain)

更奇怪了,完全没有被劫持的痕迹,确实是权威机构签发的,为什么被认为不受信任呢?

大胆猜测,小心求证

事到如今,只能认为是操作系统的问题了,我猜是操作系统内置的根证书不完整。

随便找几台机器看看,根据 https://serverfault.com/questions/62496/ssl-certificate-location-on-unix-linux 的提示,我尝试在 ubuntu 和 centos 里寻找根证书,我的根证书信息是:O=Comodo CA Limited,CN=AAA Certificate Services。

在 ubuntu-22.04 的 /etc/ssl/certs/Comodo_AAA_Services_root.pem 下成功找到。

1
2
[root@dcbf508431fa /]# realpath /usr/share/ca-certificates/mozilla/Comodo_AAA_Services_root.crt
/usr/share/ca-certificates/mozilla/Comodo_AAA_Services_root.crt

在 centos8/7/6 的 /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem 下,centos5 的 /etc/pki/tls/cert.pem 下,成功找到。

1
2
[root@55ad8e627d9f /]# grep Comodo /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
# Comodo AAA Services root

删除对应的文件后,即可复现云函数中的现象,无法访问我的博客,此时可以有很大的把握认为是环境的问题。

到环境上,执行命令,grep "#" /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem,翻了所有的别名,确实没有。

好了,破案了,我使用的ssl证书的颁发者不在操作系统认可的名单里,被这种傻逼问题折腾了一下午,绝对属于现网严重问题了,大面积可用性问题,忍不住使用一些文明用语了。

谁是罪魁祸首?

通过 cat /proc/version 得知系统信息 Linux version 3.10.0-862.14.1.5.h520.eulerosv2r7.x86_64 (abuild@szxrtosci10000) (gcc version 4.8.5 20150623 (EulerOS 4.8.5-28) (GCC) ) #1 SMP Wed Nov 25 18:33:06 UTC 2020,看起来是华为的euleros操作系统。

众所周知,华为将 openEuler 捐献到了开源基金会,而 euleros 是商业版,我并不能查明 euleros 为什么没有这个CA,于是去测试 openEuler。

使用同样的方式在 docker 里测试 openEuler 的 20.03、22.03版本,没有故障。

再仔细看版本信息,看起来像是很古老的版本了,古老到连centos5都有这张证书了,不知道exp能不能把这个老容器打穿了。

结论

euleros和华为云长点心吧。。。