CA 证书详解:从原理到实践的完整教程 / 第 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 | 服务间双向认证 |
| Kubernetes | Ingress、Service Mesh(Istio) |
| VPN | OpenVPN / 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-rsa | 低 | OpenVPN、小团队 | 无 | 不支持 | Shell |
| Vault PKI | 高 | 企业级、动态证书 | REST | 支持 | Go |
| step-ca | 中 | 企业、ACME 兼容 | REST + ACME | 支持 | Go |
8.9 本章小结
| 主题 | 关键要点 |
|---|---|
| 私有 CA 用途 | 内网服务、微服务 mTLS、IoT 设备 |
| OpenSSL CA | 最基础的方式,灵活但手动操作多 |
| CFSSL | Cloudflare 开源,适合 API 驱动的签发 |
| easy-rsa | 简单易用,适合 OpenVPN 场景 |
| Vault PKI | 企业级,支持动态证书和自动续期 |
| 证书分发 | Ansible / Kubernetes Secret / cert-manager |
| mTLS | 双向认证,服务间零信任通信 |
📚 扩展阅读
上一章:第 7 章:Let’s Encrypt 下一章:第 9 章:故障排查 — 掌握常见证书错误的诊断和修复方法。