VPC Latticeワークショップで理解を深める！

# VPC Latticeのアップデート
re:Invent周辺で、VPC Lattice関連のアップデートが大きく3つ存在しました。
- [VPC LatticeがECSをサポート開始](https://aws.amazon.com/about-aws/whats-new/2024/11/amazon-vpc-lattice-elastic-container-service/)
- [EventBridgeとStep FunctionsがプライベートAPIとの統合を発表](https://aws.amazon.com/about-aws/whats-new/2024/12/amazon-eventbridge-step-functions-integration-private-apis/)
- [VPC Lattice に VPC リソースでの TCP サポートが追加](https://aws.amazon.com/about-aws/whats-new/2024/12/vpc-lattice-tcp-vpc-resources/)

非常に興味を持ちつつも、そもそも自分はVPC Latticeが何なのかについてあまりキャッチアップできていません。

そこでAWSから提供されているVPC Latticeのワークショップを通して、理解を深めて行こうと思います！

# ワークショップ開始準備
以下のワークショップを実施していきます。

https://catalog.workshops.aws/handsonwithvpclattice/en-US

実施にあたって、VPC Latticeで接続するために必要なアプリケーションをCloudFormationで構築します。

ワークショップ冒頭の「Self Hosted」においてテンプレートが用意されているので、それを自身の環境でデプロイしましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/65f22d6a-fd08-5da5-3002-f6e58d3a9026.png)
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/3813acd0-3a6b-58de-46fc-1d5f930a6cee.png)

このCloudFormationテンプレートによって、「SuperApp」という架空の駐車場予約アプリで必要な、以下のリソースが作成されます。
- Payments VPCのアプリケーション ロード バランサーの背後にある EC2 インスタンスで実行されるPayments(支払い)サービス
- Rates VPC内の EC2 インスタンスで実行されるRates(料金)サービス
- Lambdaで実行されるReservation(予約)サービス
- クライアント VPCでのテストに使用するクライアントインスタンスのセット
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/7818a5fd-96f3-dbe4-d470-055e284d58f7.png)

:::note
すべてのVPCは`CIDR 10.0.0.0/16`でデプロイされています。
VPC Latticeは、接続するVPCのIPアドレス範囲が重複していても柔軟に対応可能なことを体感できるワークショップとなっています。
:::

今回のワークショップの目的は、「SuperApp」における**上記サービス間の通信を簡素化し、必要なセキュリティ制御を実施し、SuperApp 内のサービス間のやり取りをエンドツーエンドで可視化**することです。

したがって、実施手順はざっくり以下の3つです。
1. Service-to-service connectivity(サービス間の接続)
1. Application-layer security(アプリケーション層セキュリティ)
1. Observability(可観測性)

最終的には以下のアーキテクチャを目指して構築していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/8611093b-2bb4-2149-df43-63765af22dac.png)


# 1.Service-to-service connectivity
まずは、VPC LatticeとEC2・Lambdaを繋いでいきます。
ターゲットグループ・リスナーといったワードが出てくることから、最初は「ALBやELBのようなもの」として理解すると良さそうです。

## ステップ1.ターゲットグループを作成する
ターゲットグループは、一言でいうとアプリケーション本体です。サービス提供部分、と置き換えてもOKです。

EC2, EKS, ALB, Lambda関数に加えて、ECSも紐づけることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/378c9304-d55c-79b0-1315-43bfef4aaeda.png)

### Reservation(Lambda)
まずは予約サービス用のターゲットグループを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/9a575b46-212f-3290-b8ea-c6de515b2d00.png)

ここで何をターゲットとして指定できるかが確認できます。
アップデートされたECSも記述されていますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/628e7b10-2cf3-8bfc-c509-12c33e9a9a71.png)

Lambda関数をターゲットグループにする場合、イベント構造を選択できます。
JSON形式でイベントを送信し、すべてのリクエストに`X-Forwarded-For`ヘッダーを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/43932e12-f0b4-c6e2-3f23-13c9c9da02f6.png)

