AWS Secrets Manager 踩坑記

最近項目上在引入AWS Secrets Manager作為RDS密碼管理以取代直接將密碼放在config中的實現(xiàn),同時要加入secretsmanger的password auto-rotation,看似平平無奇的功能,卻著實讓人踩了很多坑,所以我決定吧這個悲傷的故事寫成一篇博客(因為我的博客TMD一年沒更新了)。

首先來看看secretsmanger是什么呢?顧名思義,就是一個管理的密碼serverless的服務,將通常保存在配置文件中的敏感數(shù)據(jù)如密碼,用戶名保存到secretsmanger中,可以是諸如數(shù)據(jù)庫連接憑證,也可以單純的存儲其它的第三方登錄憑證。

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的方式存儲secrets的內(nèi)容,同時支持secrets cross region replication,rotation等, 目前很多aws的服務如RDS等都支持了rotation功能,第三方的secrets也可以通過實現(xiàn)自己的lambda進行rotaion。

為什么要用secretsmanager

我一直信奉大道至簡的理念,如果沒有迫切的需求或切實的收益而增加一個依賴或服務,就是徒增(腦)煩惱(殘)。

所以為什么要加入它呢?

  1. 增加安全性:顯而易見,敏感信息將不再存儲在代碼中,同時如果項目也是構(gòu)建在AWS之上的,那么于其它服務的深度集成也是十分便利的

  2. 地區(qū)法律或規(guī)定的要求:可能受制于地方法律

  3. 或在項目合同要求密碼需要rotaion,audit等等功能,自己再去實現(xiàn)這些東西顯然是不現(xiàn)實的。

  4. 便于多服務下的統(tǒng)一密碼管理,對應于微服務或serverless的場景

先來回國一下, 在沒有引入諸如secretsmanager服務之前,我們通常如何管理密碼的呢?

  1. 純文本存儲

    一般的項目有可能直接就將密碼存到config中,可能并不加密(真實case,并不是編出來的),出于安全考慮,這個config一般不會被git trace,當然頭鐵放進去在天朝也不違法。那這樣的安全問題就顯而易見,代碼泄露或者服務器被偷都會將密碼。

    但是,這就是爛的設(shè)計嗎?我覺得不一定,至少分情況:如果一個應用的安全要求并沒有太高,如這個博客應用,這樣的設(shè)計我認為恰好是合適的,我發(fā)了篇文章手動備個份,即便整個被黑了還不是重新部署一下的事情。如果引入我下面舉例的實現(xiàn),安全性提升帶來的收益并不與付出和復雜性相等。所以我認為這樣的方式也是有其應用場景與價值的。

  2. 環(huán)境變量載入

    將密碼作為環(huán)境變量進行載人,在container或主機終止時,密碼信息便無法獲取,非敏感配置便可以放到config中并加入version control中。這樣做就需要在服務或容器或主機啟動時主動注入環(huán)境變量,如果集成了如github action這樣可以支持密碼存儲的CI/CD工具,那么也可以以比較方便且安全的方式將密碼信息注入到環(huán)境變量中。但如果手動部署(不要奇怪,即便2022,不是所有項目都前后分離,也不是所有項目都有CI/CD), 那么啟動參數(shù)將會爆炸。

  3. 存儲加密密碼文本

    在這種方式下我們將敏感信息通過密鑰進行加密,在容器&服務啟動或在密碼需要被使用時進行解密,解密的密鑰一般也可以通過環(huán)境變量在容器&服務啟動時注入。

  4. config server

    可以將密碼等信息統(tǒng)一的存儲到config server中如Spring Cloud Config,與應用服務相解耦,減少脫庫泄露密碼的風險,同時,config server在微服務的項目中可以有效的減少密碼和其它配置信息的冗余。

  5. Secrets-manager platform

    相較于config server,secrets-manager 更關(guān)注于密碼存儲的安全性,如加入secrets-manager自己的ACL, audit, secrets-rotation等功能,同時復雜度也會進一步的提升。AWS Secrets Manager 便是其中的一種,還有如Valut等(就是為了舉例搜到的,我也沒用過)。

怎么用AWS Secrets Manager

secrets-manager的使用看上去還是很簡單的,如果你使用過其它AWS的服務,那么它的套路也是熟悉的配方,我們可以通過下面的cloudformation來看一下一個比較完整的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中我們可以看到主要有三個resource: 密碼自身、與RDS attach,以及rotation的需要的資源。我們可以逐個分析一下

  1. AWS::SecretsManager::Secret

    密碼自身,可以看到上面的sample中密碼是自動生成的,我們只是做了簡單的規(guī)約。同時我們需要指定一個kms 對我們的secrets進行加密。

    這里比較tricky的一點是,如果我們想要只想存儲一個非rotation的密碼,是沒辦法直接在cloudformation中完成的,我們不能把密碼直接寫在cloudformation中,即便用參數(shù)的方式傳入,stack上的parameter是明文顯示的,所以如只是存儲非rotaiton的secrets,最好在stack生成之后在console上手動update。

  2. AWS::SecretsManager::SecretTargetAttachment

    該資源用于將創(chuàng)建的secrets與數(shù)據(jù)庫進行相關(guān)聯(lián),secrets manage目前可以支持一下服務的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

  3. AWS::SecretsManager::RotationSchedule

    定義secrets rotaion的規(guī)則。上面列出的服務都已經(jīng)很好的支持的rotation,WAS官方提供了lamba的實現(xiàn),HostedRotationLambda 會啟動一個Nested lamba來進行roation,你只需要配置好相關(guān)的參數(shù)即可。如果需要對非上述服務的密碼或第三方的其它密碼也進行rotaiton,則需要顯示部署一個進行rotaion的lambda來實現(xiàn)rotaiton。需要為rotaitonlambda指定相應的secuirty group和subnets使其可以訪問目標服務。

