最近項(xiàng)目上在引入AWS Secrets Manager作為RDS密碼管理以取代直接將密碼放在config中的實(shí)現(xiàn)烘绽,同時(shí)要加入secretsmanger的password auto-rotation嘿辟,看似平平無奇的功能例诀,卻著實(shí)讓人踩了很多坑,所以我決定吧這個(gè)悲傷的故事寫成一篇博客(因?yàn)槲业牟┛蚑MD一年沒更新了)菇民。
首先來看看secretsmanger是什么呢禁筏?顧名思義持钉,就是一個(gè)管理的密碼serverless的服務(wù),將通常保存在配置文件中的敏感數(shù)據(jù)如密碼篱昔,用戶名保存到secretsmanger中每强,可以是諸如數(shù)據(jù)庫(kù)連接憑證,也可以單純的存儲(chǔ)其它的第三方登錄憑證州刽。
AWS Secrets Manager helps you protect secrets needed to access your applications, services, and IT resources. The service enables you to easily rotate, manage, and retrieve database credentials, API keys, and other secrets throughout their lifecycle.
AWS Secrets Manager 以key/value pair的方式存儲(chǔ)secrets的內(nèi)容空执,同時(shí)支持secrets cross region replication,rotation等穗椅, 目前很多aws的服務(wù)如RDS等都支持了rotation功能辨绊,第三方的secrets也可以通過實(shí)現(xiàn)自己的lambda進(jìn)行rotaion。
為什么要用secretsmanager
我一直信奉大道至簡(jiǎn)的理念匹表,如果沒有迫切的需求或切實(shí)的收益而增加一個(gè)依賴或服務(wù)门坷,就是徒增(腦)煩惱(殘)。
所以為什么要加入它呢袍镀?
增加安全性:顯而易見默蚌,敏感信息將不再存儲(chǔ)在代碼中,同時(shí)如果項(xiàng)目也是構(gòu)建在AWS之上的苇羡,那么于其它服務(wù)的深度集成也是十分便利的
地區(qū)法律或規(guī)定的要求:可能受制于地方法律
或在項(xiàng)目合同要求密碼需要rotaion绸吸,audit等等功能,自己再去實(shí)現(xiàn)這些東西顯然是不現(xiàn)實(shí)的。
便于多服務(wù)下的統(tǒng)一密碼管理锦茁,對(duì)應(yīng)于微服務(wù)或serverless的場(chǎng)景
先來回國(guó)一下, 在沒有引入諸如secretsmanager服務(wù)之前攘轩,我們通常如何管理密碼的呢?
-
純文本存儲(chǔ)
一般的項(xiàng)目有可能直接就將密碼存到config中码俩,可能并不加密(真實(shí)case撑刺,并不是編出來的),出于安全考慮握玛,這個(gè)config一般不會(huì)被git trace够傍,當(dāng)然頭鐵放進(jìn)去在天朝也不違法。那這樣的安全問題就顯而易見挠铲,代碼泄露或者服務(wù)器被偷都會(huì)將密碼冕屯。
但是,這就是爛的設(shè)計(jì)嗎拂苹?我覺得不一定安聘,至少分情況:如果一個(gè)應(yīng)用的安全要求并沒有太高,如這個(gè)博客應(yīng)用瓢棒,這樣的設(shè)計(jì)我認(rèn)為恰好是合適的,我發(fā)了篇文章手動(dòng)備個(gè)份浴韭,即便整個(gè)被黑了還不是重新部署一下的事情。如果引入我下面舉例的實(shí)現(xiàn)脯宿,安全性提升帶來的收益并不與付出和復(fù)雜性相等念颈。所以我認(rèn)為這樣的方式也是有其應(yīng)用場(chǎng)景與價(jià)值的。
-
環(huán)境變量載入
將密碼作為環(huán)境變量進(jìn)行載人连霉,在container或主機(jī)終止時(shí)榴芳,密碼信息便無法獲取,非敏感配置便可以放到config中并加入version control中跺撼。這樣做就需要在服務(wù)或容器或主機(jī)啟動(dòng)時(shí)主動(dòng)注入環(huán)境變量窟感,如果集成了如github action這樣可以支持密碼存儲(chǔ)的CI/CD工具,那么也可以以比較方便且安全的方式將密碼信息注入到環(huán)境變量中歉井。但如果手動(dòng)部署(不要奇怪柿祈,即便2022,不是所有項(xiàng)目都前后分離哩至,也不是所有項(xiàng)目都有CI/CD), 那么啟動(dòng)參數(shù)將會(huì)爆炸躏嚎。
-
存儲(chǔ)加密密碼文本
在這種方式下我們將敏感信息通過密鑰進(jìn)行加密,在容器&服務(wù)啟動(dòng)或在密碼需要被使用時(shí)進(jìn)行解密憨募,解密的密鑰一般也可以通過環(huán)境變量在容器&服務(wù)啟動(dòng)時(shí)注入紧索。
-
config server
可以將密碼等信息統(tǒng)一的存儲(chǔ)到config server中如Spring Cloud Config,與應(yīng)用服務(wù)相解耦菜谣,減少脫庫(kù)泄露密碼的風(fēng)險(xiǎn)珠漂,同時(shí)晚缩,config server在微服務(wù)的項(xiàng)目中可以有效的減少密碼和其它配置信息的冗余。
-
Secrets-manager platform
相較于config server媳危,secrets-manager 更關(guān)注于密碼存儲(chǔ)的安全性荞彼,如加入secrets-manager自己的ACL, audit, secrets-rotation等功能,同時(shí)復(fù)雜度也會(huì)進(jìn)一步的提升待笑。AWS Secrets Manager 便是其中的一種鸣皂,還有如Valut等(就是為了舉例搜到的,我也沒用過)暮蹂。
怎么用AWS Secrets Manager
secrets-manager的使用看上去還是很簡(jiǎn)單的寞缝,如果你使用過其它AWS的服務(wù),那么它的套路也是熟悉的配方仰泻,我們可以通過下面的cloudformation來看一下一個(gè)比較完整的case:
也可以參考官方文檔中的sample
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::SecretsManager-2020-07-23
Parameters:
DatabaseARN:
Type: String
KmsKeyARN:
Type: String
SecurityGroup:
Type: String
VpcSubnets:
Type: String
Resources:
PostgresSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: secretsName
KmsKeyId: !Ref KmsKeyARN
GenerateSecretString:
SecretStringTemplate: !Sub '{"username": "postgres", "dbname": "postgres","ssl": false}'
GenerateStringKey: password
PasswordLength: 12
ExcludeCharacters: "\"@/\\"
PostgresAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
SecretId: !Ref PostgresSecret
TargetId: !Ref DatabaseARN
TargetType: AWS::RDS::DBInstance
PostgresSecretRotation:
Type: AWS::SecretsManager::RotationSchedule
DependsOn: PostgresAttachment
Properties:
SecretId: !Ref PostgresSecret
HostedRotationLambda:
RotationType: PostgreSQLSingleUser
RotationLambdaName: PostgreSecretsManagerRotationLambda
KmsKeyArn: !Ref KmsKeyARN
VpcSecurityGroupIds: !Ref SecurityGroup
VpcSubnetIds: !Ref VpcSubnets
RotationRules:
AutomaticallyAfterDays: 60
從以上的sample中我們可以看到主要有三個(gè)resource: 密碼自身荆陆、與RDS attach,以及rotation的需要的資源。我們可以逐個(gè)分析一下
-
AWS::SecretsManager::Secret
密碼自身集侯,可以看到上面的sample中密碼是自動(dòng)生成的被啼,我們只是做了簡(jiǎn)單的規(guī)約。同時(shí)我們需要指定一個(gè)kms 對(duì)我們的secrets進(jìn)行加密棠枉。
這里比較tricky的一點(diǎn)是浓体,如果我們想要只想存儲(chǔ)一個(gè)非rotation的密碼,是沒辦法直接在cloudformation中完成的辈讶,我們不能把密碼直接寫在cloudformation中命浴,即便用參數(shù)的方式傳入,stack上的parameter是明文顯示的荞估,所以如只是存儲(chǔ)非rotaiton的secrets咳促,最好在stack生成之后在console上手動(dòng)update。
-
AWS::SecretsManager::SecretTargetAttachment
該資源用于將創(chuàng)建的secrets與數(shù)據(jù)庫(kù)進(jìn)行相關(guān)聯(lián)勘伺,secrets manage目前可以支持一下服務(wù)的attach和rotaion:
Amazon Aurora on Amazon RDS
MySQL on Amazon RDS
PostgreSQL on Amazon RDS
Oracle on Amazon RDS
MariaDB on Amazon RDS
Microsoft SQL Server on Amazon RDS
Amazon DocumentDB
Amazon Redshift
-
AWS::SecretsManager::RotationSchedule
定義secrets rotaion的規(guī)則。上面列出的服務(wù)都已經(jīng)很好的支持的rotation褂删,WAS官方提供了lamba的實(shí)現(xiàn)飞醉,HostedRotationLambda 會(huì)啟動(dòng)一個(gè)Nested lamba來進(jìn)行roation,你只需要配置好相關(guān)的參數(shù)即可屯阀。如果需要對(duì)非上述服務(wù)的密碼或第三方的其它密碼也進(jìn)行rotaiton缅帘,則需要顯示部署一個(gè)進(jìn)行rotaion的lambda來實(shí)現(xiàn)rotaiton。需要為rotaitonlambda指定相應(yīng)的secuirty group和subnets使其可以訪問目標(biāo)服務(wù)难衰。
遇到了哪些坑钦无?
上面的一切都看似簡(jiǎn)單且美好,但在使用它之前你可能需要看看我遇到的這些坑.
-
對(duì)于已經(jīng)存在的用戶盖袭,需要在第一次進(jìn)行鏈接時(shí)手動(dòng)更新密碼
一般使用這些服務(wù)時(shí)可能都會(huì)有As code(infrastructure as code)的要求, 我們?nèi)绻苯訉⒚艽ahard code到cloudformation或者以參數(shù)的方式傳入失暂,那么密碼都是可見的彼宠,顯然不能這樣做,所以可能都需要使用GenerateSecretString的方式弟塞。如果是創(chuàng)建一個(gè)新的用戶和密碼凭峡,那是沒問題的,但是如果是為一個(gè)已經(jīng)存在的用戶進(jìn)行配置决记,那么就會(huì)有問題摧冀,因?yàn)樵撚脩艉兔艽a已經(jīng)存在,且密碼和generate出來的那個(gè)不一樣系宫,這樣secrets-manager便無法連接到目標(biāo)服務(wù)索昂。如果enable了auto-rotation,那么在stack創(chuàng)建時(shí)就會(huì)自動(dòng)進(jìn)行一次rotation扩借,因?yàn)檫B接無法成功楼镐,所以rotation也會(huì)失敗。因此在第一次stack創(chuàng)建時(shí)往枷,就得手動(dòng)的將服務(wù)的密碼更新為generate出來的那個(gè)框产,這樣secrets-manager才可以成功進(jìn)行鏈接和rotation。但這顯然不是一個(gè)好的實(shí)踐错洁,也沒找到什么好的辦法秉宿,好在只是在第一次stack創(chuàng)建時(shí)需要的操作。
-
無法指定具體的rotation time
我們?cè)谏厦娴腸loudformation中可以看到在enable auto-rotation的時(shí)候指定了
AutomaticallyAfterDays
, 那么secrets-manager會(huì)在指定N天后的什么時(shí)候發(fā)生rotation呢屯碴?答案就是鬼知道描睦,aws會(huì)在那一天的隨機(jī)一個(gè)時(shí)刻進(jìn)行更新,但總之我們是不可控的导而,所以最大可能有48h的一個(gè)時(shí)間差忱叭。但從文檔上看現(xiàn)在如果不使用cloudformation是可以指定具體的更新時(shí)間的,可以指定一個(gè)cronjob的expression今艺,但cloudformation不支持就很奇怪韵丑,如果建好了在去手動(dòng)更改,那As code不成了笑話了虚缎,撵彻, -
service無法被告知password rotation
當(dāng)我們將secrets migrate到secrets-manager之后,應(yīng)用服務(wù)就不需要在存儲(chǔ)密文或者加密之后的密文啦实牡,那么我們?nèi)绾潍@取密文呢陌僵?aws給出的方案是用aws-sdk進(jìn)行獲取,本質(zhì)就是一個(gè)http請(qǐng)求创坞,只需要給需要密文的服務(wù)
secretsmanager:GetSecretValue
的權(quán)限就可以了碗短。但讓人難受的是,http 是無狀態(tài)的题涨,如果我們開啟了auto-rotation偎谁,我們需要主動(dòng)去請(qǐng)求secrets-manager獲取最新的密碼信息总滩。更坑的是由于第二點(diǎn)所說的我們無法指定具體的rotation時(shí)間,這就意味著應(yīng)用服務(wù)可能會(huì)有downtime搭盾。
怎么填坑咳秉?
從上面那幾個(gè)坑中,影響最大的可能就是第三個(gè)坑了鸯隅,因?yàn)槿绻肓艘粋€(gè)新的服務(wù)帶來的居然是downtime的話澜建,顯然是無法讓人接受的。那么怎么去解決這個(gè)問題呢蝌以?
-
雙用戶
這也是aws官方給出的一個(gè)解決方案炕舵,比如對(duì)一個(gè)數(shù)據(jù)庫(kù)給應(yīng)用服務(wù)兩個(gè)用戶,兩個(gè)用戶的rotaiton時(shí)間一致跟畅,但是有幾天的間隔咽筋,比如都是都是每60天rotation一次,然后連個(gè)用戶的創(chuàng)建時(shí)間隔個(gè)十天二十天徊件。應(yīng)用服務(wù)同一時(shí)刻只會(huì)使用一個(gè)用戶奸攻,在應(yīng)用服務(wù)啟動(dòng)時(shí)可以獲取最近進(jìn)行了rotation的用戶建立連接池,同時(shí)設(shè)置幾個(gè)cronjob虱痕,如每天活著沒9天(因?yàn)楦糁?0天)再?gòu)膕ecrets-manager拿到最近更新的secrets來建立新的連接池睹耐。
-
連接檢測(cè)
只使用一個(gè)用戶,如果是數(shù)據(jù)庫(kù)部翘,就在更新將到來的24h內(nèi)(48h是極端情況)在進(jìn)行正真的查詢前加一個(gè)連接檢測(cè)硝训,看看當(dāng)前的連接是否可用,如果不可用且報(bào)了auth的錯(cuò)誤新思,就從secrets-manager獲取最新的密鑰更新連接
-
自建lambda
對(duì)于前面列出的已經(jīng)支持auto-rotaiton的服務(wù)aws已經(jīng)提供的對(duì)應(yīng)的lambda窖梁,我們只需要指定類型即可,但我們也可以指定自己的rotation lambda夹囚,這樣就可以在進(jìn)行rotation的時(shí)候在lambda中自己實(shí)現(xiàn)對(duì)應(yīng)用服務(wù)的通知纵刘,可以直接請(qǐng)求api活著通過使用sqs等。
-
手動(dòng)更改吧
因?yàn)槟壳癱onsole上是可以指定具體時(shí)間的崔兴,那么如果對(duì)infrastructure as code沒有什么要求或者就沒有as code彰导,那可以直接在aws的console上指定具體的更新時(shí)間,比如凌晨3點(diǎn)更新敲茄,然后在應(yīng)用服務(wù)器上也定時(shí)的去獲取新的密鑰,如果是一些明顯有時(shí)間區(qū)域的應(yīng)用山析,那么可能也是一個(gè)可選的方案堰燎。
以上就是全部,個(gè)人感覺笋轨,其實(shí)secretsmanger特別是rotation這里坑還是蠻大的秆剪,如果不能為業(yè)務(wù)帶來明顯的價(jià)值赊淑,還是需要慎重考慮需不需要,如密碼的更新即便在得到了通知之后還需要考慮是要建立新的連接池還是可以支持動(dòng)態(tài)密碼仅讽,這些細(xì)節(jié)大概率都會(huì)增加系統(tǒng)復(fù)雜度陶缺。
PS:可能是一年多以來打中文最多的一次了了,發(fā)現(xiàn)水果家自帶的中文輸入法聯(lián)想整的是狗屎都不如洁灵。