3. 证书颁发机构(CA)搭建:开源CA选型与私有CA实战

说到CA搭建,我先泼盆冷水——别一上来就想着自建CA。我见过太多团队,设备量不到一千台,却花了两周搭CA,最后证书轮换时把自己坑了。选型这事,说白了就是看你的规模、团队能力和运维成本。

这一章,我会把三个主流开源CA——EasyRSA、CFSSL、cert-manager——掰开揉碎讲清楚。最后带你把私有CA搭起来,跑通证书签发全流程。

核心观点:没有最好的CA,只有最适合你场景的CA。选型前先回答三个问题——你的设备量级是多少?团队有Go/Shell能力吗?证书轮换频率多高?

开源CA选型决策树 设备规模 & 团队能力 小规模 < 1万设备 EasyRSA 优点:极简,Shell一把梭 缺点:无API,手动操作多 中等规模 1万-10万 CFSSL 优点:RESTful API,灵活 缺点:配置复杂,Go依赖 大规模 > 10万设备 cert-manager 优点:K8s原生,自动轮换 缺点:强依赖K8s生态 选型建议 • 团队无Go能力 → EasyRSA • 需要API集成 → CFSSL • K8s环境 + 自动轮换 → cert-manager

3.1 三大开源CA横向对比

先看一张对比表,心里有个底。

特性 EasyRSA CFSSL cert-manager
开发语言 Shell Go Go
部署复杂度 低(一条命令) 中(需配置JSON) 高(需K8s集群)
API支持 无 RESTful API K8s CRD + API
证书轮换 手动 半自动(脚本+API) 全自动(CRD驱动)
适用场景 小规模、测试环境 中等规模、API集成 大规模、K8s原生
CRL支持 有限 完整 完整

3.2 EasyRSA:小团队的救星

EasyRSA 是 OpenVPN 项目孵化的CA工具。说白了,它就是一堆Shell脚本的集合。我最早做IoT设备证书时就用它,那时候设备才几百台,够用。

安装与初始化

# 下载EasyRSA
wget https://github.com/OpenVPN/easy-rsa/releases/download/v3.1.7/EasyRSA-3.1.7.tgz
tar xzf EasyRSA-3.1.7.tgz
cd EasyRSA-3.1.7

# 初始化PKI目录
./easyrsa init-pki

# 生成CA根证书和私钥(nopass表示不加密私钥)
./easyrsa build-ca nopass

我的习惯:生产环境一定要用密码保护CA私钥。虽然每次签名要输密码麻烦点,但安全第一。我见过有人把无密码的CA私钥传到Git仓库里——嗯,那画面太美我不敢看。

签发设备证书

# 为设备生成证书请求并签名
./easyrsa gen-req device-001 nopass
./easyrsa sign-req client device-001

# 查看证书
openssl x509 -in pki/issued/device-001.crt -text -noout

避坑指南

  • EasyRSA 默认使用 SHA256,如果你的设备只支持 SHA1,记得改 vars 文件
  • 证书有效期默认10年,大规模轮换时建议改成1-2年
  • 没有API,批量签发只能写Shell循环——我曾经写过3000台设备的循环脚本,跑了整整一个下午

3.3 CFSSL:API驱动的灵活之选

CFSSL 是 CloudFlare 开源的PKI工具包。它最大的优势是RESTful API,可以集成到你的自动化平台里。我个人比较喜欢它的多签功能——一个CA可以同时处理多个签名请求。

部署与配置

# 下载CFSSL
wget https://github.com/cloudflare/cfssl/releases/download/v1.6.4/cfssl_1.6.4_linux_amd64
wget https://github.com/cloudflare/cfssl/releases/download/v1.6.4/cfssljson_1.6.4_linux_amd64
chmod +x cfssl_* cfssljson_*
sudo mv cfssl_* /usr/local/bin/cfssl
sudo mv cfssljson_* /usr/local/bin/cfssljson

# 创建CA配置文件 ca-config.json
cat > ca-config.json << 'EOF'
{
  "signing": {
    "default": {
      "expiry": "8760h"
    },
    "profiles": {
      "server": {
        "usages": ["signing", "key encipherment", "server auth"],
        "expiry": "8760h"
      },
      "client": {
        "usages": ["signing", "key encipherment", "client auth"],
        "expiry": "8760h"
      }
    }
  }
}
EOF

# 创建CA证书请求 ca-csr.json
cat > ca-csr.json << 'EOF'
{
  "CN": "My Private CA",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "Beijing",
      "L": "Beijing",
      "O": "MyOrg",
      "OU": "Security"
    }
  ]
}
EOF

# 生成CA根证书
cfssl gencert -initca ca-csr.json | cfssljson -bare ca

启动API服务

# 启动CFSSL API服务(监听9443端口)
cfssl serve -address=0.0.0.0 -port=9443 \
  -ca=ca.pem -ca-key=ca-key.pem \
  -config=ca-config.json

通过API签发证书

# 创建设备证书请求
cat > device-csr.json << 'EOF'
{
  "CN": "device-001",
  "hosts": ["device-001.local"],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {"O": "MyOrg", "OU": "Devices"}
  ]
}
EOF

