[アップデート]Amazon Data Firehoseが認証情報をAWS Secrets Managerより取得できるようになりました
初めに
先日Amazon Data FirehoseとAWS Secrets Manager統合のリリースがありました。
これまでAmazon Data FirehoseではNew RelicのAPIキー等認証情報を入力するサービスを利用する際その値をFirehose側で直接指定する形となっておりました。
CloudFormation等を利用して構築する際は動的参照を利用することでSecrets Managerの値を参照してデプロイ時に差し替え、Secrets Manager側からのローテーション時のLambda関数処理による差し替えといったことは可能でしたがあくまで外側の仕組みとしてFirehose側の設定値を書き換えるものとなっておりました。
(シークレットの値の変更+αのαの部分がうまくやればできたけど機能としてのサポートではなかった)
今回のアップデートで認証情報の値直接ではなくシークレットのARNを指定できるようになりFirehose側が必要に応じてSecrets Managerより値を取得するようになったため、+αの処理なくシークレットの値の変更のみで対応できるようになり秘密情報のローテーションをよりシンプルに実現することができるようになります。
またシークレットの値を直接取り扱わないため設定の変更処理をより安全に実現することも可能となります。
試してみる
今回はCloudFormationを利用して設定してみます。今回はHTTPエンドポイントを利用しますがSecretsManagerConfigration
というパラメータが追加されておりこちらに対して有無効、シークレットのARN、取得に利用するIAMロールの指定をすればよさそうです。
またこの値は従来のアクセスキーの指定同様、リクエスト上のX-Amz-Firehose-Access-Key
ヘッダに反映されます。
どのヘッダに利用されるか等は送信先のサービスにより異なるため各種サービスの設定値のドキュメントをご参照ください。
実装
以下のテンプレートで環境を構築します。
AWSTemplateFormatVersion: 2010-09-09 Parameters: PostUrl: Type: String Resources: DataDeliver: Type: AWS::KinesisFirehose::DeliveryStream Properties: DeliveryStreamName: secrets-integration-test DeliveryStreamType: DirectPut HttpEndpointDestinationConfiguration: EndpointConfiguration: Url: !Ref PostUrl S3BackupMode: FailedDataOnly RoleARN: !GetAtt FirehoseRole.Arn S3Configuration: BucketARN: !GetAtt DeliveryFailedDataBucket.Arn RoleARN: !GetAtt FirehoseRole.Arn SecretsManagerConfiguration: Enabled: True SecretARN: !Ref Secrets FirehoseRole: Type: AWS::IAM::Role Properties: RoleName: secrets-integration-firehose-role Path: / AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: firehose.amazonaws.com Policies: - PolicyName: secrets-integration-deliver-policy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 's3:PutObject' Resource: !Sub ${DeliveryFailedDataBucket.Arn}/* - Effect: Allow Action: - 'secretsmanager:GetSecretValue' Resource: !Ref Secrets Secrets: Type: AWS::SecretsManager::Secret Properties: Name: firehose-integration GenerateSecretString: SecretStringTemplate: '{}' GenerateStringKey: "api_key" PasswordLength: 16 DeliveryFailedDataBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub secrets-integration-fail-log-bucket-${AWS::AccountId}
シークレットに指定すべき値についてはサービスにより異なりますので利用するサービスに合わせて設定しましょう。今回HTTPエンドポイントの場合はapi_key
がキーとなるJSON値を設定します。
今回は以下のような値となっております。
{"api_key":"tOTxb=#9Ek$si^GA"}
HTTPエンドポイントの先ではX-Amz-Firehose-Access-Key
ヘッダの値をログに出力するようにしておきます。通信はPOSTで来るため受信側でメソッドの判定がある場合は注意しましょう。
<?php use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Log; use Illuminate\Http\Request; Route::post('/integration/debug', function (Request $request) { Log::debug($request->header('x-Amz-Firehose-Access-Key')); return response('Hello World'); });
動作確認
ストリームにデータを設置するとシークレット上のapi_key
の値が出力されていることが確認できます。
% aws firehose put-record --delivery-stream-name secrets-integration-test --record Data="test" { "RecordId": "xxxxxxxxxxxx", "Encrypted": false } % tail -f storage/logs/laravel.log ... [2024-06-18 04:26:52] local.DEBUG: tOTxb=#9Ek$si^GA [2024-06-18 04:26:54] local.DEBUG: tOTxb=#9Ek$si^GA [2024-06-18 04:26:59] local.DEBUG: tOTxb=#9Ek$si^GA [2024-06-18 04:27:07] local.DEBUG: tOTxb=#9Ek$si^GA [2024-06-18 04:27:21] local.DEBUG: tOTxb=#9Ek$si^GA [2024-06-18 04:27:55] local.DEBUG: tOTxb=#9Ek$si^GA [2024-06-18 04:28:53] local.DEBUG: tOTxb=#9Ek$si^GA
Firehose側に変更が入らないように個別にシークレットの値を送信して再度データし変更されていることを確認します。
% tail -f storage/logs/laravel.log ... [2024-06-18 04:40:31] local.DEBUG: foobar [2024-06-18 04:40:40] local.DEBUG: foobar [2024-06-18 04:40:56] local.DEBUG: foobar [2024-06-18 04:41:25] local.DEBUG: foobar [2024-06-18 04:42:26] local.DEBUG: foobar [2024-06-18 04:44:33] local.DEBUG: foobar
https://docs.aws.amazon.com/ja_jp/firehose/latest/dev/using-secrets-manager.html
Firehose caches the secrets with an encryption and uses them for every connection to destinations. It refreshes the cache every 10 minutes to ensure that the latest credentials are used.
なおFirehoseではシークレットの値を約10分程度キャッシュするため切り替えタイミングと配信タイミング次第で意図せず旧値が混ざってしまう可能性があります。
実際に04:44(UTC)ごろに変更を行いデータの送信を行いましたが04:58頃もキャッシュが残っているためか一発目は古いシークレットが利用されており、その直後から値が切り替わっていることが確認できました。
% tail -f storage/logs/laravel.log ... [2024-06-18 04:44:33] local.DEBUG: foobar ... [2024-06-18 04:58:04] local.DEBUG: foobar [2024-06-18 04:58:06] local.DEBUG: foobar1 [2024-06-18 04:58:10] local.DEBUG: foobar1 [2024-06-18 04:58:18] local.DEBUG: foobar1 [2024-06-18 04:58:34] local.DEBUG: foobar1 [2024-06-18 04:59:06] local.DEBUG: foobar1
キャッシュ時間や削除については画面を見る限りでも本日入れたAWS CLIのv1側でもそれっぽい機能は見当たらないためこの辺りの制御は現状できなさそうです。
% sam-cli % bin/aws --version aws-cli/1.33.10 Python/3.12.3 Darwin/22.6.0 botocore/1.34.128 % bin/aws firehose aaa ... create-delivery-stream | delete-delivery-stream describe-delivery-stream | list-delivery-streams list-tags-for-delivery-stream | put-record put-record-batch | start-delivery-stream-encryption stop-delivery-stream-encryption | tag-delivery-stream untag-delivery-stream | update-destination help
終わりに
シークレットの値を直接参照してくれるようになったことで、必要以上に秘密情報を外側で取り合わずより安全にシンプルに対応できるものとなるアップデートでした。
Firehose側で直接値を持つわけではなくSecrets Manager側を動的に参照するためがシークレット参照による料金発生がある点は注意しましょう。
10分程度キャッシュは行われるため毎回というほどではありませんが、逆にキャッシュしてしまう関係で変更反映にラグができてしまうため送信先で認証情報が一つしか持てない場合は一時的に送信に失敗してしまう可能性があります。
追記: ただ試してる限り10分経過せずとも再試行中に新しいシークレットに変わっているような気がするのであまり気にしなくても良いかも?(要確認)
現状この辺りのコントロールができるわけではなさそうなのでローテーションが必要でも場合により別の方法でローテーションした値を直接Firehose側に持たせた方が良い場合もありそうです。
あくまで選択肢が一つ増えたほどで見たいただくのが良いのではないかなと個人的には考えております。