echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【AWS】CloudFormationで「A previous rotation isn’t complete. That rotation will be reattempted」エラーでデプロイできない場合の対処法

本日のお題

AWS環境で、AWS Secret Manager(以下「Secret Manager」)を使用して、Amazon RDS(以下「RDS」)のシークレット情報管理&シークレットローテーションを使用している場合に、AWS CloudFormation(以下「CFn」)でデプロイを実施すると、下記エラーが発生して、デプロイが失敗する場合があります。

「A previous rotation isn’t complete. That rotation will be reattempted(訳:前回のシークレットローテーションが完了していません。ローテーションは再試行されます)」

このエラーの原因と対処方法、及びAWS CDKでそれを実装する方法を記載します。

参考リンク

いきなり結論から

結論としては、ほとんどの場合「以下のいずれかの理由で、ローテーション用Lambda関数(以下「Lambda関数」)がSecret ManagerやRDSにアクセスできず、結果シークレットのローテーションが完了できなかったから」が原因になります。

  • Lambda関数が存在するサブネットに、Secret Managerへ到達するためのルーティングがない
  • Lambda関数がRDSへのアクセスを許可されていない

なおLambda関数のログを見ると*1、大抵何かしらのログが出力されており、正常終了していないことが分かります。(一番多いのが下記の「Lambda関数のタイムアウト」)

Task timed out after 30.03 seconds

Lambda関数が存在するサブネットに、Secret Managerへ到達するためのルーティングがない

これですが、Lambda関数Secret Managerに到達するためには、Lambda関数が存在するサブネットが以下いずれかの条件を満たす必要があります。

  • NAT Gatewayが存在するサブネットへルーティング可能であること
  • ポート443からのインバウンド通信を許可するSecret Manager用のインターフェースエンドポイントが存在する事

今回は、後者の方法について説明します。(この問題が起こる=該当サブネットが分離サブネット *2 の可能性が高いので)

AWS CDKで実装する

AWS CDKで後者の方法を実装するには、下記コードでOKです。

import { aws_ec2 as ec2 } from 'aws-cdk-lib';
  
// 省略してますが、vpcはnew ec2.Vpc(...)で作成したVPCになります
// 
// VPCエンドポイントに割り当てるセキュリティグループを作成する
// アウトバウンドは全許可  
const secretManagerEpSg = new ec2.SecurityGroup(this, 'SecretManagerEpSg', {
  vpc,
  allowAllOutbound: true,
  description: 'Security Group for VPC Endpoint for SecretManager',
});
  
// 上記セキュリティグループにポート443からのアクセスを許可する
secretManagerEpSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443));
  
// Lambda関数が存在するサブネットにSecret Manager用の
// インターフェースエンドポイントを作成する。
// それに先程作成したセキュリティグループを割り当てる  
const secretManagerEp = vpc.addInterfaceEndpoint('SecretManagerEndpoint', {
  service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER,
  subnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
  securityGroups: [secretManagerEpSg],
});

Lambda関数がRDSへのアクセスを許可されていない

これですが、Lambda関数がローテーションを実施するには、当然RDSへアクセスする必要があります。

なので、Lambda関数はRDSへのアクセスを許可されていないといけません。(ロール・ポリシーはもちろん、ネットワークレベルでも許可されている必要がある)

VPCの場合「Lambda関数のセキュリティグループが、RDSのセキュリティグループのインバウンドとして許可されている」必要があります。

AWS CDKで実装する

AWS CDKで上記の方法を実装するには、下記コードでOKです。

import {
  aws_rds as rds,
  aws_secretsmanager as sm,
} from 'aws-cdk-lib';  
  
// RDSクラスターのcredentialsに紐づけたシークレットの場合  
const dbAdminSecret = new sm.Secret(this, 'DbAdminSecret', {
  ...(定義は省略)      
});  
  
const cluster = new rds.DatabaseCluster(this, 'AuroraCluster', {
  credentials: rds.Credentials.fromSecret(dbAdminSecret),
  ...(その他定義は省略)
});
  
// ローテーション作成  
new sm.SecretRotation(this, 'DbAdminSecretRotation', {
  secret: dbAdminSecret,
  target: cluster,
  ...(その他定義は省略)     
});
  
// その他のシークレットの場合
const dbUserSecret = new sm.Secret(this, 'DbUserSecret', {
  ...(定義は省略)    
});
  
// ローテーション作成
new sm.SecretRotation(this, 'DbUserSecretRotation', {
  secret: dbUserSecret,
  target: cluster,
  ...(その他定義は省略)     
});
  
// 最後、シークレットをクラスターにアタッチするのを忘れないように!
// これを忘れると正常にデプロイできません 
dbUserSecret.attach(cluster);

「あれ、セキュリティグループは?」と思った人がいるかもしれませんが、上記コードを実装すれば、下記を全部自動で行ってくれます。便利!

  • ローテーション用Lambda関数、及びそのセキュリティグループの作成
  • ローテーション用Lambda関数のRDSへのアクセス許可
    • RDSのセキュリティグループのインバウンドへローテーション用Lambda関数のセキュリティグループの割り当て

上記の対処をしてもやっぱりエラーになるんだけど!という場合の対処法

上記の対応を実施しても、デプロイ時にやっぱり同じエラーが発生するケースがあります。

「エラーに対処するためのコードを実装したのに、そのコードのデプロイ時にエラーが出て反映できない」なんていう本末転倒な事態が起こるわけです。

この現象ですが、SecretRotationrotateImmediatelyOnUpdate プロパティ*3がデフォルトでtrueになっていることが原因なので、明示的に false を指定することで回避できます。

なおtrue/falseによる挙動の違いは下記となります。

  • true: CFnデプロイ時にローテーションを実施する
  • false: CFnデプロイ時にローテーションは実施せず、automaticallyAfter で設定した日数が経過したらローテーションを実施する(デフォルトは30日)

それ以外の場合

それ以外の場合、おそらくポリシー関連(ローテーション関数のロールににSecret Managerのシークレット更新、及びそれのRDS適用などのポリシーがアタッチされていない...など)が挙げられます。

ただし今回紹介したAWS CDKのソースを適用すれば、自動で適切なポリシーをアタッチしてくれるので、その辺のエラーは発生しないはずです。

もしカスタムコードを適用して「Access Denied」系のエラーが出た場合は、そのあたりを確認してみてください。

それでは、今回はこの辺で

*1:ローテーション用のLambda関数はマネジメントコンソールの [Secret Manager] - [ローテーション] - [ローテーション関数] から確認できます

*2:インターネットに接続しないサブネット

*3:シークレット更新時(=CFnデプロイ時の更新も含む)にローテーションを実施するかどうか、の設定