AWSの部屋

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

スポンサーリンク

Cognito ユーザープールをオーソライザーとして使って API Gateway のアクセスを制御する

はじめに

本日は Cognito ユーザープールを API Gateway のオーソライザーとして使ってみようと思います。

Cognito ユーザープール とは

以下のエントリーをご参照ください。

www.aws-room.com

オーソライザーを使うメリット

API Gatewayのエンドポイントはグローバルに公開されているおり、誰でもアクセス可能です。IPアドレスなどでアクセスを制御することもできますが、認証されたユーザだけがアクセス可能な API としてデプロイすることでセキュアな状態を維持することができます。

今回のゴール

Cognito 経由で REST API を使って S3 のバケット一覧を取得します

アーキテクチャ

手順

  1. ユーザープールの作成
  2. ユーザ登録
  3. IDトークンの取得
  4. IAMロール 作成
  5. Lambda 関数の作成
  6. API Gateway作成
  7. 動作確認

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

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

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

2. ユーザ登録

以下のようにユーザを作成します。

3. IDトークンの取得

このIDトークンを利用して API Gateway へアクセスしますので事前に取得します。

import boto3
import json

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

response = idp_client.admin_initiate_auth(
           UserPoolId = "xxx",
           ClientId = "xxx",
           AuthFlow = "ADMIN_NO_SRP_AUTH",
           AuthParameters={
               "USERNAME": "xxx",
               "PASSWORD": "xxx",
           }
 )

print(json.dumps(response, indent=2))

以下のようなイメージでIDトークンが取得できます(以下は値を加工しています)

{
  "ChallengeParameters": {},
  "AuthenticationResult": {
    "AccessToken": "eyJrlWcQT0_LDihkbBhWf_zaddMc6k1JEcd1hGlsyOV3D4n2qClnxlFaTDTFXZvM8gco8QX0W0_ib_Ium_4wnORiUAQQeoQK6hEZruY7IJ7EGfkTictJyQl5iOdxCnk7TYA",
    "ExpiresIn": 3600,
    "TokenType": "Bearer",
    "RefreshToken": "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.Io5wUP2Aw9ZTCnxO_xmzG-q6riAKfpdhmTLjnT6BGtRH2fowrNIg7XjueMFjztVlkXGqRr2zG1HJLe4azmomdJe1d7_-N61zgiBYG9MZI_1sF8Z",
    "IdToken": "eyJraWQiOiJiV3hrV3FOZ2lCdSsrZDJLWGwxa0JVdjlCSUVjRHZuUUg4YmlaTklSQWVJPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiIwNTYyNDVkOS03MzIwLTQzZDgtYWRiNC1kMzY3NDEyNGVjMzciLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaX99rEU6uHLA"
  },
  "ResponseMetadata": {
    "RequestId": "ed94d428-8120-4838-9429-49320925d7e6",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Sat, 19 Nov 2022 05:14:08 GMT",
      "content-type": "application/x-amz-json-1.1",
      "content-length": "4132",
      "connection": "keep-alive",
      "x-amzn-requestid": "ed94d428-8120-4838-9429-49320925d7e6"
    },
    "RetryAttempts": 0
  }
}

4. IAMロール 作成

「Lambda-S3Access」という AWS Systems Manager への権限を付与したロールを作成します。

5. Lambda 関数の作成

以下のように Lambda 関数を作成します。

項目
オプション 一から作成
関数名] get-s3-bucket-list
ランタイム] Python 3.9
アーキテクチャ] 86_64
既存のロール]] Lambda-S3Access
ソースコード

S3 のバケット一覧を取得します。

import json
import boto3

s3 = boto3.resource('s3')

def lambda_handler(event, context):

    bucket_list = []
    for bucket in s3.buckets.all():
        bucket_list.append(bucket.name)
    
    return bucket_list

6. API Gateway作成

以下のように API Gateway を作成します。

