【インフラ初心者向け】アクセスキーを使わずにGitHub ActionsでEC2に自動デプロイをしよう

AWSのEC2を使った運用を行うにあたり、ソースコードが変わるたびにGitHubからpullするのは手間がかかるので、GitHub Actionsを使った自動デプロイに挑戦しました！
それもどうせやるならアクセスキーを使わない、セキュアな方法でと意気揚々と！！
・・・が、自分でやってみて想像よりはるかに（大袈裟）つまづきポイントが多かったので、整理も兼ねて記事にしました。

## 対象
- インフラに詳しくないが、少しでも自動化できるところは実現したい
- できる限りAWSのアクセスキーは使わず、セキュリティ性を高めたい

## 前提
本記事はEC2で一旦環境構築を終えていることが前提なので、ここまでの事前準備はお願いします。
- EC2とSSH接続ができる。
- EC2上でSSH接続用のキーペアを作成してある。
- EC2に既にソースコードが配置してある。

## 大きなタスク
大きく３つのタスクがありました。
私個人はどれも全く馴染みがなかったので、とても苦労しました。
- アクセスキーを使わないためのOIDC設定
- GitHub Actionsのワークフロー作成
- ワークフローの実行コード作成

## アクセスキーを使わないためのOIDC設定
GitHub ActionsとEC2の接続については、IAMユーザーのアクセスキーを使った方法の記事をいくつかみましたが、使わないやり方を探した結果、OIDC（Open ID Connect)を使ったやり方にたどり着きました。
OpenID Connectとは、サービス間で、利用者の同意に基づきID情報を流通するための標準仕様のことです。
OIDCからGitHub Actionsまでの設定に関しては、こちらの記事を参考にしました。
[GitHub ActionsにてAWSのセキュリティグループをよりセキュアに穴あけする](https://www.secure-iv.co.jp/blog/9249 "GitHub ActionsにてAWSのセキュリティグループをよりセキュアに穴あけする")
というかソースコードと手法はほぼこのまんまです。感謝しかないです。

### AWSの設定
【AWSの設定とは何をするのでしょうか】
OIDCでアクセスすることを許可するポリシーを作成し、ロールにアタッチします。

#### ポリシーの作成
まずはポリシーの作成から。
コンソール＞IAM＞ポリシーの作成に進み、JSONタブを選択後を下記を入力
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:GetCallerIdentity",
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:AuthorizeSecurityGroupIngress",
                "ec2:RevokeSecurityGroupIngress"
            ],
            "Resource": "*"
        }
    ]
}
```
タグづけはどちらでも良いです。名前をつけたらポリシーを作成を押してください。
これで以下の画像のように、EC2への書き込み権限が付与されたポリシーができました。
![スクリーンショット 2023-01-18 10.35.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2574198/3364da86-c3d5-da37-fcc7-15cda405b6fd.png)

#### IDプロバイダの設定
IAM > ID プロバイダを表示し「プロバイダを追加」を選択します。
ここは決めうちの作業です。
プロバイダのタイプ：OpenID Connect
プロバイダのURL：https://token.actions.githubusercontent.com
対象者：sts.amazonaws.com
タグづけは任意です。あとはサムプリントを取得ボタンを押下して、画面の推移に従ってプロバイダを作成してください。
![スクリーンショット 2023-01-18 10.43.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2574198/66cfafc9-0fbc-a45f-75bd-45d087d7d614.png)


#### ロールの設定
IAM > ロール > ロールを作成から「ウェブアイデンティティ」を選択し、以下を選択して先に進んでください。
アイデンティティプロバイダー：token.actions.githubusercontent.comを選択
Audience：sts.amazonaws.comを選択
![スクリーンショット 2023-01-18 10.49.11（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2574198/7f588d1a-4e06-0a79-89e2-a4be42c441fb.png)


その後許可されたポリシーで先ほど作成したポリシーを選択後、
名前、確認、および作成の画面には以下の項目を入力してください。
ロール名：好きな名前
説明：任意
ステップ 1: 信頼されたエンティティで編集し、ロールの作成としたいのですが、
私の場合なぜかこの画面からは編集できなかったので、一旦ロールを作成してから作成後のロール＞信頼関係＞信頼ポリシーの編集で操作しました。
入力項目は下のとおりですです。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::【アカウントID】:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringLike": {　　#デフォルトはStringEqualsなので変更すること
				　　　　"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
					#デフォルトから以下を追記してます
                    "token.actions.githubusercontent.com:sub": "repo:【連携したいリポジトリ名】:*"
                }
            }
        }
    ]
}
```
以下の項目は任意の値としてください。
arn:aws:iam::【アカウントID】:oidc-provider/token.actions.githubusercontent.com→【アカウントID】にAWS IDを入れる。（ARNなどに含まれている数字です。デフォルトで既に反映しているかもしれません）
repo:【連携したいリポジトリ名】→連携したいリポジトリ名を入れる。（リポジトリURLの中の、https://github.com/の後ろの部分）

:::note info
後ろに「:\*」をつけると、どんな階層の変更でも連携がきくのでつけたほうが良いかもです！
例）リポジトリURLがhttps://github.com/topaoad/hogehoge であれば、repo:topaoadhogehoge :\*　となります。
:::

ここまででAWS上の設定は終わりです。
次からはGitHub側の作業ですので、一旦コーヒーでも飲んで次の作業を頑張りましょう！

