CI/CD 构建中能保护好 SSHKEY吗?

目录

背景

方案

编码存储

逐行存储

合并存储

打马赛克

结论


背景


使用极狐GitLab CI/CD,在部署方面,主要有两种方式:

部署到K8S集群

  • Push模式:流水线通过kubectl执行命令部署,这需要把K8S的权限给流水线,存在安全风险

  • Pull模式:使用极狐GitLab Agent for Kubernetes 或ArgoCD,通过GitOps的方式“监听”极狐GitLab的变化,触发部署

图片

部署到服务器

目前仍有不少企业因为行业性质或者场景所限,没有使用K8S等云原生技术,还在采用传统的服务器方式进行部署。一般使用ssh、scp、rsync等命令部署到服务器。极狐GitLab也提供了基于SSH keys的部署。详见:Using SSH keys with JiHu GitLab CI/CD。

需要说明的是,如果是使用专用的编译机进行编译构建,然后部署到指定的服务器,只需要实现编译机和部署服务器的免密SSH登陆即可,相对简单。但如果使用容器进行编译构建,然后部署到服务器,就需要按照上面文档中提到的,配合极狐GitLab CI/CD环境变量,将SSH_PRIVATE_KEY等变量存储到极狐GitLab Project、Group或Instance中,实现复用。且可以通过极狐GitLab CI/CD环境变量的Mask设置,掩藏这些变量在CI/CD日志中的显示。详见:极狐GitLab CI/CD variables。

但遗憾的是Mask功能目前是有限制的,对于SSH_PRIVATE_KEY这种多行的变量无法直接使用Mask功能。这样开发人员就可以在.gitlab-cti.yml文件的脚本中执行echo $SSH_PRIVATE_KEY,在流水线的日志中输出SSH Keys,存在密钥泄露风险。

The value of the variable must:

  • Be a single line.

  • Be 8 characters or longer, consisting only of:

Characters from the Base64 alphabet (RFC4648).

The @ and : characters (In GitLab 12.2 and later).

The . character (In GitLab 12.10 and later).

The ~ character (In GitLab 13.12 and later).

  • Not match the name of an existing predefined or custom CI/CD variable.

图片

图片

这个问题在极狐GitLab的Issue上挂了有一年多 ,看样子短时间没法解决。有没有其他方式Mask SSH_PRIVATE_KEY?于是开始了各种折腾。

方案


编码存储

SSH Keys不能直接Mask,但Mask的要求里面是支持Base64的。所以把SSH Keys先用Base64编码,存到CI/CD环境变量中,这样就可以Mask了,然后在.gitlab-ci.yml中解码,就可以在不影响功能的前提下实现效果。看看操作步骤:

  • Base64编码SSH_PRIVATE_KEY

# 输入示例   
echo "-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAsWchpjSe6HW8dS/DdmokMqET2+eCvD8ysOeju3Ur3cbXtZF1
*****
pbPfj6i+faMGc1wbP+Svh8P5bcWTJZvZcP87D/HRmSFz6xcT014=
-----END RSA PRIVATE KEY-----" | base64 
# 输出示例:Base64编码
LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBc1djaHBqU2U2SFc4ZFMvRG
*****
cjRzNmVjY25ZRUZxb1NSTGVNU2xMb1ZreU5VZEpQUjJRa1djQzRkVDVQZwpwYlBmajZpK2ZhTUdjMXdiUCtTdmg4UDViY1dUSlp2WmNQODdEL0hSbVNGejZ4Y1QwMTQ9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
  • 将输出的Base64编码作为SSH_PRIVATE_KEY存储在极狐GitLab CI/CD环境变量,并Mask

图片

  • 修改.gitlab-ci.yml

before_script:- 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'- eval $(ssh-agent -s)# - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -- echo "$SSH_PRIVATE_KEY" | base64 -d | ssh-add -- mkdir -p ~/.ssh- chmod 700 ~/.ssh- echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts- chmod 644 ~/.ssh/known_hosts
  • 运行测试,大功告成

这看上去是个不错的方案,但真的保证了SSH_PRIVATE_KEY安全么?我们本着Geek(作死)精神,测试一下:

  • 修改.gitlab-ci.yml,通过各种方式看看能不能打印出SSH_PRIVATE_KEY

before_script:- 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'- eval $(ssh-agent -s)# --- test begin ---- echo "$SSH_PRIVATE_KEY" - echo "$SSH_PRIVATE_KEY" >> output.txt- cat output.txt- echo "$SSH_PRIVATE_KEY" | base64 -d# --- test end ---- echo "$SSH_PRIVATE_KEY" | base64 -d | ssh-add -- mkdir -p ~/.ssh- chmod 700 ~/.ssh- echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts- chmod 644 ~/.ssh/known_hosts
  • 运行测试

图片

直接输出SSH_PRIVATE_KEY是被Mask了,但执行echo "$SSH_PRIVATE_KEY" | base64 -d,居然把SSH_PRIVATE_KEY打印了出来,所以这个方法还是存在一定的问题。

