michimani.net

AWS Copilot で生成されるタスクロールに任意のポリシーをアタッチする

2021-01-27

AWS Copilot で ECS で実行されるアプリケーションを管理する際、タスクロールもよしなに作ってくれます。今回は、そのタスクロールに任意の IAM ポリシーをアタッチしてみます。

目次

概要

AWS Copilot で生成されるタスクロールに、任意の IAM ポリシーをアタッチします。
前回のブログを書いた際に AWS の中の人からリプをもらったので、実際にやってみます。

Thank you 🙏 for the awesome blog post! You can also modify the task role using an Addons template https://t.co/Gvnq8YPw23

— Efe Karakus (@efekarakus) January 23, 2021

サンプルのアプリケーションとしては、 AWS アカウント内の S3 バケット名リストを返すような API を Go で実装してみます。その際に、最近 GA になった SDK for Go v2 も使ってみます。

アプリケーションの実装

まずは、 S3 バケット名のリストを返すような API を実装します。フレームワークとして echo を使って、 AWS SDK は v2 を使います。

labstack/echo: High performance, minimalist Go web framework

package main

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

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"github.com/labstack/echo/v4"
)

type HelloResponse struct {
	Message string `json:"message"`
}

type BucketsResponse struct {
	Buckets []string `json:"buckets"`
}

func main() {
	e := echo.New()

	e.GET("/", hello)
	e.GET("/buckets", buckets)

	e.Logger.Fatal(e.Start(":1323"))
}

func hello(c echo.Context) error {
	return c.JSON(http.StatusOK, HelloResponse{
		Message: "Hello ECS!",
	})
}

func buckets(c echo.Context) error {
	region := os.Getenv("AWS_DEFAULT_REGION")
	if region == "" {
		c.Echo().Logger.Error("Environment variable AWS_DEFAULT_REGION is undefined.")
		return c.JSON(http.StatusInternalServerError, nil)
	}

	cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region))
	if err != nil {
		c.Echo().Logger.Error("Failed to laod AWS deault config.", err.Error())
		return c.JSON(http.StatusInternalServerError, nil)
	}

	s3client := s3.NewFromConfig(cfg)
	out, err := s3client.ListBuckets(context.TODO(), &s3.ListBucketsInput{})
	if err != nil {
		c.Echo().Logger.Error("Failed to list buckets.", err.Error())
		return c.JSON(http.StatusInternalServerError, nil)
	}

	var buckets []string
	for _, b := range out.Buckets {
		buckets = append(buckets, aws.ToString(b.Name))
	}

	return c.JSON(http.StatusOK, BucketsResponse{
		Buckets: buckets,
	})
}

エンドポイントは 2 つ用意しています。

SDK for Go v2 の話を少し書いておくと、v2 では各クライアントの生成部分とメソッド実行時の引数 (第1引数に context を受け取るようになった) が変わっています。その他、パッケージの構成やメソッドについても細かい変更があるようです。
例えば *stringstring に変換する際、v1 では aws.StringValue() を使っていましたが、 v2 では aws.ToString() を使うようになっています。

sdk · pkg.go.dev

Copilot にデプロイする際の Dockerfile は下記内容で準備します。

FROM golang:1.15.5-alpine3.12 as build

ENV GOPATH=
ADD go.mod go.sum ./
RUN go mod download
ADD . .
RUN go build -o /main

ENTRYPOINT [ "/main" ]

Copilot でアプリのセットアップ

manifest.yml を作成

copilot init コマンドで manifest.yml を作成します。前回と違って今回はワークロードに Load Balanced Web Service を選択します。また、 Port にはコンテナ側で待ち受けるポート番号を指定します。今回であれば 1323 で echo のサーバーを起動しているので 1323 を指定します。

$ copilot init
...

Use existing application: No
Application name: list-s3-buckets
Workload type: Load Balanced Web Service
Service name: list-s3-buckets-app
Dockerfile: ./Dockerfile
no EXPOSE statements in Dockerfile ./Dockerfile
Port: 1323

...

この時点で下記のようなディレクトリ構成になっています。

.
├── Dockerfile
├── copilot
│   └── list-s3-buckets-app
│       └── manifest.yml
├── go.mod
├── go.sum
└── main.go

任意の IAM ポリシーを追加

今回のアプリケーションでは、 S3 のバケット一覧を取得する権限が必要になります。 IAM のアクション名でいうと s3:ListAllMyBuckets です。このアクションを許可する IAM ポリシーを作成して、 Copilot によって生成されるタスクロールにアタッチします。

