强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 8 章:搭建私有 CA

第 8 章:搭建私有 CA

在企业内网、微服务架构和 IoT 场景中,私有 CA 是管理内部证书的核心基础设施。本章介绍使用不同工具搭建私有 CA 的完整流程。


8.1 为什么需要私有 CA

公共 CA vs 私有 CA

特性公共 CA私有 CA
信任范围全球(浏览器/OS 预装)仅内部(手动分发根证书)
成本DV 免费 / OV/EV 付费自建成本(人力 + 基础设施)
审计要求必须符合 CA/B 论坛要求自定义策略
签发速度DV 秒级 / OV 数天自定义,通常秒级
适用场景面向公众的服务内网服务、微服务、IoT

私有 CA 使用场景

场景说明
企业内网内部 Web 应用、管理后台
微服务 mTLS服务间双向认证
KubernetesIngress、Service Mesh(Istio)
VPNOpenVPN / WireGuard 的客户端证书
IoT 设备设备身份认证
开发环境本地 HTTPS 开发

8.2 使用 OpenSSL 搭建私有 CA

基于 OpenSSL 的 CA 搭建已在第 6 章详细介绍。本节提供一个更完整的生产级配置。

目录结构

mkdir -p ~/private-ca/{root-ca,intermediate-ca}/{certs,crl,newcerts,private,csr}
chmod 700 ~/private-ca/root-ca/private ~/private-ca/intermediate-ca/private

# 初始化数据库
touch ~/private-ca/root-ca/index.txt
echo 1000 > ~/private-ca/root-ca/serial
touch ~/private-ca/intermediate-ca/index.txt
echo 1000 > ~/private-ca/intermediate-ca/serial

根 CA 配置

cat > ~/private-ca/root-ca/root-ca.cnf << 'EOF'
[ca]
default_ca = CA_default

[CA_default]
dir               = /home/user/private-ca/root-ca
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand

private_key       = $dir/private/root-ca.key
certificate       = $dir/certs/root-ca.crt

crlnumber         = $dir/crlnumber
crl               = $dir/crl/root-ca.crl
crl_extensions    = crl_ext
default_crl_days  = 365

default_md        = sha256
name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no
policy            = policy_strict

[policy_strict]
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[req]
default_bits        = 4096
distinguished_name  = req_distinguished_name
string_mask         = utf8only
default_md          = sha256
x509_extensions     = v3_ca
prompt              = no

[req_distinguished_name]
C  = CN
ST = Beijing
O  = MyCompany
CN = MyCompany Root CA

[v3_ca]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[crl_ext]
authorityKeyIdentifier = keyid:always
EOF

中间 CA 配置

cat > ~/private-ca/intermediate-ca/intermediate-ca.cnf << 'EOF'
[ca]
default_ca = CA_default

[CA_default]
dir               = /home/user/private-ca/intermediate-ca
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand

private_key       = $dir/private/intermediate-ca.key
certificate       = $dir/certs/intermediate-ca.crt

crlnumber         = $dir/crlnumber
crl               = $dir/crl/intermediate-ca.crl
crl_extensions    = crl_ext
default_crl_days  = 30

default_md        = sha256
name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no
policy            = policy_loose

[policy_loose]
countryName             = optional
stateOrProvinceName     = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[req]
default_bits        = 4096
distinguished_name  = req_distinguished_name
string_mask         = utf8only
default_md          = sha256
prompt              = no

[req_distinguished_name]
C  = CN
ST = Beijing
O  = MyCompany
CN = MyCompany Intermediate CA

[v3_intermediate_ca]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[server_cert]
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[client_cert]
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature
extendedKeyUsage = clientAuth

[alt_names]
DNS.1 = ${ENV::CERT_DNS1}
DNS.2 = ${ENV::CERT_DNS2}

[crl_ext]
authorityKeyIdentifier = keyid:always
EOF

生成根 CA

# 生成根 CA 私钥
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 \
  -out ~/private-ca/root-ca/private/root-ca.key
chmod 400 ~/private-ca/root-ca/private/root-ca.key

