Amazon Auroraで、IAMデータベース認証とAWS Secrets Managerによるパスワードの自動ローテーションを設定してみる

記事タイトルとURLをコピーする

こんにちは。
アプリケーションサービス部、DevOps担当の兼安です。
今回は、Amazon AuroraでIAMデータベース認証とAWS Secrets Managerによるパスワードの自動ローテーション、両方を設定してみました。

本記事のターゲット

  • アプリケーションとデータベースの接続、およびデータベース運用の知識がある方
  • CloudFormationテンプレートの知識がある方

今回はAWSにおけるデータベース接続のパスワード管理がテーマのお話なので、開発と運用のイメージがつく方を対象としています。
本記事ではCloudFormationテンプレートを用いて設定を適用するため、CloudFormationの基本的な知識が必要です。

AWSのIAMデータベース認証とは

AWSのIAMデータベース認証は、データベースへの接続において、パスワードを使用せず、認証トークンを使用します。

docs.aws.amazon.com

IAMデータベース認証の有効化の手順は、以下の通りです。

  1. Amazon Auroraクラスターの設定で、IAMデータベース認証を有効にする
  2. Amazon Aurora側でIAMデータベース認証用のデータベースユーザー(以下、DBユーザー)を作成する
  3. EC2インスタンスやLambdaなど、データベースに接続するコンピューティングリソースのIAMロールに、作成したユーザーでのIAMデータベース認証を可能にするポリシーを付与する

1.のIAMデータベース認証の有効化は、Auroraクラスターの編集画面でIAM データベース認証にチェックを入れることで可能です。
2.3.の手順は本記事の後半に記述しています。

Auroraクラスターの編集画面で「IAM データベース認証」にチェック

コンピューティングリソースからのIAMデータベース接続の流れは、以下の構成図のイメージになります。

IAMデータベース認証で認証トークンで接続するイメージ

IAMデータベース認証のメリットは、パスワードが漏洩するリスクが低いことです。
認証トークンは15分の有効期限があり、その後は再度認証トークンを取得する必要があります。
故に、認証トークンが漏洩しても、15分後には無効になるため、セキュリティリスクが低いと言えます。

私の経験上、システムの運用では、どうしてもSQLを実行する必要がある場面があります。
したがって、運用のためにパスワードをどこかに記録しておき厳重に管理する必要があります。
パスワード自体が管理不要になった場合、運用の手間とリスクが大幅に軽減されると言えるでしょう。

AWSのIAMデータベース認証の注意事項

AWSのIAMデータベース認証には注意事項があります。
公式ページの注意事項を引用します。

一般に、アプリケーションが 1 秒あたり 200 未満の接続を作成し、アプリケーションコードでユーザー名とパスワードを直接管理したくない場合は、IAM データベース認証の使用を検討してください。

引用:IAM データベース認証

このような注意事項があるのは、IAMデータベース認証は接続のたびに認証トークンの生成プロセスが必要なためと考えられます。
この問題はRDS Proxyを使用することで軽減できます。

その他の補足

認証トークンの有効期限内とセッションタイムアウトは別のパラメータです。
認証トークンの有効期限切れ=セッションタイムアウトではありません。
Aurora PostgreSQLにおいては、セッションのアイドルタイムアウトは PostgreSQL 側のidle_session_timeoutパラメータで管理されます。
参考:Amazon Aurora PostgreSQL のパラメータ

AWS Secrets Managerとは

AWS Secrets Managerは、AWSが提供するシークレット管理サービスの一つです。
Amazon AuroraとAWS Secrets Managerを使用することで、データベースユーザーのパスワードの管理負担を軽減できます。

AWS Secrets Managerは、シークレットの暗号化、自動ローテーションなどの機能を提供します。
Amazon Auroraにおいては、マスターユーザーはAWS Secrets Managerとの統合が可能です。
この機能は、Auroraクラスターの編集画面でAWS Secrets Manager で管理を選択すると有効になります。

