在 GitLab CI/CD 中使用 SSH 密钥
原文:Using SSH keys with GitLab CI/CD。作者:GitLab 文档贡献者(原页无个人署名)。适用于 Free、Premium、Ultimate,以及 GitLab.com、Self-Managed 和 Dedicated。本文依据 2026 年 10 月 9 日读取的官方文档翻译、整理。GitLab 文档会滚动更新;界面和行为可能随 GitLab 与 Runner 版本变化,实施前请核对所用版本。
GitLab 没有在构建环境(即 GitLab Runner 执行作业的环境)中内置管理 SSH 密钥的功能。需要检出内部子模块、通过 Bundler 等包管理器下载私有包、将应用部署到自己的服务器或 Heroku、从构建环境执行远程 SSH 命令,或者通过 rsync 同步文件时,都可以使用 SSH 密钥。
支持最广泛的做法是修改 .gitlab-ci.yml,把 SSH 密钥注入构建环境。这种方式适用于 Docker、Shell 等各类 执行器。私钥应安全保存,自动化作业应使用独立身份,避免复用个人 SSH 密钥,并定期轮换。

创建并使用 SSH 密钥
- 生成一对新的 SSH 密钥,参见 GitLab SSH 密钥指南。
- 把私钥保存为名叫
SSH_PRIVATE_KEY的文件类型 CI/CD 变量。 - 在作业中启动
ssh-agent,然后把私钥载入 agent。 - 把公钥配置到要访问的服务器,通常放在目标用户的
~/.ssh/authorized_keys中。如果访问私有 GitLab 仓库,还需要把公钥添加为 部署密钥。
载入密钥的命令本身不需要把私钥内容打印到日志中。但开启调试日志、添加输出命令,或者运行不可信作业,都可能泄露它。也要检查流水线的可见性。
把私钥添加为文件类型变量
在项目的 CI/CD 变量设置中创建文件类型变量:
- 把 Visibility 设为 Visible。SSH 私钥含空白字符和换行,无法满足 Masked 或 Masked and hidden 的值格式要求。不要对该变量代表的文件执行
cat或tee;一旦私钥进入作业日志,它不会被掩码保护。 - 在 Key 中填入变量名,例如
SSH_PRIVATE_KEY。 - 在 Value 中粘贴私钥全文。值必须以一个 LF 换行符结束;保存前,在最后一行末尾按 Enter 或 Return。
编校说明:文件类型变量在作业中展开为临时文件路径,而不是私钥文本。Visible 表示有相应项目、组或实例设置访问权限的用户可以查看该值;它不会自动把变量公开给所有人,也不构成日志防泄漏保证。GitLab 当前变量文档注明,18.3 起默认可见性改为 Masked;但私钥含空白和换行,仍应为此变量显式选择 Visible。作业内命令仍可读取文件变量,恶意或未审查的流水线改动也可能将其外传。应限制变量的作用域、受保护分支或标签、Runner 使用范围,以及可修改流水线配置和访问变量设置的人员;无口令密钥尤其要采用最小权限并定期轮换。
使用普通变量的另一种方式
如果不使用文件类型变量,可以参考 SSH 示例项目中的普通 CI/CD 变量方法。通常优先使用文件类型变量,因为它能保留多行格式,降低换行和转义造成的错误。
Docker 执行器中的 SSH 密钥
CI/CD 作业在 Docker 容器中运行时,构建环境相互隔离。要将代码部署到私有服务器,可以这样配置:
- 创建新的 SSH 密钥对。原文示例不设置私钥口令,否则
before_script会要求输入口令。 - 将私钥保存为文件类型变量
SSH_PRIVATE_KEY。 - 在
.gitlab-ci.yml中添加before_script。下面的示例假定使用 Debian 系镜像,并且容器用户有安装软件包的权限。
before_script:
# 未安装 ssh-agent 时安装 OpenSSH 客户端。
# RPM 系镜像应改用对应的包管理器和包名。
- 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'
# 在构建环境中启动 ssh-agent。
- eval $(ssh-agent -s)
# 文件权限过宽时,ssh-add 会拒绝载入。
# 文件类型变量的值是私钥文件路径。
- chmod 400 "$SSH_PRIVATE_KEY"
- ssh-add "$SSH_PRIVATE_KEY"
# 创建 SSH 目录并设置权限。
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
# 如果需要在作业中执行 Git 提交,可选择设置身份。
# - git config --global user.email "user@example.com"
# - git config --global user.name "User name"
before_script 可以配置为默认值,也可以只放在具体作业中。软件安装步骤依赖可用的软件源和网络;若基础镜像已提供 SSH 客户端,可以省去安装步骤。示例中的 eval 执行的是本机 ssh-agent 生成的环境设置语句,应确保调用的是可信镜像中的可信程序。
- 验证私有服务器的 SSH 主机公钥,方法见下节。
- 把第 1 步生成的公钥添加到构建环境需要访问的服务。访问私有 GitLab 仓库时,将其设置为部署密钥。
完成客户端身份与主机身份配置后,构建环境就可以访问相应的私有服务器或仓库。
Shell 执行器中的 SSH 密钥
Shell 执行器直接使用 Runner 所在机器上的环境,配置通常更简单。当前 Runner 执行器文档将 Shell executor 列为维护模式:继续接收关键安全更新,但不计划增加新功能;请按所用 Runner 版本核对适用性。原文给出的方式是在安装 GitLab Runner 的机器上生成密钥,让该机器运行的项目使用它。
编校提示:这会让同一 Runner 用户下的项目共享 SSH 身份,扩大凭据的影响范围。只有处于同一信任边界、确实应共享权限的项目才适合这样配置;不同权限或不同信任级别的项目应使用独立 Runner 或独立身份。
- 登录运行作业的服务器。
- 在终端切换为
gitlab-runner用户:
sudo su - gitlab-runner
- 生成新的 SSH 密钥对。原文示例不设置口令,否则自动化步骤会提示输入。
- 把公钥添加到需要访问的服务;私有 GitLab 仓库使用部署密钥。
生成后可以尝试连接远端,核对并接受服务器指纹:
ssh example.com
如果访问 GitLab.com 仓库,连接身份为 git@gitlab.com。接受提示之前,应通过可信的独立渠道核验指纹,不能把首次连接时看到的指纹直接当作可信证据。
验证 SSH 主机公钥
客户端公钥让服务器识别作业;服务器主机公钥让作业识别服务器。预先验证主机公钥能帮助发现中间人攻击。服务器呈现的主机公钥与 known_hosts 中的固定记录不匹配时,SSH 会拒绝该连接;如果脚本未显式捕获这个错误,CI 作业通常也会因命令失败而失败。
在可信网络中运行 ssh-keyscan 获取主机公钥;理想情况下,从私有服务器本身或受控管理网络收集,再核对管理员提供的指纹:
# 使用域名
ssh-keyscan example.com
# 或使用 IP 地址
ssh-keyscan 10.0.2.2
ssh-keyscan 只负责采集公钥,不能独立证明公钥属于正确服务器。将已核验的结果保存为项目的文件类型 CI/CD 变量:
- Key:
SSH_KNOWN_HOSTS。 - Value:经过可信核验的
ssh-keyscan输出。 - 要连接多个服务器时,把所有主机公钥放入同一个值,每行一条记录。
采用文件变量,域名或服务器信息变化时可以更新变量,不必直接改流水线文件。预先固定主机公钥也让异常变更能被发现:当远端公钥与固定记录不同,连接应失败,随后检查服务器更换、主机密钥轮换或者网络是否异常。
译文修订:原文这一段写成主机密钥突变时作业“doesn’t fail”,与同页前文及 SSH 主机校验的行为相矛盾。本译文更正为“应失败”,未沿用该明显笔误。
不要在 CI/CD 作业内临时运行 ssh-keyscan 并直接信任其结果。这样会把连接当时的攻击者公钥也写入信任列表。不要通过关闭主机校验来规避报错。
创建变量后,将以下两条命令追加到前面已有的 before_script 列表中:
# 追加到已有 before_script;不要在同一层再定义第二个同名键。
- cp "$SSH_KNOWN_HOSTS" ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
原文把它展示为另一段带 before_script: 的独立片段。这里改成“追加”形式,避免复制到同一 YAML 映射后形成重复键、覆盖载入密钥的步骤。主机公钥本身不是秘密,但它的完整性决定连接是否可信。
排查常见错误
error in libcrypto
载入 SSH 私钥时可能出现:
Error loading key "/builds/path/SSH_PRIVATE_KEY": error in libcrypto
一种常见原因是变量值最后没有 LF 换行。编辑文件类型变量,在 -----END OPENSSH PRIVATE KEY----- 的末尾按 Enter 或 Return,再保存。此错误并不只可能由换行引起;若仍失败,应检查实际密钥格式和内容是否损坏,排查时不要把私钥贴进公开日志。
保存变量时提示包含不允许的字符
Unable to create masked variable because: The value cannot contain the
following characters: whitespace characters.
这是因为 Visibility 设成了 Masked 或 Masked and hidden,而 SSH 私钥中的空格和换行不符合掩码变量的格式。将文件类型变量改为 Visible。文件变量一般只把路径提供给命令,因此可以减少无意输出正文的机会;它不能阻止命令主动读取并打印密钥。
来源、版权与审校范围
原文版权 © GitLab Inc.;原页未列个人署名。GitLab 仓库许可证规定 doc/ 下文档使用 CC BY-SA 4.0;本中文译文与改编按同一许可证提供。修改包括中文翻译、流程配图、安全边界说明、主机校验笔误更正和 YAML 片段的合并方式说明。原文页面未列个人作者。
本文依据公开文档进行了静态审校,没有启动 Runner、安装软件、载入密钥、连接服务器或执行部署。示例不含真实凭据。











暂无评论内容