在 GitLab CI/CD 中使用 SSH 密钥

在 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 密钥,并定期轮换。

GitLab 作业分别加载私钥文件变量和已核验的 known_hosts,服务端用授权公钥验证客户端,客户端用固定主机公钥验证服务端
技术示意:客户端身份与服务器身份分别验证。未完纪根据 GitLab 官方文档绘制。

创建并使用 SSH 密钥

  1. 生成一对新的 SSH 密钥,参见 GitLab SSH 密钥指南。
  2. 把私钥保存为名叫 SSH_PRIVATE_KEY 的文件类型 CI/CD 变量。
  3. 在作业中启动 ssh-agent,然后把私钥载入 agent。
  4. 把公钥配置到要访问的服务器,通常放在目标用户的 ~/.ssh/authorized_keys 中。如果访问私有 GitLab 仓库,还需要把公钥添加为 部署密钥。

载入密钥的命令本身不需要把私钥内容打印到日志中。但开启调试日志、添加输出命令,或者运行不可信作业,都可能泄露它。也要检查流水线的可见性。

把私钥添加为文件类型变量

在项目的 CI/CD 变量设置中创建文件类型变量:

  1. 把 Visibility 设为 Visible。SSH 私钥含空白字符和换行,无法满足 Masked 或 Masked and hidden 的值格式要求。不要对该变量代表的文件执行 cat 或 tee;一旦私钥进入作业日志,它不会被掩码保护。
  2. 在 Key 中填入变量名,例如 SSH_PRIVATE_KEY。
  3. 在 Value 中粘贴私钥全文。值必须以一个 LF 换行符结束;保存前,在最后一行末尾按 Enter 或 Return。

编校说明:文件类型变量在作业中展开为临时文件路径,而不是私钥文本。Visible 表示有相应项目、组或实例设置访问权限的用户可以查看该值;它不会自动把变量公开给所有人,也不构成日志防泄漏保证。GitLab 当前变量文档注明,18.3 起默认可见性改为 Masked;但私钥含空白和换行,仍应为此变量显式选择 Visible。作业内命令仍可读取文件变量,恶意或未审查的流水线改动也可能将其外传。应限制变量的作用域、受保护分支或标签、Runner 使用范围,以及可修改流水线配置和访问变量设置的人员;无口令密钥尤其要采用最小权限并定期轮换。

使用普通变量的另一种方式

如果不使用文件类型变量,可以参考 SSH 示例项目中的普通 CI/CD 变量方法。通常优先使用文件类型变量,因为它能保留多行格式,降低换行和转义造成的错误。

Docker 执行器中的 SSH 密钥

CI/CD 作业在 Docker 容器中运行时,构建环境相互隔离。要将代码部署到私有服务器,可以这样配置:

  1. 创建新的 SSH 密钥对。原文示例不设置私钥口令,否则 before_script 会要求输入口令。
  2. 将私钥保存为文件类型变量 SSH_PRIVATE_KEY。
  3. 在 .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 生成的环境设置语句,应确保调用的是可信镜像中的可信程序。

  1. 验证私有服务器的 SSH 主机公钥,方法见下节。
  2. 把第 1 步生成的公钥添加到构建环境需要访问的服务。访问私有 GitLab 仓库时,将其设置为部署密钥。

完成客户端身份与主机身份配置后,构建环境就可以访问相应的私有服务器或仓库。

Shell 执行器中的 SSH 密钥

Shell 执行器直接使用 Runner 所在机器上的环境,配置通常更简单。当前 Runner 执行器文档将 Shell executor 列为维护模式:继续接收关键安全更新,但不计划增加新功能;请按所用 Runner 版本核对适用性。原文给出的方式是在安装 GitLab Runner 的机器上生成密钥,让该机器运行的项目使用它。

编校提示:这会让同一 Runner 用户下的项目共享 SSH 身份,扩大凭据的影响范围。只有处于同一信任边界、确实应共享权限的项目才适合这样配置;不同权限或不同信任级别的项目应使用独立 Runner 或独立身份。

  1. 登录运行作业的服务器。
  2. 在终端切换为 gitlab-runner 用户:
sudo su - gitlab-runner
  1. 生成新的 SSH 密钥对。原文示例不设置口令,否则自动化步骤会提示输入。
  2. 把公钥添加到需要访问的服务;私有 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、安装软件、载入密钥、连接服务器或执行部署。示例不含真实凭据。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容