逐行存储

SSH Keys头部和尾部的-----BEGIN RSA PRIVATE KEY-----、-----END RSA PRIVATE KEY-----不能Mask,但里面的内容,每一行可以单独作为一个环境变量存储并Mask,使用的时候再进行拼接,看看操作步骤:

  • 将SSH_PRIVATE_KEY每一行拆分成一个变量,进行存储,有多少行就要存多少变量,为了偷懒,此处只列了3行,实际上我这个SSH_PRIVATE_KEY除去头尾,有26行……

图片

图片

  • 修改.gitlab-ci.yml,并加入一些测试

before_script:- 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'- eval $(ssh-agent -s)# 拼接SSH_PRIVATE_KEY- |SSH_PRIVATE_KEY=$'-----BEGIN RSA PRIVATE KEY-----\n'SSH_PRIVATE_KEY=$SSH_PRIVATE_KEY$SSH_PRIVATE_KEY1$'\n'SSH_PRIVATE_KEY=$SSH_PRIVATE_KEY$SSH_PRIVATE_KEY2$'\n'SSH_PRIVATE_KEY=$SSH_PRIVATE_KEY$SSH_PRIVATE_KEY3$'\n'SSH_PRIVATE_KEY=$SSH_PRIVATE_KEY$'-----END RSA PRIVATE KEY-----'# --- test begin ---- echo "$SSH_PRIVATE_KEY" - echo "$SSH_PRIVATE_KEY" >> output.txt- cat output.txt# --- test end ---- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -- mkdir -p ~/.ssh- chmod 700 ~/.ssh- echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts- chmod 644 ~/.ssh/known_hosts
  • 运行测试

图片

方案行是行,实际操作起来要存26个变量然后还要拼起来,实在是太土了,能不能减少行数,存一行。

合并存储

Mask不支持空格,只支持@:.~,那我们尝试把SSH_PRIVATE_KEY除了头尾的部分合并成一行,把换行符替换成支持的符号,如.,然后再与头尾进行拼接。操作步骤如下:

  • 合并SSH_PRIVATE_KEY

# 输入示例
echo "-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAsWchpjSe6HW8dS/DdmokMqET2+eCvD8ysOeju3Ur3cbXtZF1
*****
pbPfj6i+faMGc1wbP+Svh8P5bcWTJZvZcP87D/HRmSFz6xcT014=
-----END RSA PRIVATE KEY-----" | tr -d '\r' | tr "\n" "."#输出示例
-----BEGIN RSA PRIVATE KEY-----.MIIEogIBAAKCAQEAsWchpjSe6HW8dS/DdmokMqET2+eCvD8ysOeju3Ur3cbXtZF1.LMb2Rq68/FPXsteLr4Y1ECKoy/YhFpyDw1h3cLm2WBUtRjt/Tq0ASbQCWAVkDsmx.uy28WofwfEKktzy3FmDSCXbvcOQgjChAmMbALWyH****=.-----END RSA PRIVATE KEY-----.
  • 将除头尾部分存入环境变量并Mask

图片

  • 修改.gitlab-ci.yml

before_script:- 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'- eval $(ssh-agent -s)# 拼接SSH_PRIVATE_KEY- SSH_PRIVATE_KEY=$'-----BEGIN RSA PRIVATE KEY-----\n'$SSH_PRIVATE_KEY$'\n-----END RSA PRIVATE KEY-----'# --- test begin ---- echo "$SSH_PRIVATE_KEY" - echo "$SSH_PRIVATE_KEY" >> output.txt- cat output.txt- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | tr "." "\n" # --- test end ---- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | tr "." "\n" | ssh-add -- mkdir -p ~/.ssh- chmod 700 ~/.ssh- echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts- chmod 644 ~/.ssh/known_hosts
  • 运行测试

图片

和“编码存储”的方案一样,跑的通,但依旧可以通过对应的方式,打印出SSH_PRIVATE_KEY。

到这里,可以隐约猜到Mask变量的原理是简单做了一个是否包含字符串的判断。如果与环境变量的值匹配就显示[MASKED],如果不匹配就直接将变量显示出来。这也是为什么目前只允许值是单行且没有太多特殊符号的环境变量才可以MASK的原因。

打马赛克

为了验证上一步留下来的猜想,我设计了一个实验:

  • 恢复环境变量中的SSH_PRIVATE_KEY为原始内容,并且不做Mask,当然也无法Mask

图片

  • 新建一个环境变量,值为SSH_PRIVATE_KEY的一部分内容,这里设置的是SSH_PRIVATE_KEY内容的第一行,然后设置为Mask

图片

  • 恢复.gitlab-ci.yml文件,需要注意的是这里面没有任何关于MOSAIC环境变量的使用