Auroraクラスターの編集画面で「AWS Secrets Manager で管理」を選択

これを利用すると、マスターユーザーのパスワードをAWS Secrets Managerに保存しておきつつ、パスワードの自動ローテーションが可能になります。
有効にしておけば、マスターユーザーのパスワードの管理が大幅に楽になり、セキュリティリスクも低減できます。

docs.aws.amazon.com

blog.serverworks.co.jp

アプリケーションからデータベースに接続する際は、アプリケーション用にマスターユーザー以外のデータベースユーザーで接続することがあります。
マスターユーザー以外のデータベースユーザーで、パスワードのローテーションを自動化するには、AWS Secrets Managerとローテーションの設定を別途作る必要があります。
少々手間がかかりますが、アプリケーション用のデータベースユーザーもパスワードの自動ローテーションを設定しておくと、セキュリティリスクをより低減させることが可能になります。

こちらの方は設定手順が多いので、今回は後述のCloudFormationテンプレートで設定しています。

アプリケーションのデータベース接続は、IAMデータベース認証とAWS Secrets Managerのどちらを使うべきか

ここは私の考えになります。
この2つはどちらが優れているかはケースによりますが、まずは以下のように使い分けをしていきたいと考えています。

  • アプリケーションからの接続はパスワード認証を使い、使用するデータベースユーザーはAWS Secrets Managerでパスワードを自動ローテーションする
  • 踏み台サーバーからのSQL実行など、運用のために使う接続はIAMデータベース認証を使用する

アプリケーションからの接続では、まずは何かあった時に疑うべき箇所が少ない方を選びたいと考えています。
そのため、特段のセキュリティ要件がなければ、認証トークンの生成プロセスが必要なIAMデータベース認証よりも、まずはパスワード認証+AWS Secrets Managerの方を優先します。
AWS Secrets Managerを使用することでパスワードのハードコーディングや設定ファイルでの管理を避けられるため、IAMデータベース認証を無理に採用する必要はないと思います。

一方で運用では、IAMデータベース認証の方が適していると考えます。
運用においては、踏み台サーバーなどから、データベースに接続します。
この時、IAMデータベース認証なら、トークンを取得してデータベースに接続します。
AWS Secrets Managerを使用する場合、AWS Secrets Managerからパスワードを取得して、データベースに接続します。
AWS Secrets Managerで自動ローテーションを有効にしていたとしても、おそらくローテーションの間隔はトークンの有効期間より長いと思うので、漏洩時のリスクがIAMデータベース認証の方が低いと思うからです。

IAMデータベース認証とAWS Secrets Managerによるパスワードの自動ローテーションを設定してみる

では、Amazon AuroraでIAMデータベース認証とAWS Secrets Managerによるパスワードの自動ローテーション、両方を設定してみます。
サンプルとして、踏み台サーバーからIAMデータベース認証での接続と、AWS Secrets Managerを使用した接続の両方を可能にします。
後者の方は、更にRDS Proxyを使用してコネクションプールも設定します。
RDS Proxyは、コネクションプールを提供するサービスです。

踏み台サーバーからIAMデータベース認証での接続と、AWS Secrets Managerを使用した接続の両方を可能にする

設定内容はCloudFormationテンプレートで書いてみました。

ソースコード

作成したCloudFormationテンプレートは3つです。
上から順番に実行していく想定です。

  • 1_vpc.yaml
    • VPC、サブネット、ルートテーブル、IGW、NATGWを作成します
  • 2_bastion.yaml:
    • 踏み台サーバーを作成します
  • 3_aurora.yaml:
    • Auroraクラスター(IAMデータベース認証 ON)、AWS Secrets Managerとローテーション、RDS Proxyを作成します。
    • Auroraクラスターは、PostgreSQLのAurora Serverless v2を使用しています。

作成するAuroraクラスターは3種類のユーザーを持たせます。

DBユーザーの種類  パスワードの自動ローテーション IAMデータベース認証
マスターユーザー あり なし
アプリケーション用DBユーザー あり なし
IAMデータベース認証DBユーザー なし あり

