AWSの部屋

AWS学習者向けのブログです

スポンサーリンク

SQS キューのメッセージを Lambda で処理してみる

はじめに

前回のエントリーで S3 と SQS の連携を試してみました。本エントリーはその続きで SQS キューに S3 から通知がきた後に SQS キューのメッセージを Lambda で処理してみようと思います。

使用するサービス

  • Amazon S3・・・AWS が提供するオブジェクトストレージサービス
  • Amazon SQS・・・フルマネージド型のメッセージキューイングサービス
  • AWS Lambda・・・サーバーをプロビジョニングしたり管理しなくてもコードを実行できるコンピューティングサービス

アーキテクチャ

役割
  • Amazon S3・・・アップロードするファイルを格納
  • Amazon SQS・・・ S3 にアップロードされたファイルの情報を Lambda に送信
  • AWS Lambda・・・SQS から送信されたファイル情報をもとにファイルの名前を変えて S3 に同じファイルをアップロード

手順

  1. IAMロールを作成
  2. S3 バケットを作成
  3. SQS キュー を作成
  4. Lambda 関数を作成
  5. 動作確認

1. IAMロールを作成

「Lambda-SQS-Execution-Role」という名前のLambda に SQS と S3 に対する実行権限を付与するロールを作成します。

2. S3 バケットを作成

前回のエントリーを参照してください。

3. SQS キュー を作成

前回のエントリーを参照してください。

4. Lambda 関数を作成

SQS キューのメッセージを処理するLambda関数を作成します。

[関数名]:sqs-message-receive
[ランタイム]:Python 3.9
[実行ロール]:Lambda-SQS-Execution-Role

SQS キューをトリガーとして追加します。

ソースコード

S3 バケットにアップロードしたファイルの名前を変えて同じバケットにアップロードするプログラムです。

import json
import os
import urllib.parse
import boto3
import subprocess
from datetime import datetime

def lambda_handler(event, context):
    
    s3 = boto3.resource('s3')
    
    # バケット名取得
    bucket_name = event['Records'][0]['s3']['bucket']['name']
    
    # バケット取得
    bucket = s3.Bucket(bucket_name) 
    
    # アップロードしたファイル名を取得
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    
    # Lambdaのローカルのファイル保存先(/tmp)を設定
    file_path = '/tmp/' + datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
    
    try:
        # /tmpにダウンロード用のフォルダを作成
        cmd = ['mkdir', '-p', file_path]
        subprocess.run(cmd, stdout=subprocess.PIPE)
    
        # S3に格納したファイルを作成したフォルダにダウンロード
        bucket.download_file(key, os.path.join(file_path, key))   
    
        # ファイルの名前を変えてS3にPUT
        bucket.upload_file(os.path.join(file_path, key), 'hane2.jpg')
        
        return
    except Exception as e:
        print(e)

5. 動作確認

hane.jpg をアップロードします。

Lambdaによってhane2.jpg がバケットに作成されました。

さいごに

今回は簡単な処理だけだったので SQS を使う必要はないと思いますが、SQS と Lambda を実際にどのように連携するかを理解するためのいい勉強になりました。今度はデッドレターキューなど SQS をフルに活用できる構成を試してみたいですね。

S3 へのファイル格納時に SQS キューに通知をしてみる

はじめに

AWS認定試験に必ず出題される SQS ですが、今まで触ったことがなかったので本日は S3 と連携させて SQS を試してみたいと思います。

使用するサービス

  • Amazon S3・・・AWS が提供するオブジェクトストレージサービス
  • Amazon SQS・・・フルマネージド型のメッセージキューイングサービス

アーキテクチャ

手順

  1. S3 でバケットを作成
  2. SQS キューを作成
  3. S3 でイベント通知を作成
  4. 動作確認

1. S3 でバケットを作成

「20220807-sqs-test」というバケットを作成します。設定はデフォルトでOKです。

2. SQS キューを作成

「TestQueue」という SQS キューを作成します。

アクセスポリシーを以下のように設定します。

{
  "Version": "2008-10-17",
  "Id": "__default_policy_ID",
  "Statement": [
    {
      "Sid": "__owner_statement",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "SQS:*",
      "Resource": "arn:aws:sqs:ap-northeast-1:xxxxxxxxxxxxx:TestQueue",
      "Condition": {
        "ArnLike": {
          "aws:SourceArn": "arn:aws:s3:*:*:20220807-sqs-test"
        }
      }
    }
  ]
}