# 生成根 CA 证书(有效期 20 年)
openssl req -config ~/private-ca/root-ca/root-ca.cnf \
  -key ~/private-ca/root-ca/private/root-ca.key \
  -new -x509 -days 7300 -sha256 \
  -out ~/private-ca/root-ca/certs/root-ca.crt

# 验证
openssl x509 -in ~/private-ca/root-ca/certs/root-ca.crt -text -noout | head -15

生成中间 CA

# 生成中间 CA 私钥
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 \
  -out ~/private-ca/intermediate-ca/private/intermediate-ca.key
chmod 400 ~/private-ca/intermediate-ca/private/intermediate-ca.key

# 生成中间 CA CSR
openssl req -config ~/private-ca/intermediate-ca/intermediate-ca.cnf \
  -key ~/private-ca/intermediate-ca/private/intermediate-ca.key \
  -new -sha256 \
  -out ~/private-ca/intermediate-ca/csr/intermediate-ca.csr

# 根 CA 签发中间 CA 证书(有效期 10 年)
openssl ca -config ~/private-ca/root-ca/root-ca.cnf \
  -extensions v3_intermediate_ca \
  -days 3650 -notext -md sha256 \
  -in ~/private-ca/intermediate-ca/csr/intermediate-ca.csr \
  -out ~/private-ca/intermediate-ca/certs/intermediate-ca.crt \
  -batch

# 创建证书链
cat ~/private-ca/intermediate-ca/certs/intermediate-ca.crt \
  ~/private-ca/root-ca/certs/root-ca.crt \
  > ~/private-ca/intermediate-ca/certs/ca-chain.crt

# 验证中间 CA 证书
openssl verify -CAfile ~/private-ca/root-ca/certs/root-ca.crt \
  ~/private-ca/intermediate-ca/certs/intermediate-ca.crt

签发服务器证书

# 使用脚本简化签发
cat > ~/private-ca/issue-cert.sh << 'SCRIPT'
#!/usr/bin/env bash
# issue-cert.sh - 签发服务器证书
# 用法: ./issue-cert.sh <domain> [san_domain2] [san_domain3] ...

set -euo pipefail

CA_DIR="/home/user/private-ca/intermediate-ca"
DOMAIN="${1:?用法: $0 <domain> [san_domain2] ...}"

shift
SAN_DNS="DNS.1 = ${DOMAIN}"
i=2
for alt in "$@"; do
  SAN_DNS="${SAN_DNS}\nDNS.${i} = ${alt}"
  ((i++))
done

# 生成私钥
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
  -out "${CA_DIR}/newcerts/${DOMAIN}.key"

# 创建临时 CSR 配置
cat > /tmp/csr-${DOMAIN}.cnf << EOF
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req

[dn]
C = CN
ST = Beijing
O = MyCompany
CN = ${DOMAIN}

[v3_req]
subjectAltName = @alt_names
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[alt_names]
$(echo -e "$SAN_DNS")
EOF

# 生成 CSR
openssl req -new \
  -key "${CA_DIR}/newcerts/${DOMAIN}.key" \
  -out "${CA_DIR}/csr/${DOMAIN}.csr" \
  -config "/tmp/csr-${DOMAIN}.cnf"

# 签发证书
export CERT_DNS1="${DOMAIN}"
export CERT_DNS2="${2:-${DOMAIN}}"
openssl ca -config "${CA_DIR}/intermediate-ca.cnf" \
  -extensions server_cert \
  -days 375 -notext -md sha256 \
  -in "${CA_DIR}/csr/${DOMAIN}.csr" \
  -out "${CA_DIR}/certs/${DOMAIN}.crt" \
  -batch

# 创建完整证书链
cat "${CA_DIR}/certs/${DOMAIN}.crt" \
  "${CA_DIR}/certs/ca-chain.crt" \
  > "${CA_DIR}/certs/${DOMAIN}-fullchain.crt"

echo "✅ 证书已签发:"
echo "  私钥:  ${CA_DIR}/newcerts/${DOMAIN}.key"
echo "  证书:  ${CA_DIR}/certs/${DOMAIN}.crt"
echo "  完整链: ${CA_DIR}/certs/${DOMAIN}-fullchain.crt"