v2のイベント構造フォーマット例は以下です。
基本的にはこちらを使用することになると思います。
```
{
    "version": "2.0",
    "path": "/",
    "method": "GET|POST|HEAD|...",
    "headers": {
        "header-key": ["header-value", ...],
        ...
    },    
    "queryStringParameters": {
        "key": ["value", ...]
    },
    "body": "request-body",
    "isBase64Encoded": true|false,
    "requestContext": {
        "serviceNetworkArn": "arn:aws:vpc-lattice:region:123456789012:servicenetwork/sn-0bf3f2882e9cc805a",
        "serviceArn": "arn:aws:vpc-lattice:region:123456789012:service/svc-0a40eebed65f8d69c",
        "targetGroupArn": "arn:aws:vpc-lattice:region:123456789012:targetgroup/tg-6d0ecf831eec9f09",
        "identity": {
            "sourceVpcArn": "arn:aws:ec2:region:123456789012:vpc/vpc-0b8276c84697e7339",
            "type": "AWS_IAM",
            "principal": "arn:aws:iam::123456789012:assumed-role/my-role/my-session",
            "principalOrgID": "o-50dc6c495c0c9188",
            "sessionName": "i-0c7de02a688bde9f7",
            "x509IssuerOu": "string",
            "x509SanDns": "string",
            "x509SanNameCn": "string",
            "x509SanUri": "string",
            "x509SubjectCn": "string"
        },
        "region": "region",
        "timeEpoch": "1690497599177430"
    }
}
```

v1のイベント構造フォーマット例は以下です。
```
{
    "raw_path": "/path/to/resource",
    "method": "GET|POST|HEAD|...",
    "headers": {"header-key": "header-value", ... },
    "query_string_parameters": {"key": "value", ...},
    "body": "request-body",
    "is_base64_encoded": true|false
}
```
### rates(EC2)
続いて、料金サービスのEC2をターゲットとする設定を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/435a8348-394b-0906-2f8a-f0ff34996e32.png)

ここではプロトコルやVPCを選択します。サブネットは選択しません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/f667aca5-f1a1-07fd-52e3-b9e1fed54df4.png)
ここまで来たら「次へ」をクリックし、「ターゲットの登録」ページでは**そのまま「ターゲットグループの作成」をクリック**します。

理由は、EC2インスタンスではなく、**AutoScalingグループをターゲットグループに指定するから**です。

:::note
アップデート確認：
プロトコル選択で**TCP**も選択できるようになりました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/d97006aa-4d40-774f-e01e-c0064d6ea4ca.png)
:::

先ほどスキップした、ターゲットの登録を行います。ここではまだ登録済みターゲットが存在しません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/f1ab0235-1375-6650-ea19-8d1cdd5068c0.png)

EC2コンソールに移動し、料金用のAuto Scaling Groupを選択し、編集します。
※VPC Latticeコンソールではなく、EC2コンソールから設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/35d50b5d-f23f-effa-5b50-35c516869fc6.png)

ここでVPC Lattice統合オプションがあるので、ここで先ほど作成したターゲットグループを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/1d4dcfd6-1326-676c-3839-a5fac6b8764d.png)

:::note
今回は有効にしませんでしたが、ヘルスチェックの有無もここで設定できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/f5783ca0-9a81-a2f4-c926-7a02601933c7.png)
:::

これで、ターゲットグループの登録ができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/6cf40261-2c9c-7850-cf2d-0d48cb8a9e35.png)

### payments(EC2)
最後に支払い用サービスのターゲットグループを作成します。
ここではEC2の前段にあるALBを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/71dd9d60-d866-be34-abbf-dac7f0764715.png)

プロトコルやVPCは先ほどの料金用ターゲットグループと同じように設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/b57458a8-cec5-7ddb-84cf-5b671095e389.png)

そしてターゲットの登録で、ALBを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/82074f47-110c-6a82-5e82-88c3d89ac499.png)
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/c37f5ffe-d87a-3e88-f669-0c63e5e37be1.png)

これでターゲットグループの準備がすべて完了しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/031c1124-9660-592b-f3c7-8a30380b3269.png)

## ステップ2.サービスを作成する
続いて、VPC Latticeの本体とも言える、サービスの作成です。
ここでパスやルーティングの設定を行います。

以下2つのサービスを作成します。
- parking
→駐車場料金(/rate)と支払い(/payments)のターゲットを持つ
- reservation
→予約(/reservation)ターゲットを持つ

