GitLab技術貼|極狐GitLab CI/CD SSHKEY Mask的幾種方式
使用極狐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 JiHuGitLab 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,存在密鑰泄露風險。
這個問題在極狐GitLab的Issue上掛了有一年多 ,看樣子短時間沒法解決。有沒有其他方式Mask SSH_PRIVATE_KEY?于是我們進行了下面幾種方案嘗試。
方案嘗試一:編碼存儲
SSH Keys不能直接Mask,但Mask的要求里面是支持Base64的。所以把SSH Keys先用Base64編碼,存到CI/CD環境變量中,這樣就可以Mask了,然后在.gitlab-ci.yml中解碼,就可以在不影響功能的前提下實現效果??纯床僮鞑襟E:
•Base64編碼SSH_PRIVATE_KEY
#輸入示例
echo"-----BEGINRSAPRIVATEKEY-----
MIIEogIBAAKCAQEAsWchpjSe6HW8dS/DdmokMqET2+eCvD8ysOeju3Ur3cbXtZF1
*****
pbPfj6i+faMGc1wbP+Svh8P5bcWTJZvZcP87D/HRmSFz6xcT014=
-----ENDRSAPRIVATEKEY-----"|base64
#輸出示例:Base64編碼
LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBc1djaHBqU2U2SFc4ZFMvRG
*****
cjRzNmVjY25ZRUZxb1NSTGVNU2xMb1ZreU5VZEpQUjJRa1djQzRkVDVQZwpwYlBmajZpK2ZhTUdjMXdiUCtTdmg4UDViY1dUSlp2WmNQODdEL0hSbVNGejZ4Y1QwMTQ9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
•將輸出的Base64編碼作為SSH_PRIVATE_KEY存儲在極狐GitLab CI/CD環境變量,并Mask
•修改.gitlab-ci.yml
before_script:
-'command-vssh-agent>/dev/null||(apt-getupdate-y&&apt-getinstallopenssh-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
-chmod700~/.ssh
-echo"$SSH_KNOWN_HOSTS">>~/.ssh/known_hosts
-chmod644~/.ssh/known_hosts
•運行測試,大功告成
這看上去是個不錯的方案,但真的保證了SSH_PRIVATE_KEY安全么?我們本著Geek(作死)精神,測試一下:
•修改.gitlab-ci.yml,通過各種方式看看能不能打印出SSH_PRIVATE_KEY
before_script:
-'command-vssh-agent>/dev/null||(apt-getupdate-y&&apt-getinstallopenssh-client-y)'
-eval$(ssh-agent-s)
#---testbegin---
-echo"$SSH_PRIVATE_KEY"
-echo"$SSH_PRIVATE_KEY">>output.txt
-catoutput.txt
-echo"$SSH_PRIVATE_KEY"|base64-d
#---testend---
-echo"$SSH_PRIVATE_KEY"|base64-d|ssh-add-
-mkdir-p~/.ssh
-chmod700~/.ssh
-echo"$SSH_KNOWN_HOSTS">>~/.ssh/known_hosts
-chmod644~/.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-vssh-agent>/dev/null||(apt-getupdate-y&&apt-getinstallopenssh-client-y)'
-eval$(ssh-agent-s)
#拼接SSH_PRIVATE_KEY
-|
SSH_PRIVATE_KEY=$'-----BEGINRSAPRIVATEKEY-----\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$'-----ENDRSAPRIVATEKEY-----'
#---testbegin---
-echo"$SSH_PRIVATE_KEY"
-echo"$SSH_PRIVATE_KEY">>output.txt
-catoutput.txt
#---testend---
-echo"$SSH_PRIVATE_KEY"|tr-d'\r'|ssh-add-
-mkdir-p~/.ssh
-chmod700~/.ssh
-echo"$SSH_KNOWN_HOSTS">>~/.ssh/known_hosts
-chmod644~/.ssh/known_hosts
•運行測試
由此可見,這個方案行是行,但實際操作起來要存26個變量然后還要拼起來,實在是太土了,能不能減少行數,存一行呢。
方案嘗試三:合并存儲
Mask不支持空格,只支持@:.~,那我們嘗試把SSH_PRIVATE_KEY除了頭尾的部分合并成一行,把換行符替換成支持的符號,如.,然后再與頭尾進行拼接。操作步驟如下:
•合并SSH_PRIVATE_KEY
#輸入示例
echo"-----BEGINRSAPRIVATEKEY-----
MIIEogIBAAKCAQEAsWchpjSe6HW8dS/DdmokMqET2+eCvD8ysOeju3Ur3cbXtZF1
*****
pbPfj6i+faMGc1wbP+Svh8P5bcWTJZvZcP87D/HRmSFz6xcT014=
-----ENDRSAPRIVATEKEY-----"|tr-d'\r'|tr"\n""."
#輸出示例
-----BEGINRSAPRIVATEKEY-----.MIIEogIBAAKCAQEAsWchpjSe6HW8dS/DdmokMqET2+eCvD8ysOeju3Ur3cbXtZF1.LMb2Rq68/FPXsteLr4Y1ECKoy/YhFpyDw1h3cLm2WBUtRjt/Tq0ASbQCWAVkDsmx.uy28WofwfEKktzy3FmDSCXbvcOQgjChAmMbALWyH****=.-----ENDRSAPRIVATEKEY-----.
•將除頭尾部分存入環境變量并Mask
•修改.gitlab-ci.yml
before_script:
-'command-vssh-agent>/dev/null||(apt-getupdate-y&&apt-getinstallopenssh-client-y)'
-eval$(ssh-agent-s)
#拼接SSH_PRIVATE_KEY
-SSH_PRIVATE_KEY=$'-----BEGINRSAPRIVATEKEY-----\n'$SSH_PRIVATE_KEY$'\n-----ENDRSAPRIVATEKEY-----'
#---testbegin---
-echo"$SSH_PRIVATE_KEY"
-echo"$SSH_PRIVATE_KEY">>output.txt
-catoutput.txt
-echo"$SSH_PRIVATE_KEY"|tr-d'\r'|tr".""\n"
#---testend---
-echo"$SSH_PRIVATE_KEY"|tr-d'\r'|tr".""\n"|ssh-add-
-mkdir-p~/.ssh
-chmod700~/.ssh
-echo"$SSH_KNOWN_HOSTS">>~/.ssh/known_hosts
-chmod644~/.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-vssh-agent>/dev/null||(apt-getupdate-y&&apt-getinstallopenssh-client-y)'
-eval$(ssh-agent-s)
-echo"$SSH_PRIVATE_KEY"
-echo"$SSH_PRIVATE_KEY"|tr-d'\r'|ssh-add-
-mkdir-p~/.ssh
-chmod700~/.ssh
-echo"$SSH_KNOWN_HOSTS">>~/.ssh/known_hosts
-chmod644~/.ssh/known_hosts
接下來,運行測試。
正如猜想一樣,即便沒有使用MOSAIC環境變量,但它依然作為判斷是否包含字符串而被執行了。
利用這個特性,我們可以通過設置幾個馬賽克變量,給SSH_PRIVATE_KEY的部分內容打碼,就可以一定程度上防止SSH_PRIVATE_KEY的泄露。
綜上,我們可以得出結論:作為一般場景下使用,上面的四種方式任意選一個都可以實現基本的安全防護,正所謂防君子不防小人。如果要進一步提高安全性,還是如官方所說,上專業的密鑰管理工具,如Vault,或者期待下極狐GitLab在管理密鑰這塊功能的完善。
免責聲明:本文僅代表作者個人觀點,與中創網無關。其原創性以及文中陳述文字和內容未經本站證實,對本文以及其中全部或者部分內容、文字的真實性、完整性、及時性本站不作任何保證或承諾,請讀者僅作參考,并請自行核實相關內容。