CentOS 8 搭建生产级本地CA:easy-rsa实战与跨平台信任落地

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 数据库。需要手动导入:

    1. 打开 Firefox,进入 about:preferences#privacy
    2. 滚动到底部,点击“查看证书”
    3. 切换到“证书机构”(Authorities)标签页
    4. 点击“导入”,选择 ca.crt 文件
    5. 勾选“信任此 CA 用于标识网站”,点击确定

注意: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):

  1. 用新 CA(CA2)为旧 CA(CA1)签发一个交叉签名证书(Cross-Certificate)。
  2. 将 CA2 的根证书和这个交叉签名证书,一同分发给所有客户端。
  3. 客户端信任链变为: End-Entity Cert <- CA1 <- CA2 ,从而同时信任新旧两条路径。
  4. 给所有终端实体证书足够时间(如 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 丢失、加密密码遗忘。
  • 工业级备份 离线、物理、多因素

我的标准操作是:

  1. 在一台从未联网、无硬盘、仅用 Live CD 启动的物理机器上,插入一张空白 DVD-R。
  2. ca.key gpg --symmetric --cipher-algo AES256 加密,密码为 24 位随机字符串(由密码管理器生成)。
  3. 将加密后的文件刻录到 DVD,并在光盘上用油性笔手写“CA-ROOT-KEY-2023-09-01”及密码的首 6 位(用于校验)。
  4. 将 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 的全部核心技能:从环境筑基、信任落地、多场景签发,到安全加固与自动化运维。这不再是一个“能跑就行”的实验,而是一套可审计、可扩展、可传承的基础设施能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值