1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Amazon ECSとVPC Lattice 備忘録

Posted at

この記事はAIで書いてはいません。

備忘録的な感じでつらつらと。

個人的にECSで稼働させているアプリがいくつかある。

別アカウントにあるECSサービスのAPIやDBにアクセスしたく、どういう方法がいいかを考えていたところ、VPC Latticeであればアカウントまたぎかつ、VPCのCIDR重複OKであることを知った。

なにはともあれ触ってみる。

VPC Lattice

VPC Latticeについての詳しい解説は、たくさん素晴らしい記事があると思うので割愛。

ちなみに「Lattice」って格子という意味らしい。なんかかっこいい。

登場するコンポーネントを大雑把に理解

  • サービス
    • ALBのような印象。リスナーを定義してターゲットグループに振り分ける
  • サービスネットワーク
    • VPCとサービスを関連付けるハブ的な役割
    • RAMで接続先のアカウントに共有する
  • ターゲットグループ
    • IP・LAMBDA・INSTANCE・ALB のターゲットタイプを指定可能
    • 右記のプロトコルをサポート: HTTP、HTTPS、TCP

VPC Lattice & ECS

事前準備

まずは、AというアカウントにECS & Latticeを構築。手順はこのブログの通り。

Cloud ShellにVPCをアタッチし、VPC内からcurlで Latticeから払い出されたドメインを叩いて200が返ってくる事を確認。

curl -o /dev/null -s -w "%{http_code}\" http://xxx.xxx.vpc-lattice-svcs.ap-northeast-1.on.aws
200

ちなみに、Latticeから払い出されたドメイン名で引けるIPはリンクローカルアドレス

dig A +short xxxx.xxxx.vpc-lattice-svcs.ap-northeast-1.on.aws                         
169.254.171.97

VPC Latticeサービスを作成すると、そのサービスを表すDNS名が与えられます。 この名前はグローバルに一意であり、外部から解決可能です。 ただし、VPCの外部からは、DNS名は169.254.171.x/24範囲(RFC3927で定義されたIPv4リンクローカル範囲169.254/16内)およびfd00:ec2:80::/64範囲(RFC4193で定義されたIPv6一意ローカル範囲fc00::/7内)の一連のIPアドレスに解決されます。 

どうもリンクローカルアドレスでっていうのが肝っぽい。

  • DHCP無しの自動設定用としてRFC3927で予約済みアドレス
  • ルーティングしない

なので…

  • VPCのCIDRと衝突することがない
  • 同一ネットワーク内だけが通信可能

接続元が名前解決の結果、リンクローカルアドレスに到達するとVPC LatticeのIngressエンドポイントにルーティングされ、よしなにやってくれるらしい。

自宅のPC2台をLANケーブルで接続してリンクローカルアドレスでの疎通を軽く実験してみる。

WifiをOFFにしてifconfigすると…

inet 169.254.70.148 netmask 0xffff0000 broadcast 169.254.255.255 みたいな感じでアドレスが割当たる。これがリンクローカルアドレス。ping送ったり、curlでリクエスト送ってみたりしてDHCP不要をなんとなく体感。

# 2台目のPCのリンクローカルアドレスにping
ping 169.254.95.46                                                                                                      ✔  23:06:24 
PING 169.254.95.46 (169.254.95.46): 56 data bytes
64 bytes from 169.254.95.46: icmp_seq=0 ttl=255 time=1.069 ms
64 bytes from 169.254.95.46: icmp_seq=1 ttl=255 time=1.483 ms
64 bytes from 169.254.95.46: icmp_seq=2 ttl=255 time=1.512 ms
64 bytes from 169.254.95.46: icmp_seq=3 ttl=255 time=1.273 ms

# 2台目のPCで起動しているhttpサーバーにcurlしてみる
curl http://169.254.95.46:8080
hello

別アカウントにサービスネットワークを共有する

さて、ここから本題。
Bのアカウントにサービスネットワークを共有し、BアカウントにあるECSタスクから疎通確認をする。
まずRAMでサービスネットワークを別アカウントへ共有。

1.png

同じ組織内のアカウントIDを指定して共有する。
2.png

こっちはBのアカウント。共有済みになった。
3.png

共有されたサービスネットワークにBアカウントのVPCをアタッチ。
4.png

BアカウントのECSタスクから疎通確認

セキリュティグループ以外にもアクセスポリシーの制御を検証したかったので、以下のコードをECCタスク起動しておく。環境変数のフラグでaws認証情報を使用するか否かを分けてる。

なお、アクセスポリシーを有効にしているVPC LatticeへのリクエストにはSIGv4署名が必須なよう。

  • VPC Lattice は、SIGv4 または SIGv4A で署名されたリクエストの認証を試みます。認証なしではリクエストが失敗します
  • VPC Lattice ではペイロード署名をサポートしていません。x-amz-content-sha256 ヘッダーの値を "UNSIGNED-PAYLOAD" に設定して送信する必要があります