項目
API タイプを選択 REST API
プロトコルを選択する] REST
新しい API の作成 ] 新しい API
API cognito-authorizer
説明 任意
エンドポイントタイプ デフォルト
API 作成後

リソースを作成します。

「5. Lambda関数の作成」で作成した関数名を入力しメソッドを作成します。

Lambda 関数に自動的に API Gateway のトリガーが設定されます。

API をデプロイします。

デプロイ後にエンドポイントの URLが表示されます。

以下のようにオーソライザーを作成します。

「認可トークン」項目にIDトークンをコピーしてテストすると正常終了します。

リソース > メソッドリクエストを選択します。

作成したオーソライザーを選択し更新します。

再度API をデプロイします。

7. 動作確認

IDトークンなし

以下のように REST API を実行します。

curl https://m158t0jime.execute-api.ap-northeast-1.amazonaws.com/development

認証されていない旨のメッセージが返されました。

{"message":"Missing Authentication Token"}
IDトークンあり

以下のように REST API を実行します。(IDトークンは加工しています)

curl https://m158t0jime.execute-api.ap-northeast-1.amazonaws.com/development -H "Authorization: eyJraWQiOiJiV3hrV3"

S3 のバケット一覧を取得することができました。

["20220514-cloudfront-test", "20220521-web-hosting", "20220807-sqs-test", "20220809-endpoint-gateway", "20220809-vpc-endpoint", "20220810-s3-gateway-endpoint", "20220810-test-bucket", "20220812-rekognition-test", "aws-athena-query-results-370560102364-ap-northeast-1", "aws-cloudtrail-logs-370560102364-44d0a6db", "aws-presigned-url", "cf-templates-lxrja6wfp65n-ap-northeast-1", "data-lake-20210331", "log-from-kinesis-sanvarie", "test-queue-sanvarie"]

さいごに

少しとっつきにくい Cognito ですが、使いこなせば非常に便利なサービスですね。まだまだ触りのところしかわからないので引き続き勉強しようと思います。

RDS Proxy を使って Lambda から Auroraに接続してみる

はじめに

本日は RDS Proxy を使ってみようと思います。RDS Proxyは AWS認定試験で頻出するサービスなのでしっかり押さえておきたいですね。

RDS Proxy とは

Lambda 関数から DB に多くの接続が発生する可能性があるアーキテクチャの場合、リクエストの増加に伴い DB 接続の拒否が発生しますが、RDS Proxy を使うことによって DB 接続プールを確立し再利用することで効率的な接続をすることができるようになります。

RDS Proxy を使わないパターン

RDS Proxy を使うパターン

アーキテクチャ

Amazon Secrets Manager に格納した DB 接続情報を使って Lambda から Aurora に接続します。

手順

  1. DB の作成
  2. IAMロールの作成
  3. Secrets Manager の設定
  4. RDS Proxy の作成
  5. テストデータ作成
  6. Lambda 関数の作成
  7. 動作確認

1. DB の作成

以下の設定で DB を作成します。

[データベース作成方法を選択]:標準作成
[エンジンのタイプ]:Amazon Aurora [エディション]:PostgreSQL との互換性を持つ Amazon Aurora
[テンプレート]:開発/テスト
[DB クラスター識別子]:aws-room-cluster
[マスターユーザー名]:postgres
[パスワードの自動生成]:チェックしない
[DB インスタンスクラス]:メモリ最適化クラス
[可用性と耐久性]:Aurora レプリカを作成しない
[ネットワークタイプ]:IPv4
[Virtual Private Cloud (VPC)]:任意のVPC
[サブネットグループ]:任意のサブネットグループ
[パブリックアクセス]:あり
[VPCセキュリティグループ]:既存の 選択
[既存のセキュリティグループ ]:任意のセキュリティグループ
[アベイラビリティーゾーン]:任意のアベイラビリティーゾーン

DBを作成したら接続確認をします。

2. IAMロールの作成

access-rdb」という Amazon AuroraAWS Secrets Manager への権限を付与したロールを作成します。