### parkingサービス
Lattice servicesからサービスを作成していきます。識別子は自由につけてOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/b7f52a96-f877-69fd-8920-19ae85c4fec8.png)

サービスアクセスに制限を設ける場合の設定も可能ですが、ここではスキップします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/f070f806-5c94-0fdd-76d8-06fc5ffc5e1a.png)

:::note
今回は単一アカウント内の複数VPCに対してサービスを作成していますが、実際の案件ではマルチアカウント構成になることが多いと思います。

その際はここでRAMを使ってリソースを共有するように設定できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/47576b7f-21bf-b79e-c7d5-bd2b0cd486fc.png)
:::

そして、ルーティングを設定します。まずはリスナーの設定です。
どのプロトコル・どのポートでLatticeからアプリケーションへ接続できるかを定義します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/dfbcfb0b-184d-e451-b5e4-7448631897ea.png)

そしてリスナールールを作成します。
parkingサービスでは、/ratesと/paymentsという2つのターゲットグループを持つので、2つのルールを作成していきます。

まずは料金用ルール。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/ebc5fe51-26df-c045-17a2-ddabb0b5dca8.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/6ae8d325-3be6-5fd5-9fee-d20a1b08a332.png)

続いて、支払い用ルール。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/b2f415ec-c0bf-35cf-29e7-188a139c482d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/9062a3df-c681-bd4c-e948-09fbbb15eb2d.png)

これでリスナールールが2つ作成されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/0aa64146-1605-c311-cc76-7d9193e39860.png)

デフォルトでどちらのターゲットグループに料金を送信できるか決められます。
ここでは料金用ターゲットグループを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/f8c47fd0-a584-8671-3a65-6c3e3afd83bc.png)

:::note
サービスネットワークはここでは指定せず作成してOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/56c32d56-1ac7-1348-4a7f-b7d22200b3f0.png)
:::

### reservation
予約用サービスも同じように作成していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/809cd91b-3363-bd75-cadf-26d20d91d8f3.png)

ここでは、予約用Lambdaへのルーティングのみ行うため、ルールの作成は不要です。
代わりに、リスナーのデフォルトアクションとして先ほど作成したターゲットグループを設定しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/48ba9231-02c1-6386-d976-f3c6e9a52f06.png)
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/95a1ab40-bd95-5452-ef3a-94717b123abf.png)

これで、2つのサービスが作成されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/463cf88e-9615-3588-9199-a32cc4e91195.png)

## ステップ3.サービスネットワークを作成する
続いて、サービスネットワークを作成していきます。

### サービスネットワーク作成
このサービスネットワークにサービスやリソースを関連付けることで、一元的なネットワークの管理が可能になります。

早速作成していきましょう。識別子は自由でOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/9a7c2740-b3b6-59bf-74a7-2ab8a865f34a.png)

:::note
サービスネットワークもRAMで共有できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/8234571c-7064-673e-92c7-dc0aec8e8396.png)
:::

### サービスをサービスネットワークに関連付ける
サービスネットワークを作成したら、サービスと関連付けます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/8b98194e-ad4f-f871-1a69-023a34b38acd.png)

先ほど作成した2つのサービスを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/63453f65-6536-85d9-d2ee-3ef3adb9aada.png)

### クライアントVPCをサービスネットワークに関連付ける
そしてサービスネットワークとクライアントVPCを紐づけます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/629ee9e3-fd5a-68c2-95d9-1d73fbba34ff.png)
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/6a5f3c68-12b3-57d7-c58f-48b3ecfeb6e2.png)

## ステップ4.接続テスト
これまででネットワーク周りの設定は完了しました。
続いては、きちんと接続できるかのテストを行っていきましょう。

まずはClient1のEC2インスタンスにセッションマネージャーで接続します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/e23d6be4-1119-a61e-126d-9355973af066.png)

以下コマンドを実行して、Lambdaで実装されている予約サービスへのアクセスを確認します。
```
curl https://<reservationサービスのドメイン名>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/c3852cbe-86ce-1929-3f0b-80ae6a83bfd5.png)

続いて、料金サービスへの接続を確認します。
```
// 料金サービスのデフォルトルーティングを確認する(料金サービスになっていればOK)
curl http://<parkingサービスのドメイン名>