IAMデータベース認証だけは、3つのCloudFormationテンプレートを実行後は、踏み台サーバー(=Amazon EC2インスタンス)にアタッチしているIAMロールを調整する必要があります。
この後で調整していきます。

ソースコードのポイント:IAMデータベース認証

サンプルとして書いたCloudFormationテンプレートのポイントです。
IAMデータベース認証のポイントはここになります。

  DBCluster:
    Type: AWS::RDS::DBCluster
    Properties:
      Engine: aurora-postgresql
      DBClusterIdentifier: !Sub "${ServiceName}-${StageName}-aurora-cluster"
      EngineVersion: !Ref EngineVersion
      Port: 5432
      DatabaseName: !Sub "${ServiceName}_${StageName}_aurora_db"
      MasterUsername: "root"
      ManageMasterUserPassword: true # マスターユーザーのパスワードは自動ローテーションを設定
      EnableIAMDatabaseAuthentication: true # IAM認証を有効にする

EnableIAMDatabaseAuthenticationをTrueにすることで、IAMデータベース認証が使用可能になります。
この後は、SQLによりユーザー作成とIAMロールの編集が必要で、それは今回の記事ではCloudFormationテンプレートに含めていません。
後述の通り手動で設定しています。

ソースコードのポイント:AWS Secrets Managerによる自動ローテーション

AWS Secrets Managerによる自動ローテーションのポイントはここになります。

  # Secrets Manager
  # アプリケーションがAuroraに接続するためのパスワードを管理するとし、パスワードの長さなども設定しています
  DBSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: !Sub "${ServiceName}-${StageName}-db-secret"
      Description: "Secret for Aurora Serverless"
      GenerateSecretString:
        SecretStringTemplate: !Sub '{"username": "aurora_app_user"}'
        GenerateStringKey: password
        PasswordLength: 16
        ExcludeCharacters: "\"'@/\\"
      Tags:
        - Key: Name
          Value: !Sub "${ServiceName}-${StageName}-db-secret"
        - Key: ServiceName
          Value: !Ref ServiceName
        - Key: StageName
          Value: !Ref StageName

  # Secrets Managerのローテーションスケジュール
  # アプリケーション用データベースユーザーのパスワードを定期的にローテーションするためのスケジュールを設定しています
  # ローテーションのタイプはシングルユーザーローテーションを設定しています
  # ローテーションの間隔は90日に設定しています
  DBSecretRotationSchedule:
    Type: AWS::SecretsManager::RotationSchedule
    Properties:
      SecretId: !Ref DBSecret
      # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-hostedrotationlambda.html
      HostedRotationLambda:
        RotationType: PostgreSQLSingleUser  # シングルユーザーローテーション
        ExcludeCharacters: '"@/\'
        VpcSecurityGroupIds: !Ref DBSecretRotationScheduleSecurityGroup
        # ローテーション用のLambdaがSecrets Managerのエンドポイントからアクセスするため、NAT Gatewayを付けているサブネットを指定する
        # https://repost.aws/ja/knowledge-center/rotate-secrets-manager-secret-vpc
        VpcSubnetIds: !Join
          - ","
          - - Fn::ImportValue: !Sub "${ServiceName}-${StageName}-subnet-protected-1"
            - Fn::ImportValue: !Sub "${ServiceName}-${StageName}-subnet-protected-2"
      RotateImmediatelyOnUpdate: False
      RotationRules:
        AutomaticallyAfterDays: 90

ローテーションの動作イメージ

ローテーションは、それ用のAWS Lambda関数がAuroraSecrets Managerが更新をかけるという動きをします。
このAWS Lambda関数を作っている記述はここです。

RotationType: PostgreSQLSingleUser  # シングルユーザーローテーション

RotationTypeで指定した値に応じて、公式のテンプレートをもとにしたAWS Lambda関数ができます。

参考:AWS::SecretsManager::RotationSchedule HostedRotationLambda