3. Secrets Manager の設定

以下のようにシークレットを作成します。

[シークレットのタイプ]:Amazon RDS データベースの認証情報
[ユーザー名]:「1. DB の作成」で設定したユーザ
[パスワード]:「1. DB の作成」で設定したパスワード
[暗号化キー]:任意の暗号化キー
[データベース]:「1. DB の作成」で作成したデータベース

4. RDS Proxy の作成

以下のように RDS Proxy を作成します。

[エンジンファミリー]:PostgreSQL
[プロキシ識別子]:任意の文字列
[データベース]:「1. DB の作成」で作成したAurora DB クラスタ
[接続プールの最大接続数]:100
[Secrets Manager のシークレット]:「4. Secrets Manager の設定」で作成したシークレット
[IAM ロール]:IAMロールを作成
[IAM 認証]:許可されていません

5. テストデータ作成

test というテーブルに以下のようなデータを作成しました。

6. Lambda 関数の作成

以下のように Lambda 関数を作成します。

[オプション]:一から作成
[関数名]:access-rds-through-proxy
[ランタイム]:Python 3.8
[アーキテクチャ]:x86_64
[既存のロール]:access-rdb

レイヤーの追加

psycopg2 を使用して PostgreSQL に接続するために以下のようにレイヤーの追加を行います。

[レイヤーソース]:ARN を指定
[ARNを指定]:arn:aws:lambda:ap-northeast-1:898466741470:layer:psycopg2-py38:1

ソースコード

Secrets Manager からDB 接続情報を取得して 作成したデータベースにあるテーブルにアクセスします。

import boto3
from botocore.exceptions import ClientError
import base64
import psycopg2
import json

secret_name = "aurora"
region_name = "ap-northeast-1"
    
def lambda_handler(event, context):
    secret_dict = json.loads(get_secret())

    connect = psycopg2.connect(
                  host = secret_dict['host'],
                  user = secret_dict['username'],
                  password = secret_dict['password'],
                  port = 5432,
                  dbname = 'postgres'
              )
    with connect.cursor() as cur:
        cur.execute('select * from public.test')
        rows = cur.fetchall()

    print(rows)

def get_secret():
    session = boto3.session.Session()
    client = session.client(
        service_name='secretsmanager',
        region_name=region_name
    )

    try:
        get_secret_value_response = client.get_secret_value(
            SecretId=secret_name
        )

    except ClientError as e:
        print(e.response['Error']['Code'])
        if e.response['Error']['Code'] == 'DecryptionFailureException':
            raise e
        elif e.response['Error']['Code'] == 'InternalServiceErrorException':
            raise e
        elif e.response['Error']['Code'] == 'InvalidParameterException':
            raise e
        elif e.response['Error']['Code'] == 'InvalidRequestException':
            raise e
        elif e.response['Error']['Code'] == 'ResourceNotFoundException':
            raise e
    else:
        if 'SecretString' in get_secret_value_response:
            secret = get_secret_value_response['SecretString']
        else:
            secret = base64.b64decode(get_secret_value_response['SecretBinary'])

        return secret

7. 動作確認

Lambda を実行すると以下のように Aurora に格納したテストデータを取得することができました。

さいごに

今まで Aurora はあまり使ってこなかったのですが、RDS Proxy の学習とともに Aurora や Secrets Manager に対する理解も深めることができました。途中つまずくところもあったのですが、やはり座学よりも自分で手を動かした方が知識が身体に染み付く気がしますね。

パラメータストアを使って Lambda から機密情報にアクセスする

はじめに

本日はパラメータストアを使ってみようと思います。AWS認定試験に頻出のサービスなのでしっかり理解しておきたいですね。

パラメータストアとは

AWS Systems Manager のサービスの一つで、データベースへの接続文字列のようなパラメータを一元管理します。

アーキテクチャ

API Gateway 経由でパラメータストアに登録されているDB接続情報を取得します。