# 清理临时文件
rm -f "/tmp/csr-${DOMAIN}.cnf"
SCRIPT

chmod +x ~/private-ca/issue-cert.sh

# 使用脚本签发证书
~/private-ca/issue-cert.sh example.com www.example.com api.example.com

8.3 使用 CFSSL

CFSSL 是 Cloudflare 开源的 PKI/TLS 工具集,适合大规模证书管理。

安装 CFSSL

# 使用 Go 安装
go install github.com/cloudflare/cfssl/cmd/cfssl@latest
go install github.com/cloudflare/cfssl/cmd/cfssljson@latest
go install github.com/cloudflare/cfssl/cmd/mkbundle@latest

# 或下载预编译二进制
curl -sL https://github.com/cloudflare/cfssl/releases/latest/download/cfssl_linux-amd64 -o /usr/local/bin/cfssl
curl -sL https://github.com/cloudflare/cfssl/releases/latest/download/cfssljson_linux-amd64 -o /usr/local/bin/cfssljson
chmod +x /usr/local/bin/cfssl /usr/local/bin/cfssljson

# 验证
cfssl version

创建 CA 配置

mkdir -p ~/cfssl-ca && cd ~/cfssl-ca

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

# CA CSR
cat > ca-csr.json << 'EOF'
{
  "CN": "MyCompany CA",
  "key": {
    "algo": "rsa",
    "size": 4096
  },
  "names": [
    {
      "C": "CN",
      "ST": "Beijing",
      "L": "Beijing",
      "O": "MyCompany",
      "OU": "Security"
    }
  ]
}
EOF

生成 CA 证书

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

# 生成的文件
ls ca*.pem ca*.csr
# ca-key.pem    (CA 私钥)
# ca.pem        (CA 证书)
# ca.csr        (CA CSR)

签发服务器证书

# 创建服务器 CSR
cat > server-csr.json << 'EOF'
{
  "CN": "example.com",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "Beijing",
      "O": "MyCompany",
      "OU": "Engineering"
    }
  ],
  "hosts": [
    "example.com",
    "www.example.com",
    "api.example.com",
    "192.168.1.100",
    "10.0.0.1"
  ]
}
EOF

# 签发证书
cfssl gencert \
  -ca=ca.pem -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=server \
  server-csr.json | cfssljson -bare server

# 验证
openssl x509 -in server.pem -text -noout | grep -E "Issuer|Subject|DNS|IP"

批量签发

# 批量签发多个域名的证书
for domain in api.example.com web.example.com admin.example.com; do
  cat > "${domain}-csr.json" << EOF
{
  "CN": "${domain}",
  "key": {"algo": "rsa", "size": 2048},
  "names": [{"C": "CN", "ST": "Beijing", "O": "MyCompany"}],
  "hosts": ["${domain}"]
}
EOF

  cfssl gencert \
    -ca=ca.pem -ca-key=ca-key.pem \
    -config=ca-config.json \
    -profile=server \
    "${domain}-csr.json" | cfssljson -bare "${domain}"

  echo "✅ ${domain} 证书已签发"
done

CFSSL API 服务器

# 启动 CFSSL API 服务器(适合团队共享)
cfssl serve \
  -address=0.0.0.0 \
  -port=8888 \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json

# 通过 API 签发证书
curl -s -X POST http://localhost:8888/api/v1/cfssl/newcert \
  -H "Content-Type: application/json" \
  -d '{
    "request": {
      "CN": "new-service.example.com",
      "hosts": ["new-service.example.com"],
      "key": {"algo": "rsa", "size": 2048},
      "names": [{"C": "CN", "ST": "Beijing", "O": "MyCompany"}]
    },
    "profile": "server"
  }' | jq .

8.4 使用 easy-rsa

easy-rsa 是 OpenVPN 项目维护的 CA 管理工具,简单易用。

安装与初始化

# 安装
sudo apt install easy-rsa     # Debian/Ubuntu
sudo dnf install easy-rsa     # RHEL/CentOS

# 创建 CA 目录
make-cadir ~/my-easy-rsa-ca
cd ~/my-easy-rsa-ca

# 初始化 PKI
./easyrsa init-pki

