PAYATHON 2026

如何在 GitLab CI 中将 SSH 私钥配置为 masked 变量?

数据小陈

完整的多行 OpenSSH 私钥不能直接设为 GitLab 的 masked 变量,因为掩码值必须是无空格的单行字符串。通常应将完整私钥保存在 File 类型变量中,并将 Visibility 设为 Visible。如果必须使用 masked 变量,可以保存完整私钥的单行 Base64 编码,但解码后的私钥不会因此自动受到掩码保护。GitLab 变量掩码说明

为什么删除换行后会报错?

ssh-add 需要读取可解析的私钥文件。如果把内容改成 myprivatekey==,并移除头尾标记和原有结构,就无法得到有效的 OpenSSH 私钥。

私钥末尾缺少 LF 换行也可能导致 error in libcrypto,不能仅凭这条报错确定唯一原因。应先恢复完整私钥内容,包括 -----BEGIN OPENSSH PRIVATE KEY-----、正文和结束标记,并在最后一行之后保留换行。GitLab 私钥错误排查

推荐配置:使用 File 类型变量

在项目的 Settings > CI/CD > Variables 中添加:

  • Key:SENTINEL_DB_RESET_SSH_KEY。
  • Type:File。
  • Visibility:Visible。
  • Value:完整私钥,保留所有换行,结束标记后再按一次回车。

File 类型变量在 job 中的值是临时文件路径,因此可以保留原来的 chmod 和 ssh-add 用法。Visible 表示没有启用日志掩码,不能据此认为文件内容可以安全输出。GitLab SSH 私钥配置说明

还要预先核验服务器的 SSH host key,并将对应的 known_hosts 内容保存为另一个 File 类型变量 SSH_KNOWN_HOSTS。不要只依赖 job 运行时执行的 ssh-keyscan 来建立信任。GitLab 主机密钥校验说明

下面的示例假定容器中已有所需 SSH 工具、私钥可以非交互加载,且服务器已配置对应公钥:

reset_database:
  stage: .pre
  image: kroniak/ssh-client:latest
  resource_group: portal_test_vm_lock
  before_script:
    - |
      set +x
      eval "$(ssh-agent -s)"
      chmod 600 "$SENTINEL_DB_RESET_SSH_KEY"
      ssh-add "$SENTINEL_DB_RESET_SSH_KEY"
      mkdir -p ~/.ssh
      chmod 700 ~/.ssh
      cp "$SSH_KNOWN_HOSTS" ~/.ssh/known_hosts
      chmod 600 ~/.ssh/known_hosts
  script:
    - ssh -o BatchMode=yes -o StrictHostKeyChecking=yes "root@$PORTAL_INSTANCE" /path/to/script.sh "$CI_PIPELINE_ID"

示例修正了 YAML 缩进,并删除了原命令中重复的 ssh。实际使用时,还需确认变量的作用域允许当前 job 读取。

必须使用 masked 变量时:编码完整私钥

在可信的本地环境中,对完整私钥文件编码,并将结果保存到另一个文件:

base64 < ./id_ed25519 | tr -d '\r\n' > ./id_ed25519.b64

这里删除的是 Base64 输出中的换行,解码后仍会还原私钥内部原有的换行。不要先删除原始私钥的换行。

将 id_ed25519.b64 的内容保存为普通 Variable 类型变量,例如 SENTINEL_DB_RESET_SSH_KEY_B64,Visibility 选择 Masked,关闭变量引用展开。是否能保存仍以自托管实例的实际校验规则为准。

在前面示例的 before_script 中,用以下代码替换私钥加载部分,保留后面的 known_hosts 配置:

set +x
umask 077
mkdir -p ~/.ssh
chmod 700 ~/.ssh
printf '%s' "$SENTINEL_DB_RESET_SSH_KEY_B64" \
  | base64 -d > ~/.ssh/ci_private_key
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/ci_private_key
rm -f ~/.ssh/ci_private_key

这段代码适用于支持 base64 -d 的 job 环境。Base64 是可逆编码,不是加密;本地生成的 .b64 文件也应按私钥管理,不能提交到仓库。

masked 能否保护解码后的私钥?

不能依赖它做到这一点。GitLab 掩码针对保存的变量值,不会自动解码后再匹配日志中的私钥。对编码值进行变换也可能使掩码无法匹配。GitLab 掩码限制

采用任一方案时,都应避免输出私钥文件,避免在处理私钥时开启 set -x 或调试跟踪,也不要把私钥放入 artifacts 或 cache。set +x 只能关闭 shell 命令跟踪,无法阻止程序主动打印文件内容。

如果启用 Protect variable,还要确认 job 所在分支或标签满足保护条件,否则变量可能不可用。masked 也无法阻止有权限运行的恶意脚本读取并发送私钥,因此仍需审查流水线变更。GitLab CI/CD 变量安全说明

备注:内容仅供参考。