最近在搭建证书透明度日志服务,折腾了几天,最后结果是 “测试完美通关,部署各种出错”。So, 先暂停一段时间,记录一下这几天傻傻折腾的过程。
注 后面certificate-transparency
简写为CT。
CT是什么?
从中文翻译就可看出,证书透明嘛,就是让证书透明化。
至于各中细节,官方有更详细的文档说明我就不赘述了。
搭建CT日志服务
1)Before 搭建
看完上面的博客或文档,我们知道CT系统的组成有这么三个部分:
1)Certificate Logs;2)Certificate Monitors;3)Certificate Auditors。
而我们这里搭建的就是第一部分 Certificate Logs ,也就是我说的CT日志服务。
CT的开源项目除了包含了CT日志服务(ct-server),还有一个日志的只读服务(ct-mirror)以及一些日志的管理维护日志服务。这里我们主要搭建的是ct-server,当然也会用到一些工具。
在Deploying a CT Log指明了两种搭建方法,这里我用docker搭建,docker-compose编排。
其中主要需要的相关服务:
- ct-server (CT日志的主体服务)
- prom (监控服务)
- etcd (数据同步服务)
就像官方的部署图
拟部署CT三台日志服务,三台etcd组成集群,负载的负载均衡就是后话了。
2)编译代码
在构建之前,我们先需要编译代码。这里就得注意一下编译的环境了。由于一开始傻傻的文档都没看,直接在我的Mac(os x 10.13)上编译,结果各种C库问题。
在文档中已经明确指出:
The supported platforms are:
- Linux: tested on Ubuntu 14.04; other variants (Fedora 22, CentOS 7) may require tweaking of compiler options.
- OS X: version 10.10
- FreeBSD: version 10.*
我这里采用的是Ubuntu14.04平台,在docker内编译,Dockerfile如下:
FROM ubuntu:14.04 # 安装基本环境 RUN apt-get update \ && apt-get upgrade -y \ && apt-get install --no-install-recommends --no-install-suggests -y \ autoconf \ automake \ libtool \ shtool \ clang \ git \ make \ tcl \ pkg-config \ python \ curl \ ca-certificates # 由于depot_tools资源在墙外,docker内下载很慢,我就直接下的COPY进去了 # git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git COPY depot_tools /root/depot_tools # 由于ubuntu上的cmake版本还停留在 需要手动升级下cmake,同上的原因,我也就copy进来了 # curl http://www.cmake.org/files/v3.4/cmake-3.4.1.tar.gz COPY cmake-3.4.1 /root/cmake-3.4.1 RUN apt-get install -y build-essential \ && cd /root/cmake-3.4.1 \ && ./configure \ && make \ && apt-get install -y checkinstall \ && checkinstall \ && make install \ && cmake --version WORKDIR /root # 编译 RUN export PATH=$PATH:/root/depot_tools \ && export CXX=clang++ CC=clang \ && mkdir ct && cd ct \ && gclient config --name="certificate-transparency" https://github.com/google/certificate-transparency.git@master \ && gclient sync --disable-syntax-validation \ && make -C certificate-transparency check
直接docker build -t ctlog .
然后你就可以去喝杯茶了。不出意外,大概1个小时左右,你会看到
Testsuite summary for certificate-transparency 0.9 ============================================================================ # TOTAL: 41 # PASS: 41 # SKIP: 0 # XFAIL: 0 # FAIL: 0 # XPASS: 0 # ERROR: 0 ============================================================================ ...
之后,我们进docker将编译好的文件映射出来即可。
将ct/certificate-transparency
文件夹拷出后,我们大概可以看到:
# certificate-transparency git:(master) tree . ├── AUTHORS ├── Dockerfile ├── ... ├── cloud │ ├── etcd │ │ └── Dockerfile │ └── prometheus │ ├── Dockerfile │ └── prometheus.conf ├── cpp │ ├── server │ │ ├── ct-mirror │ │ ├── ct-server │ ├── tools │ │ ├── ct-clustertool │ │ └── prepare_etcd.sh └── ...
这里的ct-server
就是我们要的可执行文件了。除了该文件外,我们还需要一些工具,脚本,如:etcd初始化脚本prepare_etcd.sh
和CT集群工具ct-clustertool
。到这里,我们的编译工作就完成了。
3)构建与编排
文档上说的很清楚,编排之前我们还需要一些文件,至于具体需要哪些,我们结合ct-server参数做一个大概了解。
- 必须参数
--key=<pemfile>
秘钥PEM文件,ct日志服务用来加密和签名的。无需密码保护。--trusted_cert_file=<pemfile>
可信CA PEM文件,你的日志可接收的可信CA。--leveldb_db=<file>.ldb
日志文件数据库。--etcd_servers=<host>:<port>,<host>:<port>,...
etcd服务。--server=<hostname>
主机名。(可解析 可路由)--port=<port>
端口。
- 可选参数
-log_dir=<dir>
日志文件夹--v=<num>
日志输出等级 (像debug一样)--monitoring=<prometheus|gcm>
监控--tree_signing_frequency_seconds=<secs>
STH树的刷新时间 << MMD--guard_window_seconds=<secs>
表示在对日志的新条目进行排序之前要拖延多长时间.--etcd_delete_concurrency=<num>
表示可以同时删除多少个etcd条目。--num_http_server_threads=<num>
表示有多少线程用于服务传入的HTTP请求。
从参数可以看出,我们需要
私钥
# openssl 生成 openssl ecparam -name prime256v1 > privkey.pem # 生成参数文件 openssl ecparam -in privkey.pem -genkey -noout >> privkey.pem # 生成key openssl ec -in privkey.pem -noout -text # 查看key openssl ec -in privkey.pem -pubout -out pubkey.pem # 生成公钥
CA证书
文档上提供的是从Ubuntn的根证书库拿证书,我们也这么做,不过如果你想向Google提交你的日志服务,你还需要内置一章Google的测试证书,供Google测试使用。
sudo apt-get install -qy ca-certificates sudo update-ca-certificates cat /etc/ssl/certs/* > ca-roots.pem
etcd
etcd我用的quay.io/coreos/etcd:v3.2.0
镜像,用DISCOVERY模式。
prometheus
刚开始我也用的是最新版本的prometheus,但当我启动后,发现ct_server中/mertic
获取到的数据居然无法兼容,我最后还是用了v1.0.0
版。
在这些都开始后我们就开始构建镜像,编排docker了,相关的Dockerfile及文件我都上传到了我的GitHub,其中为了方便操作,我用Makefile
编排了一下。
CURRENT_DIR=`pwd` help: @echo "docker build help..." @docker build -f Dockerfile-help -t ct_help . # 用于日志的key pre_key: @openssl ecparam -name prime256v1 > server-key.pem && \ openssl ecparam -in server-key.pem -genkey -noout >> server-key.pem && \ openssl ec -in server-key.pem -noout -text && \ openssl ec -in server-key.pem -pubout -out server-pub.pem # 根证书 pre_cacerts: @docker run --rm -v $(CURRENT_DIR):/tmp -it ct_help bash -c '\ cat /etc/ssl/certs/* /tmp/google_test.pem > /tmp/ca-cert.pem' prom: @echo "docker build promtheus..." @docker build -f Dockerfile-prom -t ct_prom . ct_log: @echo "docker build ct log..." @docker build -f Dockerfile-log -t ct_log . etcd: @echo "docker build etcd ..." @docker build -f Dockerfile-etcd -t ct_etcd . up: @export ETCD_DISCOVERY=`curl -s -w "\n" 'https://discovery.etcd.io/new?size=3'` && \ printf "\033[92m[%-14s]\033[0m %s\n" "etcd discovery" $$ETCD_DISCOVERY && \ docker-compose up -d # 启动后需要初始化etcd内的内容 init_etcd: @docker run --rm -v $(CURRENT_DIR):/tmp \ --add-host "etcdhost:192.168.11.65" -t ct_help bash -c '\ cd /tmp && ./prepare_etcd.sh etcdhost 14001 server-key.pem' # 停止 down: @docker-compose down # 清除日志文件 clean_data: @rm -rf /mnt/data/ct # 测试 test: curl -s 127.0.0.1:18081/ct/v1/get-sth # curl -s 127.0.0.1:18082/ct/v1/get-sth # curl -s 127.0.0.1:18083/ct/v1/get-sth submit: @docker run --rm -v $(CURRENT_DIR):/tmp \ --add-host "host:192.168.11.65" -t ct_help bash -c '\ cd /tmp && ./ct-submit host:18081 < test_full_chain.pem > test.sct' check_etcd: curl -s -L http://127.0.0.1:14001/health # curl -s -L http://127.0.0.1:14002/health # curl -s -L http://127.0.0.1:14003/health pre: help pre_key pre_cacerts build: prom ct_log etcd run: up init_etcd
只需要简单的make pre build run
你就可以搭建起一个CT日志服务了,不过在此之前不要忘了将Makefile
,prometheus.yaml
,docker-compose.yaml
中的IP改成你的哦。
4)部署与测试
其实按照上面操作你已经搭建起了一个CT日志服务。
make pre
准备了一个后面需要的Ubuntu docker,生成秘钥以及根证书。make build
将需要的容器构建。make run
就是启动以及向ETCD写入一些日志相关的Key以及CT集群的一些初始化工作,具体内容可看prepare_etcd.sh
脚本。
在这之后,make test
我们可以看到:
$ curl -s 127.0.0.1:18081/ct/v1/get-sth { "tree_size": 0, "timestamp": 1515742224849, "sha256_root_hash": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", "tree_head_signature": "BAMARzBFAiEAimYSepMbRdR5cTCo9OwW3m3w7fQPIuO1L0LPC+7NaUgCIGNcLLwehMMckj//tGAJfEFPOYiWTGuM+jno87XK6RwT" }# $ curl -s 127.0.0.1:18082/ct/v1/get-sth { "tree_size": 0, "timestamp": 1515742224849, "sha256_root_hash": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", "tree_head_signature": "BAMARzBFAiEAimYSepMbRdR5cTCo9OwW3m3w7fQPIuO1L0LPC+7NaUgCIGNcLLwehMMckj//tGAJfEFPOYiWTGuM+jno87XK6RwT" }# $ curl -s 127.0.0.1:18083/ct/v1/get-sth { "tree_size": 0, "timestamp": 1515742224849, "sha256_root_hash": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", "tree_head_signature": "BAMARzBFAiEAimYSepMbRdR5cTCo9OwW3m3w7fQPIuO1L0LPC+7NaUgCIGNcLLwehMMckj//tGAJfEFPOYiWTGuM+jno87XK6RwT" }%
说明第一棵大小为0,时间戳为1515742224849的树已经建好了,CT等着我们去提交了。
接下来,我们提交一张证书测试一下。
make submit
提交了一张我已经准备好的证书,返回的SCT写入到了test.sct文件。
$ xxd test.sct 00000000: 00e8 03a0 42ca d5c6 8b75 dd99 7618 a739 ....B....u..v..9 00000010: b365 a64b 1bc3 4fa3 6f10 1169 dd0b 4c48 .e.K..O.o..i..LH 00000020: 9400 0001 60e9 4672 9f00 0004 0300 4830 ....`.Fr......H0 00000030: 4602 2100 a6c2 3520 9cd0 c5cc 9549 7db3 F.!...5 .....I}. 00000040: 97a1 702a c2f9 8c7c 566d 3f2a 0124 a5c6 ..p*...|Vm?*.$.. 00000050: edfa 709a 0221 00da 9fb9 b301 6bf1 873f ..p..!......k..? 00000060: bc75 7c5a f25c 13f7 f3ad dddd ec5a 017b .u|Z........Z.{ 00000070: 25de 4de2 452f 44 %.M.E/D
我们的一张证书已经成功提交到自己CT日志服务。大概在40s之后,也就是--tree_signing_frequency_seconds
加上--guard_window_seconds
的时间过后,一个新树就生成了。再次make test
$ curl -s 127.0.0.1:18081/ct/v1/get-sth { "tree_size": 1, "timestamp": 1515742344849, "sha256_root_hash": "pI7dv4Bi3dyBvx83s13fuWbNwGQQmafY344Wyf0m7OI=", "tree_head_signature": "BAMASDBGAiEAmVi6bsH3+NMxaiapBXA80Ygolc1kGLgPAhSMUEXcCzoCIQDdB4YdxH08lmeIZ8DDttjPtm5NtZV8CCNZ1+xyT0d05A==" }# $ curl -s 127.0.0.1:18082/ct/v1/get-sth { "tree_size": 1, "timestamp": 1515742344849, "sha256_root_hash": "pI7dv4Bi3dyBvx83s13fuWbNwGQQmafY344Wyf0m7OI=", "tree_head_signature": "BAMASDBGAiEAmVi6bsH3+NMxaiapBXA80Ygolc1kGLgPAhSMUEXcCzoCIQDdB4YdxH08lmeIZ8DDttjPtm5NtZV8CCNZ1+xyT0d05A==" }# $ curl -s 127.0.0.1:18083/ct/v1/get-sth { "tree_size": 1, "timestamp": 1515742344849, "sha256_root_hash": "pI7dv4Bi3dyBvx83s13fuWbNwGQQmafY344Wyf0m7OI=", "tree_head_signature": "BAMASDBGAiEAmVi6bsH3+NMxaiapBXA80Ygolc1kGLgPAhSMUEXcCzoCIQDdB4YdxH08lmeIZ8DDttjPtm5NtZV8CCNZ1+xyT0d05A==" }%
这颗 Merkle树已经包含了我提交的证书。
哦,还漏了一点,我们可以从prometheus看到所有的服务,也可以制造一些数据报表,设置报警规则等。
问题
一切似乎进展的很顺利,但,当我上云时,问题出现了。
三台机器,私网全部流量互通,每个机器一个CT已经etcd服务,节点三多跑一个Prometheus服务。当我将一切搭建好后,向其中一个节点提交日志,我们会发现除主简单外,其他两个节点日志打印错误如下:
I1228 09:30:09.295418 8 fetcher.cc:225] error fetching entries at index 0: UNKNOWN: I1228 09:30:09.295658 10 fetcher.cc:225] error fetching entries at index 0: UNKNOWN: I1228 09:30:09.332808 13 fetcher.cc:225] error fetching entries at index 0: UNKNOWN: I1228 09:30:09.333170 14 fetcher.cc:225] error fetching entries at index 0: UNKNOWN: I1228 09:30:09.333434 12 fetcher.cc:225] error fetching entries at index 0: UNKNOWN: I1228 09:30:09.333684 9 fetcher.cc:225] error fetching entries at index 0: UNKNOWN: I1228 09:30:09.333931 11 fetcher.cc:225] error fetching entries at index 0: UNKNOWN: I1228 09:30:09.334175 15 fetcher.cc:225] error fetching entries at index 0: UNKNOWN: I1228 09:30:09.334420 8 fetcher.cc:225] error fetching entries at index 0: UNKNOWN: I1228 09:30:09.334666 10 fetcher.cc:225] error fetching entries at index 0: UNKNOWN: I1228 09:30:09.334946 13 fetcher.cc:225] error fetching entries at index 0: UNKNOWN:
同时,这两个节点CPU用量会飙升。
当然,在CT项目的Issues里我们发现了有人遇到了几乎同样的问题。在最后一句大神的回复中,我们或了解到,他们正在开发一个通用的透明化项目Trillian,当然该项目不仅仅可以用于证书透明化。借助于Merkle树的快速查询,不可修改等特性未来或许我们会在更多地方看到他。