3. S3 でイベント通知を作成

イベント名とイベントタイプを設定し、作成した SQS キューを設定して「変更の保存」をクリックします。

4. 動作確認

バケットにファイルをアップロードします。

「メッセージを送受信」をクリックします。

「メッセージをポーリング」をクリックします。

メッセージが2件表示されます。

上のキューのメッセージ

アップロードしたファイルの情報などが記載されています。

{"Records":[{"eventVersion":"2.1","eventSource":"aws:s3","awsRegion":"ap-northeast-1","eventTime":"2022-08-07T14:33:59.870Z","eventName":"ObjectCreated:Put","userIdentity":{"principalId":"AWS:AIDAVMRY2N7OKTN33RYNV"},"requestParameters":{"sourceIPAddress":"60.95.0.122"},"responseElements":{"x-amz-request-id":"Q73VJ1CPJ64CKJQ0","x-amz-id-2":"jqP4VGy4ubSEOvB+XRCdTjWUJEuCkkWRyiRlxdKCNqjP8cTjRUg0JGhDYsW9RprSsQPqdnlOviWD11mpmynwSJzlRyzzT8rgCka5XEnLzq8="},"s3":{"s3SchemaVersion":"1.0","configurationId":"SQS-Event","bucket":{"name":"20220807-sqs-test","ownerIdentity":{"principalId":"A2B5KBXGR14B9R"},"arn":"arn:aws:s3:::20220807-sqs-test"},"object":{"key":"hane.jpg","size":9846,"eTag":"ad1cdeed43375dca5b5e892be0968525","sequencer":"0062EFCD57CFFC5419"}}}]}
下のキューのメッセージ

こちらは S3 からの通知など概要レベルの情報が記載されています。

{"Service":"Amazon S3","Event":"s3:TestEvent","Time":"2022-08-07T14:33:31.875Z","Bucket":"20220807-sqs-test","RequestId":"0D8YVTX0QRFW78X3","HostId":"xj8sYZZALIXycdCYK54KnulIng2R3MMjMqjMAMM3gCQ5UqsHcL46nXX3FZ8x/Wpg4Duwz1IHXGY="}

さいごに

はじめて SQS を使用してみましたが設定は簡単にできました。何となく理解していたものが実際にサービスを使用することで理解が深まった気がします。次は SQS と Lambda の連携を試してみようと思います。

CloudFront の署名付きURL を使ったコンテンツの配信方法

はじめに

S3 にあるファイルを特定のユーザーに限定して配信したい場合などがあるかと思いますが、その際、署名付きURL が役に立ちます。S3 と署名付きURLだけでも実現可能なのですが、本エントリーではよりセキュアな配信を可能にするために CloudFront も使ってみようと思います。

署名付きURLとは

アクセスを許可したいオブジェクトに対して期限を指定して URL を発行する機能です。バケットやオブジェクトのアクセス制御を変更することなく特定のオブジェクトに一時的にアクセスを許可したい場合に非常に有効です。

使用するサービス

アーキテクチャ

実際に運用する際は上記のようになるのが一般的かと思いますが、今回は EC2 は使用せずに署名付きURLをローカルで作成します。

手順

  1. S3 バケットにファイルをアップロード
  2. ローカルで公開鍵・秘密鍵を作成
  3. 公開鍵を CloudFront に登録
  4. キーグループを CloudFront に 作成
  5. CloudFrontディストリビューションを作成
  6. 署名付きURLの生成
  7. 動作確認

1. S3 バケットにファイルをアップロード

以下のように画像をアップロードしました。

アップロードした画像はこちらです。

2. ローカルで公開鍵・秘密鍵を作成

署名付き URL を生成するためには公開鍵・秘密鍵を作成する必要があります。OpenSSL を使用して作成してみます。

まずは秘密鍵を作成します。

openssl genrsa -out private_key_presigned_url.pem 2048

秘密鍵から、CloudFront に登録するための公開鍵を作成します。

openssl rsa -pubout -in private_key_presigned_url.pem -out public_key_presigned_url.pem

作成完了です。

3. 公開鍵を CloudFront に登録

「パブリックキーを作成」をクリックします。

「名前」と「キー」を入力して「パブリックキーを作成」をクリックします。「キー」に入力する値は以下のコマンドで取得します。

cat public_key_presigned_url.pem | pbcopy