// パスを指定し、料金サービスへのルーティングを確認する
curl http://<parkingサービスのドメイン名>/rates
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/7e11be5d-fb6a-e4c6-20d4-3c657589c502.png)

最後に、支払いサービスへの接続を確認します。
```
curl http://<parkingサービスのドメイン名>/payments
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/998e0ece-2e08-93ec-dfc7-1e7ac96df3d5.png)

各コマンド実行時に、上記のような表示がされていればOKです！
また、Client2でも同様の結果を得られます。

# 2.Application-layer security
続いて、VPC Latticeを用いてのセキュリティを実装していきます。

具体的には、以下の3層でセキュリティを確保します。
1. サービスとVPCを、サービスネットワークに関連付ける
1. サービスネットワークにおいて、ネットワークACLやセキュリティグループを実装する
1. サービスネットワークとサービスにおいて、VPC Lattice認証ポリシーを設定する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/420774b7-fb17-5e66-a861-9e4ee6e2253b.png)

## ステップ1.認証情報をClient EC2インスタンスで設定
この後、VPC Latticeでのアクセスに認証情報を必須とします。
それにあたっての前準備として、Clientインスタンスで認証情報を登録します。

具体的には、AWS SigV4を使用してhttpリクエストに署名し、プログラムによるプロセスをシミュレートするPythonプログラムもダウンロードします。

Client EC2インスタンス1と2の両方で以下のコマンドを実行していきます。

```
// 必要なモジュールインストール
cd /home/ssm-user/
wget https://d3fh841oeihish.cloudfront.net/signSigV4.py
pip3 install botocore
pip3 install requests
sudo yum -y install jq

// EC2インスタンスが持つIAMロールのARNを取得し、環境変数に保存する
export AWS_ROLE_ARN=$(aws sts get-caller-identity | jq -r '.Arn')
export AWS_ROLE_NAME=$(aws sts get-caller-identity | jq -r '.Arn | split("/")[1]')

// 以下の出力を後続で使用するため、テキストエディタに保存しておく
echo $AWS_ROLE_ARN
echo $AWS_ROLE_NAME

// 資格情報を環境変数に設定する
AWS_SESSION_TOKEN=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/${AWS_ROLE_NAME} | jq -r '.Token')
AWS_ACCESS_KEY_ID=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/${AWS_ROLE_NAME} | jq -r '.AccessKeyId')
AWS_SECRET_ACCESS_KEY=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/${AWS_ROLE_NAME} | jq -r '.SecretAccessKey')
```

:::note
先ほどインストールしたPythonプログラムですが、オレゴンリージョンがデフォルトで設定されています。
もし別のリージョンでワークショップを実施している場合は、viコマンドなどからガイルを編集し、リージョン設定を変更しておきましょう。
(私はバージニア北部リージョンで実施していました。)
```signSigV4.py
(略)
def make_request(endpoint_url, method):
    session = botocore.session.Session()
        sigv4 = SigV4Auth(session.get_credentials(), 'vpc-lattice-svcs', 'us-east-1')
    endpoint = endpoint_url
    data = "some-data-here"
    headers = {'Content-Type': 'application/json'}
(略)
```
:::

## ステップ2.サービスネットワークの保護
まずはサービスネットワークにおけるセキュリティの実装です。

先ほど作成したサービスネットワークの「アクセス」設定を編集していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/f3ef2ceb-a49b-f886-06de-ae8366bd89e7.png)

IAMで認証されたアクセスのみを許可するように設定します。
認証ポリシーは自動で入力されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/c597e76b-cf39-d194-78e7-5f9ef61e20c4.png)

:::note
ポリシーのCondition句で、「anonymousユーザーを除外する」というように設定してありますが、これが「認証されていないユーザーを除外する」という意味になっています。
```json
"Condition": {
    "StringNotEqualsIgnoreCase": {
        "aws:PrincipalType": "anonymous"
    }
}
```
:::

その後、再びClient EC2インスタンスから接続確認をしてみます。
先程と同様のコマンドでアクセスしようとすると、拒否されるようになっています。
```
curl https://<reservationサービスのドメイン名>
curl http://<parkingサービスのドメイン名>/rates
curl http://<parkingサービスのドメイン名>/payments
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/f7c30276-ada2-bcf1-1413-ffe3f48a42d3.png)

