CircleCIで AWS CLI v2 を使ってみた

CircleCIでAWSに対するCI/CDパイプラインを組むとき、AWS CLI (v2)を使ってみました。
2020.08.25

CircleCIからAWSにデプロイするCI/CDを組んでいます。 その際に使っているAWS CLI(v1)をAWS CLI(v2)に変えてみようとなりました。

そこで、CircleCIでAWS CLI(v2)を簡単に使ってみました。本記事では、CircleCIでaws s3 lsコマンドを実行してバケット一覧を取得します。

本記事よりも、下記のほうが簡単に導入できます。

おすすめの方

  • CircleCIからAWSにアクセスしたい方(AssumeRoleを使う)
  • CircleCIでAWS CLI(v2)を使いたい方

事前準備

アクセスキーが流出しても被害が最小限になるIAMユーザを作成して使います。

CircleCIで使うIAMユーザとIAMロールを作成

CloudFormationテンプレートを作成する

次のCloudFormationテンプレートをprepare.yamlとして作成します。 sts:ExternalIdには推測されにくい任意の文字列を設定します。これはあとでCircelCIの環境変数に設定します。

prepare.yaml

AWSTemplateFormatVersion: "2010-09-09"
Description: IAM User and IAM Role for CircleCI

Resources:
  # デプロイ用のIAMユーザ
  DeployUser:
    Type: AWS::IAM::User
    Properties:
      UserName: !Sub circleci-sample-user
 
  # デプロイ用のIAMユーザに付与するIAMポリシー(AssumeRoleできる)
  DeployUserPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub circleci-sample-policy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Resource: !GetAtt DeployRoleForUser.Arn
      Users:
        - !Ref DeployUser
 
  # デプロイ用のIAMユーザがAssumeRoleするIAMロール(S3に対するRead権限のみ付与)
  DeployRoleForUser:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub circleci-sample-role-for-user
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              AWS:
                - !GetAtt DeployUser.Arn
            Condition:
              StringEquals:
                sts:ExternalId: any-id-foo-bar
      Policies:
        - PolicyName: !Sub circleci-sample-policy-for-user
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - s3:List*
                Resource:
                  - "*"
      MaxSessionDuration: 3600

Outputs:
  DeployRoleArn:
    Value: !GetAtt DeployRoleForUser.Arn

デプロイする

aws cloudformation deploy \
    --template-file prepare.yaml \
    --stack-name CircleCI-Sample-Prepare-Stack \
    --capabilities CAPABILITY_NAMED_IAM

IAMユーザのアクセスキーを取得する

IAMユーザのアクセスキーを取得します。これはあとでCircelCIの環境変数に設定します。

aws iam create-access-key \
    --user-name circleci-sample-user
  • AccessKeyId: xxxxxx
  • SecretAccessKey: yyyyyy

IAMロールのARNを取得する

作成したIAMロールのARNを取得します。これはあとでCircelCIの環境変数に設定します。

aws cloudformation describe-stacks \
    --stack-name CircleCI-Sample-Prepare-Stack \
    --query 'Stacks[].Outputs'
  • OutputValue: arn:aws:iam::1234567890:role/circleci-sample-role-for-user

AssumeRole用のスクリプトを作成

AssumeRole用に次のスクリプトをassume_role.shとして作成します。 このスクリプトではIAMユーザがAssumeRoleを行い、指定したIAMロールとして振る舞うための一時的なアクセスキーを取得しています。

assume_role.sh

#!/usr/bin/env bash

set -xeuo pipefail

aws_sts_credentials="$(aws sts assume-role \
  --role-arn "$AWS_DEPLOY_IAM_ROLE_ARN" \
  --role-session-name "$ROLE_SESSION_NAME" \
  --external-id "$AWS_DEPLOY_IAM_ROLE_EXTERNAL_ID" \
  --duration-seconds 900 \
  --query "Credentials" \
  --output "json")"