4. キーグループを CloudFront に 作成

「キーグループを作成」をクリックします。

登録した公開鍵を設定し、「キーグループを作成」をクリックします。

5. CloudFrontディストリビューションを作成

ディストリビューションを作成」をクリックします。

「オリジンドメイン」に作成した S3 バケット、「S3バケットアクセス」で「はい、OAIを使用します」を選択します。

「ビューワーのアクセスを制限する」で「Yes」を選択、作成したキーグループを設定してデゥストリビューションを作成をクリックします。

6. 署名付きURLの生成

AWS CLI を使って CloudFront の署名付き URL を生成します。詳細はこちらを参照してください。

aws cloudfront sign \
--url https://d3pmk7agw3wwym.cloudfront.net/hane.jpg \
--key-pair-id xxxxxxxxx \
--private-key private_key_presigned_url.pem \
--date-less-than 2022-08-07T08:30:00+09:00

以下のように署名付きURLが生成されました。

https://d3pmk7agw3wwym.cloudfront.net/hane.jpg?Expires=1659828600&Signature=zJkStrKGj-pciCgh-vOyaMnD6zDMXvsalGA0PPGh2IdoHXyFX-
~中略~
rQKarrjBT3OymyWJDBZrjL6oJDORU2v1v94aDdeIukeXyIVqbyeZ8N7Py0aZznXVI16qsgtPHqzTx~OZXgOEYgrJRR73DzF08YbT3TTNUSGo70gMwMLzXKsCq0VWRWxE5PNZTZ1-ULaL3A__&Key-Pair-Id=xxxxxxxx

7. 動作確認

署名付きURLでアクセス

S3バケットに格納されている画像にアクセスすることができました。

通常URLでアクセス

アクセスできないことがわかりました。

署名付き URL の有効期限が切れた後に署名付きURLでアクセス

アクセスできないことがわかりました。

さいごに

AWS認定試験には必ずと言っていいほど出題される署名付きURLに関して何となく理解していたつもりでしたが、実際に自分で使い方を試してみることでより理解が深まった気がします。やはり座学ではなく実践で学ぶのが一番ですね。

SAML による AWS へのシングルサインオン

はじめに

AWS アカウントに IAM ユーザーを作成しログインする代わりに、ID プロバイダー(IdP)を使⽤しシングルサインオンすることができます。これは、組織に独⾃のID 基盤がある場合や、複数の AWS アカウントを使⽤している場合に便利です。今回は AWS 認定ソリューションアーキテクト - プロフェッショナル試験に向けて、SAML を使った AWS へのシングルサインオンに挑戦してみようと思います。

シングルサインオンとは

1度のユーザー認証によって複数のシステム(業務アプリケーションやクラウドサービスなど)の利用が可能になる仕組みを指します。

シングルサインオンに必要な要素

要素 説明 本エントリーで活用するサービス
アイデンティティストア IDプロバイダー(IdP)のユーザ管理 GMOトラスト・ログイン
IDプロバイダー(IdP) ユーザー・アカウントを管理するサービス GMOトラスト・ログイン
サービスプロバイダー なんらかのサービスを提供する企業または組織 AWS

AWSにおけるシングルサインオンのイメージ

手順

  1. IDプロバイダーの設定1
  2. サービスプロバイダーの設定
  3. IAM ロールの作成
  4. IDプロバイダーの設定2
  5. アイデンティティストアのユーザの作成
  6. 動作確認

1. IDプロバイダーの設定1

アカウントの登録後、ID プロバイダー情報のメタデータをダウンロードします。アプリの登録はせずにこのまま次の手順に進みます。

管理者画面に遷移します。

アプリの追加ボタンを押します。

AWS IAM(SAML) をクリックします。

ID プロバイダー情報のメタデータをダウンロードします。

2. サービスプロバイダーの設定

IAM管理画面で「プロバイダを追加」をクリックします。

前工程でダウンロードしたメタデータを選択し、プロバイダーの追加をします。

プロバイダのARNを控えておきます。

3. IAM ロールの作成

先ほど作成したプロバイダを選択し、以下のようにロールを作成します(アタッチするポリシーやロール名は適当でOK)。

ロールの ARN を控えておきます。

4. IDプロバイダーの設定2

「サービスプロバイダーの設定」の「SAML属性の設定」に控えておいた「ロールの ARN」と「プロバイダの ARN」を「,」で区切った「ロールのARN,プロバイダのARN」の形式で入力し、アプリの登録を行います。

