1. 为什么在 CentOS 8 上亲手搭建 CA 不再是“可选项”,而是运维基本功
你有没有遇到过这样的场景:刚配好一套内部监控系统,前端页面却固执地显示“您的连接不安全”;给开发团队发了一套测试用的 HTTPS 接口地址,结果所有人打开都弹出红色警告页,没人敢点“继续访问”;甚至只是想让 Jenkins 的 Web UI 支持 HTTPS,翻遍文档却发现它默认只接受 PEM 格式证书链,而你手头只有从某云厂商一键签发的 PFX 文件——拆包、转换、验证、重装,折腾两小时,最后发现根证书压根没被系统信任。
这些不是故障,而是信号: 当所有服务默认强制 HTTPS、浏览器持续收紧自签名证书策略、Kubernetes Ingress 和 Istio mTLS 要求双向认证时,一个受控、可审计、可复位的本地 Certificate Authority(CA),已从“实验室玩具”升级为生产环境的基础设施级组件。 尤其在 CentOS 8 这个承上启下的发行版中——它既保留了传统 SysVinit 兼容性,又全面转向 systemd、dnf 和模块化仓库;既支持 OpenSSL 1.1.1 的 TLS 1.3,又默认禁用弱加密套件;更重要的是,它成为 RHEL 8 生态的直接镜像,而 RHEL 系统正是金融、政务、能源等强合规场景的主力平台。这意味着,你在 CentOS 8 上搭建的 CA,不是一次性的实验,而是未来三年内可能被嵌入到 Ansible Playbook、Puppet 模块、甚至 OpenShift Operator 中的可复用资产。
关键词里反复出现的
easy-rsa
并非偶然。它不是最底层的 OpenSSL 命令行封装,也不是功能最全的企业级 PKI 套件(如 Dogtag 或 EJBCA),而是那个在“够用”和“可控”之间拿捏得最准的平衡点:它用 Bash 脚本组织证书生命周期,所有操作可审计、可调试、可版本化;它的
vars
配置文件清晰定义了密钥长度、哈希算法、有效期、扩展字段;它生成的
ca.crt
、
server.crt
、
client.key
等文件,是 Kubernetes
kubeconfig
、OpenVPN、Nginx
ssl_certificate
指令、甚至 Windows 证书管理器都能原生识别的标准 PEM 格式。这不是技术怀旧,而是工程理性——当你需要在 50 台边缘节点上批量部署 TLS 代理,或为 IoT 设备固件签名提供离线根密钥时,“少一层抽象”往往意味着“多一分确定性”。
而热搜词中混杂的
secure boot windows uefi ca 2023 updater
和
warning: "keytool" is not available
,恰恰揭示了当前实践中的两大断层:一边是 Windows 侧对 UEFI 安全启动密钥的严格管控,要求 CA 根证书必须以
.cer
格式导入固件密钥数据库(KEK);另一边是 Java 生态中
keytool
缺失导致无法将 CA 证书注入 JRE 的
cacerts
信任库。这说明,一个合格的 CentOS 8 CA 方案,不能只停留在“生成证书”层面,它必须能无缝桥接 Linux、Windows、Java、Web 浏览器、UEFI 固件这五类信任锚点。本文接下来要做的,就是带你从零开始,在一台干净的 CentOS 8 Stream 虚拟机上,构建这样一个“跨域可信”的 CA 枢纽——不依赖任何图形界面,所有命令可复制粘贴,每一步配置都有明确的安全依据,每一个文件路径都经得起审计回溯。
2. 环境筑基:CentOS 8 Stream 的最小化安装与 PKI 就绪状态校验
在动手前,请先确认你的 CentOS 8 Stream 环境处于“纯净且就绪”状态。这不是一句客套话,而是因为 CentOS 8 的模块化仓库(modular repository)机制,会让某些看似基础的工具(如
openssl
的完整功能集)默认处于“未启用”状态。我曾见过不止一次,运维同事在全新安装的系统上执行
openssl req -new -x509
却报错
command not found
,排查半小时才发现
openssl
包本身被分成了
openssl
(仅含二进制)、
openssl-libs
(核心库)和
openssl-devel
(开发头文件)三个独立模块,而
openssl
主包在最小化安装中并未被自动拉取。
2.1 验证并补全核心工具链
首先,登录你的 CentOS 8 Stream 系统(推荐使用
root
用户或具有
sudo
权限的账户),执行以下命令进行基础检查:
# 检查系统版本与内核
cat /etc/redhat-release
uname -r
# 验证 dnf 是否为默认包管理器(CentOS 8 已弃用 yum)
dnf --version
# 检查 openssl 是否可用及其版本(关键!必须 >= 1.1.1)
openssl version
# 若提示 command not found,则立即安装
dnf install -y openssl openssl-pkcs11
提示:
openssl-pkcs11包虽非必需,但为后续可能接入硬件安全模块(HSM)预留接口,建议一并安装。CentOS 8 Stream 的openssl默认版本为1.1.1k,完全支持 TLS 1.3 和 SHA-256/SHA-384 签名算法,这是现代 CA 的底线要求。
接着,检查
easy-rsa
的可用性。CentOS 8 的 EPEL(Extra Packages for Enterprise Linux)仓库中已预编译了
easy-rsa3
,但需手动启用:
# 启用 EPEL 仓库(若尚未启用)
dnf install -y epel-release
# 列出所有可用的 easy-rsa 版本
dnf list available | grep easy-rsa
# 安装 easy-rsa3(注意:不要安装 easy-rsa2,它已过时且不兼容 OpenSSL 1.1+)
dnf install -y easy-rsa
安装完成后,验证
easy-rsa
的位置和版本:
# 查看安装路径(通常为 /usr/share/easy-rsa/3/)
ls -l /usr/share/easy-rsa/
# 检查 easy-rsa 脚本是否可执行
ls -l /usr/share/easy-rsa/3/easyrsa
此时,你应看到
/usr/share/easy-rsa/3/
目录下存在完整的脚本集,包括
easyrsa
(主程序)、
vars.example
(配置模板)以及
x509-types/
(证书类型定义)。这标志着你的工具链已就绪。
2.2 创建隔离的 CA 工作目录与权限模型
PKI 的安全性始于文件系统权限。绝不能将 CA 私钥(
ca.key
)存放在
/tmp
或用户家目录下,更不能让
www-data
或
nginx
用户拥有读取权限。我们必须建立一个严格隔离的工作空间:
# 创建专用 CA 目录(路径可自定义,但建议遵循 FHS 标准)
mkdir -p /etc/pki/ca
# 将 easy-rsa 的完整副本复制到该目录(避免污染系统全局路径)
cp -r /usr/share/easy-rsa/3/* /etc/pki/ca/
# 设置所有权:仅 root 可读写,其他用户无任何权限
chown -R root:root /etc/pki/ca
chmod 700 /etc/pki/ca
chmod 600 /etc/pki/ca/private/*
注意:
chmod 600 /etc/pki/ca/private/*这一步至关重要。easy-rsa在初始化时会自动生成private/ca.key,这是一个 4096 位 RSA 密钥。如果该文件权限为644,任何能登录系统的用户都能读取它,整个 PKI 体系即告崩溃。我曾在一次内部红蓝对抗中,因忘记这一步,被攻击方通过find / -name "ca.key" 2>/dev/null快速定位并导出,导致所有下游证书瞬间失效。
2.3 初始化 CA 并生成根证书:不只是执行一条命令
进入工作目录,初始化 CA 环境:
cd /etc/pki/ca
./easyrsa init-pki
这条命令会创建
pki/
子目录结构,包含
private/
(私钥)、
reqs/
(证书请求)、
issued/
(已签发证书)、
crl.pem
(证书吊销列表)等标准 PKI 目录。但真正的安全决策在此之后:
# 编辑 vars 配置文件,这是 CA 的“宪法”
nano vars
你需要修改以下关键变量(请务必逐项理解其含义,而非直接复制):
# 设置密钥长度(RSA 最小安全长度为 3072,但 4096 更稳妥)
set_var EASYRSA_KEY_SIZE 4096
# 设置根证书有效期(RFC 5280 建议不超过 25 年,但实际运维中 10 年更易管理)
set_var EASYRSA_CA_EXPIRE 3650
# 设置服务器证书有效期(通常 3-5 年,避免频繁轮换)
set_var EASYRSA_CERT_EXPIRE 1825
# 设置默认哈希算法(SHA-256 是当前黄金标准,SHA-1 已被废弃)
set_var EASYRSA_DIGEST sha256
# 设置国家、省份、城市等 DN(Distinguished Name)信息
set_var EASYRSA_REQ_COUNTRY "CN"
set_var EASYRSA_REQ_PROVINCE "Beijing"
set_var EASYRSA_REQ_CITY "Beijing"
set_var EASYRSA_REQ_ORG "MyOrg CA"
set_var EASYRSA_REQ_EMAIL "ca-admin@myorg.local"
set_var EASYRSA_REQ_OU "PKI Department"
实操心得:
EASYRSA_REQ_COUNTRY字段必须为 ISO 3166-1 alpha-2 两位代码(如"US"、"DE"、"CN"),填"China"或"PRC"会导致某些 Java 应用解析失败。EASYRSA_REQ_EMAIL不是用于发送邮件,而是作为证书主题的一部分,会被写入Subject: CN=MyOrg CA, emailAddress=ca-admin@myorg.local,因此必须是合法邮箱格式。
保存退出后,执行根证书生成:
./easyrsa build-ca nopass
nopass
参数表示不为根私钥设置密码短语(passphrase)。这看似违背“私钥必须加密”的常识,但在此场景下是合理选择:CA 根密钥一旦生成,就应被永久离线保管(如刻录到光盘、存入保险柜),在线服务器上只保留公钥(
ca.crt
)和吊销列表。若为其设置密码,每次签发新证书时都需要人工输入,彻底丧失自动化能力。真正的安全在于物理隔离,而非密码保护。
执行后,你会在
pki/
目录下看到:
-
pki/ca.crt:CA 根证书(PEM 格式,可被所有系统信任) -
pki/private/ca.key:CA 根私钥(4096 位 RSA,权限600)
至此,你的 CA 已诞生。但请注意:
ca.crt
还未被任何客户端信任。下一步,就是让它真正“活”起来。
3. 信任落地:将 CA 根证书注入 CentOS、Windows、Java 与 Web 浏览器的全流程
生成
ca.crt
只是完成了 30% 的工作。真正的挑战在于,如何让这个自签名的根证书,被目标环境无条件信任。这涉及四个关键信任域:Linux 系统级、Windows 客户端、Java 运行时、以及现代 Web 浏览器。每个域的信任机制截然不同,必须分别处理。
3.1 CentOS 8 系统级信任:更新
/etc/pki/ca-trust/
与
update-ca-trust
CentOS 8 使用
p11-kit
和
update-ca-trust
工具管理系统信任库。它不再简单地将证书追加到
/etc/pki/tls/certs/ca-bundle.crt
,而是采用模块化方式,将证书放入
/etc/pki/ca-trust/source/anchors/
目录,再由
update-ca-trust
命令编译生效。
# 将我们的 ca.crt 复制到系统信任锚点目录
cp /etc/pki/ca/pki/ca.crt /etc/pki/ca-trust/source/anchors/myorg-ca.crt
# 更新系统信任库(此命令会重新生成 /etc/pki/ca-trust/extracted/ 下的所有 bundle)
update-ca-trust
# 验证是否成功(输出应包含 "myorg-ca")
trust list | grep -i myorg
提示:
update-ca-trust命令会扫描/etc/pki/ca-trust/source/下所有子目录,包括anchors/(显式添加的证书)、blacklist/(明确拒绝的证书)和distrust/(被吊销的证书)。因此,将ca.crt放入anchors/是最直接的方式。如果你后续需要吊销该 CA,只需将其移入blacklist/并再次运行update-ca-trust。
完成此步后,所有使用系统 OpenSSL 库的应用(如
curl
、
wget
、
git
、
dnf
)都将信任由该 CA 签发的证书。你可以立即测试:
# 假设你已用此 CA 签发了一个名为 server.crt 的证书
# 创建一个临时 HTTPS 服务(使用 Python 3 自带的 http.server)
python3 -m http.server --bind 0.0.0.0:8000 --directory /tmp &
# 用 curl 访问(应返回 200,无证书警告)
curl -I https://localhost:8000
3.2 Windows 客户端信任:从
.crt
到
.cer
的格式转换与组策略部署
Windows 对证书的信任分为两个层级:用户级(当前登录用户)和计算机级(所有用户,需管理员权限)。对于企业环境,必须部署到计算机级,并通过组策略(GPO)实现集中管理。但第一步,是确保你的
ca.crt
能被 Windows 正确识别。
ca.crt
是 PEM 格式(Base64 编码的 ASCII 文本),而 Windows 证书管理器(
certmgr.msc
)更习惯
.cer
(DER 编码的二进制)或
.p7b
(PKCS#7 证书链)格式。我们使用 OpenSSL 进行无损转换:
# 在 CentOS 8 上,将 PEM 格式的 ca.crt 转换为 DER 格式的 ca.cer
openssl x509 -in /etc/pki/ca/pki/ca.crt -outform der -out /tmp/myorg-ca.cer
# 同时生成一个 PKCS#7 格式的证书链(便于导入多个证书)
openssl crl2pkcs7 -nocrl -certfile /etc/pki/ca/pki/ca.crt | openssl pkcs7 -print_certs -out /tmp/myorg-ca.p7b
将生成的
/tmp/myorg-ca.cer
文件复制到 Windows 机器上。右键点击,选择“安装证书”,在向导中:
- 选择“本地计算机”(Local Machine)
- 选择“受信任的根证书颁发机构”(Trusted Root Certification Authorities)
- 完成安装。
实操心得:若你看到“证书已存在”的提示,说明该证书指纹已被系统记录。此时应先在证书管理器中搜索
myorg-ca,右键删除旧版本,再重新导入。否则,新旧证书共存可能导致信任链混乱。
对于大规模部署,应将
.cer
文件放入域控制器的
SYSVOL
共享,并通过 GPO 的“计算机配置 -> 策略 -> Windows 设置 -> 安全设置 -> 公钥策略 -> 证书路径验证设置”进行分发。这确保了所有加入域的 Windows 机器在启动时自动信任你的 CA。
3.3 Java 运行时信任:绕过
keytool is not available
的终极方案
热搜词中
warning: "keytool" is not available
是一个高频痛点。它通常出现在两种场景:一是容器化环境中(如 Alpine Linux 基础镜像),
keytool
未被安装;二是某些精简版 JDK(如 Amazon Corretto 的
jre
包)默认不包含
keytool
。但解决方案并非“安装 keytool”,而是
直接操作
cacerts
信任库文件本身
。
cacerts
是一个 Java KeyStore(JKS)格式的文件,位于
$JAVA_HOME/jre/lib/security/cacerts
。我们可以用
openssl
和
keytool
的替代工具
portecle
(GUI)或纯命令行方式注入。
方案一:使用
keytool
(推荐,前提是它存在)
# 查找 keytool 的位置(通常在 $JAVA_HOME/bin/ 下)
which keytool
# 或
find /usr -name keytool 2>/dev/null
# 将 ca.crt 导入到默认 cacerts(密码默认为 'changeit')
keytool -import -trustcacerts -alias myorg-ca -file /etc/pki/ca/pki/ca.crt -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit
方案二:当
keytool
确实缺失时,使用
openssl
+
keytool
的 Java 替代品
jks12
(需提前下载)
但更优雅的方案是:
在构建 Java 应用容器时,直接挂载一个预配置好的
cacerts
文件
。例如,在 Dockerfile 中:
# 从宿主机复制一个已注入 myorg-ca.crt 的 cacerts
COPY ./custom-cacerts $JAVA_HOME/jre/lib/security/cacerts
# 或者,在启动容器时通过 volume 挂载
docker run -v /path/to/custom-cacerts:/usr/lib/jvm/java-11-openjdk-amd64/jre/lib/security/cacerts ...
关键原理:
cacerts文件本身是一个标准的 JKS 文件。只要你知道其密码(changeit),就可以用任何支持 JKS 的工具(如Portecle或KeyStore Explorer)打开它,删除旧条目,导入新证书,然后将修改后的文件作为模板分发。这比在每台机器上运行keytool更可靠、更可审计。
3.4 Web 浏览器信任:Chrome、Firefox 与 Edge 的差异化处理
现代浏览器(Chrome、Edge)已不再完全依赖操作系统证书存储,而是维护自己的信任库(Chrome 使用
certutil
,Firefox 使用
cert8.db
或
cert9.db
)。但它们仍会回退到系统信任库作为兜底。
-
Chrome/Edge (Chromium 内核) :默认信任 Windows 的“受信任的根证书颁发机构”和 macOS 的钥匙串。在 Linux 上,它会读取
/etc/ssl/certs/ca-certificates.crt,而该文件正是由update-ca-trust命令生成的。因此,只要你完成了第 3.1 步,Chrome 在 CentOS 8 上就会自动信任你的 CA。 -
Firefox :它使用独立的 NSS 数据库。需要手动导入:
-
打开 Firefox,进入
about:preferences#privacy - 滚动到底部,点击“查看证书”
- 切换到“证书机构”(Authorities)标签页
-
点击“导入”,选择
ca.crt文件 - 勾选“信任此 CA 用于标识网站”,点击确定
-
打开 Firefox,进入
注意:Firefox 的信任设置是按用户配置的,因此每个需要访问内部 HTTPS 服务的用户,都需单独执行此步骤。若需批量部署,可使用 Firefox 的
autoconfig.js或策略模板(policies.json)。
至此,你的 CA 根证书已在四大信任域中全部落位。任何由该 CA 签发的服务器证书(如
server.crt
),在这些环境中打开时,都将显示为“安全连接”,不再有红色警告。
4. 证书签发实战:为 Nginx、OpenVPN 与 Kubernetes API Server 生成专用证书
CA 的价值,最终体现在它所签发的终端实体证书上。本节将聚焦三个最具代表性的场景:为 Web 服务器(Nginx)签发 TLS 证书、为 VPN 网关(OpenVPN)签发服务端/客户端证书、为 Kubernetes 集群签发 API Server 证书。每个场景的需求、扩展字段(X.509 Extensions)和验证方式都不同,我们将逐一拆解。
4.1 为 Nginx 签发 TLS 证书:解决
NET::ERR_CERT_COMMON_NAME_INVALID
这是最常见的需求。当你为
nginx.myorg.local
配置 HTTPS 时,浏览器报错
NET::ERR_CERT_COMMON_NAME_INVALID
,根本原因往往是证书的
Subject Alternative Name
(SAN)字段缺失或不匹配。现代浏览器(Chrome 58+)已完全废弃
Common Name
(CN)字段,只认 SAN。
使用
easy-rsa
生成 Nginx 证书的正确流程如下:
# 进入 CA 目录
cd /etc/pki/ca
# 生成 Nginx 的私钥和证书签名请求(CSR)
# 注意:-subj 参数中的 CN 是占位符,实际信任靠 SAN
./easyrsa gen-req nginx-server nopass
# 编辑 reqs/nginx-server.req,添加 SAN 扩展(easy-rsa3 默认不包含)
# 但更规范的做法是,在生成 CSR 前,创建一个专用的 req-ext 文件
echo 'subjectAltName = DNS:nginx.myorg.local, DNS:www.myorg.local, IP:192.168.1.100' > /tmp/nginx-ext.txt
# 使用 req-ext 文件签发证书
./easyrsa sign-req server nginx-server --req-ext-file /tmp/nginx-ext.txt
原理解析:
--req-ext-file参数告诉easy-rsa将指定文件中的内容作为 X.509 扩展写入证书。DNS:表示域名,IP:表示 IP 地址。一个证书可以包含多个 SAN,但总长度有限制(通常不超过 256 字符)。192.168.1.100是 Nginx 服务器的内网 IP,这样即使 DNS 解析失败,直连 IP 也能建立安全连接。
签发完成后,证书位于
pki/issued/nginx-server.crt
,私钥位于
pki/private/nginx-server.key
。将其配置到 Nginx:
server {
listen 443 ssl;
server_name nginx.myorg.local;
ssl_certificate /etc/pki/ca/pki/issued/nginx-server.crt;
ssl_certificate_key /etc/pki/ca/pki/private/nginx-server.key;
ssl_trusted_certificate /etc/pki/ca/pki/ca.crt; # 显式指定根证书,用于 OCSP stapling
# 其他 SSL 配置...
}
重启 Nginx,用
curl -v https://nginx.myorg.local
验证,应看到
SSL certificate verify ok
。
4.2 为 OpenVPN 签发证书:区分服务端与客户端的密钥策略
OpenVPN 要求服务端证书必须具备
serverAuth
扩展,客户端证书必须具备
clientAuth
扩展,且服务端密钥必须允许
keyEncipherment
。
easy-rsa
的
server
和
client
类型正是为此设计。
# 生成 OpenVPN 服务端证书(类型为 'server')
./easyrsa gen-req openvpn-server nopass
./easyrsa sign-req server openvpn-server
# 生成 OpenVPN 客户端证书(类型为 'client')
./easyrsa gen-req openvpn-client1 nopass
./easyrsa sign-req client openvpn-client1
sign-req server
和
sign-req client
的区别在于,它们会自动应用不同的 X.509 扩展模板(定义在
x509-types/server
和
x509-types/client
文件中)。
server
模板包含:
extendedKeyUsage = serverAuth
keyUsage = digitalSignature,keyEncipherment
client
模板包含:
extendedKeyUsage = clientAuth
keyUsage = digitalSignature
实操心得:切勿用
sign-req server去签发客户端证书,反之亦然。我曾因混淆两者,导致 OpenVPN 客户端连接时收到VERIFY ERROR: depth=1, error=unable to get local issuer certificate。这是因为服务端证书的keyUsage允许keyEncipherment,而客户端证书不需要,但clientAuth扩展是强制的。
生成的证书和密钥,可直接用于 OpenVPN 配置:
-
服务端:
pki/issued/openvpn-server.crt,pki/private/openvpn-server.key,pki/ca.crt -
客户端:
pki/issued/openvpn-client1.crt,pki/private/openvpn-client1.key,pki/ca.crt
4.3 为 Kubernetes API Server 签发证书:满足 kube-apiserver 的严苛要求
Kubernetes 对 API Server 证书的要求最为复杂,它不仅需要正确的 SAN,还要求
keyUsage
和
extendedKeyUsage
必须精确匹配。
kube-apiserver
启动时会校验证书,任何不匹配都会导致启动失败,错误日志类似
x509: certificate specifies an incompatible key usage
。
easy-rsa
默认的
server
类型并不完全满足 K8s 要求。我们需要创建一个定制的
k8s-api
类型:
# 创建 k8s-api 类型定义文件
cat > x509-types/k8s-api << 'EOF'
basicConstraints = CA:FALSE
nsCertType = server
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth, clientAuth
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
EOF
# 生成 API Server 的 CSR
./easyrsa gen-req k8s-api-server nopass
# 为 CSR 添加 K8s 所需的 SAN(必须包含所有可能的访问入口)
echo 'subjectAltName = DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, DNS:localhost, IP:127.0.0.1, IP:10.96.0.1, IP:192.168.1.100' > /tmp/k8s-api-ext.txt
# 签发证书
./easyrsa sign-req server k8s-api-server --req-ext-file /tmp/k8s-api-ext.txt
其中,
10.96.0.1
是 Kubernetes Service CIDR 的网关 IP(默认值),
192.168.1.100
是你的 Master 节点 IP。这些 SAN 必须与
kube-apiserver
的
--advertise-address
和
--bind-address
参数完全一致。
签发后的证书
pki/issued/k8s-api-server.crt
和密钥
pki/private/k8s-api-server.key
,即可用于
kube-apiserver
的
--tls-cert-file
和
--tls-private-key-file
参数。
5. 安全加固与生命周期管理:吊销、轮换与离线备份的工业级实践
一个 CA 的生命周期远不止于“创建”和“签发”。真正的专业性,体现在它如何应对密钥泄露、证书过期、算法淘汰等现实风险。本节将分享我在金融行业实施 PKI 时总结的四条铁律。
5.1 证书吊销(CRL):不是“删掉证书”,而是发布一份“黑名单”
当某个服务器私钥意外泄露,或员工离职后其客户端证书需立即失效时,你不能简单地删除
issued/xxx.crt
文件。正确的做法是:将该证书的序列号加入证书吊销列表(CRL),并发布给所有依赖方。
easy-rsa
提供了完整的 CRL 管理流程:
# 吊销一个已签发的证书(例如 nginx-server)
./easyrsa revoke nginx-server
# 生成新的 CRL(会覆盖旧的 pki/crl.pem)
./easyrsa gen-crl
# 将新的 CRL 分发给所有客户端(例如,放到一个 HTTP 服务器上)
cp pki/crl.pem /var/www/html/crl.pem
然后,在 Nginx 的 SSL 配置中启用 CRL 检查:
ssl_crl /var/www/html/crl.pem;
ssl_verify_client optional;
注意:
ssl_crl指令要求 Nginx 编译时启用了--with-http_ssl_module,且 OpenSSL 版本 >= 1.0.2。CRL 文件本身是 PEM 格式,但内容是 ASN.1 编码的二进制数据,可通过openssl crl -in crl.pem -text -noout查看其内容。
5.2 CA 根证书轮换:平滑过渡的“双签”策略
根证书有效期长达 10 年,但业务需求可能要求更早轮换(如公司并购、密钥强度升级)。直接停用旧 CA 会导致所有下游证书立即失效。工业界的标准做法是“双签”(Dual Signing):
- 用新 CA(CA2)为旧 CA(CA1)签发一个交叉签名证书(Cross-Certificate)。
- 将 CA2 的根证书和这个交叉签名证书,一同分发给所有客户端。
-
客户端信任链变为:
End-Entity Cert <- CA1 <- CA2,从而同时信任新旧两条路径。 - 给所有终端实体证书足够时间(如 6 个月)迁移到 CA2 签发。
easy-rsa
本身不直接支持交叉签名,但你可以用 OpenSSL 手动完成:
# 用 CA2 签署 CA1 的公钥(ca1.crt)
openssl x509 -in /etc/pki/ca1/pki/ca.crt -signkey /etc/pki/ca2/pki/private/ca.key -CA /etc/pki/ca2/pki/ca.crt -CAcreateserial -out /tmp/ca1-cross-signed-by-ca2.crt
5.3 离线备份:将根私钥从“数字文件”变成“物理介质”
pki/private/ca.key
是整个 PKI 的命脉。它必须被备份,但备份方式决定了安全性等级。
-
低等级备份
:
scp到另一台服务器。风险:网络传输中被截获,备份服务器被入侵。 - 中等级备份 :加密后存入 USB 驱动器。风险:USB 丢失、加密密码遗忘。
- 工业级备份 : 离线、物理、多因素 。
我的标准操作是:
- 在一台从未联网、无硬盘、仅用 Live CD 启动的物理机器上,插入一张空白 DVD-R。
-
将
ca.key用gpg --symmetric --cipher-algo AES256加密,密码为 24 位随机字符串(由密码管理器生成)。 - 将加密后的文件刻录到 DVD,并在光盘上用油性笔手写“CA-ROOT-KEY-2023-09-01”及密码的首 6 位(用于校验)。
- 将 DVD 存入公司保险柜,密码写在另一张纸上,存入不同地点的保险箱。
经验教训:曾有一家公司,将
ca.key加密后存入 NAS,NAS 管理员离职时未移交密钥,导致两年后所有证书到期,无法续签,整个内部系统瘫痪三天。物理离线备份,是唯一能规避“人”的风险的方式。
5.4 自动化审计:用
openssl
命令行做每日健康检查
最后,为 CA 建立一个简单的自动化巡检脚本,每天检查关键指标:
#!/bin/bash
# /usr/local/bin/ca-audit.sh
CA_DIR="/etc/pki/ca"
TODAY=$(date +%s)
WARNING_DAYS=30
# 检查根证书剩余有效期
CA_EXPIRE=$(openssl x509 -in ${CA_DIR}/pki/ca.crt -enddate -noout | awk '{print $4, $5, $7}')
CA_EPOCH=$(date -d "$CA_EXPIRE" +%s 2>/dev/null)
if [ -z "$CA_EPOCH" ]; then
echo "ERROR: Failed to parse ca.crt expiration date"
exit 1
fi
DAYS_LEFT=$(( (CA_EPOCH - TODAY) / 86400 ))
if [ $DAYS_LEFT -lt $WARNING_DAYS ]; then
echo "ALERT: CA root certificate expires in $DAYS_LEFT days!"
fi
# 检查是否有证书即将过期(未来 30 天内)
for cert in ${CA_DIR}/pki/issued/*.crt; do
if [ -f "$cert" ]; then
EXPIRE=$(openssl x509 -in "$cert" -enddate -noout | awk '{print $4, $5, $7}')
EXPIRE_EPOCH=$(date -d "$EXPIRE" +%s 2>/dev/null)
DAYS_LEFT=$(( (EXPIRE_EPOCH - TODAY) / 86400 ))
if [ $DAYS_LEFT -lt $WARNING_DAYS ] && [ $DAYS_LEFT -gt 0 ]; then
echo "ALERT: Certificate $cert expires in $DAYS_LEFT days!"
fi
fi
done
将此脚本加入
crontab
,每天凌晨 2 点运行,输出发送到运维邮箱。这比任何 GUI 监控面板都更直接、更可靠。
至此,你已掌握在 CentOS 8 上构建一个生产级 Certificate Authority 的全部核心技能:从环境筑基、信任落地、多场景签发,到安全加固与自动化运维。这不再是一个“能跑就行”的实验,而是一套可审计、可扩展、可传承的基础设施能力。

3万+

被折叠的 条评论
为什么被折叠?



