记一次 GitLab Let‘s Encrypt 证书申请失败的排查与修复

文章目录

环境: GitLab CE 16.2.9 · Docker Compose · Nginx TCP 透传


背景

在执行 gitlab-ctl reconfigure 时,GitLab 内置的 Let's Encrypt 自动申请证书功能报错,导致 reconfigure 失败,GitLab 无法正常启动 HTTPS。


错误现象

复制代码
There was an error running gitlab-ctl reconfigure:
letsencrypt_certificate[gitlab.example.com] (letsencrypt::http_authorization line 6) had an error:
KeyError: acme_certificate[staging] (letsencrypt::http_authorization line 43) had an error:
KeyError: key not found: "token"

完整堆栈指向:

复制代码
/opt/gitlab/embedded/lib/ruby/gems/3.0.0/gems/acme-client-2.0.14/
  lib/acme/client/resources/authorization.rb:59 `initialize_challenge'
→ KeyError: key not found: "token"

架构说明

复制代码
外网用户
  │
  ├─ :80/:443  →  Nginx (stream 模块 TCP 透传)
  │               └─→  10.41.0.7:4080 / 4443
  │                     └─→  GitLab Docker 容器
  └─ :22       →  Nginx (stream 模块 TCP 透传)
                  └─→  10.41.0.7:4022

Nginx 使用四层 TCP 透传(stream 模块),80/443/22 端口直接转发到内网 GitLab 容器,不做任何 HTTP 层处理


排查过程

Step 1:确认 HTTP-01 Challenge 链路是否畅通

Let's Encrypt HTTP-01 验证需要从公网访问:

复制代码
http://<your-domain>/.well-known/acme-challenge/<token>

在容器内手动放置一个测试文件:

bash 复制代码
docker compose exec gitlab bash -c \
  "mkdir -p /var/opt/gitlab/nginx/www/.well-known/acme-challenge && \
   echo 'challenge-test-ok' > /var/opt/gitlab/nginx/www/.well-known/acme-challenge/testtoken"

从外网验证:

bash 复制代码
curl http://gitlab.example.com/.well-known/acme-challenge/testtoken
# 返回:challenge-test-ok  ✅

链路完全正常,排除网络/防火墙问题。

Step 2:阅读堆栈,定位根本原因

查看出错的 gem 源码:

bash 复制代码
docker compose exec gitlab sed -n '50,65p' \
  /opt/gitlab/embedded/lib/ruby/gems/3.0.0/gems/acme-client-2.0.14/\
lib/acme/client/resources/authorization.rb

关键代码:

ruby 复制代码
def initialize_challenge(attributes)
  arguments = {
    type:   attributes.fetch('type'),
    status: attributes.fetch('status'),
    url:    attributes.fetch('url'),
    token:  attributes.fetch('token'),   # ← 问题所在
    error:  attributes['error']
  }
  Acme::Client::Resources::Challenges.new(@client, **arguments)
end

根本原因:

Let's Encrypt 在返回 authorization 时,会包含多种 challenge 类型(http-01dns-01 等)。其中 dns-01 类型的 challenge 对象不含 token 字段 ,但 acme-client 2.0.14 使用了 Hash#fetch(无默认值),直接抛出 KeyError

这是 acme-client gem 的一个已知 bug,在更高版本中已修复,但 GitLab 16.2.9 打包的版本过旧,未包含该修复。

Step 3:尝试配置规避(无效)

尝试在 gitlab.rb 中显式设置:

ruby 复制代码
letsencrypt['staging'] = false
letsencrypt['contact_emails'] = ['admin@example.com']

结论: 无效。问题在 gem 底层代码,配置层无法绕过。


修复方案

直接 patch 容器内的 gem 源码,为 tokenfetch 调用添加默认值 nil

bash 复制代码
# 打补丁
docker compose exec gitlab sed -i \
  "s/token: attributes.fetch('token'),/token: attributes.fetch('token', nil),/" \
  /opt/gitlab/embedded/lib/ruby/gems/3.0.0/gems/acme-client-2.0.14/\
lib/acme/client/resources/authorization.rb

# 验证修改生效
docker compose exec gitlab grep 'token' \
  /opt/gitlab/embedded/lib/ruby/gems/3.0.0/gems/acme-client-2.0.14/\
lib/acme/client/resources/authorization.rb
# 期望输出:token: attributes.fetch('token', nil),

# 重新执行 reconfigure
docker compose exec gitlab gitlab-ctl reconfigure

执行后证书申请成功,问题解决。


原理说明

fetch('token', nil) 让没有 token 字段的 challenge 类型(如 dns-01)返回 nil 而不是抛出异常。后续 acme cookbook 在筛选 http-01 challenge 时会忽略这些类型,整个流程正常推进。


注意事项

事项 说明
Patch 持久化 此 patch 直接修改容器内文件,重新拉取镜像后会丢失。但证书一旦申请成功,后续自动续签(renew)不会再触发这个 bug,因为证书文件已经存在于 volume 中
升级建议 GitLab 16.2.9 已停止维护,存在多个高危安全漏洞。建议按官方升级路径逐步升级至受支持版本
受影响版本 使用 acme-client 2.0.14 的 GitLab 版本均可能受此影响

参考

相关推荐
郝亚军1 天前
gitlab常用命令
gitlab
xixingzhe24 天前
Java 使用 GitLab4J 实现 WebHook
gitlab
童槿顏丶6 天前
GitLab部署到Linux
linux·运维·gitlab
InfinitePlus6 天前
Gitlab docker版本安装
docker·容器·gitlab
进击切图仔6 天前
GitLab 新用户创建与项目授权操作指南
gitlab
技术小结-李爽7 天前
【工具】git与gitee和gitlab和github等等有什么区别?
git·gitee·gitlab
xiaoxiangsiyan8 天前
GitLab CI/CD 自托管(EE 企业版)+ Kubernetes Runner 集群 + ArgoCD(GitOps 部署)
运维·网络·ci/cd·容器·kubernetes·gitlab·argocd
csdn2015_15 天前
vscode从gitlab拉项目到本地
vscode·gitlab
AOwhisky18 天前
AI审AI:GitLab上线AI代码审查,开发者可以松一口气了吗?
人工智能·gitlab
杨了个杨898219 天前
GitLab的简介及安装
gitlab