そこで、以下のように認証付きでコマンドを実行してみます。
```
curl https://<reservationサービスのドメイン名> --aws-sigv4 "aws:amz:us-east-1:vpc-lattice-svcs" --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" --header "x-amz-security-token:$AWS_SESSION_TOKEN" --header "x-amz-content-sha256:UNSIGNED-PAYLOAD"

curl http://<parkingサービスのドメイン名>/rates --aws-sigv4 "aws:amz:us-east-1:vpc-lattice-svcs" --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" --header "x-amz-security-token:$AWS_SESSION_TOKEN" --header "x-amz-content-sha256:UNSIGNED-PAYLOAD"

curl http://<parkingサービスのドメイン名>/payments --aws-sigv4 "aws:amz:us-east-1:vpc-lattice-svcs" --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" --header "x-amz-security-token:$AWS_SESSION_TOKEN" --header "x-amz-content-sha256:UNSIGNED-PAYLOAD"
```

すると、きちんと接続できるようになっていました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/d5e87ebb-fccf-b78b-21ca-de846dd7f844.png)

## ステップ3.細かな認証ポリシーで個々のサービスを保護
先ほどはサービスネットワークにおいて、認証されているアクセスのみ許可するという、かなり粗めの認証ポリシーとなっていました。

ここでは各サービスにおいて、より細かな認証ポリシーを実装することでよりセキュアなネットワークにしていきましょう。

まずはparkingサービスの「アクセス」設定を編集していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/08aba045-98cc-1e0b-722c-9e05571c9c3a.png)

先程と同様にIAMでの認証を設定していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/65296ff2-f880-87ab-3222-bca0a696673f.png)

ここでは以下のポリシーを入力します。
```json
{
   "Version": "2012-10-17",
   "Statement": [
       {
           "Effect": "Allow",
           "Principal": {
               "AWS": "<'LatticeWorkshop InstanceClient1' IAM ARN>"
           },
           "Action": "vpc-lattice-svcs:Invoke", 
           "Resource": "<ARN of Parking Service>/rates"
       },
       {
           "Effect": "Allow",
           "Principal": {
               "AWS": "<'LatticeWorkshop InstanceClient2' IAM ARN>"
           },
           "Action": "vpc-lattice-svcs:Invoke",
           "Resource": "<ARN of Parking Service>/payments",
           "Condition": {
               "StringEquals": {
                   "vpc-lattice-svcs:RequestMethod": "GET"
               }
           }
       }
   ]
}
```

また、上記のポリシーでは、以下のような設定がされています。
|  | クライアント1 | クライアント2 |
|:-|:-:|:-:|
| /rates | ○ | ✗ |
| /payments (GETメソッド) | ✗ | ○ |
| /payments (POSTなどその他) | ✗ | ✗ |


そして、再びEC2インスタンスからコマンドを実行します。
```
curl http://<parkingサービスのドメイン名>/rates --aws-sigv4 "aws:amz:us-east-1:vpc-lattice-svcs" --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" --header "x-amz-security-token:$AWS_SESSION_TOKEN" --header "x-amz-content-sha256:UNSIGNED-PAYLOAD"

curl http://<parkingサービスのドメイン名>/payments --aws-sigv4 "aws:amz:us-east-1:vpc-lattice-svcs" --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" --header "x-amz-security-token:$AWS_SESSION_TOKEN" --header "x-amz-content-sha256:UNSIGNED-PAYLOAD"

curl -d "hello=world" -X POST http://<parkingサービスのドメイン名>/payments --aws-sigv4 "aws:amz:us-east-1:vpc-lattice-svcs" --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" --header "x-amz-security-token:$AWS_SESSION_TOKEN" --header "x-amz-content-sha256:UNSIGNED-PAYLOAD"
```
クライアント1での実行は、以下のように/rates apiを呼び出すことができ、/payments apiへのアクセスは拒否されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/c923d9fe-d3fb-6ad4-9527-9ed2dfddcd39.png)

