對(duì)于分布式系統(tǒng)來(lái)說(shuō)捶闸,我們通常會(huì)使用跨越網(wǎng)絡(luò)的服務(wù)來(lái)完成業(yè)務(wù)邏輯的處理,比如訂單中心的saveOrder接口需要將訂單數(shù)據(jù)持久化到阿里云上的DRDS分布式數(shù)據(jù)庫(kù)中吮炕,將訂單保存完成的消息發(fā)發(fā)送給RocketMQ消息隊(duì)列來(lái)通知下游系統(tǒng)等。為了訪問(wèn)數(shù)據(jù)庫(kù)服務(wù),我們通常需要一對(duì)用戶名和密碼暴备,或者授權(quán)的Token,通常我們稱之為credentials们豌。本質(zhì)上這些credentials信息主要目的是保護(hù)我們的隱私數(shù)據(jù)不被未授權(quán)的用戶訪問(wèn)涯捻,竊取,因此如何確保這些數(shù)據(jù)的安全就顯得尤為重要望迎。
比如遵守最基本的安全規(guī)范障癌,筆者在系列文章的第一篇就提到過(guò)多個(gè)基本的安全規(guī)范,其中最小權(quán)限(least privilege)應(yīng)該是最被大家熟知的辩尊,在上邊描述的場(chǎng)景中涛浙,最小權(quán)限的含義是隱私銘感數(shù)據(jù)只被授權(quán)的用戶或者組件使用(這里的“只被”就是least privilege原則的體現(xiàn))。
credentials類信息最基本的安全屬性就是機(jī)密性摄欲,只對(duì)持有訪問(wèn)權(quán)限的用戶開(kāi)放蝗拿。舉個(gè)例子,我們可以通過(guò)加密來(lái)講數(shù)據(jù)庫(kù)訪問(wèn)用戶名和密碼加密蒿涎。不過(guò)加密需要秘鑰哀托,因此這個(gè)秘鑰需要在所有允許訪問(wèn)的實(shí)體之間共享,包括服務(wù)器端劳秋。除了這種data at rest的加密手段仓手,當(dāng)數(shù)據(jù)被訪問(wèn)的時(shí)候胖齐,也就是從存儲(chǔ)介質(zhì)被讀出并返回給應(yīng)用程序的過(guò)程中,我們需要通過(guò)data in transit加密手段來(lái)抵御MIMA攻擊(man in the middle attack)嗽冒。
對(duì)加密機(jī)制不是太了解的同學(xué)很容易犯“加密后呀伙,整個(gè)世界就太平”的錯(cuò)誤,這很容易理解添坊,我們將機(jī)密數(shù)據(jù)加密后剿另,可以安全的傳遞給任何相關(guān)方而不會(huì)造成泄漏或者竊取。即便是中間人攻擊截取到了隱私數(shù)據(jù)贬蛙,由于沒(méi)有秘鑰雨女,也無(wú)法獲取原始的數(shù)據(jù)。但是如果數(shù)據(jù)的接收端需要讀取原始數(shù)據(jù)阳准,密文肯定不行氛堕,需要對(duì)數(shù)據(jù)解密,那么接收端解密數(shù)據(jù)的秘鑰從哪里來(lái)呢野蝇?我們可以簡(jiǎn)單的說(shuō)在通過(guò)另外一個(gè)秘鑰來(lái)安全的傳輸解密數(shù)據(jù)的秘鑰讼稚,那么這個(gè)“另外一個(gè)秘鑰”又如何確保安全的傳輸和分發(fā)呢?
隨著參與信息交互的parties越來(lái)越多绕沈,很容易出現(xiàn)秘鑰泄漏锐想,因此我們需要秘鑰revoke(回收,下線)的機(jī)制乍狐,來(lái)在泄漏發(fā)生后痛倚,讓系統(tǒng)能夠馬上將泄漏的秘鑰失效,來(lái)最大限度的保護(hù)數(shù)據(jù)安全澜躺。當(dāng)然revoke機(jī)制在日常的運(yùn)營(yíng)中也是非常需要的蝉稳,比如員工離職,轉(zhuǎn)崗都需要有機(jī)制能夠revoke他們持有的賬戶掘鄙,來(lái)確保信息安全耘戚。
對(duì)于安全級(jí)別要求高的場(chǎng)合,安全規(guī)范規(guī)定必須使用short-lived秘鑰操漠,通過(guò)縮短秘鑰的生命周期來(lái)縮小攻擊面收津。比如當(dāng)秘鑰被竊取后,可能攻擊者還沒(méi)有來(lái)得及使用秘鑰竊取數(shù)據(jù)浊伙,秘鑰的有效期就過(guò)了撞秋。不過(guò)“人肉”的方式來(lái)進(jìn)行秘鑰替換不太可取,運(yùn)維成本太高嚣鄙,因此大部分成熟的系統(tǒng)中都是借助于軟件來(lái)定期更換訪問(wèn)秘鑰吻贿,來(lái)縮短攻擊面,提升體統(tǒng)的安全性哑子。
從秘鑰生命周期的角度看舅列,我們不應(yīng)該讓?xiě)?yīng)用程序的生命周期和秘鑰的聲明周期之間有強(qiáng)依賴肌割,我們必須能夠在不重新發(fā)布應(yīng)用程序新版本的情況下,更新秘鑰信息帐要。相信強(qiáng)依賴的場(chǎng)景大家都很熟悉把敞,在代碼中硬編碼秘鑰信息,證書(shū)信息榨惠,然后將應(yīng)用程序打包部署奋早,這是一種典型的將秘鑰的聲明周期和應(yīng)用程序生命周期強(qiáng)綁定的案例。
當(dāng)秘鑰被產(chǎn)生后赠橙,特別是生產(chǎn)環(huán)境耽装,需要訪問(wèn)秘鑰的用戶數(shù)量遠(yuǎn)比訪問(wèn)應(yīng)用程序使用這些秘鑰的用戶數(shù)量低,因?yàn)槲覀儽仨毚_保秘鑰被產(chǎn)生后简烤,不應(yīng)該被不相關(guān)的人查看,更不能被修改摇幻。在系統(tǒng)中需要針對(duì)這些隱私信息設(shè)計(jì)完整的審計(jì)日志機(jī)制横侦,這樣當(dāng)出現(xiàn)異常的時(shí)候,我們就可以盡快行動(dòng)來(lái)分析對(duì)秘鑰的訪問(wèn)是否在預(yù)期之內(nèi)绰姻。缺少對(duì)秘鑰信息訪問(wèn)審計(jì)是筆者接觸過(guò)最多的安全問(wèn)題枉侧,等隱私數(shù)據(jù)被泄漏后才來(lái)分析是否有日志記錄了某些異常行為,大概率為時(shí)已晚狂芋。
秘鑰信息的訪問(wèn)者中不僅限于真實(shí)的用戶榨馁,也包含服務(wù)或者應(yīng)用系統(tǒng),由于咱們討論的是容器安全帜矾,因此容器也需要這些秘鑰數(shù)據(jù)來(lái)訪問(wèn)外部服務(wù)翼虫,數(shù)據(jù)庫(kù)服務(wù)等。因此如何把秘鑰等信息安全的暴露給容器實(shí)例使用屡萤,是我們這篇文章要解決的核心問(wèn)題珍剑。
筆者之前通過(guò)多篇文章詳細(xì)介紹過(guò)容器的隔離性,以及這種隔離性的脆弱性死陆,因此從安全的角度招拙,我們要安全的把秘鑰給容器實(shí)例也不是太容易,從筆者過(guò)往的經(jīng)驗(yàn)看措译,可行的手段包括但不限于:
- 將秘鑰信息包含在容器鏡像中别凤,也就是在docker build的時(shí)候?qū)⒚罔€信息寫(xiě)入到鏡像的root文件系統(tǒng)
- 在鏡像文件中定義環(huán)境變量
- 在容器啟動(dòng)的時(shí)候,通過(guò)YAML文件來(lái)配置環(huán)境變量來(lái)初始化環(huán)境變量
- 容器啟動(dòng)的時(shí)候從外部服務(wù)拉取配置信息
- 容器實(shí)例掛載外部的存儲(chǔ)介質(zhì)(比如和宿主機(jī)共享目錄來(lái)讀取配置信息)
雖然筆者列出來(lái)這五種方式领虹,但是需要強(qiáng)調(diào)的是规哪,前兩種方式(文件和環(huán)境變量)非常不可取,原因很明顯塌衰,我們?cè)阽R像構(gòu)建的時(shí)候就確定了敏感數(shù)據(jù)由缆,會(huì)造成如下問(wèn)題:
- 如果敏感數(shù)據(jù)被保存在代碼中注祖,那么任何有代碼訪問(wèn)權(quán)限的同學(xué)都可以看到這些隱私數(shù)據(jù)。筆者在華南某頭部客戶的項(xiàng)目上處理過(guò)一個(gè)非常常見(jiàn)的場(chǎng)景均唉,開(kāi)發(fā)同學(xué)講秘鑰進(jìn)行加密后是晨,放在源代碼中。雖然這看起來(lái)很安全舔箭,因?yàn)樵L問(wèn)源代碼的同學(xué)是無(wú)法獲取秘鑰信息罩缴,但是你有沒(méi)有考慮過(guò),代碼在使用這個(gè)秘鑰的時(shí)候层扶,解密秘鑰從哪里來(lái)呢箫章?答案是寫(xiě)在代碼中。
- 將秘鑰信息硬編碼到容器鏡像中另外明顯的問(wèn)題是無(wú)法進(jìn)行秘鑰定期更換镜会,因?yàn)楦鼡Q秘鑰就意味著更換鏡像版本檬寂。雖然說(shuō)就改一行代碼,但是造成的線上穩(wěn)定性風(fēng)險(xiǎn)會(huì)遠(yuǎn)遠(yuǎn)大于只更改一行代碼戳表,因?yàn)闆](méi)有人可以保證重新編譯打包的代碼不加帶任何其他的修改桶至。筆者在華南某頭部客戶的項(xiàng)目上,100多人的研發(fā)團(tuán)隊(duì)匾旭,你其實(shí)很難控制每個(gè)版本不夾帶私貨镣屹,因此解耦秘鑰和應(yīng)用程序的生命周期才是正道。
筆者還遇到過(guò)開(kāi)發(fā)人員說(shuō)為了提升開(kāi)發(fā)的效率价涝,把秘鑰寫(xiě)到源代碼中女蜈,后邊通過(guò)技術(shù)債來(lái)重構(gòu),實(shí)際上這種思路非常危險(xiǎn)色瘩。因?yàn)榇蟛糠诌@樣的技術(shù)債要么被忽略伪窖,要么在backlog中的優(yōu)先級(jí)非常低,低到幾乎就等同于不存在居兆。
如果將秘鑰信息靜態(tài)保存在鏡像中不可取惰许,自然而然的想法就是動(dòng)態(tài)加載,我們通過(guò)客戶端工具從代碼中動(dòng)態(tài)加載秘鑰信息史辙,雖然說(shuō)這種方式技術(shù)上可行汹买,但是從筆者的經(jīng)驗(yàn)看,這種方式在真實(shí)場(chǎng)景中用的并不多聊倔。原因是通常我們需要安全的鏈路來(lái)傳輸秘鑰數(shù)據(jù)晦毙,因此我們最少需要使用X.509格式的證書(shū)(certificate),而維護(hù)和管理這些證書(shū)有會(huì)涉及到安全和運(yùn)維耙蔑。不過(guò)隨著行業(yè)的不斷發(fā)展见妒,相信大家一定聽(tīng)過(guò)邊車模式,服務(wù)網(wǎng)格甸陌,我們可以借助于服務(wù)網(wǎng)格提供的control plane來(lái)管理服務(wù)和服務(wù)之間的mTLS需要的證書(shū)须揣,因此通過(guò)代碼加載證書(shū)這種方式應(yīng)該還會(huì)有繼續(xù)存在的場(chǎng)景盐股。
接著我們來(lái)討論一下通過(guò)環(huán)境變量來(lái)傳遞秘鑰信息給容器實(shí)例這種方式,雖然說(shuō)這種方式看起來(lái)很自然耻卡,也很有效疯汁,但是讀過(guò)筆者關(guān)于Kubernetes存儲(chǔ)機(jī)制系列文章的同學(xué),應(yīng)該對(duì)筆者的結(jié)論:優(yōu)先使用數(shù)據(jù)卷來(lái)提供配置信息給容器實(shí)例卵酪,對(duì)敏感隱私數(shù)據(jù)來(lái)說(shuō)更應(yīng)該如此的觀點(diǎn)記憶猶新幌蚊。背后的邏輯如下:
- 很多系統(tǒng)在crash的時(shí)候,會(huì)“自動(dòng)”將系統(tǒng)運(yùn)行的環(huán)境信息dump到日志中溃卡,這就包括了環(huán)境變量信息溢豆,而日志系統(tǒng)很多時(shí)候并沒(méi)有非常嚴(yán)格的權(quán)限訪問(wèn)控制,因此環(huán)境變種中的這些敏感信息(比如數(shù)據(jù)庫(kù)訪問(wèn)賬戶和密碼)會(huì)不經(jīng)意的被泄漏瘸羡。
- 在容器化部署的場(chǎng)景下漩仙, 我們通過(guò)docker inspec可以看到容器的所有環(huán)境變量信息,無(wú)論是在build階段的鏡像文件犹赖,還是在正在運(yùn)行的容器實(shí)例队他。對(duì)于有管理員權(quán)限的用戶,通過(guò)docker inspec會(huì)被動(dòng)的訪問(wèn)到所有環(huán)境變量中的信息冷尉,無(wú)論是否敏感漱挎。
咱們通過(guò)一個(gè)具體的例子來(lái)說(shuō)明一下系枪,筆者在自己的機(jī)器上啟動(dòng)了一個(gè)vagrant操作系統(tǒng)實(shí)例雀哨,安裝docker后拉取了nginx鏡像,通過(guò)docker inspect命令就可以訪問(wèn)到所有的環(huán)境變量信息私爷,如下所示:
vagrant@vagrant:~$ docker image inspect --format '{{.Config.Env}}' nginx
[PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin NGINX_VERSION=1.17.6 NJS_VERSION=0.3.7 PKG_RELEASE=1~buster]
接著咱們啟動(dòng)一個(gè)nginx實(shí)例雾棺,看看容器實(shí)例是否也這么脆弱把所有的環(huán)境變量信息通過(guò)inspec返回?
首先啟動(dòng)一個(gè)nginx容器實(shí)例衬浑,并設(shè)置了環(huán)境變量
vagrant@vagrant:~$ docker run -e SECRET_ENV=qiwanghan --rm -d nginx
12bcf3c571268f697f1e562a49e8d545d78aae65b0a102d2da78596b655e2f9a
然后我們來(lái)inspec一下捌浩,看看我們?cè)趩?dòng)時(shí)候設(shè)置的環(huán)境變量?jī)?nèi)否被輕易獲取:
vagrant@vagrant:~$ docker container inspect --format '{{.Config.Env}}' 12bcf
[SECRET_ENV=qiwanghan PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin NGINX_VERSION=1.17.6 NJS_VERSION=0.3.7 PKG_RELEASE=1~buster]
熟悉云原生架構(gòu)模式的同學(xué)應(yīng)該對(duì)Heroku團(tuán)隊(duì)總結(jié)的12-factor不陌生工秩,這個(gè)最佳實(shí)踐中的確鼓勵(lì)(encourage)我們通過(guò)環(huán)境變量來(lái)傳遞配置信息尸饺,因此你會(huì)發(fā)現(xiàn)很多早期的容器化部署大量使用環(huán)境變量傳遞配置信息,包括秘鑰這樣的敏感數(shù)據(jù)助币。如果讀者面對(duì)的是這種場(chǎng)景浪听,那么建議采用如下的風(fēng)險(xiǎn)應(yīng)對(duì)方案來(lái)降低信息泄露的風(fēng)險(xiǎn):
- 應(yīng)用程序的日志被輸出之前,通過(guò)過(guò)濾器來(lái)過(guò)濾掉敏感數(shù)據(jù)
- 或者通過(guò)邊車模式眉菱,init Container來(lái)從外部讀取敏感數(shù)據(jù)
另外必須著重強(qiáng)調(diào)的點(diǎn)是迹栓,環(huán)境變量包含的配置信息在容器實(shí)例創(chuàng)建的時(shí)候被生成,因此在容器的生命周期中無(wú)法修改俭缓,如果秘鑰比如數(shù)據(jù)庫(kù)密碼被修改克伊,那么就需要重啟容器實(shí)例才能生效酥郭,我們無(wú)法從外部在不重啟容器實(shí)例的情況下來(lái)更換秘鑰。
基于筆者多年的實(shí)踐經(jīng)驗(yàn)愿吹,將秘鑰暴露給容器實(shí)例最佳的方式就是通過(guò)掛載數(shù)據(jù)卷不从,并且通過(guò)掛載內(nèi)存型文件系統(tǒng),敏感數(shù)據(jù)被加載到容器實(shí)例后洗搂,仍然會(huì)保證足夠的安全性消返。并且由于文件是從宿主機(jī)被掛載到容器實(shí)例,當(dāng)我們更新了宿主機(jī)上的秘鑰后耘拇,容器中的數(shù)據(jù)可以自動(dòng)被更新撵颊,這就意味著我們可以在不重啟容器的情況下來(lái)更替秘鑰,可運(yùn)維性得到了極大的提升惫叛。
咱們稍微將如何暴露秘鑰數(shù)據(jù)給容器實(shí)例稍微做一些擴(kuò)展倡勇,在K8S的場(chǎng)景下,由于K8S原生的支持secret對(duì)象類型嘉涌,因此咱們前邊描述的安全方案基本都適用妻熊,詳細(xì)說(shuō)明如下:
- 在Kubernetes中,敏感數(shù)據(jù)secret對(duì)象和應(yīng)用程序?qū)嵗裏o(wú)任何耦合仑最,因此我們對(duì)secret對(duì)象的管理可以有自己的機(jī)制和生命周期扔役。
- secret對(duì)象在etcd默認(rèn)情況下不是加密存儲(chǔ),不過(guò)我們可以通過(guò)encryptionconfiguration警医,sealsecret以及其他機(jī)制很容易實(shí)現(xiàn)encrypted at rest亿胸。
- secret下不同的組件之間傳遞采用加密的形式,比如API Server和etcd之間预皇。
- secret可以通過(guò)環(huán)境變量或者數(shù)據(jù)卷的形式被暴露給容器實(shí)例侈玄,特別是對(duì)于數(shù)據(jù)卷的形式,secret被掛載為內(nèi)存型文件系統(tǒng)吟温,來(lái)提升安全性序仙。
- 我們可以設(shè)置Kubernetes RBAC(基于角色的權(quán)限控制機(jī)制)來(lái)實(shí)現(xiàn)secret被寫(xiě)入而不被其他用戶和實(shí)體讀取的場(chǎng)景(比如生產(chǎn)環(huán)境)。
基于筆者過(guò)去幾年的經(jīng)驗(yàn)看鲁豪,對(duì)于云原生的應(yīng)用程序潘悼,我們一般需要阿里云提供的KMS這樣的托管服務(wù)來(lái)提供安全的秘鑰存儲(chǔ)和交互,阿里云KMS可以帶來(lái)如下的好處:
- 完善的秘鑰定期更換機(jī)制爬橡,減輕安全性較高場(chǎng)景下的運(yùn)維壓力
- 幫助企業(yè)建立完善的治唤,標(biāo)準(zhǔn)化的秘鑰管理機(jī)制,極大的提升了企業(yè)的整體安全
文章的最后堤尾,筆者還是要再次強(qiáng)調(diào)安全沒(méi)有一勞永逸肝劲,咱們無(wú)論是把秘鑰以數(shù)據(jù)卷還是環(huán)境變量暴露給容器實(shí)例,本質(zhì)上對(duì)運(yùn)行容器的root用戶可見(jiàn)。舉個(gè)例子辞槐,如果秘鑰數(shù)據(jù)在磁盤(pán)上(通過(guò)掛載數(shù)據(jù)卷的方式暴露)掷漱,那么root用戶是可以通過(guò)羅列掛載到容器的內(nèi)存型文件系統(tǒng)來(lái)獲取隱私數(shù)據(jù),如下邊例子所示榄檬。
root@vagrant:/$ mount -t tmpfs
...
tmpfs on /var/lib/kubelet/pods/f02a9901-8214-4751-b157-d2e90bc6a98c/volumes/kuber
netes.io~secret/coredns-token-gxsqd type tmpfs (rw,relatime)
tmpfs on /var/lib/kubelet/pods/074d762f-00ed-48e6-a22f-43fc673df0e6/volumes/kuber
netes.io~secret/kube-proxy-token-bvktc type tmpfs (rw,relatime)
tmpfs on /var/lib/kubelet/pods/e1bad0db-8c0b-4d7b-8867-9fc019de258f/volumes/kuber
netes.io~secret/default-token-h2x8p type tmpfs (rw,relatime)
...
通過(guò)查看掛載的文件夾的名字(我們通常會(huì)把秘鑰寫(xiě)到以secret卜范,key,credentials命名的文件夾中)鹿榜,不難找到隱私數(shù)據(jù)并獲取海雪。對(duì)于環(huán)境變量這種方式,對(duì)root用戶就更簡(jiǎn)單了舱殿。比如我們啟動(dòng)了一個(gè)ubuntu操作系統(tǒng)并設(shè)置了環(huán)境變量:
vagrant@vagrant:~$ docker run --rm -it -e SECRET=qiwangyue ubuntu sh
$ env
...
SECRET=qiwangyue
...
我們很容易通過(guò)env命令打印出所有的環(huán)境變量數(shù)據(jù)奥裸,不過(guò)這還不是最糟糕的。咱們?cè)谇斑叺奈恼轮薪榻B過(guò)如何獲取運(yùn)行在容器實(shí)例中進(jìn)程的ID沪袭,比如:
vagrant@vagrant:~$ ps -C sh
PID TTY TIME CMD
19312 pts/0 00:00:00 sh
由于進(jìn)程的信息都在文件夾/proc下湾宙,因此我們不難在這個(gè)文件夾下找到和環(huán)境變量相關(guān)的信息:
vagrant@vagrant:~$ sudo cat /proc/19312/environ
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOSTNAME=2cc99c98ba5aTERM=xtermSECRET=qiwangyueHOME=/root
從上邊的例子中可以看出,我們?cè)O(shè)置的環(huán)境變量信息其實(shí)都存儲(chǔ)在操作系統(tǒng)的文件夾中冈绊,很容易被有相應(yīng)權(quán)限的用戶獲取到侠鳄。讀者可能會(huì)問(wèn),為啥不加密存儲(chǔ)呢死宣?其實(shí)加存儲(chǔ)只是將問(wèn)題推到另外一個(gè)緩解伟恶,我們?nèi)绾卧谌萜髦邪踩墨@取解密用的秘鑰?因此本質(zhì)上問(wèn)題還是沒(méi)有被解決毅该。
因此筆者強(qiáng)烈建議閱讀這篇文章的同學(xué)博秫,如果自己負(fù)責(zé)某個(gè)核心應(yīng)用的開(kāi)發(fā)和運(yùn)維,那么請(qǐng)馬上評(píng)估自己的環(huán)境鹃骂,首先解決容器默認(rèn)以root運(yùn)行的這種情況台盯,原因很簡(jiǎn)單:容器中的root等同于宿主機(jī)上的root罢绽,容器實(shí)例被攻破就等同于這臺(tái)機(jī)器以及運(yùn)行在這臺(tái)機(jī)器上的所有應(yīng)用被攻破畏线,如果你不想因此而被炒魷魚(yú),請(qǐng)馬上行動(dòng)良价。
好了寝殴,今天這篇文章的內(nèi)容就這么多了,咱們下篇文章繼續(xù)討論容器的運(yùn)行安全明垢,這是很多同學(xué)很熟悉的容器安全領(lǐng)域蚣常,敬請(qǐng)期待!