Amplify + Cognito + React アプリから Kinesis Data Firehose にログをストリームする

こんにちは、広野です。

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へのセキュアなファイルアップロードにも使っているので、別の機会に紹介しようと思います。

この記事がみなさまのお役に立てれば幸いです。

著者について
広野 祐司

AWSサーバーレスアーキテクチャを駆使して社内クラウド人材育成アプリや教育コンテンツをつくっています。
ReactでSPAを書き始めたら快適すぎて、他の開発言語には戻れなくなりました。React仲間を増やしたいです。
取得資格:AWS認定は7つ、ITサービスマネージャ、ITIL v3 Expert、等
2019, 2020 AWS APN Top engineers 受賞
好きなAWSサービス:AWS Amplify / Amazon Cognito / AWS Step Functions / AWS CloudFormation

広野 祐司をフォローする
クラウドに強いによるエンジニアブログです。
SCSKは専門性と豊富な実績を活かしたクラウドサービス USiZE(ユーサイズ)を提供しています。
USiZEサービスサイトでは、お客様のDX推進をワンストップで支援するサービスの詳細や導入事例を紹介しています。
AWSサーバレスアーキテクチャ
TechHarmony
タイトルとURLをコピーしました