例:arn:aws:iam::000000000000:role/saml-role, arn:aws:iam::000000000000:saml-provider/sanvarie_test

5. アイデンティティストアのユーザの作成

このようにユーザを二つ作成しました。

6. 動作確認

ログインができている場合、画面の上部にロール名/ユーザー名@アカウントIDが表記されます。

一人目のユーザでログイン

二人目のユーザでログイン

さいごに

何となくでしかシングルサインオンの仕組みを理解していなかったのですが、実現に必要なものを整理し実際に手を動かすことによって理解が深まった気がします。次回は OpenID Connect を使ったシングルサインオンにも挑戦してみようと思います。

CloudFormation でインフラ環境を自動構築する

はじめに

マネジメントコンソールで手作業でインフラ環境を構築していくのは、最初は直感的でわかりやすいかと思います。ただ、同じ環境を複数用意する、複数の環境に同じ修正を横展開するといった作業を人力でやり続けることは効率が悪く、設定ミスも発生しやすくなります。今回はそのような時に役立つ CloudFormation を使ってみたいと思います。

使用するサービス

  • AWS CloudFormation・・・AWS リソースを自動構築するためのサービス

CloudFormation の利用の流れ

  1. CloudFormation テンプレートを作成する
  2. テンプレートを適用する
  3. CloudFormation スタックが作成され、それに紐づく形で AWS リソースが自動構築される

スタックとは

CloudFormation で構築された AWS リソースはスタックという集合にまとめられます。テンプレートを修正し、スタックを指定して再度適用することで、スタック上の AWS リソースの設定を変更したり、リソースを削除することができます。

テンプレートとは

スタックの設計図です。JSONYAML 形式で記述します。テンプレートに関する詳細はこちらをご参照ください。

今回のゴール

CloudFormation のテンプレート(yml)を作成し、それを使用して以下のようなインフラ環境を構築する。

作成する AWS リソース

  • VPC
  • サブネット(パブリック×1、プライベート×1)
  • ルートテーブル(パブリック×1、プライベート×1)
  • インターネットゲートウェイ
  • セキュリティグループ(パブリック×1、プライベート×1)
  • EC2(パブリック×1、プライベート×1)

CloudFormation テンプレートの解説

CloudFormation テンプレートはいくつかのセクションに分かれています。本エントリーではよく使われる以下3つのセクションについて説明します。

Resources セクション

構築する AWS リソースの設計を記述するセクション。例えば、VPC の設定は以下のように行います。このテンプレートでは「fjVpc」が VPC リソースを表す論理 ID で、この ID を使ってリソース間の紐づけを行います。

# Resources Section
Resources:
  # VPCの設定
  fjVpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: fjVpc
Parameters セクション

実行時に値を選択(入力)するセクション。例えば、以下のようにインスタンスタイプを変数として定義し、実行時に選択する形式にすることができます。

# Parameters Section
Parameters:
  InstanceType:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t2.small
      - t2.medium
    Desctiption: Select EC2 instance type.
  KeyPair:
    Description: Select KeyPair Name.
    Type: AWS::EC2::KeyPair::KeyName
Mappings セクション

変数を Map 形式に定義できるセクション(実行環境によって変わる値を定義するのに用いられることが多い)。例えば、AMI ID はリージョンによって変わるため、以下のように AMI ID を定義します。

# Mappings Section
Mappings:
  RegionMap:
    us-east-1:
      hvm: 'ami-a4c7edb2'
    ap-northeast-1:
      hvm: 'ami-3bd3c45c'

CloudFormation テンプレートの作成

以下のようなテンプレートを作成しました。

AWSTemplateFormatVersion: "2010-09-09"
Description: Network and server resource template

# Parameters Section
Parameters:
  InstanceType:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t2.small
      - t2.medium
    Description: Select EC2 instance type.
  KeyPair:
    Description: Select KeyPair Name.
    Type: AWS::EC2::KeyPair::KeyName
    
# Mappings Section
Mappings:
  RegionMap:
    us-east-1:
      hvm: 'ami-a4c7edb2'
    ap-northeast-1:
      hvm: 'ami-3bd3c45c'
      
