🚜

GitHub ActionsでIAM Roleが利用できるようになったと聞いたので、簡単なパイプラインを組んでみた(lambda編)

2021/09/23に公開

https://dev.classmethod.jp/articles/github-actions-without-permanent-credential/

GitHubActionsについてはあまり触れてこなかったのですが、どうもこれまではAWSのIAMユーザーを作成してアクセスキーとシークレットアクセスキーを発行して、環境変数に持たせるようなやり方をする必要があったようなのですが、クレデンシャルを持たせないでもIAM Roleを利用できるようになったとのこと。

簡単なLamdba関数のソースをGit管理し、それがmainブランチに適用されたタイミングでLamdba関数を更新するパイプラインを組んでみました。

Lambdaの概要

特定のタグ付けがなされていて、停止中のインスタンスを起動させます。

AWS認定試験の問題にも出てきそうですが

  • 平日の夜になれば開発環境用のEC2を停止させる
  • また、平日の朝になればそれらEC2を起動する

といったユースケーズで使うようなイメージです。

前準備

IAMポリシー

GithubActionsのワークフローではこちらのIAMポリシーを利用します。

// GithubActions用
{
  "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "s3:PutObject",
          "iam:ListRoles",
          "lambda:UpdateFunctionCode",
          "lambda:CreateFunction",
          "lambda:UpdateFunctionConfiguration"
        ],
        "Resource": "*"
      }
    ]
}

また lamdba関数にアタッチするIAMポリシーも作成しておきます。

// lambda関数用(余分なActionsが含まれています)
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "ec2:StartInstances",
                "ec2:DescribeInstances",
                "ec2:DescribeImages",
                "ec2:DescribeTags",
                "ec2:DescribeSnapshots"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

IAM IDプロバイダー

OpenIDConnect を利用するための IDプロバイダを準備します。

  • プロバイダ
    • vstoken.actions.githubusercontent.com
  • プロバイダタイプ
    • OpenID Connect
  • 対象者
    • sigstore

【参考】

AWS federation comes to github actions

https://awsteele.com/blog/2021/09/15/aws-federation-comes-to-github-actions.html

OpenID Connectとは

https://tech-lab.sios.jp/archives/8651

https://qiita.com/TakahikoKawasaki/items/498ca08bbfcc341691fe

IAMロール

Github Actions用

  • 信頼されたエンティティ
    • ↑で作成した IDプロバイダを選択
  • ポリシー
    • GithubActions用に作成したIAMポリシー

lambda関数用

  • 信頼されたエンティティ
    • AWSサービス → Lamda関数を選択
  • ポリシー
    • Lambda関数用に作成したポリシー

GitHubActions用に作成したロールのARNをコピーしておく

Lambda関数を準備

今回は aws-cliの update-functionsでアップデートすることを想定しているため、一旦元となるlamdba関数を作成する。

  • 関数の名前
    • aws_ec2_start
  • IAMロール
    • ↑でLamda関数用に作成したIAMロールを指定

Secretsを設定

ロールのARNをgithubのSecretsに登録しておきます。

https://qiita.com/developer-kikikaikai/items/60b209c065f076dca7a1

ROLE_ARN=${GithubActions用に作成したIAMロールのARN}
# ${}の部分は消してください

コードの用意

実際のリポジトリはこちらです。

ファイル構造などはこちらをご覧ください

https://github.com/bun913/ec2_auto_start

GithubActions用のyaml設定

name: DeployLamdaFunctions

on:
  push:
    branches:
      - main
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    env:
      WORK_DIR: ./aws_ec2_start
    steps:
      - uses: actions/checkout@v2
        with:
          ref: main
      - uses: actions/setup-python@v1
        with:
          python-version: 3.8
      - name: Configure AWS
        run: |
          export AWS_ROLE_ARN=${{ secrets.ROLE_ARN }}
          export AWS_WEB_IDENTITY_TOKEN_FILE=/tmp/awscreds
          export AWS_DEFAULT_REGION=ap-northeast-1
          echo AWS_WEB_IDENTITY_TOKEN_FILE=$AWS_WEB_IDENTITY_TOKEN_FILE >> $GITHUB_ENV
          echo AWS_ROLE_ARN=$AWS_ROLE_ARN >> $GITHUB_ENV
          echo AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION >> $GITHUB_ENV
          curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sigstore" | jq -r '.value' > $AWS_WEB_IDENTITY_TOKEN_FILE
      - name: zip source code
        run: |
          pip3 install -r requirements.txt -t ./src/
          cd src;zip -r ../package.zip .
      - name: deploy lambda function
        run: |
          # aws_ec2_start という名前で先にlambda関数を作成しておく必要がある
          aws lambda update-function-code --function-name aws_ec2_start --zip-file fileb://package.zip --publish

lambda関数用のコードはこのような感じ

import boto3
from botocore.exceptions import ClientError
import json


class ResponseHandler:
    ERR_TEMPLATE = {
        "errorType": "",
        "errorMessage": ""
    }
    RES_TEMPLATE = {
        "Message": ""
    }

    def __init__(self):
        self.errors = []

    def append_eroor(self, type: str, message: str):
        """返却するエラー内容をセット

        Args:
            type str: エラータイプ
            message str: エラーメッセージ
        """
        dic = ResponseHandler.ERR_TEMPLATE.copy()
        dic['errorType'] = type
        dic['errorMessage'] = message
        self.errors.append(dic)

    def return_errors(self) -> str:
        """エラーメッセージの返却

        Returns:
            str: JSON形式のエラー内容
        """
        raise Exception(json.dumps(self.errors))

    def get_respose(self, body: str) -> str:
        """
        レスポンスをJsonで返却
        通常成功時はEC2インスタンスのidをリスト形式で返却

        Args:
            body str: レスポンス内容
        """
        dic = ResponseHandler.RES_TEMPLATE.copy()
        dic['Message'] = body
        return json.dumps(dic)


response = ResponseHandler()


def lambda_handler(event, context):
    instances = get_ec2_instances()
    try:
        
        instances.start()
    except ClientError as e:
        response.append_eroor(type="ClientError", message=str(e))
        response.return_errors()
    except Exception as e:
        response.append_eroor(type="UnExpectedError", message=str(e))
        response.return_errors()
    id_list = [instance.id for instance in instances]
    return response.get_respose(",".join(id_list))


def get_ec2_instances():
    """EC2インスタンスを取得
    """
    ec2 = boto3.resource('ec2')
    filter = [
        {
            'Name': 'tag:Reboot',
            'Values': ['True']
        },
        {
            'Name': 'instance-state-name',
            'Values': ['stopped']
        }
    ]
    result = ec2.instances.filter(Filters=filter)
    return result

デプロイの経過

こんな感じで githubの mainリポジトリにコードがコミットされると・・・

Lanmda関数も更新されている!

さらに変更がマージされると・・・・

バージョンがアップされている!

CloudWatchEventsの設定

CI / CDパイプラインとは関係ありませんが、Cloud Watch Eventsで特定の時間にLambda関数が実行されるように設定しました。

Discussion