このAWS Lambda関数は、更新を行うためにAWS Secrets ManagerのAPIを呼び出しが可能である必要があります。
同時にAuroraを更新する必要があるため、VPCの中いる必要があります。
これらの理由により、AWS Lambda関数は、NAT Gateayのあるプライベートサブネットに属させるか、AWS Secrets Manager側でVPCエンドポイントを作る必要があります。
本記事ではAWS Lambda関数をNAT Gateayのあるプライベートサブネットに属させることで動作させています。
なお、パブリックサブネットに属させてもAPIコールでタイムアウトになりました。

もう一つのポイントは踏み台サーバー側にアタッチしているIAMロールに、シークレットの取得許可を付与しているところです。

        - PolicyName: !Sub "${ServiceName}-${StageName}-bastion-policy"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: 
                  - secretsmanager:GetSecretValue
                  - secretsmanager:DescribeSecret
                Resource: "*" # サンプルのため全てのリソースに対して許可しています、本番環境では適切なリソースを指定してください

AWS Secrets Managerから接続情報を取得して接続

3つのCloudFormationテンプレートを実行後、AWS Secrets Managerから取得した接続情報を使って接続してみます。
本記事のCloudFormationテンプレートで作成する踏み台サーバーは、SSH接続を許可していません。
AWSマネジメントコンソール上で、Amazon EC2のコンソール操作ができるSession Managerで接続してください。

マスターユーザーでの接続

Session Managerで踏み台サーバーのコンソールを開いたら、Bashスクリプトを使って接続していきます。
最初は、マスターユーザーで接続します。

AWS Secrets Managerに、マスタユーザー用のシークレットができているのでこの名前で接続情報を取得します。

AWS Secrets Managerの一覧でマスタユーザー用のシークレットをクリック

シークレットの名前をコピー

SECRET_JSON=$(aws secretsmanager get-secret-value --secret-id "{マスタユーザー用のシークレットの名前}" --query SecretString --output json | jq -r)

aws secretsmanager get-secret-valueは以下のような形式でユーザー名とパスワードを返すので、これを解析して接続に使用します。

{
    "username": "マスターユーザーのユーザー名",
    "password": "マスターユーザーのパスワード"
}

接続情報(JSON)からユーザー名、パスワードを取得して環境変数に格納します。

DB_USER=$(echo $SECRET_JSON | jq -r .username)
DB_PASS=$(echo $SECRET_JSON | jq -r .password)

これらを使って接続ができるはずです。
psqlは別途インストールが必要です

PGPASSWORD=$DB_PASS psql -h {ライターインスタンスのエンドポイント} -U $DB_USER -d {データベース名} -p {ポート番号}

これでまず、マスターユーザーはAWS Secrets Managerでパスワードを取得してから接続する手順となっていることがわかります。
次に、アプリケーション用のDBユーザーを作ります。

アプリケーション用DBユーザーの作成

マスターユーザー同様、AWS Secrets Managerから接続情報を取得します。
AWS Secrets Managerは、マスターユーザー用ではなく、アプリケーション用DBユーザーのシークレットの方を指定します。

SECRET_JSON=$(aws secretsmanager get-secret-value --secret-id {アプリケーション用DBユーザーのシークレットの名前} --query SecretString --output json | jq -r)

アプリケーション用DBユーザーのシークレットは、aws secretsmanager get-secret-valueで以下のような形式で接続情報を返します。

{
    "dbClusterIdentifier": "DBクラスター識別子",
    "password": "パスワード",
    "dbname": "データベース名",
    "engine": "エンジン",
    "port": ポート番号,
    "host": "エンドポイント",
    "username": "DBユーザー名"
}

この中からパスワードをコピーます。
Aurora PostgreSQLにマスターユーザーでログインした状態で次のSQLでユーザーを作成します。

CREATE USER {アプリケーション用DBユーザー名} WITH LOGIN PASSWORD '{シークレットからコピーしたパスワード}';