# 查看配置
cat vars.example

自定义配置

# 复制并编辑配置
cp vars.example vars

cat >> vars << 'EOF'
set_var EASYRSA_REQ_COUNTRY    "CN"
set_var EASYRSA_REQ_PROVINCE   "Beijing"
set_var EASYRSA_REQ_CITY       "Beijing"
set_var EASYRSA_REQ_ORG        "MyCompany"
set_var EASYRSA_REQ_EMAIL      "[email protected]"
set_var EASYRSA_REQ_OU         "Engineering"
set_var EASYRSA_ALGO           "ec"
set_var EASYRSA_DIGEST         "sha512"
set_var EASYRSA_CA_EXPIRE      3650
set_var EASYRSA_CERT_EXPIRE    365
set_var EASYRSA_CRL_DAYS       30
EOF

操作流程

# 1. 创建 CA(交互式)
./easyrsa build-ca

# 2. 创建服务器证书请求
./easyrsa gen-req server.example.com nopass

# 3. 签发服务器证书
./easyrsa sign-req server server.example.com

# 4. 创建客户端证书
./easyrsa gen-req client1 nopass
./easyrsa sign-req client client1

# 5. 创建 DH 参数(如果使用 RSA)
./easyrsa gen-dh

# 6. 创建 TLS 认证密钥(可选)
openvpn --genkey secret ta.key

# 7. 吊销证书
./easyrsa revoke client1
./easyrsa gen-crl

# 查看已签发的证书
ls pki/issued/
cat pki/index.txt

easy-rsa 一键脚本

#!/usr/bin/env bash
# easy-rsa-setup.sh - 一键初始化 CA 并签发证书
set -euo pipefail

EASYRSA_DIR="/opt/easy-rsa"
DOMAIN="${1:?用法: $0 <domain>}"

cd "$EASYRSA_DIR"

# 初始化
./easyrsa --batch init-pki

# 创建 CA
./easyrsa --batch build-ca nopass

# 生成服务器证书
./easyrsa --batch gen-req "$DOMAIN" nopass
./easyrsa --batch sign-req server "$DOMAIN"

# 生成 DH 参数
./easyrsa --batch gen-dh

echo "✅ CA 和证书已创建"
echo "  CA 证书:   ${EASYRSA_DIR}/pki/ca.crt"
echo "  服务证书:  ${EASYRSA_DIR}/pki/issued/${DOMAIN}.crt"
echo "  服务私钥:  ${EASYRSA_DIR}/pki/private/${DOMAIN}.key"
echo "  DH 参数:   ${EASYRSA_DIR}/pki/dh.pem"

8.5 使用 HashiCorp Vault

Vault 的 PKI Secrets Engine 适合动态证书管理。

启用 PKI 引擎

# 启用 PKI 引擎
vault secrets enable pki

# 调整最大租约
vault secrets tune -max-lease-ttl=87600h pki

# 生成根 CA
vault write pki/root/generate/internal \
  common_name="MyCompany Root CA" \
  ttl=87600h

# 配置 URL
vault write pki/config/urls \
  issuing_certificates="http://127.0.0.1:8200/v1/pki/ca" \
  crl_distribution_points="http://127.0.0.1:8200/v1/pki/crl"

# 创建角色
vault write pki/roles/server \
  allowed_domains="example.com,internal.com" \
  allow_subdomains=true \
  max_ttl=720h

# 签发证书
vault write pki/issue/server \
  common_name="web.example.com" \
  ttl=72h

# 通过 API 签发
curl -s -X PUT \
  -H "X-Vault-Token: $VAULT_TOKEN" \
  -d '{"common_name":"api.example.com","ttl":"72h"}' \
  http://127.0.0.1:8200/v1/pki/issue/server | jq .

8.6 证书分发

使用 Ansible 分发证书