そのために、 copilot/list-s3-buckets-app 配下に addons ディレクトリを作成し、その中に CloudFormation のテンプレートファイルを作成します。今回であれば s3-list-bucket-policy.yml です。

.
├── Dockerfile
├── README.md
├── copilot
│   └── list-s3-buckets-app
│       ├── addons
│       │   └── s3-list-bucket-policy.yml
│       └── manifest.yml
├── go.mod
├── go.sum
└── main.go

テンプレートファイルの内容は下記の通りです。

Parameters:
  App:
    Type: String
    Description: Your application's name.
  Env:
    Type: String
    Description: The environment name for the service.
  Name:
    Type: String
    Description: The name of the service.

Resources:
  ListBucketPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Version: 2012-10-17
        Statement:
        - Sid: ListBucketActions
          Effect: Allow
          Action:
            - s3:ListAllMyBuckets
          Resource: "*"

Outputs:
  ListBucketPolicyArn:
    Description: "The ARN of the ManagedPolicy to attatch to the task role."
    Value: !Ref ListBucketPolicy

CFn テンプレートを作成するうえで注意したいのは下記の 2 点です

その他、追加のリソースの定義については下記のドキュメントを参照してください。

Additional AWS Resources - AWS Copilot CLI

デプロイ

test 環境を作ってデプロイします。

$ copilot env init --name test --profile default --default-config
$ copilot deploy --env test

前回の Job の場合と違って、 Service のほうがデプロイに時間がかかる印象です。(10分弱)

確認

デプロイされたアプリケーションの URL はデプロイ時にも出力されますが、再度確認するためには copilot svc show コマンドを実行します。

$ copilot svc show
Only found one service, defaulting to: list-s3-buckets-app
About

  Application       list-s3-buckets
  Name              list-s3-buckets-app
  Type              Load Balanced Web Service

Configurations

  Environment       Tasks               CPU (vCPU)          Memory (MiB)        Port
  -----------       -----               ----------          ------------        ----
  test              1                   0.25                512                 1323

Routes

  Environment       URL
  -----------       ---
  test              http://list-Publi-1JMT8L0BFYS47-390312034.ap-northeast-1.elb.amazonaws.com

Service Discovery

  Environment       Namespace
  -----------       ---------
  test              list-s3-buckets-app.list-s3-buckets.local:1323

Variables

  Name                                Container            Environment         Value
  ----                                ---------            -----------         -----
  COPILOT_APPLICATION_NAME            list-s3-buckets-app  test                list-s3-buckets
  COPILOT_ENVIRONMENT_NAME              "                    "                 test
  COPILOT_LB_DNS                        "                    "                 list-Publi-1JMT8L0BFYS47-390312034.ap-northeast-1.elb.amazonaws.com
  COPILOT_SERVICE_DISCOVERY_ENDPOINT    "                    "                 list-s3-buckets.local
  COPILOT_SERVICE_NAME                  "                    "                 list-s3-buckets-app

httpie で確認してみます。

$ http http://list-Publi-1JMT8L0BFYS47-390312034.ap-northeast-1.elb.amazonaws.com
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 25
Content-Type: application/json; charset=UTF-8
Date: Tue, 26 Jan 2021 14:21:40 GMT

{
    "message": "Hello ECS!"
}
$ http http://list-Publi-1JMT8L0BFYS47-390312034.ap-northeast-1.elb.amazonaws.com/buckets
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: application/json; charset=UTF-8
Date: Tue, 26 Jan 2021 14:28:33 GMT
Transfer-Encoding: chunked

{
    "buckets": [
        "bucket_00001",
        ...
        "bucket_99999"
    ]
}

CLI で http リクエストするなら HTTPie が便利 - michimani.net

まとめ

AWS Copilot で ECS で実行されるアプリケーションをデプロイする際に、作成されるタスクロールに任意の IAM ポリシーをアタッチしてみた話でした。
これどうするんかなーと思っていたところで解決案をもらったわけですが、めちゃくちゃ簡単に任意のポリシーをアタッチできました。まあ、ドキュメントにしっかりと書いてあったんですが。もし同じ内容でお困りの方がいたら参考にしてみてください。

今回のコードは GitHub に置いています。

hello-ecs/list-s3-buckets at master · michimani/hello-ecs

以上、よっしー (michimani) でした。