# Resources Section
Resources:
  ############### VPC ###############
  fjVpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: fjVpc
  
  ############### Subnet, RouteTable, IGW ###############
  
  ## パブリックサブネット
  fjSubnetPublic:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.0.0/24
      VpcId:
        Ref: fjVpc
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: fj-subnet-public
        - Key: Type
          Value: Public
          
  ## プライベートサブネット
  fjSubnetPrivate:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.1.0/24
      VpcId:
        Ref: fjVpc
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: fj-subnet-private
        - Key: Type
          Value: Isolated
  
  ## パブリックサブネット用のルートテーブル
  fjRoutePublic:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: fjVpc
      Tags:
        - Key: Name
          Value: fj-route-public
          
  ## パブリックサブネットへルート紐付け
  fjRoutePublicAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: fjRoutePublic
      SubnetId:
        Ref: fjSubnetPublic

  ## パブリックサブネット用ルートテーブルのデフォルトルート
  fjRoutePublicDefault:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: fjRoutePublic
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: fjIgw
    DependsOn:
      - fjVpcgwAttachment

  ## プライベートサブネット用のルートテーブル
  fjRoutePrivate:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: fjVpc
      Tags:
        - Key: Name
          Value: fj-route-private
          
  ## プライベートサブネットへルート紐付け
  fjRoutePrivateAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: fjRoutePrivate
      SubnetId:
        Ref: fjSubnetPrivate
  
  # インターネットへ通信するためのゲートウェイの作成
  fjIgw:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: fj-igw
  fjVpcgwAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId:
        Ref: fjVpc
      InternetGatewayId:
        Ref: fjIgw
  
  ############### Security groups ###############
  ## インターネット公開のセキュリティグループの生成
  fjSgPublic:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for public
      GroupName: public
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          Description: Allow all outbound traffic by default
          IpProtocol: "-1"
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          Description: from 0.0.0.0/0:80
          FromPort: 80
          IpProtocol: tcp
          ToPort: 80
        - CidrIp: 0.0.0.0/0
          Description: from 0.0.0.0/0:22
          FromPort: 22
          IpProtocol: tcp
          ToPort: 22
      Tags:
        - Key: Name
          Value: fj-sg-public
      VpcId:
        Ref: fjVpc
  
  ## プライベートサブネットインスタンス用のセキュリティグループの生成
  fjSgPrivate:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security Group of private
      GroupName: private
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          Description: Allow all outbound traffic by default
          IpProtocol: "-1"
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          Description: from 0.0.0.0/0:22
          FromPort: 22
          IpProtocol: tcp
          ToPort: 22
        - CidrIp: 0.0.0.0/0
          Description: from 0.0.0.0/0:22
          FromPort: -1
          IpProtocol: icmp
          ToPort: -1
      Tags:
        - Key: Name
          Value: fj-sg-private
      VpcId:
        Ref: fjVpc
        
  ############### Server ###############
  
  ## EC2(パブリックサブネット)
  fjEC2Public:
    Type: AWS::EC2::Instance
    Properties: 
      ## Mappingsセクションの値をFindMap関数で取得
      ImageId: !FindInMap [ RegionMap, !Ref 'AWS::Region', hvm]
      ## Parametersセクションの値をRef関数で取得
      InstanceType: !Ref InstanceType
      SubnetId: !Ref fjSubnetPublic
      Tags:
        - Key: Name
          Value: fj-ec2-public
      SecurityGroupIds:
        - !Ref fjSgPublic
      KeyName: !Ref KeyPair
  
  ## EC2(プライベートサブネット)
  fjEC2Private:
    Type: AWS::EC2::Instance
    Properties: 
      ## Mappingsセクションの値をFindMap関数で取得
      ImageId: !FindInMap [ RegionMap, !Ref 'AWS::Region', hvm]
      ## Parametersセクションの値をRef関数で取得
      InstanceType: !Ref InstanceType
      SubnetId: !Ref fjSubnetPrivate
      Tags:
        - Key: Name
          Value: fj-ec2-private
      SecurityGroupIds:
        - !Ref fjSgPrivate
      KeyName: !Ref KeyPair

CloudFormation スタックの作成

作成した CloudFormation テンプレートを使用して CloudFormation スタックを作成します。

Parameters セクションで設定した項目に対して値を入力します。この後はすべてデフォルト設定でOKです。

スタックが作成されました。

AWS リソースの確認

以下のように AWS リソースが作成されていることが確認できました。

VPC

サブネット

ルートテーブル

インターネットゲートウェイ

セキュリティグループ

EC2

動作確認

EC2 の動作確認を行います。