手順

  1. 機密情報をパラメータストアに登録
  2. IAMロール 作成
  3. Lambda 関数の作成
  4. API Gateway作成
  5. 動作確認

1. 機密情報をパラメータストアに登録

以下3つの値をパラメータストアに登録します。

DB名

[名前]:db-name
[利用枠]:標準
[タイプ]:安全な文字列
[KMS キーソース]:現在のアカウント
[KMS キー ID]:alias/aws/ssm
[値]:test

ユーザ名

[名前]:user-name
[利用枠]:標準
[タイプ]:安全な文字列
[KMS キーソース]:現在のアカウント
[KMS キー ID]:alias/aws/ssm
[値]:sanvarie

パスワード

[名前]:password
[利用枠]:標準
[タイプ]:安全な文字列
[KMS キーソース]:現在のアカウント
[KMS キー ID]:alias/aws/ssm
[値]:hoge

2. IAMロール 作成

access-parameter-store」という AWS Systems Manager への権限を付与したロールを作成します。

3. Lambda 関数の作成

以下のように Lambda 関数を作成します。

[オプション]:一から作成
[関数名]:get-db-connection
[ランタイム]:Python 3.9
[アーキテクチャ]:x86_64
[既存のロール]:access-parameter-store

ソースコード

パラメータストアに登録されている値を取得します。

import json
import boto3

parameter_names = ['db-name','user-name','password']

def lambda_handler(event, context):

    output_text = get_ssm_param()

    return {
        'statusCode': 200,
        'body': json.dumps({
            'output_text': output_text
        }),
        'isBase64Encoded': False,
        'headers': {}
    }
    

def get_ssm_param():
    ssm = boto3.client('ssm')
    
    response = ssm.get_parameters(
        Names = parameter_names,
        WithDecryption = True
    )
    
    params = {}
    
    # パラメータを配列に格納
    for param in response['Parameters']:
        params[param['Name'] ] = param['Value']

    return params

4. API Gateway作成

REST API として API を作成します。

リソースを作成します。

「3. Lambda関数の作成」で作成した関数名を入力しメソッドを作成します。

Lambda 関数に自動的に API Gateway のトリガーが設定されます。

API をデプロイします。

デプロイ後にエンドポイントの URLが表示されます。

5.動作確認

Postman を使ってエンドポイントの URL に以下のようなリクエストを送ってみます。

https://1f9hnppgfj.execute-api.ap-northeast-1.amazonaws.com/development/parameter

パラメータストアに格納した値を取得することができました。

さいごに

このように パラメータストアを使うことによって簡単に機密情報へのアクセスをすることができます。AWS Secrets Manager を使っても同様のことができますので、二つのサービスの違いを知るために AWS Secrets Manager も使ってみようと思います。

Amazon Rekognition を使った画像分析の結果を DynamoDB に格納する

はじめに

近年、画像を分析してそこから情報やインサイトを抽出することが一般的になってきています。画像の分析は高度な知識が必要ですが、Amazon Rekognition を使うことによって簡単に画像を分析することができるので本エントリーで試してみようと思います。

Amazon Rekognition とは

画像や動画を分析するサービスです。有名人を検出したり表情から感情を分析したりすることができます。Amazon Rekognition には以下2つサービスがあり、本エントリーでは「Amazon Rekognition Image」を使います。

Amazon Rekognition が提供する機能

本エントリーでは Amazon Rekognition Image の「ラベル検出」を使います。

例えば、ラベル検出を以下の画像に対して実行すると、画像内の「車」「自動車」「人」などがラベルとして検出されて、それぞれの信頼度がパーセンテージで表示されます。

アーキテクチャ

S3 にアップロードした画像の分析を行い、結果を DynamoDB に保存します。

役割
サービス名 内容
Amazon S3 画像ファイルの格納を Lambda に通知
AWS Lambda Amazon Rekognition の実行と分析結果の DynamoDB への格納
Amazon Rekognition 画像分析の実行
Amazon DynamoDB 画像分析結果の格納

