こんにちは、広野です。
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へのセキュアなファイルアップロードにも使っているので、別の機会に紹介しようと思います。
この記事がみなさまのお役に立てれば幸いです。



