CircleCIから安全にAWSのプロダクション環境へデプロイする

CircleCIから複数のAWSアカウント、たとえばステージング環境とプロダクション環境(本番環境)へ、安全にデプロイする方法を記載します。

# 課題
- CircleCIはAWSのCodePipeline等より使い勝手が良いのでAWSのプロダクション環境へのデプロイに使いたい
    - しかしステージング環境とプロダクション環境は別のAWSアカウントで構築しておりIAMのアクセスキーの切り替えが必要
    - そしてIAM権限をAWSの外に出すのはセキュリティ上の懸念がある


# 対策
- 別アカウントなので認証情報の切り替えが必要
    - => **CircleCIの[Contexts](https://circleci.com/docs/ja/2.0/contexts/)を利用する**
- IAM権限をAWSの外に出すのはセキュリティ懸念がある
    - => **利用するときだけIAMアクセスキーを有効化する** 
        - Slackから簡単にキーの有効/無効を切り替えられるようにしておきましょう

順に説明してゆきます。

## ブランチごとにAWS認証情報を切り替え
AWSの別アカウントで構築したプロダクションへデプロイするために、ブランチごとにAWS認証情報を切り替える手順を記載します。

ここではmasterブランチへの更新をプロダクション環境、その他のブランチへの更新をステージングへデプロイすると想定します。
まず前提として、CircleCIの[環境変数](https://circleci.com/docs/ja/2.0/env-vars/)を利用してステージングへのデプロイを実現できます。
手順の詳細は割愛しますが、環境変数に `AWS_DEFAULT_REGION`、`AWS_ACCESS_KEY_ID`、`AWS_SECRET_ACCESS_KEY`(シークレットキー) を登録した後に[Orbs](https://circleci.com/orbs/registry/orb/circleci/aws-cli/)を利用します。
※従来の設定項目であるAWS Permissionsは非推奨となり、将来的に廃止される予定です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50056/91f55924-5f04-8047-f282-56271ceb23ad.png)


この状態から、masterブランチへの更新時のみ別のIAM認証を使いたい場合、以下の手順で実現できます。

- プロダクション環境へのデプロイ用に[Context](https://circleci.com/docs/ja/2.0/contexts/)を作成し、`AWS_ACCESS_KEY_ID` と `AWS_SECRET_ACCESS_KEY` を登録
    - ここでは例として `production_deployment` という名前で作成します
      ![context.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50056/2776a664-40f2-9004-1b02-9c980ddc9289.gif)
- 登録したContext `production_deployment` は、以下のような感じで利用できます。

```yaml
version: 2.1 # Contextを利用するにはWorkflowsを使う必要があるので`2.1`を指定しています

orbs: # Orbs, CircleCIのスニペットみたいなものです
  aws-cli: circleci/aws-cli@0.1.13

executors:
  default:
    working_directory: ~/repo
    docker:
      - image: circleci/python:3.7.1-node

jobs:
  deploy:
    executor:
      name: default

    steps:
      - checkout

      - restore_cache:
          keys:
            - v1-npm-dependencies-{{ checksum "package-lock.json" }}
            - v1-npm-dependencies-

      - run:
          command: npm ci

      - save_cache:
          paths:
            - ./node_modules
          key: v1-npm-dependencies-{{ checksum "package-lock.json" }}

      # Orbsを使用してaws-cliのインストールとAWS認証情報の反映を行う
      - aws-cli/install
      - aws-cli/configure

      - deploy:
          command: npm run deploy

workflows:
  deploy:
    jobs:
      - deploy: # このjobはfiltersによりmasterブランチ「以外」で実行される
          filters:
            branches:
              ignore:
                - master
      - deploy: # このjobはfiltersによりmasterブランチ「のみ」で実行される
          context: production_deployment # ここで先程作成したcontextのAWS認証情報を指定
          filters:
            branches:
              only:
                - master
   ```

このように `config.yml` を記述することで、masterブランチへの更新はContextのAWS認証情報、それ以外は環境変数の認証情報と切り分けることができます。
この例では環境変数を使用していますが、今後はステージング環境などへの認証情報もContextで管理するのが保守性の観点で筋が良いでしょう。

## プロダクション環境のIAMアクセスキーは利用時のみ有効化
CircleCIをプロダクション環境へのデプロイに利用する時、どうしてもネックとなるのが **AWS外にそこそこ強力なIAMアクセスキーを出さなければならないこと** です。

もちろんCircleCIでContextを利用すれば外部に公開されることはなく、そのContextも[グループによる制限](https://circleci.com/docs/ja/2.0/contexts/#%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AE%E5%88%B6%E9%99%90)を指定することできめ細やかなパーミッションの管理ができます。しかしCircleCIがクラックされたり、何か不足の事態が起こらないとも限りません。
なのでここでは、 **プロダクションへのデプロイ時のみデプロイ用のIAMアクセスキーを有効化する** 運用を行うための手順を記載します。

### 手順

1. まず、`circleci`という名前でCircleCIからのデプロイ専用のIAMユーザを作成し、アクセスキーを作成します（手順割愛）。
1. そのアクセスキーを上記手順に従いCircleCIのContextへ登録します。

以上の操作で、マネコンからアクセスキーの有効/無効を切り替えることで運用が可能となりました。これでも良いのですが、いちいちマネコンを開くのは面倒です。
そこで、ここではSlackを利用してみましょう。

### IAMアクセスキーの有効/無効 切り替えSlackコマンドの作成
Slackを利用して簡単かつ安全にアクセスキーの有効/無効を切り替える環境を構築します。

### SlackのユーザIDを取得
まずコマンドを実行できる(=強力なIAMアクセスキーの有効/無効を切り替えられる)Slackユーザーを決め、そのユーザIDを取得します。`sot528` のような **ユーザ名** ではなく `U8FS891QA` というような **ユーザID** です。SlackのAPIトークンがあれば[ここでメンバーのユーザIDを取得](https://api.slack.com/methods/users.list/test)できます。

ここでは例として、`U1234567`と`U89ABCDE`という2人のユーザに権限を割り当てるものとします。

### SlackのOutgoing Webhookを作成
※ [Outgoing Webhook](https://api.slack.com/custom-integrations/outgoing-webhooks)はDuplicatedになっているようです。知らなかった..まあ今回は気にせず使いましょう。

Outgoing Webhookを **2件** 作成し、そのトークンを取得しておきます。作成画面↓の赤枠部分です。作成画面は開いておいてください。
<img width="1146" alt="スクリーンショット 2019-06-20 1.09.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50056/a4eb95d1-011e-c0c0-aa20-77d05debe165.png">

ここでは例として、`XXXXXXXX`と`YYYYYYYY`という2つのトークンを利用するものとします。

### IAM操作用のserverlessアプリをデプロイ
この運用を行うためにシンプルなツールを作りましたのでこちらを使います。[serverlessフレームワーク](https://serverless.com/)を使用してAPI Gateway => Lambdaの構築を行っています。

- https://github.com/AlisProject/toggle-iam-access-key

#### 使い方

```bash
git clone https://github.com/AlisProject/toggle-iam-access-key.git
npm ci
```

環境変数を設定します。ここでは[direnv](https://github.com/direnv/direnv)を使っています。

```bash
cp .envrc.sample .envrc
direnv edit
```

今回の例では、設定する値は以下のとおりです。
複数の値が存在するものはカンマ区切りで指定します。

- SLACK_ACCESS_TOKENS: `XXXXXXXX,YYYYYYYY` 
- ACCEPTED_SLACK_USERS: `U1234567,U89ABCDE`
- AWS_IAM_USER_NAME: `circleci`
- AWS_IAM_ACCESS_KEY_ID: IAMユーザ `circleci` のアクセスキーのID
- その他、AWSの認証情報: このツールをデプロイするのに必要なAWSの認証情報です

デプロイします。

```bash
npm run deploy:production
```
成功したら以下のような表示が出るので、 `endpoints` を2件控えておきます。

<img width="1498" alt="スクリーンショット 2019-06-20 1.20.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50056/055ee8a0-d551-98ef-a407-61d256d3633e.png">

さきほど開いておいた2つのOutgoing Webhookの作成画面で、このendpointsを1つずつ `URL(s)` に設定します。末尾が `enable` がIAMキーを有効化するもので、`disable`が無効化するものです。

それぞれ適当な `Trigger Word(s)` も設定しましょう。ここではenableに `key!`、 disableに`key_disable!`を割り当てます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50056/6e89b3ea-7832-06d1-5b55-cc551aae02e3.png)

これは必須ではないですが、お好みで、`Customize Name` に `IAM`、`Customize Icon`に [IAMのアイコン](https://www.google.com/search?q=aws+iam+icon&rlz=1C5CHFA_enJP728JP728&source=lnms&tbm=isch&sa=X&ved=0ahUKEwiBlO6o-_XiAhVXw4sBHbjgBE0Q_AUIECgB&biw=2844&bih=1451)とか登録しておくとわかりやすくてGoodです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50056/35117157-3109-e046-5638-af26508847dc.png)

さて、準備が整いました。SlackからIAMアクセスキーの開閉をしてみましょう。Outgoing Webhookは公開チャンネルしか有効ではないのでご注意。

![toggle.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50056/d3a25f78-9943-890d-51b1-2a85f7d4870c.gif)

鍵の開閉ができましたね。これでだいぶ楽に運用できそうです。
ちゃんとマネコンでアクセスキーが有効化/無効化されていることを確認しておきましょう。

ちなみに許可されていないIDのSlackユーザがコマンドを叩くと。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50056/79f60ed5-6982-7dd9-7e0a-587bca80fde1.png)

(:point_up:՞ਊ ՞):point_up:ウェーイ!

# 備考
- 今回の手順では割愛していますが、CircleCIのプロダクション環境用のContextには[グループによる制限](https://circleci.com/docs/ja/2.0/contexts/#%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AE%E5%88%B6%E9%99%90)を付加した方が良いでしょう。
- ~~現状、CircleCIのContextを複数指定することはできないようです。~~
    - ~~これができると大変に便利なんですが。Jobを細かく分けろという話ですかね~~
    - **2020/09/15にこの機能が追加されました。これは嬉しい!**
        - https://ideas.circleci.com/cloud-feature-requests/p/multiple-contexts-in-a-workflow
- 重要なリポジトリは本番環境への反映前にCircleCIのワークフローで承認(approve)を挟むと良いかと思います
    - [CircleCIのManual ApprovalをSlackへ通知する(Orb利用)](https://qiita.com/sot528/items/853c331939f360c11233)
- CodePipelineやCodeBuildなど、AWSのCode兄弟達とも格闘しましたが、私の結論は **CircleCI最強** です..
    - もちろん要件によりCode兄弟の方が適している場合もありますが
- TODO: **全ビルドが終わったら勝手にIAMを無効化する** まで自動化したい
- ご参考までに: だいたいのリポジトリでCircleCI使用していてほとんどコード公開してます
    - https://github.com/AlisProject
- もっとよろしい運用をご存知でしたらご教示ください🙏