手順

  1. S3 バケットの作成
  2. DynamoDB テーブルの作成
  3. IAM ロールの作成
  4. Lambda 関数の作成
  5. S3 バケットへのイベント通知の作成
  6. 動作確認

1. S3 バケットの作成

「20220812-rekognition-test」バケットを作成します。

2. DynamoDB テーブルの作成

検出したラベルを保存する DynamoDB のテーブルを作成します。

[テーブル名]:rekognition-label
[パーティションキー]:image
[ソートキー]:label

3. IAM ロールの作成

S3、Rekognition、DynamoDBへの権限を付与したロールを作成します。

4. Lambda 関数の作成

以下のように Lambda 関数を作成します。

[オプション]:一から作成
[アーキテクチャ]:x86_64
[関数名]:rekognitionFunction
[ランタイム]:Python 3.9
[既存のロール]:execute-rekognition

コード

Amazon Rekognition の実行と分析結果を DynamoDB へ格納する Lambda 関数のコードです。

import json
import urllib
import boto3

# 使用するサービス
s3 = boto3.client('s3')
rekognition = boto3.client('rekognition')
dynamodb = boto3.resource('dynamodb')

# DynamoDB テーブル
table = dynamodb.Table('rekognition-label')
 
def lambda_handler(event, context):
    
    # バケット名取得
    bucket = event['Records'][0]['s3']['bucket']['name']
    
    # オブジェクト名取得
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    
    # ラベル検出の実行
    detect = rekognition.detect_labels(
        Image={
            'S3Object': {
                'Bucket': bucket,
                'Name': key
            }
        }
    )
    
    # ラベルを取得
    labels = detect['Labels']
    
    for i in range(len(labels)):
        label_name = labels[i]['Name']
        label_confidence = int(labels[i]['Confidence'])
        
        # 信頼度が80%以上のラベルのみDynamoDBに保存する
        if label_confidence >= 80:
            table.put_item(
                Item={
                    'image': key,
                    'label': label_name,
                    'confidence': label_confidence,
                }
            )

5. S3 バケットへのイベント通知の作成

作成方法については以下のエントリーをご参照ください。

www.aws-room.com

6. 動作確認

使用する画像

こちらの画像を使用します。

処理実行

S3 バケットに上記の画像をアップロードします。

DynamoDBにラベルが格納されたことを確認します。Boardwalk や Building など写真にある物体を認識していることがわかります。

さいごに

Amazon Rekognition を使えばさくっと画像分析の実行が可能ということがわかりました。今後は Amazon Rekognition Video を使って動画の分析もやってみようと思います。

EventBridge を使用して EC2 インスタンスの停止を検知し再起動させる

はじめに

システム運用時に何かしらのイベントをきっかけに特定の処理を実行したり、スケジュールされた処理を実行したいことがあると思いますが、そういった際には EventBridge を使うと便利です。

Amazon EventBridge とは

AWSサービス、カスタムアプリケーション、 SaaSアプリケーションのための サーバレスイベントバスサービスで、多数のAWS サービスとSaaS アプリケーションを接続します。

イベントバスとは

イベントを受信するパイプライン

アーキテクチャ

ユーザが誤って EC2 インスタンスを停止したと仮定して、そのインスタンスを再起動させる処理を実行します。

イベント種類 内容
EventBridgeに直接送信されるイベント AWSリソースの状態変化を起点にイベントが発生する
CloudTrail経由で送信されるイベント ユーザーのAWSリソースに対する操作(=API呼び出し)を 起点にイベントが発生する
EventBridgeで未サポートのサービス

AWSのリソースの操作ログ を取得する AWS CloudTrail 経由でイベントを取得することが可能です。

手順

  1. EC2インスタンスの起動
  2. IAMロールの作成
  3. Lambda 関数の作成
  4. EventBridge ルールの作成
  5. 動作確認

1. EC2インスタンスの起動

