ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

内网HTTPS部署实战:自建CA签发SSL证书全流程指南

内网HTTPS部署实战:自建CA签发SSL证书全流程指南 1. 内网环境部署HTTPS到底难在哪里1.1 一个让运维和开发都头疼的现实场景你有没有遇到过这种情况公司内部跑着一个GitLab、一套监控面板或者一个内部知识库系统大家平时用http://192.168.1.100这种地址访问用着也没什么大问题。但突然有一天对接的小程序要求请求地址必须是HTTPS或者浏览器更新之后每次打开都弹您的连接不是私密连接再或者你在网页里调用摄像头、麦克风这类能力时被直接拒绝——原因就一条你的页面不是安全上下文。不止如此如果业务系统需要做OAuth2登录、对接企业微信、对接第三方平台对方基本都会要求回调地址是HTTPS。甚至一些浏览器的新特性比如PWA、Service Worker、Web Push都强制要求HTTPS才能用。这时候内网没有HTTPS就真的寸步难行。但内网环境和公网不一样。公网上申请SSL证书特别简单去证书服务商买个域名证书做DNS验证或者文件验证五分钟签下来。内网呢首先你的服务器通常没有公网域名只有内网IP比如192.168开头或者10开头其次就算你有内部域名比如gitlab.corp.local这种CA机构也没法验证这个域名的所有权因为这是一个私有域名只有在你们内网DNS里才能解析。所以申请这两个字在内网环境里需要换一条完全不同的思路。1.2 证书本质是信任链不是网线密码想搞清楚内网怎么做HTTPS得先弄明白SSL证书到底在干什么。很多人以为证书是给数据传输加个密其实更核心的职责是证明身份。HTTPS通信时服务器会把证书发给浏览器浏览器需要验证这个证书是不是可信的。验证方式就是看证书是谁签发的——如果签发者是一台浏览器本身信任的CA机构比如DigiCert、Lets Encrypt、阿里云这些那证书就是受信任的如果签发者是一台不知名的服务器甚至是你自己浏览器就会立刻拉响警报。这就是根证书信任链。公网证书能被全世界浏览器信任是因为这些CA机构的根证书早早就被预装进了操作系统和浏览器里。而内网自建CA签出来的证书之所以报警就是因为浏览器的信任列表里没有你的CA根证书。所以内网HTTPS的核心工作就变成了两件事第一用一套可靠的密钥体系把证书签出来第二把签发证书的CA根证书想办法装到所有需要访问的客户端设备里。想明白这两点剩下的实操就都顺理成章了。2. 方案选型三条技术路线的利弊权衡2.1 自建CA签发证书最灵活也最推荐所谓自建CA就是你自己当一次证书颁发机构自己生成根证书然后用根证书去签发服务器证书。这个方案我在实际工作中用得最多尤其适合以下几种情况系统需要长期稳定运行、访问的客户端可控、证书数量可能比较多比如几十个内部域名和IP、并且不想依赖外部CA机构。优点是自由度极高。你可以给内网IP签证书给*.corp.local签通配符证书给十几个域名签多域名证书有效期也可以自己定签发时间以秒算不像公网证书还要等验证。缺点是根证书需要自己分发并安装到每一台客户端的受信任根证书库里而且如果根证书私钥泄露整个信任体系都要重建。所以在自建CA时根证书私钥的安全级别一定不能低一般建议离线保存甚至放在物理隔离的机器上。2.2 用公网免费证书签发内网域名可行但限制多很多人一开始会想阿里云、腾讯云不都有免费SSL证书嘛直接买一个给内网用不行吗这里有一个关键限制这些免费证书绝大多数只能签公网可解析的域名而且要验证域名所有权。你拿一个192.168.1.10的IP去申请或者拿一个只在公司内网DNS里存在的oa.corp.com去申请验证无论如何都过不了。唯一的变通办法是你有一个真实的公网域名并且能在公网的DNS解析上加一条记录比如把gitlab.company.com解析到一个内网IP这在有些公司会做DNS split-horizon然后用这种域名去申请证书。申请成功后证书可以通过下载部署到内网服务器上。因为域名是真实的、验证能通过证书自然有效。但这么做的问题也很明显DNS记录需要公网可管理证书有效期一般只有一年到期要记得手动续期如果你的网络环境要求服务器完全不出公网连域名验证的请求都出不去这条路基本走不通。2.3 企业级CA服务和开源工具适合更大规模的场景如果你的公司已经上了AD域环境是Windows Server那么可以考虑用Active Directory Certificate Services搭建企业CA然后通过组策略自动把根证书下发给所有域内机器。这个方案在大型企业里很成熟客户端基本免配置运维很省心但前提是你已经有一套Windows域环境并且愿意维护CA的策略、证书模板、续期规则学习成本和管理成本都不小。另外还有一些开源项目比如OpenCA、EJBCA、Smallstep CA功能更偏向企业级支持ACME协议、自动化续期适合对自动化要求很高的团队。内网规模不大、又不想引入太重架构时直接OpenSSL自建CA反而是性价比最高的选择。小步快跑先把HTTPS跑起来后续再迁到企业级CA也不迟。3. 环境准备与OpenSSL基础认知3.1 检查环境并安装OpenSSL无论走哪条路线OpenSSL都是最基础的工具。大多数Linux发行版都预装了但版本最好别太老。先检查一下openssl version我建议使用OpenSSL 1.1.1以上的版本因为支持TLS 1.3而且命令行为更规范。如果版本太旧或者没有安装在CentOS/RHEL上执行sudo yum install openssl -yDebian/Ubuntu执行sudo apt install openssl -yWindows环境可以用Git自带的OpenSSL也可以下载Win64版OpenSSL原理一样。在实际操作之前建议先确定好要签的域名/IP列表写在一个文本文件里后面生成证书的时候会用到。3.2 证书相关的几个概念必须先理清内网自建证书最容易犯的一个错误是不理解SAN字段。SANSubject Alternative Name主题备用名称是证书里用来声明这个证书适用于哪些域名或IP的扩展字段。现在主流浏览器已经不认CN字段里的域名了只看SAN。所以哪怕你在证书的CN里写了192.168.1.10只要SAN里没有它浏览器照样报错。这是一个几乎每个人都会踩的坑后面配置的时候一定注意。还有一个概念是证书链。服务器证书leaf certificate由CA根证书签发有的场景下还会有中间CA证书。浏览器在验证时会拿着服务器证书去追逐签发者一直找到自己信任的根证书为止。自建CA如果只有一层根证书直接签发服务器证书那证书链就是简短的服务器配置时只需要把服务器证书文件和私钥放上去就行。但如果你在根证书和服务器证书之间再加了中间CA那部署时就需要把服务器证书和中间CA证书合并成一个.pem文件。我第一篇自建CA时没有放中间CA一切正常后来为了安全性测试加了中间CA结果在Nginx上忘了拼接证书链浏览器一直报告证书链不完整查了半天。这个细节后面会再提醒。4. 自建CA并签发内网证书的完整实操4.1 创建CA根证书一次性做对省得返工先准备好一个工作目录推荐建在/opt/ssl或者~/ssl下面证书文件都放这里便于统一管理mkdir -p /opt/ssl cd /opt/ssl生成CA私钥设置权限openssl genrsa -out ca.key 2048 chmod 600 ca.key这里我选用了2048位RSA。对于内网环境来说2048位完全够用兼顾安全性和性能。如果你想更保险可以用4096但服务器握手时的CPU开销会略高一些TLS 1.3场景下其实差别不大。生成私钥之后接着生成自签的根证书openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt \ -subj /CCN/STBeijing/LBeijing/OMyCorp/OUIT Security/CNMyCorp Internal CA在这个命令里-days 3650表示根证书有效期10年。-subj里/C是国家/ST是省份/L是城市/O是组织名/OU是部门/CN是通用名。这里CNMyCorp Internal CA只是这个CA自己的名字不是将来网站的域名别搞混。生成后可以用下面的命令看一眼证书的基本信息确认有效期和签名算法没问题openssl x509 -in ca.crt -text -noout | head -204.2 为内网域名或IP生成服务端证书根证书建好了下一步就是给具体的站点签发证书。这一步要严格按顺序来先生成私钥再生成证书签名请求CSR最后用CA根证书对这个CSR签名。假设我们要签的站点是gitlab.corp.local对应的IP是192.168.1.100那就先创建一个OpenSSL扩展配置文件把域名和IP都放到SAN里cat san.cnf EOF [req] distinguished_name req_distinguished_name req_extensions v3_req prompt no [req_distinguished_name] C CN ST Beijing L Beijing O MyCorp OU IT Department CN gitlab.corp.local [v3_req] keyUsage keyEncipherment, dataEncipherment extendedKeyUsage serverAuth subjectAltName alt_names [alt_names] DNS.1 gitlab.corp.local DNS.2 gitlab IP.1 192.168.1.100 IP.2 127.0.0.1 EOF这个文件的价值就在于[alt_names]部分。DNS.1是内部域名DNS.2可以是短主机名IP.1是服务器内网IP。如果你还有更多域名或者IP继续往下加DNS.3、IP.3就行。这样签出来的证书就是一张标准的多域名证书也叫SAN证书浏览器不会因为域名或IP不完全匹配而报警。然后生成服务端私钥和CSRopenssl genrsa -out server.key 2048 openssl req -new -key server.key -out server.csr -config san.cnf chmod 600 server.key最后用CA根证书签发openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ -out server.crt -days 825 -sha256 -extfile san.cnf -extensions v3_req这里有两个关键参数。-CAcreateserial会生成一个ca.srl序列号文件用于记录证书序列号防止不同证书使用相同序列号后续签发更多证书时要保证这个文件一直在。-days 825是我有意设置的有效期约2年多一点这个值不是随手写的——很多公司内部的安全扫描会关注有效期过长的证书超过2年可能会被判为不合规而1年又太短签发和维护负担大825天既符合业界惯例也适合内网。签完之后用下面的命令检查证书内容openssl x509 -in server.crt -text -noout | grep -A 3 Subject Alternative Name确认DNS和IP都列在SAN里这一步没问题证书基本就成功了一大半。4.3 密钥安全和文件权限的细节证书里面私钥是最重要的资产。内网环境虽然没有公网那么大的攻击面但该做的安全措施一样不能少。我习惯的做法是CA根证书的私钥ca.key单独放在一个离线目录或者放在一台只用于签名、不对外提供服务的机器上。普通服务器上只放server.key和server.crt。同时server.key的文件权限设置为600属主是启动Nginx的用户。有些人会问能不能把CA私钥和服务端私钥放到同一个文件夹可以但前提是你能保证这台机器不会被攻破。如果只是临时测试放在一起问题不大如果是正式使用还是建议分开。内网环境里很多安全问题都是因为觉得内网安全而放松了警惕实际上内网才是最容易被横向渗透的。还有一个小细节签发完证书后server.csr这个文件就没有用了可以直接删掉但如果是给外部CA用的CSR则必须好好保存因为后面续期可能还要用。5. Nginx部署HTTPS与参数优化5.1 部署Nginx并打开443端口证书签好只是一个开始真正让用户拿到HTTPS服务的是Web服务器部署。这里我拿Nginx举例因为它最常用、配置最直观也是我实际部署最多的Web服务。假设你的Nginx已经装在服务器上了如果还没有CentOS执行sudo yum install nginx -yDebian/Ubuntu执行sudo apt install nginx -y安装完成之后先确认443端口没有被防火墙挡住sudo firewall-cmd --permanent --add-servicehttps sudo firewall-cmd --reload如果用的是ufw执行sudo ufw allow https。端口不开的话配置再对也连不上。建议在开始配置之前先用ss -lntp | grep 443确认一下端口状态避免后面反复排查。5.2 编写HTTPS站点配置并做好HTTP跳转在/etc/nginx/conf.d/下新建一个配置文件比如ssl.conf。其中核心配置如下server { listen 80; server_name 192.168.1.100 gitlab.corp.local; return 301 https://$host$request_uri; } server { listen 443 ssl; http2 on; server_name 192.168.1.100 gitlab.corp.local; ssl_certificate /opt/ssl/server.crt; ssl_certificate_key /opt/ssl/server.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }第一段server块负责把HTTP请求301跳到HTTPS。注意return 301 https://$host$request_uri;会保留用户访问的域名和路径这样用户输http://192.168.1.100会自动跳到https://192.168.1.100体验上是无缝的。第二段server块才是真正的HTTPS服务。ssl_certificate指向刚才生成的server.crtssl_certificate_key指向server.key。http2 on开启HTTP/2在同一个连接上并行传输资源内网访问静态页面、大文件时体感提升还是比较明显的。代理后端地址我写的是http://127.0.0.1:8080实际部署时改成你真正的后端服务端口可以是Tomcat、Spring Boot、Gunicorn也可以是另一个Nginx。注意proxy_set_header X-Forwarded-Proto $scheme;这行很重要后端程序需要知道原始请求是HTTP还是HTTPS很多应用如果拿不到这个头会构造出错误的回调地址或重定向地址尤其在你对接SSO登录时特别容易出现。配置完后测试语法并重载sudo nginx -t sudo systemctl reload nginx在重载之前务必用nginx -t检查配置如果证书路径写错、括号没配对、分号漏了这里直接就能发现比重启到一半再报错要安全得多。5.3 用浏览器和命令行验证证书链与HTTPS效果配置好了先用浏览器访问https://192.168.1.100验证一下。如果你还没有安装CA根证书这里一定会显示证书不受信任的报错。注意这个报错是正常的因为我们还没做后面第6步的信任配置。要验证证书本身是否是正常的可以点击高级和继续前往能进入页面说明证书链和配置都是对的差的就是客户端信任这一环。命令行上用curl -v能看到完整的握手过程curl -vk https://192.168.1.100加-k是为了跳过证书信任校验否则curl会直接报错退出。但输出里如果能看到SSL certificate verify ok这样的日志前提是加--cacert ca.crt或系统信任CA后那就说明证书验证链路完全通了。输出里还能看到协议版本、加密套件协商结果比如* TLSv1.3 (OUT), TLS handshake, Client hello (1):说明TLS 1.3已生效。如果出现unsafe legacy renegotiation disabled之类的警告通常是客户端和服务器支持的协议版本差距太大看看服务端或客户端哪一方的OpenSSL版本太老。验证的同时我也习惯顺手做一次证书有效期检查。内网证书经常被人忘记续期问题往往爆发在最尴尬的时间点。用一行命令就能看到证书过期时间openssl x509 -in /opt/ssl/server.crt -noout -enddate也可以直接在Nginx运行时用openssl s_client -connect 192.168.1.100:443查询在线证书信息。6. 客户端信任CA根证书彻底告别浏览器报警6.1 Windows端导入根证书的方法证书部署到服务器上只是完成了前半程。浏览器访问时报NET::ERR_CERT_AUTHORITY_INVALID大多数原因是客户端不信任你的CA根证书。Windows系统导入根证书的方式并不复杂但有一个关键选择是导入到当前用户还是本地计算机。如果是给公司几百台电脑统一下发更规范的做法是通过组策略。先在任意一台Windows机器上执行certlm.msc注意是本地计算机证书管理找到受信任的根证书颁发机构下的证书目录右键 - 所有任务 - 导入选中ca.crt一路下一步选择将所有证书放入下列存储并确认存储位置是受信任的根证书颁发机构完成导入。然后通过AD域组策略把根证书下发到所有域内机器路径是计算机配置 - 策略 - Windows设置 - 安全设置 - 公钥策略 - 受信任的根证书颁发机构导入即可。这里有个容易翻车的点如果你在用户态的certmgr.msc里导入了根证书浏览器却依然报警不要慌换certlm.msc试试。很多浏览器、尤其是Chrome读取的是本地计算机的证书库不是当前用户的库。第一次做证书分发时我就是导到了用户库Chrome死活不认后来改成本地计算机库才好。导入完成后重启浏览器再访问https://192.168.1.100地址栏的小锁图标就出来了这说明证书信任链已经通了。如果还有其他开发机、测试机、同事的电脑也都按同样的方式导入。6.2 Linux和macOS下的信任配置Linux端的操作差别比较大。Ubuntu/Debian系把CA证书放在/usr/local/share/ca-certificates/目录下后缀名必须是.crt然后执行sudo cp ca.crt /usr/local/share/ca-certificates/mycorp-ca.crt sudo update-ca-certificatesCentOS/RHEL系则是放到/etc/pki/ca-trust/source/anchors/然后执行sudo cp ca.crt /etc/pki/ca-trust/source/anchors/mycorp-ca.crt sudo update-ca-trustmacOS系统比较简单双击ca.crt打开钥匙串访问把它导入到系统钥匙串然后双击该证书在信任一栏里选择始终信任。macOS有时会弹出问你是否确认信任此证书选始终信任即可。Java应用和Node.js环境的信任库又是另一套逻辑。Java有自己的cacerts文件需要通过keytool导入这个在内网环境对接Java微服务时几乎必做sudo keytool -import -trustcacerts -alias mycorp-ca -file ca.crt -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit而Node.js默认使用系统信任库导入系统CA后就生效但如果你的应用通过NODE_TLS_REJECT_UNAUTHORIZED0跳过校验那属于临时调试正式环境不能用。我见过不少团队在联调时图省事加了这个环境变量上线时忘删结果生产环境的HTTPS证书校验形同虚设一旦有中间人攻击数据就直接暴露了非常危险。6.3 移动端和IoT设备怎么处理手机和平板访问内网HTTPS应用是另一个高频场景。iOS设备可以通过Safari直接下载CA证书文件然后在设置 - 通用 - 关于本机 - 证书信任设置中开启对自建CA的完全信任。这里有个非常容易被忽略的步骤即使你在描述文件中安装了根证书也必须再单独开启完全信任开关否则系统只信任证书链里的下级证书但不会把它作为根来信任访问HTTPS站点依旧报错。Android设备相对简单一些在设置 - 安全 - 加密与凭据 - 安装CA证书里选择ca.crt安装即可。但要注意不同ROM的路径和名称差异很大某些国产系统的入口藏在更多安全设置下面。Android 7.0之后普通App默认不信任用户安装的CA证书只有系统内置的CA才会被自动信任。如果要在调试包里抓HTTPS请求需要把App的网络安全配置文件network_security_config.xml里显式信任用户证书或者用adb把CA证书安装到系统分区需要root这个操作要小心。对于打印机、扫描仪、IoT设备这类没法安装根证书的终端通常就是它们访问你或者你访问它们两种模式。如果只是设备主动回连服务器那服务器端证书不需要它们验证反过来如果设备上有内置浏览器需要访问HTTPS服务就只能看设备固件是否支持导入自定义CA了绝大多数低端设备不支持这时建议给这些设备单独保留HTTP访问或者用内网DNS解析的方式做一次程序级别的证书校验。7. 常见问题与排查技巧实录7.1 高频报错对照表与解决办法自建CA这套流程走下来每个人都会在不同阶段遇到问题。我把这些年踩过的坑和学员、同事问得最多的问题整理成了一张对照表现象可能原因解决办法浏览器报 NET::ERR_CERT_AUTHORITY_INVALID客户端未安装CA根证书安装ca.crt到系统受信任根证书库浏览器报 NET::ERR_CERT_COMMON_NAME_INVALID证书SAN里没有当前访问的域名或IP重新签发证书把域名或IP写入SAN扩展浏览器报证书已过期证书有效期到期或服务器时间不准查看证书enddate重新签发校准服务器时间curl请求报证书链不完整部署时未拼接中间CA证书将服务器证书与中间CA合并成一个chain.pemHTTPS能开但页面加载极慢TLS握手耗时长可能是DH参数太小或客户端协商慢生成2048位以上dhparam.pem检查证书链性能Java程序调接口报PKIX path build failedJava cacerts里没有导入CA根证书用keytool导入ca.crt并重启应用某些老浏览器不支持TLS 1.3客户端OpenSSL版本太旧服务端至少保留TLS1.2让新旧客户端都能协商Nginx重载提示cannot load certificate证书文件路径或权限不对检查server.crt/server.key路径、属主和权限这个表里最阴险的问题是服务器时间不准。证书的有效期是基于系统时间的如果服务器时间比真实时间快了或者慢了一天就会出现证书还没生效或者证书已经过期的诡异报错而证书文件本身根本没问题。排查这类问题第一个动作就是看时间date sudo ntpdate ntp.aliyun.com内网机器建议配置好NTP定时同步不止是为了证书校验日志分析、分布式系统协调都会用到。7.2 证书续期和自动化别再手动签发了自建CA虽然签发方便但有效期的维护是个长期活。服务器证书825天到期后需要重新生成CSR并用CA签名再替换到Nginx里重载。这个过程手动做一两次没问题但一旦你有几十台服务器、几十张证书就必须自动化了。最轻量的自动化方案是用Shell脚本配合cron。脚本内容也不复杂核心逻辑是先检查已有证书的过期时间判断剩余天数是否低于阈值低于阈值就重新生成私钥和CSR、用CA签名、将新证书复制到生产目录、重载Nginx。我见过很多团队的实现方法各不相同但核心都围绕这几个步骤。如果你的技术栈已经上了容器化和服务编排可以进一步接入ACME协议自动签发/续期。step-ca是Smallstep出的轻量CA服务支持ACME能实现完全自动化的内网证书生命周期管理。这个方案比纯脚本要稳妥得多但需要额外维护一个CA服务。对于规模小、阶段性改造的团队我觉得先从Shell脚本自动化开始就够了不必一上来就引入重型工具。另外要提醒一句根证书有效期通常是10年但绝不是永远。你必须在根证书到期之前提前规划替换方案否则你签发的所有服务器证书都会一夜之间全部失效。这件事极易被忽略因为根证书太长时间不用动一旦到期就是整个内网HTTPS体系的灾难。我一般会在根证书到期前一年就开始准备新根逐步推动客户端替换信任。最好顺便建立一个证书到期监控表纳入监控系统到期前30天、7天分别告警。7.3 我踩过几次坑之后的一些体会第一次在真实环境做内网HTTPS改造时我犯过一个特别低级的错误签证书的时候忘了加SAN然后拿着证书部署到Nginx上浏览器一直报证书不匹配。当时以为证书生成有问题反反复复试了很久最后仔细一查才发现SAN里面根本没写IP。后来我养成了一个习惯每签一张证书都要用openssl x509 -text仔细核对SAN还要用openssl verify做一遍链式校验openssl verify -CAfile ca.crt server.crt这一步能发现很多证书本身的问题比如签名算法不对、证书链断裂、密钥长度不符比到浏览器里再排查高效得多。另一个经验是关于多域名证书的。如果内网有多个系统不需要每个系统都签一张新证书一张证书里把DNS和IP全部列进去部署的时候所有站点共用这一张就行。虽然安全性上少了一些隔离但对内网环境运维来说维护成本低很多。如果对隔离有要求也可以按业务域分别建多个CA这个就看团队自己的取舍了。最后再说一个很多人忽略的点内网HTTPS改造不是只改一个Nginx就完了。你的监控脚本、CI/CD系统、日志采集器、数据库连接池这些组件凡是需要访问这个HTTPS服务的都要配置好对应的CA信任。也就是说证书的信任分发是全局性的不只是给人类用的浏览器装上就行。我在实际落地时会把已信任该CA的环境清单单独列成一页wiki每加一个系统就更新一次这样后续排查问题时能快速定位是哪一环没有加入信任链。内网做HTTPS本质上没有想象中那么难核心就是自建CA 正确签发 客户端信任 自动化续期。只要把这条链路打通后续所有业务系统都能在此基础上快速获得HTTPS能力不用再依赖公网CA也更灵活自主。希望这份实操笔记能帮你少走一些弯路。
返回列表