パブリックサブネット用の EC2

SSH で EC2 にログインします。

EC2 へのログインを確認しました。

プライベートサブネット用の EC2

パブリックサブネット用の EC2 から SSH で プライベートサブネット用の EC2 へのログインをすることができました。

また、ping も通ることがわかりました。

さいごに

CloudFormation テンプレートを実際に作成したのは今回が初めてでした。ただ、AWS のネットワークやサーバー構築に関して基本的な知識があればすぐに理解できると感じました(AWS に関する基本的な理解がない場合はかなり厳しそう)。今回はシンプルな構成のインフラ環境でしたが、次は少し複雑な環境構築にも挑戦してみようと思います。

CloudWatch のログを Kinesis Data Firehose 経由で S3 に保存する

はじめに

CloudWatch でログを収集することができますが、収集したログを S3 に自動転送したいことがあるかと思います。そんな時は Kinesis Data Firehose が役に立つということで、実際に自分で手を動かして試してみました。

使用するサービス

  • Amazon CloudWatch・・・AWS リソースと AWS で実行するアプリケーションのモニタリングサービス
  • Amazon Simple Storage Service (Amazon S3)・・・AWSが提供するオブジェクトストレージサービス
  • Amazon Kinesis Data Firehose・・・ユーザからの入力やアクセスログなどで生成されたデータソースを、指定したAWSのサービスにストリーミングデータとして転送するサービス

アーキテクチャ

手順

  1. S3
    1. バケットを作成
  2. Kinesis
    1. IAMロールを作成
    2. 配信ストリームを作成
  3. CloudWatch
    1. IAMロールを作成
    2. ロググループ作成
    3. ログストリーム作成
    4. サブスクリプションフィルタ設定
  4. 動作確認
    1. AWS CLI で CloudWatch にログを出力
    2. S3 のバケットを確認

1. S3

1. バケットを作成

以下のようにバケットを作成します。設定はすべてデフォルトでOKです。

2. Kinesis

1. IAMロールを作成

Kinesis Data Firehose に S3バケットへデータを出力する権限を与えます。今回は以下のようなロールを作成しました。

2. 配信ストリームを作成

以下のように値を設定して配信ストリームを作成します。

項目
Source Direct PUT
Destination Amazon S3
Delivery stream name 配信ストリーム名
S3 bucket s3://<作成したバケット名>

このように配信ストリームが作成されました。

3. CloudWatch

1. IAMロールを作成

CloudWatch に Kinesis Data Firehose へデータを出力する権限を与えます。今回は以下のようなロールを作成しました。(CloudWatchのロールに関しては作り方が少しトリッキーなため、こちらをご参照ください)

2. ロググループを作成

以下のようにロググループを作成します。

3. ログストリームを作成

作成したロググループにログストリームを作成します。

以下のようにログストリームを作成しました。

4. サブスクリプションフィルタを作成

作成したロググループにサブスクリプションフィルタを作成します。

以下のように値を設定します。

項目
Destination account 現在のアカウント
Kinesis Firehose delivery stream 作成した配信ストリーム名
既存のロールを選択 CloudWatch用に作成したIAMロール
ログの形式 その他
サブスクリプションフィルター名 サブスクリプションフィルターの名前
テストするログデータを選択 作成したログストリーム名

4. 動作確認

1. AWS CLI で CloudWatch にログを出力

AWS CLI で 以下のようにしてログを出力します。

aws logs put-log-events --log-group-name log-to-kinesis --log-stream-name log-stream-to-kinesis --log-events timestamp=1653457849182,message="test"
2. S3 のバケットを確認

作成したバケットに以下のようなデータが格納されていることを確認します。

データは圧縮されているので、ダウンロードして解凍するとログが格納されていることが確認できます。

{"messageType":"CONTROL_MESSAGE","owner":"CloudwatchLogs","logGroup":"","logStream":"","subscriptionFilters":[],"logEvents":[{"id":"","timestamp":1653552879998,"message":"CWL CONTROL MESSAGE: Checking health of destination Firehose."}]}{"messageType":"DATA_MESSAGE","owner":"370560102364","logGroup":"log-to-kinesis","logStream":"log-stream-to-kinesis","subscriptionFilters":["log-to-kinesis-filter"],"logEvents":[{"id":"36873342191118257728364867749675187185934849327962587136","timestamp":1653457849182,"message":"test"}]}

さいごに