これでユーザーが作成できました。
私が試した限り、DBユーザーを作るときはログインパスワードにAWS Secrets Managerのシークレットからコピーしたパスワードをセットする必要がありました。
DBユーザーのパスワードがAWS Secrets Managerのシークレットにあるパスワードが一致しない場合、後述のローテーションでエラーが発生するのを確認しています。
DBユーザーのパスワードとAWS Secrets Managerのシークレットにあるパスワード、両者は常に一致している必要があるようです。

アプリケーション用DBユーザーでの接続

AWS Secrets Managerからアプリケーション用DBユーザーのシークレットから接続情報を取得します。

SECRET_JSON=$(aws secretsmanager get-secret-value --secret-id {アプリケーション用DBユーザーのシークレットの名前} --query SecretString --output json | jq -r)

接続情報(JSON)からエンドポイント、DB名、ユーザー名、パスワードなどを取得して環境変数に格納します。

DB_HOST=$(echo $SECRET_JSON | jq -r .host)
DB_USER=$(echo $SECRET_JSON | jq -r .username)
DB_PASS=$(echo $SECRET_JSON | jq -r .password)
DB_NAME=$(echo $SECRET_JSON | jq -r .dbname)
DB_PORT=$(echo $SECRET_JSON | jq -r .port)

それらを使って接続ができるはずです。

PGPASSWORD=$DB_PASS psql -h $DB_HOST -U $DB_USER -d $DB_NAME -p $DB_PORT

一度接続が確認ができたら、シークレットのローテーションを実行してパスワードを更新してみます。
シークレットのローテーションタブにある、すぐにシークレットをローテーションさせるをクリックします。

「すぐにシークレットをローテーションさせる」をクリック

シークレットのローテーションを実行した後に、もう一度シークレットを取得してみます。

aws secretsmanager get-secret-value --secret-id {アプリケーション用DBユーザーのシークレットの名前} --query SecretString --output json | jq -r

返ってくるJSONに記載されているパスワードが前と違っているはずです。
この状態でこの項の最初にあるSECRET_JSON=$(aws secretsmanager get-secret-value・・・からやり直すと、引き続き接続が可能なはずです。
これで、AWS Secrets Managerがパスワードをローテーションできることが確認できます。

RDS Proxy経由での接続

アプリケーション用DBユーザーを使い、RDS Proxy経由で接続する場合は、-hで指定しているホスト名をRDS Proxyのエンドポイントに変更します。

PGPASSWORD=$DB_PASS psql -h {RDS Proxyのエンドポイント} -U $DB_USER -d $DB_NAME -p $DB_PORT

これで接続ができるはずです。

IAMデータベース認証で接続

Amazon Aurora側でIAMデータベース認証用のユーザーを作成する

マスターユーザーで接続して、IAMデータベース認証用のユーザーを作成します。

CREATE USER {アプリケーション用DBユーザー名}; 
GRANT rds_iam TO {アプリケーション用DBユーザー名};

参考:IAM 認証を使用したデータベースアカウントの作成

ユーザーの作成が終わったら、踏み台サーバー(=Amazon EC2)にアタッチしているIAMロールに、作成したユーザーでのIAMデータベース認証を可能にするポリシーを付与します。

IAMロールに付与されているポリシーが下記状態だとします。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "secretsmanager:GetSecretValue",
                "secretsmanager:DescribeSecret"
            ],
            "Resource": "*",
            "Effect": "Allow"
        }
    ]
}

以下のように追記します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "secretsmanager:GetSecretValue",
                "secretsmanager:DescribeSecret"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "rds-db:connect"
            ],
            "Resource": "arn:aws:rds-db:{リージョン}:{AWSアカウントID}:dbuser:{DBクラスターリソースID}/{IAM認証用のDBユーザー名}",
            "Effect": "Allow"
        }
    ]
}

ポリシーのポイントは、ResourceにIAMデータベース認証用のユーザー名を指定していることです。
ここで指定したユーザー名でのIAMデータベース認証が可能になります。

参考:IAM データベースアクセス用の IAM ポリシーの作成と使用