遇到了哪些坑?

上面的一切都看似簡單且美好,但在使用它之前你可能需要看看我遇到的這些坑.

  1. 對于已經(jīng)存在的用戶,需要在第一次進行鏈接時手動更新密碼

    一般使用這些服務時可能都會有As code(infrastructure as code)的要求, 我們?nèi)绻苯訉⒚艽ahard code到cloudformation或者以參數(shù)的方式傳入,那么密碼都是可見的,顯然不能這樣做,所以可能都需要使用GenerateSecretString的方式。如果是創(chuàng)建一個新的用戶和密碼,那是沒問題的,但是如果是為一個已經(jīng)存在的用戶進行配置,那么就會有問題,因為該用戶和密碼已經(jīng)存在,且密碼和generate出來的那個不一樣,這樣secrets-manager便無法連接到目標服務。如果enable了auto-rotation,那么在stack創(chuàng)建時就會自動進行一次rotation,因為連接無法成功,所以rotation也會失敗。因此在第一次stack創(chuàng)建時,就得手動的將服務的密碼更新為generate出來的那個,這樣secrets-manager才可以成功進行鏈接和rotation。但這顯然不是一個好的實踐,也沒找到什么好的辦法,好在只是在第一次stack創(chuàng)建時需要的操作。

  2. 無法指定具體的rotation time

    我們在上面的cloudformation中可以看到在enable auto-rotation的時候指定了AutomaticallyAfterDays, 那么secrets-manager會在指定N天后的什么時候發(fā)生rotation呢?答案就是鬼知道,aws會在那一天的隨機一個時刻進行更新,但總之我們是不可控的,所以最大可能有48h的一個時間差。但從文檔上看現(xiàn)在如果不使用cloudformation是可以指定具體的更新時間的,可以指定一個cronjob的expression,但cloudformation不支持就很奇怪,如果建好了在去手動更改,那As code不成了笑話了,,

  3. service無法被告知password rotation

    當我們將secrets migrate到secrets-manager之后,應用服務就不需要在存儲密文或者加密之后的密文啦,那么我們?nèi)绾潍@取密文呢?aws給出的方案是用aws-sdk進行獲取,本質(zhì)就是一個http請求,只需要給需要密文的服務secretsmanager:GetSecretValue的權(quán)限就可以了。但讓人難受的是,http 是無狀態(tài)的,如果我們開啟了auto-rotation,我們需要主動去請求secrets-manager獲取最新的密碼信息。更坑的是由于第二點所說的我們無法指定具體的rotation時間,這就意味著應用服務可能會有downtime。

怎么填坑?

從上面那幾個坑中,影響最大的可能就是第三個坑了,因為如果引入了一個新的服務帶來的居然是downtime的話,顯然是無法讓人接受的。那么怎么去解決這個問題呢?

  1. 雙用戶

    這也是aws官方給出的一個解決方案,比如對一個數(shù)據(jù)庫給應用服務兩個用戶,兩個用戶的rotaiton時間一致,但是有幾天的間隔,比如都是都是每60天rotation一次,然后連個用戶的創(chuàng)建時間隔個十天二十天。應用服務同一時刻只會使用一個用戶,在應用服務啟動時可以獲取最近進行了rotation的用戶建立連接池,同時設(shè)置幾個cronjob,如每天活著沒9天(因為隔著10天)再從secrets-manager拿到最近更新的secrets來建立新的連接池。

  2. 連接檢測

    只使用一個用戶,如果是數(shù)據(jù)庫,就在更新將到來的24h內(nèi)(48h是極端情況)在進行正真的查詢前加一個連接檢測,看看當前的連接是否可用,如果不可用且報了auth的錯誤,就從secrets-manager獲取最新的密鑰更新連接

  3. 自建lambda

    對于前面列出的已經(jīng)支持auto-rotaiton的服務aws已經(jīng)提供的對應的lambda,我們只需要指定類型即可,但我們也可以指定自己的rotation lambda,這樣就可以在進行rotation的時候在lambda中自己實現(xiàn)對應用服務的通知,可以直接請求api活著通過使用sqs等。

  4. 手動更改吧

    因為目前console上是可以指定具體時間的,那么如果對infrastructure as code沒有什么要求或者就沒有as code,那可以直接在aws的console上指定具體的更新時間,比如凌晨3點更新,然后在應用服務器上也定時的去獲取新的密鑰,如果是一些明顯有時間區(qū)域的應用,那么可能也是一個可選的方案。

以上就是全部,個人感覺,其實secretsmanger特別是rotation這里坑還是蠻大的,如果不能為業(yè)務帶來明顯的價值,還是需要慎重考慮需不需要,如密碼的更新即便在得到了通知之后還需要考慮是要建立新的連接池還是可以支持動態(tài)密碼,這些細節(jié)大概率都會增加系統(tǒng)復雜度。

PS:可能是一年多以來打中文最多的一次了了,發(fā)現(xiàn)水果家自帶的中文輸入法聯(lián)想整的是狗屎都不如。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容