Kinesis Data Firehose は扱うのが難しいイメージでしたが、設定自体は非常に簡単でしたね。今回試したことはログ収集の初歩的なことだと思うので、次はもう一歩踏み込んだ形のログ収集にチャレンジしてみようと思います。

Cognito を使ったユーザ認証で S3 にアクセスしてみる

はじめに

アプリケーションの開発時、認証部分の開発は必須かと思います。しかし、認証まわりの開発は地味ですが意外と手間と時間がかかります。Cognito は認証基盤を短期間でアプリケーションに実装することができるサービスで認証部分の開発工数削減が見込めます。

今回のゴール

Cognito でユーザ認証を行い S3 にアクセスしてみようと思います。

使用するサービス

サービス 説明
Amazon Cognito Web アプリケーションやモバイルアプリケーションに対する安全な認証を提供するサービス
Amazon Simple Storage Service (Amazon S3) データを格納・管理できるオブジェクトストレージサービス

Amazon Cognito の主な 2 つのコンポーネント

Amazon Cognito にはユーザープールと ID プールというコンポーネントがあります。

ユーザープール

認証基盤を開発しなくてもウェブおよびモバイルアプリケーションからのサインアップ、サインインに使用することができます。ユーザーの ID やパスワードの認証情報をアプリ内部の「ユーザーディレクトリ」という領域に保存し、その情報を利用してアプリの「認証」を行います(ユーザープールはアクセスしてきたユーザーが誰であるかを確認します)。また、Cognito ユーザープールのみで認証することもできますが、SNS など外部の認証も使用することができます。

ID プール

AWS の他のサービスへのアクセス権をユーザーに付与する AWS 認証情報を提供します。例えば、Amazon S3バケットAmazon DynamoDB テーブルなどへのアクセスをユーザーに許可します。また、ID プールには認証プロバイダーを設定できます。認証プロバイダーには、SNS などの Web ID フェデレーションや Cognito ユーザープールが設定できます。

ユーザープールと ID プールの違い

「ユーザープール」は「認証(アクセスしてきたユーザーが誰か)」を処理しますが、「ID プール」は「認可(そのユーザーが利用できるサービスであるか)」を処理するという違いがあります。
また、「ユーザープール」の認証処理の対象は「アプリ」ですが、「ID プール」は「AWS のサービス」が対象となってるところも違いの一つです。

ユーザープールと ID プールをどのように活用すべきか

ユーザープールと ID プールの活用のポイントは、両方ともセットで使うことです(「認証(誰が)」と「認可(何のサービスを)」を組み合わせて利用することではじめて認証サービスとして機能するため)。

アーキテクチャ

S3 に対する読み取りだけ許可をするように Cognito で制御します。

実行環境

macOS 12.3.1
Python 3.9.6
Boto3 1.20.53

手順

  1. ユーザープールの作成
  2. ID プールの作成
  3. ユーザ登録
  4. S3 にアクセス

1. ユーザープールの作成

以下の設定でユーザープールを作成します。

項目
プロバイダーのタイプ Cognito ユーザープール
Cognito ユーザープールのサインインオプション E メール
パスワードポリシーモード Cognito のデフォルト
多要素認証 MFAなし
ユーザーアカウントの復旧 デフォルト
属性検証とユーザーアカウントの確認 デフォルト
属性変更の確認 デフォルト
必須の属性 email
属性変更の確認 デフォルト
E メール Cognito で E メールを送信
ユーザープール名 任意のユーザープール名
ホストされた認証ページ デフォルト
アプリケーションタイプ 秘密クライアント
ホストされた認証ページ デフォルト
アプリケーションクライアント名 任意のアプリケーションクライアント名
クライアントのシークレット クライアントのシークレットを生成しない
高度なアプリケーションクライアントの設定 デフォルト
属性の読み取りおよび書き込み許可 デフォルト

2. ID プールの作成

以下の設定で ID プールを作成します。

項目
ID プール名 任意のID プール名
認証されていない ID デフォルト
認証フローの設定 デフォルト
認証プロバイダー Cognito
ユーザープール ID 作成したユーザープールのユーザープール ID
アプリクライアント ID 作成したユーザープールのクライアント ID

ID プールの作成後にロールの作成を求められます。

ポリシーを以下のように設定します。

認証ユーザに付与するロール
"Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "mobileanalytics:PutEvents",
        "cognito-sync:*",
        "cognito-identity:*",
        "s3:Get*",
         "s3:List*",
         "s3-object-lambda:Get*",
         "s3-object-lambda:List*"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}