クライアント2での実行は、以下のように/rates apiを呼び出すことができず、/payments apiのGETメソッドはコール可能で、POSTメソッドは拒否されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/c07c4e0a-7e5d-c6f7-12b7-88a7622d0547.png)

先ほどよりも細かく認証ポリシーを設定することができました！

## ステップ4.AWS SigV4の使用
続いて、reservationサービスに対しても認証ポリシーを設定していきます。
ここでは、AWS SigV4を用いた認証を行います。

AWS SigV4(Signature Version 4)とは、AWS APIリクエストに認証情報を追加するためのAWS署名プロトコルです。
(細かいことを調べるとかなり深い沼だったので、詳細は別途纏めます。)

reservationサービスのアクセス設定を編集していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/a2d8833d-c8b3-f103-e14a-161acf946768.png)

サービスネットワークと同じように、認証されたアクセスのみを許可するように設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/2f69aa33-cca5-fa6a-005a-7e72f6261a5d.png)

Client EC2インスタンスに接続し、以下コマンドを実行して動作確認を行います。
```
cd /home/ssm-user
python3 signSigV4.py https://<reservationサービスのドメイン名> POST
python3 signSigV4.py https://<reservationサービスのドメイン名> GET
```
以下のような出力がなされていれば成功です！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/99fb1474-61e6-ee90-59ab-1ebeb7318cd7.png)

# 3.Observability(可観測性)
最後に、VPC Latticeからのログ出力についてです。
以下の3つがログ出力先として選択できます。
- CloudWatch Logs
- Data Firehose
- S3

まずはサービスネットワークでのログ出力設定です。
アクションからログ設定が編集できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/6fa6a3e7-174b-1bb4-54fc-f7e897439da3.png)

この時、`Service access logs`と`Resource access logs`の2つから選べます。
どちらもS3にログを出力してもらうように設定したのですが、結果的には`Service access logs`しかログが出力されていませんでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/80a1fcd1-c8a1-59e1-bc25-e105e7359cdd.png)

続いて、サービスでのログ設定を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/17bd62e4-0722-644f-10b3-e2d757617e29.png)

こちらは`Service access logs`のみ設定可能でした。
CloudWatch Logsにログを出力してもらいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/f691215f-cbef-f20d-3c33-a993f170339e.png)

その後、以下コマンドをそれぞれのClientインスタンスで実行します。
```Client EC2 instance 1.
cd /home/ssm-user
    
for ((i=1;i<=30;i++)); do curl http://<parkingサービスのドメイン名>/rates --aws-sigv4 "aws:amz:us-east-1:vpc-lattice-svcs" --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" --header "x-amz-security-token:$AWS_SESSION_TOKEN" --header "x-amz-content-sha256:UNSIGNED-PAYLOAD"; done

for ((i=1;i<=30;i++)); do curl http://<parkingサービスのドメイン名>/payments --aws-sigv4 "aws:amz:us-east-1:vpc-lattice-svcs" --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" --header "x-amz-security-token:$AWS_SESSION_TOKEN" --header "x-amz-content-sha256:UNSIGNED-PAYLOAD"; done

for ((i=1;i<=30;i++)); do python3 signSigV4.py https://<reservationサービスのドメイン名> POST; done

for ((i=1;i<=30;i++)); do python3 signSigV4.py https://<reservationサービスのドメイン名> GET; done

for ((i=1;i<=30;i++)); do curl https://<reservationサービスのドメイン名> --aws-sigv4 "aws:amz:us-east-1:vpc-lattice-svcs" --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" --header "x-amz-security-token:$AWS_SESSION_TOKEN" --header "x-amz-content-sha256:UNSIGNED-PAYLOAD"; done
```