## GitHub Actionsのワークフロー作成
【ワークフロー作成とは何をするのでしょうか】
ソースディレクトリ直下にアクションを記載した.ymlファイルを作成します。
### secretの設定
ワークフロー作成の前に、接続の際に必要な情報で秘匿にしたいものをsecretに登録します。
リポジトリ＞Settingsの中にあるActionsから設定画面に進んでください。
![スクリーンショット 2023-01-18 15.00.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2574198/a50a5ed1-bf2b-2b4c-77d9-1fab099f0905.png)
今回必要となるものは以下のものです。
何を設定して良いかわからないものもあったので、細かく記載しました。

:::note info
EC2_ROLE_ARN・・・作成したIAMロールのARN（arn:aws・・・で始まるやつ）
EC2_SECURITY_GROUP_ID・・・接続したいEC2にアタッチされているセキュリティグループのID
GIT_PRIVATE_KEY・・・EC2にssh接続する際に使用する、EC2で作成したキーペアの秘密鍵（-----BEGIN RSA PRIVATE KEY-----で、全部まるっと入れてOK)
EC2_USER_NAME・・・ EC2にssh接続する際に使用する、ユーザー名（Linuxの初期設定はec2-user)
EC2_HOST_NAME・・・EC2にssh接続する際に使用する、EC２のパブリックIPアドレス
:::

### ディレクトリ及びソースコードの作成
自分のソースディレクトリ直下（.git と同階層）に、.github/workflowsディクレクトリを作成しその中に、任意の名前のymlファイルを作成します。
自分はaws.ymlとファイルを作成しました。
![スクリーンショット 2023-01-18 17.15.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2574198/9154dd3c-495a-f790-903c-401bc5f9b6a2.png)
※今回はローカル内で手動作成してますが、GitHub内でテンプレートから作成したものpullしてきても良いです。

### コードの記載
あとは作成したymlファイルに以下の通り書いてください。
細かい点を説明するとキリがない（私自身そこまで知見がない説）ので、割愛します。
とはいえ、on、jobs、buildといった大きい項目の概念は知っておいた方が良いので調べておくと良いかもです。
以下のコードの内容を言語化すると、
「mainブランチへのプッシュ（プルリク含む）が起きたら、EC2のhogehogeディレクトリにルーティングし、そこへmainブランチからプルする」というものになります。

```yaml
name: EC2連携&自動デプロイ
on:
  workflow_dispatch:
  # mainブランチへのプッシュがトリガーとなる。プルリクでもOK
  push:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: write # AWSとのやり取りでIDの取得等に必要
      contents: read # リポジトリの取得に必要
    steps:
      # IPの取得
      - name: Public IP
        id: ip
        uses: haythem/public-ip@v1.2
      # AWS認証
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-region: ap-northeast-1
          role-to-assume: ${{ secrets.EC2_ROLE_ARN }}
          role-session-name: github-actions-deploy-${{ github.event.repository.name }}-${{ github.run_number }}
      # EC2にDeploy
      - name: Deploy to EC2
        # ssh接続のための接続
        env:
          PRIVATE_KEY: ${{ secrets.GIT_PRIVATE_KEY }}
        # セキュリティグループにIPを追加する（セキュリティグループに穴をあけて、あいているうちにGitHubからのpullを実行する）
        run: |
          aws ec2 authorize-security-group-ingress --group-id ${{ secrets.EC2_SECURITY_GROUP_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32
          echo "${{ secrets.GIT_PRIVATE_KEY }}" > private_key
          chmod 600 private_key
          ssh -oStrictHostKeyChecking=no ${{ secrets.EC2_USER_NAME }}@${{ secrets.EC2_HOST_NAME }} -i private_key "cd /home/ec2-user/hogehoge && git fetch --prune && git checkout main && git pull origin main"

      # 追加したIPをセキュリティグループから除外する（セキュリティグループにあけた穴をふさぐ）
      - name: Revoke security group
        if: always()
        run: |
          aws ec2 revoke-security-group-ingress --group-id ${{ secrets.EC2_SECURITY_GROUP_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32

```

:::note warn
注意事項
    branches: [main]には、アクションを起動させたいトリガーとなる任意のブランチを記載してください。
	上記の例は/home/ec2-user/hogehoge ディレクトリとしていますが、環境構築した際にソースコードを配置したディクレクトリを選択してください。
	  chmod 600 private_key コードを書かないと秘密鍵がガバガバだと怒られますので、つけましょう。
:::

パーミッションなど、SSH接続から接続後のコードに関する部分は、こちらを参考にしました
[GitHub ActionsでEC2に自動デプロイ環境を構築する方法](https://note.com/ryoppei/n/nf2bf7ee78b0b "GitHub ActionsでEC2に自動デプロイ環境を構築する方法")
## ワークフローの実行コード作成
【実行コード作成とは何をするのでしょうか】
具体的な作業内容を書きます。

### GitHubにプッシュして動作確認
ここまで終わったら、GitHubへプッシュしましょう。
無事終わっていたら下の画像のように緑色のチェックマークがでます。
失敗したら赤色の✖︎がでます。
ここまでエラーが出そうな注意点を挙げながら説明したつもりですが、それでもエラーが起きてしまったら、ググってみたり詳しいつよつよエンジニアに聞いてみましょう。

![スクリーンショット 2023-01-18 14.55.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2574198/29bd77d7-d0ab-4eb1-f844-5e80ac74bbdb.png)

## まとめ
私はインフラに明るくなく、ちょっとでも詰まると原因の究明と基礎知識のインプットイベントが発生し、大変時間がかかりました。
それでも、従前よりセキュアな方法で工数を減らすための環境構築を完遂できたので達成感は割とありました。
今後も現状がより良くなること実現するため、コツコツ頑張りたいと思います！