はじめに
こんにちは、おのやんです。
みなさん、RDSのパスワードは暗号化できていますか?私はできていませんでした(超絶懺悔)。
CloudFormationのテンプレートにて、RDSの設定を記述する場合があるかと思います。この際、CloudFormationテンプレートに直接パスワードなどの機密情報を記述するのは、セキュリティ上のベストプラクティスに反します。
テンプレートはソースコードリポジトリに保存されることも多く、手違いで公開されてしまうとパスワードが流出するリスクもあるため、平文記述は極力避けたいです。
ということで、今回はRDSのパスワードをSecrets Managerで管理する構成を、CloudFormationテンプレートに記述してみたいと思います。
危ない例
こちらは危険なCloudFormationテンプレートの例です。パスワードが平文で記述されています。このテンプレートがパブリックな場所に置かれると、パスワードが流出してしまいます。
Resources:
RDSDBInstance:
Type: "AWS::RDS::DBInstance"
Properties:
DBInstanceIdentifier: sample-rds
DBName: db
DBInstanceClass: !Ref DBInstanceClass
Engine: mysql
EngineVersion: 8.0
Port: 3306
PubliclyAccessible: false
AllocatedStorage: "20"
MasterUsername: admin
MasterUserPassword: password # パスワードが平文で保存されている!これは危ない!
DBSubnetGroupName: !Ref SubnetGroup
セキュアな例
こちらはセキュアなCloudFormationテンプレートの例です。RDSのパスワードをSecrets Managetで管理してあるため、セキュリティをより確保しながらRDSを構築することができます。
Resources:
RDSSecret: # ここでRDSのパスワードとなるランダム文字列を生成
Type: "AWS::SecretsManager::Secret"
Properties:
Name: "RDSSecret"
Description: "RDS password for my RDS instance"
GenerateSecretString:
SecretStringTemplate: '{"username": "admin"}'
GenerateStringKey: "password"
PasswordLength: 16
ExcludeCharacters: '"@/\'
RDSDBInstance:
Type: "AWS::RDS::DBInstance"
Properties:
DBInstanceIdentifier: sample-rds
DBName: db
DBInstanceClass: !Ref DBInstanceClass
Engine: mysql
EngineVersion: 8.0
Port: 3306
PubliclyAccessible: false
AllocatedStorage: "20"
MasterUsername: admin
MasterUserPassword: !Sub '{{resolve:secretsmanager:${MyRDSSecret}:SecretString:password}}' # Secrets Managerのランダム文字列を間接的に参照!これなら安心!
DBSubnetGroupName: !Ref SubnetGroup
CloudFormationテンプレートのSecret Manger部分の詳細
さきほどのCloudFormationテンプレートのSecret Manger部分を再度示します。
Resources:
RDSSecret:
Type: "AWS::SecretsManager::Secret"
Properties:
Name: "MyRDSSecret"
Description: "RDS password for my RDS instance"
GenerateSecretString:
SecretStringTemplate: '{"username": "admin"}'
GenerateStringKey: "password"
PasswordLength: 16
ExcludeCharacters: '"@/\'
SecretStringTemplate
の部分では、シークレットに含める項目をJSON文字列として定義しています。ここに記載の通り、生成されるシークレットには"username":"admin"
の項目が含まれることになります。
GenerateStringKey
の部分は、上記のSecretStringTemplate
で定義したJSONに追加されるキーを示します。
PasswordLength
の部分では、ランダムに生成するパスワードの長さを指定します。ここでは16
と指定していますので、16文字のランダムな文字列が生成されることになります。
ExcludeCharacters
の部分では、パスワード生成時に含めない文字を指定します。特定のシステムで特定の文字列が使えない、みたいな場合に、それらの文字を除外します。今回では、"
、@
、/
、\
の文字を除外しています。
これらの設定を踏まえると、16文字のランダムなパスワードが生成され、password
というキーにランダムパスワードが対応付けられます。そして、JSONに{"username": "admin"}
が追加されます。最終的なシークレットは、以下の通りになります。
{
"username": "admin",
"password": "<16文字のランダムなパスワード>"
}
CloudFormationテンプレートのRDS部分の詳細
さきほどのCloudFormationテンプレートのRDS部分を再度示します。
RDSDBInstance:
Type: "AWS::RDS::DBInstance"
Properties:
DBInstanceIdentifier: sample-rds
DBName: db
DBInstanceClass: !Ref DBInstanceClass
Engine: mysql
EngineVersion: 8.0
Port: 3306
PubliclyAccessible: false
AllocatedStorage: "20"
MasterUsername: admin
MasterUserPassword: !Sub '{{resolve:secretsmanager:${RDSSecret}:SecretString:password}}'
DBSubnetGroupName: !Ref SubnetGroup
こちらの項目にて、Secrets Managerに保存しているシークレットを参照しています。
MasterUserPassword: !Sub '{{resolve:secretsmanager:${RDSSecret}:SecretString:password}}'
基本的には、RDSSecret
という名前でシークレットを指定しています。そして、このシークレットが持つpassword
という属性値をSecrets Managerから取得しています。取得したこの値は、RDSインスタンスのマスターユーザーのパスワードとして設定されます。
また、{{resolve:secretsmanager:${RDSSecret}:SecretString:password}}
の部分では、Secrets Managerのシークレット値を解決しています。secretsmanager
の後に続くARNによって、特定のシークレットを指定しています。そして、SecretString:password
の部分で、シークレットのJSON構造からパスワードを取得しているという感じです。
さいごに
RDSのパスワードをCloudFormationテンプレートで設定する際は、Secrets Managerを使うことで安全に管理できます。平文で保存することもないため、テンプレートからパスワードが漏れるといった心配もないですね。
本記事がお役に立てば幸いです。では!