```Client EC2 instance 2.
cd /home/ssm-user

for ((i=1;i<=30;i++)); do curl http://<parkingサービスのドメイン名>/rates --aws-sigv4 "aws:amz:us-east-1:vpc-lattice-svcs" --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" --header "x-amz-security-token:$AWS_SESSION_TOKEN" --header "x-amz-content-sha256:UNSIGNED-PAYLOAD"; done

for ((i=1;i<=30;i++)); do curl <parkingサービスのドメイン名>/payments --aws-sigv4 "aws:amz:us-east-1:vpc-lattice-svcs" --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" --header "x-amz-security-token:$AWS_SESSION_TOKEN" --header "x-amz-content-sha256:UNSIGNED-PAYLOAD"; done

for ((i=1;i<=30;i++)); do curl <parkingサービスのドメイン名>/payments hello=world --aws-sigv4 "aws:amz:us-east-1:vpc-lattice-svcs" --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" --header "x-amz-security-token:$AWS_SESSION_TOKEN" --header "x-amz-content-sha256:UNSIGNED-PAYLOAD"; done
```

上記コマンドでログが格納されたので、S3とCloudWatch Logsを確認しに行きます。

まずはS3です。
ResourceAccessLogは出力がありませんでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/05c6fd11-fb41-835f-0265-afe5a2a84e7a.png)

ServiceAccessLogには出力されていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/bc620495-abff-aed7-101b-44d408a3b05b.png)

どんなログが出力されているのか確認してみましょう。
ワークショップの手順的にはS3 Selectでクエリを投げているのですが、S3 Selectは新規利用ができなくなっているので、別途Athenaで実施する必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/c387df27-6a7c-2b25-f217-62a43cf962cd.png)

試したい方向けに、Athena用のコマンドを掲載しておきます。
```athena-create-table.sql
CREATE EXTERNAL TABLE vpc_lattice_logs (
    serviceNetworkArn STRING,
    resolvedUser STRING,
    listenerType STRING,
    authDeniedReason STRING,
    targetGroupArn STRING,
    sourceVpcArn STRING,
    destinationVpcId STRING,
    sourceIpPort STRING,
    listenerId STRING,
    targetIpPort STRING,
    failureReason STRING,
    serviceArn STRING,
    sourceVpcId STRING,
    startTime TIMESTAMP,
    requestMethod STRING,
    requestPath STRING,
    protocol STRING,
    responseCode INT,
    bytesReceived BIGINT,
    bytesSent BIGINT,
    duration INT,
    userAgent STRING,
    hostHeader STRING,
    serverNameIndication STRING,
    requestToTargetDuration INT,
    responseFromTargetDuration INT,
    sslCipher STRING,
    tlsVersion STRING,
    callerPrincipal STRING,
    callerPrincipalOrgID STRING,
    callerX509IssuerOU STRING,
    callerX509SubjectCN STRING,
    callerX509SANNameCN STRING,
    callerX509SANDNS STRING,
    callerX509SANURI STRING
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://your-bucket-name/path-to-logs/';
```
↑location配下のS3パスは適宜書き換えてください。

続いて以下コマンドで、エンドポイントごとのアクセス数を集計します。

```athena-access-endpoints.sql
SELECT 
    requestPath,
    requestMethod,
    COUNT(*) as request_count,
    AVG(duration) as avg_duration_ms
FROM vpc_lattice_logs
GROUP BY requestPath, requestMethod
ORDER BY request_count DESC;
```

以下のように、エンドポイントとメソッドごとに纏めてくれるはずです！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/7a05d427-4161-15df-e5af-73d0d7670958.png)


最後に、CloudWatch Logsも確認しておきましょう。
コンソールのロググループから確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/af1b23b5-2a94-643f-a3fa-bb17e3600a2e.png)

許可されたアクセスのログは以下のように出力されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/fdc7147c-c9c0-b205-e964-3a3a6c032924.png)

許可されていないアクセスのログは`authDeniedReason`が記載されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/78c80bab-7744-38a1-11e9-0072c84d038d.png)


# まとめ
再掲ですが、最終的に完成した構成としては以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/8611093b-2bb4-2149-df43-63765af22dac.png)

VPC Latticeの概念を理解するのに、取り組みやすいハンズオンとなっていました！
かなり有用なサービスだと認識しているので、引き続きキャッチアップしていこうと思います。

個人的に疑問なのは、案件や模擬試験でたまに見る以下のような構成もVPC Latticeを使えば楽につかえるようになったりするのかな？というところです。
技術的には可能そうですが、細かい要件を満たせるのかがチョット不明です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/9a151ae0-a94c-455b-3e10-72857078963f.png)

この部分については自分で触って試してみようと思います！