cat <<EOT > "aws-env.sh"
export AWS_ACCESS_KEY_ID="$(echo $aws_sts_credentials | jq -r '.AccessKeyId')"
export AWS_SECRET_ACCESS_KEY="$(echo $aws_sts_credentials | jq -r '.SecretAccessKey')"
export AWS_SESSION_TOKEN="$(echo $aws_sts_credentials | jq -r '.SessionToken')"
EOT

実行権限を付与しておきます。

chmod 755 assume_role.sh

CircleCIの設定ファイルを作成

.circleciディレクトリを作成し、その中にconfig.ymlを作成します。

mkdir .circleci
touch .circleci/config.yml

続いて、config.ymlファイルの中身を記述します。

config.yml

version: 2.1
executors:
  my-executor:
    docker:
      - image: circleci/python:3.8.5
    working_directory: ~/work

commands:
  restore:
    steps:
      - restore_cache:
          key: work-v1-{{ .Revision }}

  save:
    steps:
      - save_cache:
          paths:
            - "aws-cli"
          key: work-v1-{{ .Revision }}

  install:
    steps:
      - run:
          name: install
          command: |
            if [[ ! -d aws-cli ]]; then
              curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
              unzip awscliv2.zip
              sudo ./aws/install --install-dir ~/work/aws-cli
            fi

  access:
    steps:
      - run:
          name: access
          command: |
            export PATH=$PATH:$HOME/work/aws-cli/v2/current/bin

            aws --version

            ./assume_role.sh
            source aws-env.sh

            aws s3 ls

jobs:
  setup:
    executor: my-executor
    steps:
      - checkout
      - restore
      - install
      - save

  aws_access:
    executor: my-executor
    steps:
      - checkout
      - restore
      - install
      - save
      - access

workflows:
  version: 2.1
  aws-test-workflow:
    jobs:
      - setup:
          filters:
            branches:
              only:
                - master
      - aws_access:
          requires:
            - setup
          filters:
            branches:
              only:
                - master

CircleCIの設定

リポジトリのPush

まずはGitHubにリポジトリをPushしておきます。

git push origin master

プロジェクトの設定

CircleCIにログインし、先ほどのリポジトリの「Set Up Project」を選択してプロジェクト設定を行います。

CircleCIのプロジェクトセットアップを行う

環境変数の設定

Pipelineが動き出すので、右上にある「Project Settings」を選択します。

CircleCIのプロジェクト設定を行う

「Environment Variables」を選択し、環境変数の設定を行います。 コピペ時に空白が混ざらないように注意してください(たまに良くある)。

CircleCIの環境変数を設定する

Name Value
AWS_ACCESS_KEY_ID 取得したAccessKeyId
AWS_SECRET_ACCESS_KEY 取得したSecretAccessKey
AWS_DEPLOY_IAM_ROLE_ARN 取得したIAMロールのARN
AWS_DEPLOY_IAM_ROLE_EXTERNAL_ID sts:ExternalIdで設定した値
AWS_DEFAULT_REGION ap-northeast-1
AWS_DEFAULT_OUTPUT json
ROLE_SESSION_NAME CircleCI

いざ、実行!

既に動いて失敗しているPipelineがあると思うので、「Rerun」します。

CircleCIのパイプラインをRerunする

しばらくすると、成功しました!

CircleCIのパイプラインが成功する

Jobの詳細を確認すると、S3バケットの一覧がバッチリ取得できています!

CircleCIでS3バケット一覧が取得できた様子

片付け

実験終了後、作成したIAMユーザなどを削除しておきます。使わないIAMユーザ(アクセスキー有)を放置するのは、セキュリティ的によろしくないからです。

aws cloudformation delete-stack \
	--stack-name CircleCI-Sample-Prepare-Stack

さいごに

CircleCIでAWS CLI(v2)を使ってみました。 本記事では直接インストールしましたが、Dockerイメージを使う方式でも対応できそうです。

参考