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

构建 EAI 中间层

证书不同、规格不同 — 搭建对外网关

在 TT Lab 中继续学习

目标

搭建通过双向 TLS 与两个外部机构对接的外联前置机。为每个机构签发证书,把机构配置档案放在数据里,翻译成机构规格后发送,只从允许的地址接收,并监控证书到期。

为什么重要

外部机构不会迁就我们的标准。如果不把规格、证书、允许地址的差异关在外联前置机这一个地方,每增加一个机构,就得修改内层系统。而且对外故障的常客——证书到期、证书弄混、长度基准的误解——都可以在这一层拦住或提早发觉。

步骤

  1. 用 python3 /opt/lab/fixtures/eaimw/fep/pki.py init /root/eaimw/fep/partners 创建两个机构的 CA。为每个机构生成私钥和 CSR(主体中包含 O=LabHub Bank,CN 中包含 labhub),并用 python3 /opt/lab/fixtures/eaimw/fep/pki.py sign A <CSR> /root/eaimw/fep/a.crt、… sign B <CSR> /root/eaimw/fep/b.crt 获得签名。密钥放在 /root/eaimw/fep/a.key·/root/eaimw/fep/b.key。
  2. 根据定义书 /opt/lab/fixtures/eaimw/fep/ORGS.md,编写 /root/eaimw/fep/orgs.json。最上层的键是机构代码(201·301),值为 host·port·format(fixed/json)·encoding(euc_kr/utf-8)·len_includes_self(A 为 true,B 为 null)·ca·cert·key(绝对路径)·allow_ips(列表)。
  3. 启动机构 A 服务器(nohup python3 /opt/lab/fixtures/eaimw/fep/org_server.py A > /root/eaimw/fep/a.out 2>&1 &,9441),用 openssl s_client 提供 a.crt、a.key 和机构 A 的 CA,发送一行 PING,把收到的 PONG <지문>(占位符为指纹)保存到 /root/eaimw/fep/a-ping.txt。
  4. 在同一条命令中换上 b.crt、b.key 去连接机构 A,把标准输出和标准错误一起(2>&1)保存到 /root/eaimw/fep/wrong.txt。握手必须被拒绝。
  5. /root/eaimw/fep/fepgw.py send <기관코드> <요청JSON파일>(占位符依次为机构代码、请求 JSON 文件):从配置档案(FEP_ORGS,默认 /root/eaimw/fep/orgs.json)中找到机构,用该机构的证书建立双向 TLS 连接,并用该机构的 CA 校验机构服务器证书。如果有环境变量 FEP_PORT_<기관코드>(占位符为机构代码),端口就用它。201 把请求 {guid,date,amount,acct,name} 按 A 规格定长(长度包含自身)发送,并把结果以 {"org","rspCode","guid","realName","clientFp"} 的一行 JSON 输出;301 把 {guid,custId} 通过 POST /v1/credit-check 发送,并以 {"org","rspCode","guid","grade","clientFp"} 输出。响应码转换为 LH-STD(00→0000,14→B202,30→E102,其余 E500)。
  6. /root/eaimw/fep/inbound.py --port <P>:接收外部机构发起的连接(明文 TCP,A 规格)。如果来源地址不在任何机构的 allow_ips 中,就不读取而直接断开,并在 FEP_LOG(默认 /root/eaimw/fep/inbound.log)中留下一行 DENY <주소>(占位符为地址)。如果是允许的地址,就返回 A 规格的响应(报文类型 0210、相同的交易唯一编号、响应码 00、实名栏为 20 个空格、指纹栏为 64 个 -,长度包含自身)。
  7. /root/eaimw/fep/certcheck.py --warn-days <N>:对配置档案中的每个机构,按机构代码顺序输出一行 <기관코드> <만료일 YYYY-MM-DD> <남은일수> <OK|WARN>(占位符依次为机构代码、到期日、剩余天数)。剩余天数小于 N 就是 WARN,只要有一个 WARN,就以非 0 的退出码结束。

参考

为每个机构签发证书

创建两个机构 CA,为每个机构生成密钥和 CSR 并获得签名,得到 a.crt/a.key 和 b.crt/b.key。

私钥放在我们这边,只发送 CSR。机构 A 必须由 A 签名,机构 B 必须由 B 签名——如果同一个 CA 给两者都签名,机构就无法拒绝对方的证书。

把机构配置档案做成数据

根据 ORGS.md,在 /root/eaimw/fep/orgs.json 中写入两个机构的地址、格式、编码、长度基准、证书和允许 IP。

为了不让代码中出现机构名称,把各机构不同的值全部转移到这个文件里。长度基准对 JSON 机构不适用,所以是 null。

与机构 A 通过双向 TLS 对接

启动机构 A 服务器,用 a.crt 发送 PING,并把 PONG <指纹> 保存到 /root/eaimw/fep/a-ping.txt。

给 openssl s_client 同时提供 -cert、-key(我的身份)和 -CAfile(机构 CA,校验服务器)。加上 -quiet,就只留下响应正文。指纹是机构所看到的我的证书。

换装证书就会被拒绝

用 b.crt、b.key 去连接机构 A,并把输出和错误一起保存到 /root/eaimw/fep/wrong.txt。

只把第 3 步命令中的证书和密钥换掉。拒绝原因(TLS alert)会输出到标准错误,所以请用 2>&1 一起保存。

按机构选择后发送

让 /root/eaimw/fep/fepgw.py send <机构> 用各机构的证书和规格发送,并输出把结果规范化为 LH-STD 代码的一行 JSON。

从配置档案中查看 format 来选择适配器。A 在生成完整正文之后,用 len(正文)+4 作为长度(包含自身)。服务器校验用 create_default_context(cafile=机构 CA),我的身份用 load_cert_chain。

入站一方——只从允许的地址接收

让 /root/eaimw/fep/inbound.py --port P 只接收来自配置档案 allow_ips 的连接并以 A 规格应答,其余的断开并记录 DENY。

socketserver 处理器的 self.client_address[0] 就是来源地址。允许列表是所有机构配置档案中 allow_ips 的合集。拒绝时不要读取,直接返回。

提前知道到期

让 /root/eaimw/fep/certcheck.py --warn-days N 为每个机构输出到期日、剩余天数、OK/WARN,只要有 WARN,就以非 0 的退出码结束。

读取证书的 notAfter,以天数计算它与现在(UTC)的差。有的机构更换要花几周,所以基准要设得宽裕些——监控工具通过退出码来发现。