EC2 インスタンスを起動させます。

2. IAMロールの作成

「AmazonEC2FullAccess」ポリシーを付与した「Lambda-Execute-Instance」ロールを作成します。これで Lambda から EC2 へのアクセスができるようになります。

3. Lambda 関数の作成

以下のように Lambda 関数を作成します。

[関数名]:MyFunctionEC2
[ランタイム]:Python 3.9
[既存のロール]:Lambda-Execute-Instance

EC2 インスタンスを開始する処理を実装します。

iimport boto3
import os

# インスタンスID
instances = [os.environ['INSTANCE_ID']]

def lambda_handler(event, context):
    ec2 = boto3.client('ec2', region_name='ap-northeast-1')
    
    # インスタンスの状態変化を取得
    action = event["Action"]
    
    # インスタンスの状態を取得
    response = ec2.describe_instances(InstanceIds=instances)
    ec2_status = response['Reservations'][0]['Instances'][0]['State']['Name']

    # インスタンスが停止されたかを判定
    if action == "Stop":
        ec2.start_instances(InstanceIds=instances)
        print('インスタンスを開始: ' + str(instances[0]))
    else:
        print('インスタンスに対する処理を実行しない')

デプロイが完了したら環境変数を設定します。

キー
INSTANCE_ID EC2 インスタンスの ID

4. EventBridge ルールの作成

少し長いですが、以下のように EventBridge ルールを作成します。

以下の JSON を Lambda に送信します。

Lambda のトリガーとして EventBridge が登録されました。

5. 動作確認

インスタンスの停止

インスタンスを停止します。

インスタンスの開始

インスタンスの停止後に自動的にインスタンスが開始されました。

さいごに

EventBridge を使うことによって簡単に EC2 インスタンスの状態変化を検知することができることがわかりました。AWS認定試験に必ず出題される EventBridge ですが、他にも様々なことができるので今度は他の機能も使ってみようと思います。

S3 に保存した CloudTrail のログを Athena で参照してみる

はじめに

CloudTrail を使うことによって「いつ」「誰が」「何を」実行したのかをログとして残すことができます。今回は CloudTrail で残したログを Athena で参照してみようと思います。

使用するサービス

サービス名 内容
Amazon S3 AWS が提供するオブジェクトストレージサービス
AWS CloudTrail AWS アカウントのガバナンス、コンプライアンス、運用監査、リスク監査を行うためのサービス
Amazon Athena Amazon S3 内のデータを SQL 分析できるインタラクティブなクエリサービス

アーキテクチャ

手順

  1. CloudTrail で証跡の作成
  2. S3 でログの確認
  3. Athena でテーブルの作成
  4. Athena でクエリの実行

1. CloudTrail で証跡の作成

以下のように設定し、証跡の作成を行います。

これで S3 に対するすべてのイベントをログに記録することができるようになります。

2. S3 でログの確認

「20220810-test-bucketバケットを作成し、その中に「hane.jpg」をアップロードしました。

このようにバケットにログが格納されました。

ログを確認すると作成したバケットに関するログが残っていることがわかります。

3. Athena でテーブルの作成

CloudTrail コンソールから「Athena テーブルを作成」をクリックします。

ログが格納されるバケットを選択しテーブルを作成します。

Athena のクエリエディタでテーブルが作成されていることを確認します。

4. Athena でクエリの実行

Athena でクエリを実行しログを参照します。以下はサンプルとして10行だけデータを抽出しています。

select eventTime as "イベント時間",
       userIdentity.userName as "ユーザー名",
       eventName as "イベント名",
       eventSource as "イベントソース"
  from "default"."cloudtrail_logs_aws_cloudtrail_logs_370560102364_44d0a6db"
 limit 10;

このように「いつ」「誰が」「何を」したのかを把握することができます。

また、以下のように対象のファイルに絞ってログを抽出することもできます。