before_script:- 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'- eval $(ssh-agent -s)- echo "$SSH_PRIVATE_KEY" - echo "$SSH_PRIVATE_KEY" | tr -d '\r'| ssh-add -- mkdir -p ~/.ssh- chmod 700 ~/.ssh- echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts- chmod 644 ~/.ssh/known_hosts
  • 运行测试

图片

正如猜想一样,即便没有使用MOSAIC环境变量,但它依然作为判断是否包含字符串而被执行了。

利用这个特性,我们可以通过设置几个马赛克变量,给SSH_PRIVATE_KEY的部分内容打码,就可以一定程度上防止SSH_PRIVATE_KEY的泄露。

结论


作为一般场景下使用,上面的四种方式任意选一个都可以实现基本的安全防护,正所谓防君子不防小人。如果要进一步提高安全性,还是如官方所说,上专业的密钥管理工具,如Vault,或者期待下极狐GitLab在管理密钥这块功能的完善。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/228539.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Web】BJDCTF 2020 个人复现

目录 ①easy_md5 ②ZJCTF,不过如此 ③Cookie is so subtle! ④Ezphp ⑤The Mystery of IP ①easy_md5 ffifdyop绕过SQL注入 sql注入:md5($password,true) 右键查看源码 数组绕过 ?a[]1&b[]2 跳转到levell14.php 同样是数组绕过 param1[…

通过git上传文件到github仓库

一、新建github仓库 访问github官网:GitHub: Let’s build from here GitHub 点击个人头像,在右侧栏选择Your repositories。 点击New,新建一个github仓库。 创建Repository name仓库名,如果这个仓库名已经创建过的话&#xff…

​无人机摄影测量

无人机摄影测量技术是传统航空摄影测量手段的有力补充,具有机动灵活、高效快速、精细准确、作业成本低、生产周期短、影像获取空间分辨率高、高危地区探测等优势。无人机与航空摄影测量相结合使得“无人机数字低空遥感”成为航空遥感领域的一个崭新发展方向。无人机…

探索短剧市场的商机:打造短视频平台的全方位指南

目前短剧市场蓬勃发展,上半年备案数远超电视剧,彰显了短剧小程序市场潜力巨大,商业价值巨大。用户对短小精悍娱乐内容的需求不断增加,而新兴市场中有限的短剧小程序正好能够迎合这一需求。 搭建短视频平台的关键步骤: …

等保——密评技术要求

密评简介 密评定义:全称商用密码应用安全评估, 是指对采用商用密码技术、产品和服务集成建设的网络和信息系统密码应用的合规性、正确性、有效性进行评估。密评对象:重要信息系统、关键信息基础设施、网络安全等保三级及以上的系统。评测依据&#xff1…

太阳能监控智慧杆供电系统

太阳能监控智慧杆系统工作时无需水、油、汽、燃料,只要有光就能发电的特点,是清洁、无污染的可再生能源,而且安装维护简单,使用寿命长,可以实现无人值守,倍受人们的青睐,是新能源的领头羊。近年…

Unity 轨道展示系统(DollyMotion)

DollyMotion 🍱功能展示🥙使用💡设置路径点💡触发点位切换💡动态更新路径点💡事件触发💡设置路径💡设置移动方案固定速度方向最近路径方向 💡设置移动速度曲线 &#x1f…

在线陪诊系统: 医学科技的革新之路

医疗服务的数字化时代已经到来,而在线陪诊系统正是医学科技革新的杰出代表。通过巧妙的技术代码,这一系统不仅实现了患者和医生之间的远程互动,还将医疗服务推向了一个更加智能化的未来。在这篇文章中,我们将深入探讨在线陪诊系统…

订单管理系统怎么用?有哪些好用的订单管理系统?

订单管理系统怎么用?有哪些好用的订单管理系统?阅读本文你将了解:1、订单管理系统的核心功能;2、订单管理系统的拓展功能;3、订单管理系统推荐。 订单管理系统在当今商业环境中扮演着至关重要的角色。它是企业内部运营…

蓝桥杯day02——移动机器人

1.题目 有一些机器人分布在一条无限长的数轴上,他们初始坐标用一个下标从 0 开始的整数数组 nums 表示。当你给机器人下达命令时,它们以每秒钟一单位的速度开始移动。 给你一个字符串 s ,每个字符按顺序分别表示每个机器人移动的方向。L 表…

Anaconda离线下载torch与安装包

一、下载离线安装包 命令: pip download 安装包名 -d 安装到文件夹名 -i https://pypi.tuna.tsinghua.edu.cn/simple执行这样的命令就会把安装包的离线文件下载到指定文件夹中。 操作: 打开cmd命令行,并进入相应的目录中。 如果是tor…

封装一些可能会用到的JS的Dom操作方法(非JS自带的方法)

1. 父元素节点下的子元素节点逆序 HTMLElement.prototype.childRevers function () {var all_num this.childElementCount;if (all_num) {while(all_num--){this.appendChild(this.children[all_num]);}} } // 获取 ul 父节点对象 var oul document.getElementsByTagName(u…