こんにちは、広野です。
AWS Amplify + Amazon Cognito + React でWEBアプリをつくっている中で、Amazon Kinesis Data Firehose にログをストリームしたくなったのでその方法を紹介します。
インターネット上には断片的な情報は多いのですが、網羅的な情報が見つからず実装に苦労しました。
やりたいこと
とにかく React アプリから Amazon Kinesis Data Firehose にログを投げたい。
なお、その後はS3に出力することになりますが Amazon Kinesis Data Firehose の実装については本記事では言及いたしません。
実現方法
- React アプリのユーザは Amazon Cognito ユーザープールで認証できている。
- Amazon Cognito フェデレーティッドアイデンティティで、Amazon Cognito ユーザープールの認証済みユーザに対して特定のAWSリソースへのアクセス許可をもらう。
※ここでは Amazon Kinesis Data Firehose へのログ書込権限
※ロールベースアクセスコントロールとも言う - React アプリには、Amazon Kinesis Data Firehose にログをストリームするためのamplifyモジュールをロードさせる。
- React アプリ内でログを投げたい局面で、amplifyモジュールを呼び出しログデータを渡す。
設定・コード解説
Amazon Cognito 設定
Amazon Cognito ユーザープールは React アプリからのユーザ認証ができていることが前提とします。ここでは、Amazon Cognito フェデレーティッドアイデンティティの設定を追加します。
Amazon Cognito フェデレーティッドアイデンティティの設定そのものは大したことありません。以下の画面のような情報が設定されていれば大丈夫です。パラメータ詳細はこの記事の最後に AWS CloudFormation テンプレートのサンプルを付けますのでご覧下さい。
- 関連づける Amazon Cognito ユーザープールの情報
- 関連づける Amazon Cognito ユーザープールで認証されたユーザ、認証されていないユーザそれぞれに割り当てるIAMロール
※これが肝。適切に権限を割り当てる必要あり。後述します。
IAM ロール設定
Amazon Cognito フェデレーティッドアイデンティティに関連付けるIAMロールを設定します。
マネジメントコンソールからリソースを手動作成した場合、デフォルトで Amazon Cognito と Amazon Pinpoint 関連の権限が付与されたIAMロールが作成されます。そこに、Amazon Kinesis Data Firehose への書込権限ポリシーを追加します。
ここでは、認証済みユーザのみに追加することにします。
{ "Action": [ "firehose:PutRecord", "firehose:PutRecordBatch" ], "Resource": "FirehoseのARN" "Effect": "Allow" }
詳細なパラメータは AWS CloudFormation テンプレートのサンプルをご覧下さい。
今回は Amazon Kinesis Data Firehose への書込権限追加でしたが、その他のサービスへのアクセスも任意に記述できます。このように、Cognito ユーザープールの認証済みユーザにAWSリソースへのアクセス権限を付与することが簡単にできます。
React アプリのコード
使用Reactモジュール
- react v17.0.2
- aws-amplify v4.3.15
App.js
アプリの画面設計によって App.js でないケースもありますが、root的な位置にあるソースコードに Amazon Kinesis Data Firehose にログをストリームするためのモジュールをロードさせます。ここでは、どのFirehoseリソースに投げるかは記述しません。
以下のコードはパラメータを環境変数化させています。
import React from 'react'; import Amplify, { Analytics, AWSKinesisFirehoseProvider } from 'aws-amplify'; //追記 //Amplify Cognito 連携設定 Amplify.configure({ Auth: { region: process.env.REACT_APP_REGION, userPoolId: process.env.REACT_APP_USERPOOLID, userPoolWebClientId: process.env.REACT_APP_USERPOOLWEBCLIENTID, identityPoolId: process.env.REACT_APP_IDPOOLID //Amazon Cognito フェデレーティッドアイデンティティ連携用 } }); //Amplify Kinesis 連携設定 Analytics.addPluggable(new AWSKinesisFirehoseProvider()); Analytics.configure({ AWSKinesisFirehose: { region: process.env.REACT_APP_REGION, bufferSize: 1000, flushSize: 100, flushInterval: 5000, // 5s resendLimit: 5 } });
ログデータ送信関数
Reactコードのどこかに、Amazon Kinesis Data Firehose へのログデータ送信関数を作っておくと便利です。要件によりますが極力汎用化できるといいですね。
streamName のところに、Amazon Kinesis Data Firehose の 配信ストリーム名が入ります。
data として渡すログデータは、AWS公式ドキュメントによると BLOB (バイナリデータ) を入れるように書いてあります。ですがテキストデータであればそのまま Firehose で受信できたので、JSONオブジェクトを JSON.stringify でテキストデータに変換したものを送信しています。
Firehose側でデリミタ設定していない場合は、data に + “\n” で改行を付けてあげないとS3ログ出力の際に改行なしですべてのログが1行目に出力されます。
import { Analytics } from 'aws-amplify'; //ログデータ送信関数 const putLogs = async (data) => { //共通取得項目 data.loggedAt = Date.now(); //日時情報 data.useragent = window.navigator.userAgent; //ユーザエージェント情報 try { Analytics.record({ data: JSON.stringify(data), streamName: process.env.REACT_APP_FIREHOSE_STREAMNAME }, "AWSKinesisFirehose"); } catch (error) { console.log(error); } };
ログ送信はこんな感じで。
const data = { "key1": "AAA", "key2": "BBB" }; putLogs(data);
CloudFormation テンプレート
サンプルとなるテンプレートです。以下リソースが作られます。
- Amazon Cognito ユーザープール
- TOTP MFA 有効
- アプリクライアント
- Amazon Cognito フェデレーティッドアイデンティティ
- IAM ロール
AWSTemplateFormatVersion: 2010-09-09 Description: CloudFormation template example # ------------------------------------------------------------# # Input Parameters # ------------------------------------------------------------# Parameters: DomainName: Type: String Description: Domain name for URL. Default: example.com MaxLength: 40 MinLength: 5 SubDomainName: Type: String Description: Sub domain name for URL. Default: subdomain MaxLength: 20 MinLength: 1 CognitoAdminEmail: Type: String Description: Cognito Admin e-mail address. (e.g. xxx@xxx.xx) Default: xxx@example.com MaxLength: 100 MinLength: 5 Resources: # ------------------------------------------------------------# # Cognito IdP Roles (IAM) # ------------------------------------------------------------# CognitoIdPAuthRole: Type: AWS::IAM::Role Properties: RoleName: CognitoIdPAuthRole Description: This role allows Cognito authenticated users to access AWS resources. AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: cognito-identity.amazonaws.com Action: "sts:AssumeRoleWithWebIdentity" Condition: StringEquals: "cognito-identity.amazonaws.com:aud": !Ref IdPool "ForAnyValue:StringLike": "cognito-identity.amazonaws.com:amr": authenticated Policies: - PolicyName: CognitoIdPAuthRolePolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "mobileanalytics:PutEvents" - "cognito-sync:*" - "cognito-identity:*" Resource: "*" - Effect: Allow Action: - "firehose:PutRecord" - "firehose:PutRecordBatch" Resource: "arn:aws:firehose:${AWS::Region}:${AWS::AccountId}:deliverystream/UserActivityLogStreams" CognitoIdPUnauthRole: Type: AWS::IAM::Role Properties: RoleName: CognitoIdPUnauthRole Description: This role allows Cognito unauthenticated users to access AWS resources. AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: cognito-identity.amazonaws.com Action: "sts:AssumeRoleWithWebIdentity" Condition: StringEquals: "cognito-identity.amazonaws.com:aud": !Ref IdPool "ForAnyValue:StringLike": "cognito-identity.amazonaws.com:amr": unauthenticated Policies: - PolicyName: CognitoIdPAuthRolePolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "mobileanalytics:PutEvents" - "cognito-sync:*" Resource: "*" # ------------------------------------------------------------# # Cognito # ------------------------------------------------------------# UserPool: Type: AWS::Cognito::UserPool Properties: UserPoolName: CognitoUserPoolExample MfaConfiguration: "ON" EnabledMfas: - SOFTWARE_TOKEN_MFA Policies: PasswordPolicy: MinimumLength: 8 RequireUppercase: true RequireLowercase: true RequireNumbers: true RequireSymbols: false TemporaryPasswordValidityDays: 30 AccountRecoverySetting: RecoveryMechanisms: - Name: verified_email Priority: 1 AdminCreateUserConfig: AllowAdminCreateUserOnly: true AutoVerifiedAttributes: - email - phone_number DeviceConfiguration: ChallengeRequiredOnNewDevice: false DeviceOnlyRememberedOnUserPrompt: false EmailConfiguration: EmailSendingAccount: DEVELOPER From: !Sub ${CognitoAdminEmail} ReplyToEmailAddress: !Sub ${CognitoAdminEmail} SourceArn: !Sub arn:aws:ses:ap-northeast-1:${AWS::AccountId}:identity/${CognitoAdminEmail} EmailVerificationMessage: "Verification code: {####}" EmailVerificationSubject: "Verification code" UsernameConfiguration: CaseSensitive: false UserPoolAddOns: AdvancedSecurityMode: "OFF" UserPoolClient: Type: AWS::Cognito::UserPoolClient Properties: UserPoolId: !Ref UserPool ClientName: example-appclient GenerateSecret: false RefreshTokenValidity: 3 AccessTokenValidity: 6 IdTokenValidity: 6 ExplicitAuthFlows: - ALLOW_USER_SRP_AUTH - ALLOW_REFRESH_TOKEN_AUTH PreventUserExistenceErrors: ENABLED SupportedIdentityProviders: - COGNITO CallbackURLs: - !Sub https://${SubDomainName}.${DomainName}/index.html LogoutURLs: - !Sub https://${SubDomainName}.${DomainName}/index.html DefaultRedirectURI: !Sub https://${SubDomainName}.${DomainName}/index.html AllowedOAuthFlows: - implicit AllowedOAuthFlowsUserPoolClient: true AllowedOAuthScopes: - email - openid IdPool: Type: AWS::Cognito::IdentityPool Properties: IdentityPoolName: CognitoIdentityPoolExample AllowClassicFlow: false AllowUnauthenticatedIdentities: false CognitoIdentityProviders: - ClientId: !Ref UserPoolClient ProviderName: !GetAtt UserPool.ProviderName ServerSideTokenCheck: true IdPoolRoleAttachment: Type: AWS::Cognito::IdentityPoolRoleAttachment Properties: IdentityPoolId: !Ref IdPool Roles: authenticated: !GetAtt CognitoIdPAuthRole.Arn unauthenticated: !GetAtt CognitoIdPUnauthRole.Arn
まとめ
いかがでしたでしょうか?
とにかく React アプリからログを投げたいという無責任な?気持ちで作りましたが、Firehose君はしっかりと受け止めてくれるので安心して放り投げられます。w
Amazon Cognito フェデレーティッドアイデンティティを使った仕組みは他にも応用できそうです。実はS3へのセキュアなファイルアップロードにも使っているので、別の機会に紹介しようと思います。
この記事がみなさまのお役に立てれば幸いです。