# playbook: deploy-cert.yml
---
- name: Deploy TLS certificates
  hosts: webservers
  become: true
  vars:
    cert_domain: "example.com"
  
  tasks:
    - name: Create SSL directory
      file:
        path: /etc/ssl/private
        state: directory
        mode: '0700'
        owner: root
        group: root

    - name: Copy certificate
      copy:
        src: "certs/{{ cert_domain }}-fullchain.crt"
        dest: "/etc/ssl/certs/{{ cert_domain }}.crt"
        mode: '0644'
      notify: reload nginx

    - name: Copy private key
      copy:
        src: "certs/{{ cert_domain }}.key"
        dest: "/etc/ssl/private/{{ cert_domain }}.key"
        mode: '0600'
        owner: root
        group: root
      notify: reload nginx

    - name: Copy CA certificate
      copy:
        src: certs/ca-chain.crt
        dest: /usr/local/share/ca-certificates/my-company-ca.crt
      notify: update ca-certificates

  handlers:
    - name: reload nginx
      service:
        name: nginx
        state: reloaded

    - name: update ca-certificates
      command: update-ca-certificates

使用 Kubernetes Secret

# 创建 TLS Secret
apiVersion: v1
kind: Secret
metadata:
  name: example-com-tls
  namespace: default
type: kubernetes.io/tls
data:
  tls.crt: <base64-encoded-fullchain>
  tls.key: <base64-encoded-private-key>

---
# 创建 CA ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: private-ca
  namespace: cert-manager
data:
  ca.crt: |
    -----BEGIN CERTIFICATE-----
    <base64-encoded-ca-cert>
    -----END CERTIFICATE-----
# 使用 kubectl 创建
kubectl create secret tls example-com-tls \
  --cert=fullchain.crt \
  --key=privkey.key

# 创建 CA ConfigMap
kubectl create configmap private-ca \
  --from-file=ca.crt=ca-chain.crt

cert-manager 自动管理

# cert-manager ClusterIssuer(使用私有 CA)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: private-ca-issuer
spec:
  ca:
    secretName: ca-key-pair

---
# 自动签发证书
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-com
spec:
  secretName: example-com-tls
  issuerRef:
    name: private-ca-issuer
    kind: ClusterIssuer
  commonName: example.com
  dnsNames:
    - example.com
    - www.example.com
    - "*.example.com"
  duration: 2160h    # 90 天
  renewBefore: 360h  # 到期前 15 天续期

8.7 mTLS(双向 TLS)

mTLS 工作流程

标准 TLS(单向):
  客户端 ──验证──▶ 服务器证书
  客户端 ◀──加密── 服务器

mTLS(双向):
  客户端 ──验证──▶ 服务器证书
  客户端 ◀──验证── 客户端证书
  客户端 ◀──加密── 双向

Nginx mTLS 配置

server {
    listen 443 ssl;
    server_name api.internal.example.com;
    
    # 服务器证书
    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    
    # 客户端证书验证
    ssl_client_certificate /etc/nginx/ssl/ca-chain.crt;
    ssl_verify_client on;
    ssl_verify_depth 2;
    
    location / {
        # 将客户端证书信息传递给后端
        proxy_set_header X-SSL-Client-CN $ssl_client_s_dn_cn;
        proxy_set_header X-SSL-Client-Serial $ssl_client_serial;
        proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
        proxy_pass http://backend:8080;
    }
}
# 使用客户端证书访问
curl --cert client.crt --key client.key \
  --cacert ca-chain.crt \
  https://api.internal.example.com

8.8 各工具对比

工具复杂度适合场景API集群语言
OpenSSL小规模、一次性不支持C
CFSSL大规模、API 服务REST支持Go
easy-rsaOpenVPN、小团队不支持Shell
Vault PKI企业级、动态证书REST支持Go
step-ca企业、ACME 兼容REST + ACME支持Go

8.9 本章小结

主题关键要点
私有 CA 用途内网服务、微服务 mTLS、IoT 设备
OpenSSL CA最基础的方式,灵活但手动操作多
CFSSLCloudflare 开源,适合 API 驱动的签发
easy-rsa简单易用,适合 OpenVPN 场景
Vault PKI企业级,支持动态证书和自动续期
证书分发Ansible / Kubernetes Secret / cert-manager
mTLS双向认证,服务间零信任通信

📚 扩展阅读


上一章第 7 章:Let’s Encrypt 下一章第 9 章:故障排查 — 掌握常见证书错误的诊断和修复方法。