この記事は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でサービスネットワークを別アカウントへ共有。
共有されたサービスネットワークにBアカウントのVPCをアタッチ。
BアカウントのECSタスクから疎通確認
セキリュティグループ以外にもアクセスポリシーの制御を検証したかったので、以下のコードをECCタスク起動しておく。環境変数のフラグでaws認証情報を使用するか否かを分けてる。
なお、アクセスポリシーを有効にしているVPC LatticeへのリクエストにはSIGv4署名が必須なよう。
- VPC Lattice は、SIGv4 または SIGv4A で署名されたリクエストの認証を試みます。認証なしではリクエストが失敗します
- VPC Lattice ではペイロード署名をサポートしていません。
x-amz-content-sha256
ヘッダーの値を"UNSIGNED-PAYLOAD"
に設定して送信する必要があります
サンプルコード
こちら を参考にしたサンプルコード
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
}
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を使って接続してみようと思う。