非認証ユーザに付与するロール
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "mobileanalytics:PutEvents",
        "cognito-sync:*"
      ],
      "Resource": [
        "*"
      ]
    }
  ]

3. ユーザ登録

ユーザープール内にユーザが未登録なため、ユーザの登録をします。

本来は認証画面を作りたいところなのですが、時間的に難しいので以下のコードでユーザ登録を行います。

import boto3

idp_client = boto3.client('cognito-idp')

aws_result = idp_client.admin_create_user(
    # ユーザープールID
    UserPoolId='xxxxx',
    Username='xxx@gmail.com', # なぜかUsernameにメールアドレスを求められる・・・
    UserAttributes=[
        {
            'Name': 'email',
            'Value': 'xxx@gmail.com'
        },
        {
            'Name': 'email_verified',
            'Value': 'true'
        }
    ],
    TemporaryPassword = 'xxx'
)

ユーザが作成されました。コードの詳細はこちらこちらでご確認ください。

4. S3 にアクセス

S3 にある以下のバケットのオブジェクトを取得します。

import boto3

# CognitoIdentityProviderクライアント(Using Amazon Cognito user pools API)
idp_client = boto3.client('cognito-idp')

# CognitoIdentityクライアント(Using Amazon Cognito Federated Identities)
identity_client = boto3.client('cognito-identity')

# 設定情報
account_id = 'xxx'
region = 'ap-northeast-1'
user_pool_id = 'xxx'
identity_pool_id = 'xxx'
client_id = 'xxx'
auth_flow = "ADMIN_NO_SRP_AUTH"
user_name = 'xxx'
password = 'xxx'
logins_key = f"cognito-idp.{region}.amazonaws.com/{user_pool_id}"

def admin_initiate_auth():
    response = idp_client.admin_initiate_auth(
                UserPoolId = user_pool_id,
                ClientId = client_id,
                AuthFlow = auth_flow,
                AuthParameters={
                    "USERNAME": user_name,
                    "PASSWORD": password,
                }
            )
    return response

def get_id(id_token):
    response = identity_client.get_id(
        AccountId = account_id,
        IdentityPoolId = identity_pool_id,
        Logins={
            logins_key: id_token
        }
    )
    return response

def get_credentials_for_identity(identity_id, id_token):
    response = identity_client.get_credentials_for_identity(
        IdentityId = identity_id,
        Logins = {
            logins_key: id_token
        }
    )
    return response

# ログイン
auth_response = admin_initiate_auth()

# IDトークンを取得
id_token = auth_response['AuthenticationResult']['IdToken']

# アイデンティティIDを取得
id_response = get_id(id_token)
identity_id = id_response['IdentityId']

# S3にアクセスするクレデンシャルを取得
credentials_response = get_credentials_for_identity(identity_id, id_token)
Credentials = credentials_response['Credentials']
access_key_id = Credentials['AccessKeyId']
secret_access_key = Credentials['SecretKey']
session_token = Credentials['SessionToken']

# S3にアクセス
s3_client = boto3.client('s3',
            region_name="ap-northeast-1",
            aws_access_key_id=access_key_id,
            aws_secret_access_key=secret_access_key,
            aws_session_token=session_token
        )

# バケットにあるアイテムを検索
object_list = s3_client.list_objects_v2(Bucket='cf-templates-lxrja6wfp65n-ap-northeast-1')
for obj in object_list['Contents']:
    print(obj['Key'])

以下のようにバケット内のアイテムを取得できました。

20221507Y9-network_and_server.yml
2022150G7w-network_and_server.yml
2022150HcQ-network_and_server.yml
2022150KyE-network_and_server.yml
2022150clO-network_and_server.yml
2022150mbX-network_and_server.yml
2022150nGG-network_and_server.yml
2022150yTO-network_and_server.yml

バケットにオブジェクトを PUT すると Access Denied されます(想定通りの動き)

s3_client.put_object(Body = 'IMG_0687.jpeg', Bucket='cf-templates-lxrja6wfp65n-ap-northeast-1', Key = 'test.jpeg')

さいごに

少し長くなってしまいましたが、Cognito を使って S3 にアクセスすることができました。ユーザープールや ID プールなどややっこしくて理解するのに時間がかかりましたが、いい勉強になりました。次は認証画面も作ってみようと思います。