# 调用API签名
curl -s -X POST http://localhost:9443/api/v1/cfssl/sign \
  -H "Content-Type: application/json" \
  -d '{
    "certificate_request": '$(cat device-csr.json | base64 -w0)',
    "profile": "client"
  }' | cfssljson -bare device-001

注意:CFSSL API 默认没有认证。生产环境一定要加TLS和认证,或者放在内网。我曾经在客户现场看到CFSSL暴露在公网——嗯,那台服务器第二天就被挖矿了。

3.4 cert-manager:K8s环境的首选

如果你的基础设施跑在Kubernetes上,cert-manager 几乎是唯一选择。它通过CRD(Custom Resource Definition)管理证书生命周期,自动轮换、自动续期。

安装

# 使用Helm安装
helm repo add jetstack https://charts.jetstack.io
helm repo update

helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.14.5 \
  --set installCRDs=true

创建ClusterIssuer(集群级CA)

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-issuer
spec:
  selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: my-ca
  namespace: cert-manager
spec:
  isCA: true
  commonName: "My Private CA"
  secretName: ca-key-pair
  privateKey:
    algorithm: RSA
    size: 2048
  issuerRef:
    name: selfsigned-issuer
    kind: ClusterIssuer

自动签发设备证书

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: device-001
  namespace: default
spec:
  commonName: "device-001"
  dnsNames:
  - "device-001.local"
  secretName: device-001-tls
  duration: 2160h  # 90天
  renewBefore: 360h  # 提前15天续期
  issuerRef:
    name: my-ca
    kind: ClusterIssuer

我的经验:cert-manager 的自动轮换是它最大的卖点。我在一个10万节点的K8s集群上跑过,证书轮换时几乎零感知。但要注意——CRD数量多了以后,etcd压力会上升,建议给cert-manager单独配一个etcd集群。

3.5 私有CA搭建实战:从零到签发

好,理论讲完了。现在咱们动手搭一个私有CA。我选CFSSL作为演示,因为它兼顾了灵活性和自动化能力。

步骤一:准备环境

# 创建目录结构
mkdir -p /opt/private-ca/{certs,crl,csr,private}
cd /opt/private-ca

# 生成CA根证书(使用OpenSSL直接生成,不依赖CFSSL)
openssl genrsa -out private/ca.key 4096
openssl req -new -x509 -days 3650 \
  -key private/ca.key \
  -out certs/ca.crt \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/OU=CA/CN=My Private CA Root"

步骤二:配置CFSSL

# 创建CFSSL配置文件
cat > cfssl-config.json << 'EOF'
{
  "signing": {
    "default": {
      "expiry": "43800h",
      "usages": ["signing", "key encipherment"]
    },
    "profiles": {
      "device": {
        "usages": ["signing", "key encipherment", "client auth"],
        "expiry": "8760h"
      },
      "server": {
        "usages": ["signing", "key encipherment", "server auth"],
        "expiry": "8760h"
      }
    }
  }
}
EOF

# 启动CFSSL服务(使用我们自己的CA)
cfssl serve -address=0.0.0.0 -port=9443 \
  -ca=certs/ca.crt \
  -ca-key=private/ca.key \
  -config=cfssl-config.json \
  -loglevel=0 &

步骤三:批量签发脚本

#!/bin/bash
# batch-sign.sh - 批量签发设备证书

CA_API="http://localhost:9443/api/v1/cfssl/sign"
DEVICE_LIST="devices.txt"
OUTPUT_DIR="./issued-certs"

mkdir -p $OUTPUT_DIR

while IFS= read -r device; do
  echo "Signing certificate for $device..."
  
  # 生成CSR
  cat > /tmp/${device}-csr.json << EOF
{
  "CN": "$device",
  "hosts": ["$device.local"],
  "key": {"algo": "rsa", "size": 2048},
  "names": [{"O": "MyOrg", "OU": "Devices"}]
}
EOF

  # 调用API签名
  curl -s -X POST $CA_API \
    -H "Content-Type: application/json" \
    -d "{\"certificate_request\": \"$(cat /tmp/${device}-csr.json | base64 -w0)\", \"profile\": \"device\"}" \
    | cfssljson -bare ${OUTPUT_DIR}/${device}
    
  echo "Done: $device"
done < "$DEVICE_LIST"

步骤四:验证证书链

# 验证设备证书是否由CA签发
openssl verify -CAfile certs/ca.crt issued-certs/device-001.pem

# 查看证书有效期
openssl x509 -in issued-certs/device-001.pem -noout -dates

关键检查点:

  • CA私钥权限必须为600,属主root
  • 证书链必须完整(CA证书 + 设备证书)
  • CRL(证书吊销列表)要定期生成,我一般每周跑一次cron
  • 备份CA私钥到离线存储——丢了它,所有设备都得重新注册

3.6 选型决策清单

最后,给你一个决策清单。下次选型时,对着打勾就行。

条件 推荐方案 理由
设备 < 1万,无API需求 EasyRSA 零依赖,一条命令搞定
设备 1万-10万,需API集成 CFSSL RESTful API,灵活扩展
设备 > 10万,K8s环境 cert-manager 自动轮换,CRD驱动
设备 > 10万,非K8s环境 CFSSL + 自研调度 API + 自定义轮换策略
测试/开发环境 EasyRSA 快速验证,用完即弃