DBクラスターリソースIDはAuroraクラスターの赤枠の部分で確認できます。

リソースIDの確認

認証トークンを取得してから接続

IAMデータベース認証による接続は、認証トークンを取得してから接続します。

export PGPASSWORD="$(aws rds generate-db-auth-token --hostname {Auroraのライターまたはリーダーエンドポイント} --port {ポート番号} --region {リージョン} --username {IAMデータベース認証用DBユーザー名})"         

取得した認証トークンを使って接続します。

psql "host={Auroraのライターまたはリーダーエンドポイン} port={ポート番号} dbname={データベース名} user={IAMデータベース認証用のDBユーザー名} sslmode=verify-full sslrootcert=/sample_dir/global-bundle.pem"

参考:コマンドラインから IAM 認証を使用して DB インスタンスに接続する: AWS CLI および psql クライアント

/sample_dir/global-bundle.pemの部分は、Amazon RDSのSSL証明書をダウンロードしてそのパスを指定します。
SSL証明書のダウンロードは、下記のページを参考にしてください。

SSL/TLS を使用した DB クラスターへの接続の暗号化


以上で設定作業は完了です。
これで、IAMデータベース認証とAWS Secrets Managerによるパスワードの自動ローテーション、両方を使うことができるようになりました。
両方の接続コマンドを見てみると、両方とも情報を取得して接続するという2段階のステップが必要なことがわかります。

まとめ

IAMデータベース認証とAWS Secrets Managerによるパスワードの自動ローテーションは、どちらもセキュリティリスクの低減と運用の手間軽減を実現できるメリットがあります。
両者の違いは以下のとおりです。

項目 IAMデータベース認証 AWS Secrets Managerによるパスワードの自動ローテーション
認証方法 認証トークン パスワード
有効期限 15分 ローテーション設定次第
アプリケーション側の変更 認証トークン取得の処理が必要 取得APIの実装が必要

IAMデータベース認証には接続に関する注意事項があります。
私個人は、アプリケーションからの接続においてはまずはAWS Secrets Managerを使用する方を優先的に考えたいと思います。
本記事が何かの参考になれば幸いです。

なお、本記事で使用したCloudFormationテンプレートではAWS Secrets Managerにローテーションを設定しています。
このローテーションですが、実は、ローテーションでパスワードが変わると、古いパスワードを使用した接続に問題が発生する可能性があります。
この問題の対策として、マルチユーザーローテーションを使用する方法があります。
マルチユーザーローテーションについては、別途記事にまとめたいと思います。

Appendix

本記事執筆にあたり感じたハマりポイントです。

  • マスターユーザーとそれ以外のDBユーザーで自動ローテーションの設定の仕方が違います。
  • マスターユーザー以外のDBユーザーのパスワードの自動ローテーションは、AWS Secrets Managerの設定を作り込んで行います。この時、対象のDBユーザーの作成とGRANTなどの設定は自身で行う必要があります。AWS Secrets Manager側はそれらを行いません。
  • AWS Secrets Managerによるパスワードの自動ローテーションは、シークレットに保存しているパスワードとDBユーザーの現在のパスワードが合わなくなるとローテーションができなくなるようです。
  • AWS Secrets Managerによるパスワードの自動ローテーションは、自動ローテーション用のAWS Lambda関数により行われ、AWS Lambda関数からAWS Secrets ManagerのAPIを呼び出します。そのAWS Lambda関数はNAT Gatewayのあるサブネットに属させるか、AWS Secrets Manager側でVPCエンドポイントを用意しないとローテーションが失敗します。
  • Auroraクラスター側は、自動ローテーション用のAWS Lambda関数からの接続許可を許していないとローテーションが失敗します。

兼安 聡(執筆記事の一覧)

アプリケーションサービス部 DS3課所属
2024 Japan AWS Top Engineers (Database)
2024 Japan AWS All Certifications Engineers
Certified ScrumMaster
PMP
広島在住です。今日も明日も修行中です。