select eventTime as "イベント時間",
       userIdentity.userName as "ユーザー名",
       eventName as "イベント名",
       eventSource as "イベントソース"
  from "default"."cloudtrail_logs_aws_cloudtrail_logs_370560102364_44d0a6db"
 where requestparameters like '%hane.jpg%' -- 検索対象のファイル名
 limit 10;

さいごに

まだ簡単な使い方だけですが、CloudTrail の基本的な使い方は理解することができました。今後は AWS認定ソリューションアーキテクト-プロフェッショナル試験に向けて理解を深めていこうと思います。

VPCエンドポイント経由で S3 にアクセスする

はじめに

EC2 から AWS サービスへの通信は、デフォルトではインターネットを経由したアクセスが必要になりますが、VPCエンドポイントを利用することで、AWS のデータセンター内のインターナルネットワーク経由でのアクセスが可能になります。今回はセキュアな通信方法として VPCエンドポイントを経由した S3 へのアクセスを試してみようと思います。

VPCエンドポイントとは

VPCAWS サービス間の接続を有効にするコンポーネントのことで、以下2つの種類の VPCエンドポイントがあります。本エントリーでは「ゲートウェイエンドポイント」を使います。

ゲートウェイエンドポイント

VPC のサービス専用のゲートウェイをアタッチします。ゲートウェイエンドポイントの対象サービスは S3 と DynamoDB です。

インターフェイスエンドポイント

VCP のサブネットに ENI を作成します。ENI に割り当てられたプライベートIPアドレスを使用してサービスにアクセスします(AWS PrivateLinkという技術を使用)。インターフェイスエンドポイントの対象サービスは S3を含み数多くあります。

アーキテクチャ

インターネット経由での S3 のアクセスを拒否し、VPCエンドポイント経由での S3 へのアクセスのみを許可します。

手順

  1. S3 バケットの作成
  2. IAMロールの作成
  3. EC2 インスタンスの起動
  4. VPCエンドポイントの作成
  5. バケットポリシーの設定
  6. 動作確認

1. S3 バケットの作成

「20220809-endpoint-gateway」というバケットを作成します。

適当にファイルもアップロードします。

2. IAMロールの作成

「EC2toS3」という EC2 が S3 へアクセスするための IAMロールを作成します。

3. EC2 インスタンスの起動

作成した IAMロールを設定し、プライベートサブネットに EC2 インスタンスを起動します。

また、パブリックサブネットにも同様に EC2 インスタンスを起動します。

4. VPCエンドポイントの作成

サービス「com.amazonaws.ap-northeast-1.s3」を選択します。

プライベートサブネットに属しているルートテーブルを選択してエンドポイントを作成します。

このようにエンドポイントが作成されます。

5. バケットポリシーの設定

バケットポリシーを以下のように設定し、VPCエンドポイントを経由した場合のみバケットへのアクセスを許可します。

{
  "Id": "VPCe",
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VPCe",
      "Action": "s3:*",
      "Effect": "Deny",
      "Resource": "arn:aws:s3:::20220809-endpoint-gateway",
      "Condition": {
        "StringNotEquals": {
          "aws:SourceVpce": [
            "vpce-02d3065c3ea6a5612"
          ]
        }
      },
      "Principal": "*"
    }
  ]
}

6. 動作確認

インターネット経由でのアクセス

パブリックサブネットにあるインスタンスから S3 バケットにアクセスします。

aws s3 ls --region ap-northeast-1 --recursive s3://20220809-endpoint-gateway

アクセスが拒否されました。想定通りの動きですね。

VPCエンドポイント経由でのアクセス

パブリックサブネットにあるインスタンスから S3 バケットにアクセスします。

aws s3 ls --region ap-northeast-1 --recursive s3://20220809-endpoint-gateway

S3 バケットにアクセスすることができました。

さいごに

このように VPCエンドポイントを使うことでセキュアな通信が可能になります。今回はゲートウェイエンドポイントのみでしたので、次はインターフェイスエンドポイントも使ってみようと思います。