サンプルコード

こちら を参考にしたサンプルコード

main.go
package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"os"

	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/smithy-go/aws-http-auth/credentials"
	"github.com/aws/smithy-go/aws-http-auth/sigv4"
	v4 "github.com/aws/smithy-go/aws-http-auth/v4"
)

func main() {
	url := os.Getenv("URL")
	if url == "" {
		fmt.Println("Error: URL environment variable is required")
		os.Exit(1)
	}

	useIAM := os.Getenv("IAM") == "true"

	if useIAM {
		if err := requestWithIAM(url); err != nil {
			fmt.Printf("Error with IAM request: %v\n", err)
			os.Exit(1)
		}
	} else {
		if err := requestSimple(url); err != nil {
			fmt.Printf("Error with simple request: %v\n", err)
			os.Exit(1)
		}
	}
}

func requestWithIAM(url string) error {
	region := os.Getenv("AWS_REGION")
	if region == "" {
		region = "ap-northeast-1"
	}

	cfg, err := config.LoadDefaultConfig(context.TODO(),
		config.WithRegion(region))
	if err != nil {
		return fmt.Errorf("failed to load AWS config: %w", err)
	}

	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}
	
	sdkCreds, err := cfg.Credentials.Retrieve(context.TODO())
	if err != nil {
		return fmt.Errorf("failed to retrieve credentials: %w", err)
	}

	creds := credentials.Credentials{
		AccessKeyID:     sdkCreds.AccessKeyID,
		SecretAccessKey: sdkCreds.SecretAccessKey,
		SessionToken:    sdkCreds.SessionToken,
	}

	signer := sigv4.New(func(o *v4.SignerOptions) {
		o.DisableDoublePathEscape = true
		o.AddPayloadHashHeader = true
		o.DisableImplicitPayloadHashing = true
	})

	err = signer.SignRequest(&sigv4.SignRequestInput{
		Request:     req,
		Credentials: creds,
		Service:     "vpc-lattice-svcs",
		Region:      region,
	})
	if err != nil {
		return fmt.Errorf("failed to sign request: %w", err)
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to execute request: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("failed to read response body: %w", err)
	}

	fmt.Printf("Status: %s\n", resp.Status)
	return nil
}

func requestSimple(url string) error {
	resp, err := http.Get(url)
	if err != nil {
		return fmt.Errorf("failed to execute request: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("failed to read response body: %w", err)
	}

	fmt.Printf("Status: %s\n", resp.Status)
	return nil
}

Dockerfile
FROM golang:1.21-alpine AS builder

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY main.go ./
RUN go build -o server main.go

FROM alpine:latest

RUN apk --no-cache add ca-certificates coreutils

WORKDIR /root/

COPY --from=builder /app/server .

EXPOSE 80

CMD ["tail", "-f", "/dev/null"]

上記のコードをデプロイして、ECS Exec。まずはアクセスポリシーなし状態でコンテナ内からプログラムを実行し疎通確認

~ # export URL=http://xxx.xxxx.vpc-lattice-svcs.ap-northeast-1.on.aws
~ # export IAM=false
~ # ./server
Status: 200

疎通OK。簡単にアカウントまたぎのECSサービス間通信が確認できた。

アクセスポリシー

アクセスポリシーを設定してみる。
AアカウントにてLatticeサービスへのアクセスポリシーを「IAM」に変更。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "<BアカウントのECSタスクロールARN>"
            },
            "Action": "vpc-lattice-svcs:Invoke",
            "Resource": "<LatticeサービスARN>/*"
        }
    ]
}

先程同様にBアカウントのECSタスクにECS Execする。

環境変数のフラグを立てて、プログラムを実行する。
これでECSタスクロールの認証情報を使用してリクエストを送っている状態になる。

~ # export IAM=true
~ # ./server
Status: 403 Forbidden
Body: AccessDeniedException: 
User: arn:aws:sts::xxx:assumed-role/ecsTaskRole/xxx is not authorized to perform:
vpc-lattice-svcs:Invoke on resource: arn:aws:vpc-lattice:ap-northeast-1:xxxxx:service/svc-xxx/ 
because no identity-based policy allows the vpc-lattice-svcs:Invoke action

権限不足で弾かれた。

ECSタスクロールに以下の権限を与えたうえで再度実行する。きっと通るはず。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "vpc-lattice-svcs:Invoke"
            ],
            "Resource": "<VPC LatticeサービスARN>/*",
            "Effect": "Allow"
        }
    ]
}

ヨシ。疎通成功!

~ # ./server
Status: 200 OK

さいごに

少し全体像がなんとなく理解できてきた。

次は別アカウントのRDSにVPC Latticeを使って接続してみようと思う。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?