GitHubActionsとTerraformでS3バケットを作成してみた

# 背景・目的
以前、下記の記事でTerraformや、GitHub Actionsについて知識の整理や、検証しました。
今回は、これらをつなげてGitHub ActionsからAWSのS3を作成してみます。

- [GitHub Actionsを調べてみた](https://qiita.com/zumax/items/df1f36b96ee819cfb25e)
- [【GitHub Actions】ビルドとテストの自動化を整理する](https://qiita.com/zumax/items/11b2d4606af191a3d4b5)
- [TerraformでS3を作成してみた](https://qiita.com/zumax/items/4a7e1002050a5f2e5903)


# 実践
今回、構築したい環境は下記のとおりです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/bd494e1c-4841-d817-0ea4-2674a0f0a824.png)



## 前提
- AWSアカウントを保持していること
- GitHubアカウントを保持していること
- VSCode
- MacOS

## ①.GitHubリポジトリ
### リポジトリの作成とダウンロード
1. プライベートリポジトリを用意します
1. git cloneによりローカルにダウンロードします
    ```bash
    $ git clone git@github.com:XXXX/XXXX.git
    ```
1. VSCodeで「フォルダーをワークスペースに追加」をクリックし、ダウンロードしたフォルダを追加します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/074ae3d9-0ec4-e2f0-9133-846f5859d169.png)

## ②.OIDCの設定(AWSとGithub Actionsと連携)
OIDC（Open ID Connect）により、GitHub Actions ワークフローから、AWS 内のリソースにアクセスします。

### AWSの作業
1. AWSにサインインします

#### IDプロバイダの追加
1. IAMに移動します
1. ナビゲーションペインのIDプロバイダーをクリックします
1. 「プロバイダを追加」をクリックします
1. 下記を入力し、「プロバイダを追加」をクリックします
    - プロバイダのタイプ：OpenID Connect
    - プロバイダのURL：https://token.actions.githubusercontent.com
    - 対象者：sts.amazonaws.com
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/d8bce246-7eae-f2ba-1013-f63b3848ac7b.png)

1. できました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/45b0506f-883c-d892-d249-22af4eb4c07c.png)

#### ロールと信頼ポリシーの構成
1. IAMのナビゲーションペインで、「ロール」をクリックします
1. 「ロールを作成」をクリックします
1. 下記を指定し、「次へ」をクリックします
    - 信頼されたエンティティタイプ：ウェブアイデンティティ
    - アイデンティティプロバイダー：上記で追加した、IDプロバイダ
    - Audience：sts.amazonaws.com（IDプロバイダを選択するとデフォルトで設定）
    - GitHub 組織:Githubアカウント
    - GitHubリポジトリ:リポジトリを指定
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/dd665d4f-f19c-8131-d35d-7a2f504f810b.png)


1. **とりあえず疎通**のために、「IAMReadOnlyAccess」を追加します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/af199f5f-8c30-cc47-76c4-3294cb42ff3c.png)

1. ロール名を指定して、「ロールの作成」をクリックします

### GitHubの作業
#### Secretsの設定
1. GitHubにサインインし、対象リポジトリを選択します
1. ①「Settings」＞②「Secrets and variables > Actions」>③「Secrets」>④「New repository secrets」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/58d0ff82-e738-1f52-1ea4-deb516776ad4.png)
1. 下記の2つのシークレットを追加します
    - AWS_ID:AWSアカウントID
    - ROLE_NAME：作成したロール名
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/8b83ff29-11ed-f963-ad38-2efc999a58f0.png)

#### GitHub Actionsによる疎通
GitHub Actionsでは、.github/workflowディレクトリ直下へyml（yaml）ファイルを配置することで動作します。

1. `.github/workflows`ディレクトリを作成します
1. oidc-ymlファイルを作成します
1. 下記のコードを書きます
    ```yml
    name: OpenID Connect
    on: push
    env:
      ROLE_ARN: arn:aws:iam::${{secrets.AWS_ID}}:role/${{secrets.ROLE_NAME}}
      SESSION_NAME: gh-oidc-${{github.run_id}}-${{github.run_attempt}}
    jobs:
      connect:
        runs-on: ubuntu-latest
        permissions:
          id-token: write
        steps:
          - uses: aws-actions/configure-aws-credentials@v4
            with:
              role-to-assume: ${{env.ROLE_ARN}}
              role-session-name: ${{env.SESSION_NAME}}
              aws-region: ap-northeast-1
          - run: aws iam list-users
          - run: aws iam create-user --user-name invalid || true 
    ```

1. Commitし、Pushします
    ```shell
    $ git commit -m "oidc test connection" .github/workflows/oidc-test.yml
    $ git push origin main
    ```
1. 成功しました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/2ac6d8d1-99cf-9f49-c9c8-7d02eccc9161.png)

## ③.ワークフローの作成
### GitHubの作業

1. GitHub Actionsで「New workflow」をクリックします
1. terraformのworkflowテンプレートを選択します（Configureをクリックします）
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/e5f8bbfa-bc2b-63c1-3743-8a712c455b60.png)
1. 「Commit changes」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/5e0fb764-7e69-758c-8aaf-152d42271e23.png)

### ローカルの作業
1. ローカルでgit pullします
1. テンプレートに対して、下記のようにOIDCのロールが使えるように設定します
    - envの追加
    - AWS configureの追加
    - permissionの追加
    ```yml
    name: 'Terraform'
    
    on:
      push:
        branches: [ "main" ]
      pull_request:
    env:
      ROLE_ARN: arn:aws:iam::${{secrets.AWS_ID}}:role/${{secrets.ROLE_NAME}}
      SESSION_NAME: gh-oidc-${{github.run_id}}-${{github.run_attempt}}
      REIGION_NAME: ap-northeast-1
    permissions:
      contents: read
    
    jobs:
      terraform:
        name: 'Terraform'
        runs-on: ubuntu-latest
        environment: production
        permissions:
          id-token: write
          contents: read
    
        # Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest
        defaults:
          run:
            shell: bash
    
        steps:
        # Checkout the repository to the GitHub Actions runner
        - name: Checkout
          uses: actions/checkout@v4
    
        - name: AWS configure
          uses: aws-actions/configure-aws-credentials@v4
          with:
            role-to-assume: ${{env.ROLE_ARN}}
            role-session-name: ${{env.SESSION_NAME}}
            aws-region: ${{env.REIGION_NAME}}
    
    
        # Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc.
        - name: Terraform Init
          run: terraform init --backend-config=backend.hcl
    
        # Checks that all Terraform configuration files adhere to a canonical format
        - name: Terraform Format
          run: terraform fmt -check
    
        # Generates an execution plan for Terraform
        - name: Terraform Plan
          run: terraform plan -input=false
    
          # On push to "main", build or change infrastructure according to Terraform configuration files
          # Note: It is recommended to set up a required "strict" status check in your repository for "Terraform Cloud". See the documentation on "strict" required status checks for more information: https://help.github.com/en/github/administering-a-repository/types-of-required-status-checks
        - name: Terraform Apply
          if: github.ref == 'refs/heads/main' && github.event_name == 'push'
          run: terraform apply -auto-approve -input=false
    ```

## ④.ステートファイル格納用のバックエンドを作成
### S3バケット
1. AWSにサインインします
1. S3に移動し、バケットを作成します
1. ステートファイルが、誤って消えないようにバージョンニングを設定しておきます
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/86c1fbc8-5b28-31de-9fb5-0d6046aaa154.png)

### terraform
1. backend.tfとbackend.hclファイルを用意します
    ```shell
    $ ls -ltr
    total 24
    -rw-r--r--  1 XXXX  YYYY   11  7 21 14:52 README.md
    -rw-r--r--  1 XXXX  YYYY   65  7 21 17:42 backend.tf
    -rw-r--r--  1 XXXX  YYYY  132  7 21 17:42 backend.hcl
    -rw-r--r--  1 XXXX  YYYY  230  7 21 17:46 provider.tf
    $ cat backend.*
    bucket  = "XXXXXX"
    region  = "ap-northeast-1"
    encrypt = true
    profile = "XXXX"
    terraform {
      backend "s3" {
        key = "terraform.tfstate"
      }
    }
    $
    ```

1. provider.tfファイルを用意します
    ```shell
    $ cat provider.tf
    terraform {
      required_version = "~> 1.8.0"
      required_providers {
        aws = {
          source  = "hashicorp/aws"
          version = "~> 5.44.0"
        }
      }
    }
    
    provider "aws" {
      region  = "ap-northeast-1"
      profile = "XXXXX"
    }
    $
    ```

1. main.tfファイルを用意します
    ```terraform
    data "aws_caller_identity" "this" {}
    
    
    # S3 Bucket
    # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket
    resource "aws_s3_bucket" "terraform_demo" {
      bucket = "terraform-demo-${data.aws_caller_identity.this.account_id}"
    }
    
    resource "aws_s3_bucket_policy" "terraform_demo" {
      bucket = aws_s3_bucket.terraform_demo.id
    
      policy = jsonencode({
        Version = "2012-10-17"
        Statement = [
          {
            Sid       = "AllowSSLRequestsOnly"
            Effect    = "Deny"
            Principal = "*"
            Action    = "s3:*"
            Resource = [
              aws_s3_bucket.terraform_demo.arn,
              "${aws_s3_bucket.terraform_demo.arn}/*",
            ]
            Condition = {
              Bool = {
                "aws:SecureTransport" = "false"
              }
            }
          }
        ]
      })
    }
    ```

1. AWS SSOでログインします
    ```
    $ aws sso login --sso-session XXX
    ```

1. terraform initを実行します
    ```
    $ terraform init --backend-config=backend.hcl
    ```

1. terraform planを実行します
    ```
    $ terraform plan                             
    ```
1. 問題なければそのまま`terraform apply`を実行します
    ```
    $ terraform apply
    ```
1. S3バケットは問題なくできていました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/f45b9989-5698-c763-e5d0-28f0c31e576d.png)

1. tfstateもバックエンドのS3にできていました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/205171b7-cfd9-47ee-96fd-6f72be3bd25a.png)

## ⑤.GitHubActionでTerraformを実行する
### IAMロールの修正
作成したIAMロールに、ポリシーをアタッチします
1. AWSにサインインします
1. IAMに移動し、作成したIAMロールをクリックします
1. インラインポリシーを追加をクリックします
1. 下記のポリシーを追加します
    ```shell
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "VisualEditor0",
                "Effect": "Allow",
                "Action": [
                    "s3:PutObject",
                    "s3:GetObjectAcl",
                    "s3:GetObject",
                    "s3:ListBucket"
                ],
                "Resource": [
                    "arn:aws:s3:::{バックエンドのS3バケット}",
                    "arn:aws:s3:::{バックエンドのS3バケット}/*"
                ]
            },
            {
                "Sid": "VisualEditor1",
                "Effect": "Allow",
                "Action": "s3:*",
                "Resource": [
                    "arn:aws:s3:::{テスト用のバケット}",
                    "arn:aws:s3:::{テスト用のバケット}/*"
                ]
            }
        ]
    }
    ```

1. ポリシー名を設定し、「ポリシーの作成」をクリックします


### provider.tf
1. ロールを使って作成するので、profileを削除します。また、バージョンは1.8.0は使えなかったので、1.9.0に変更しています
    ```terraform
    terraform {
      required_version = "~> 1.9.0"
      required_providers {
        aws = {
          source  = "hashicorp/aws"
          version = "~> 5.44.0"
        }
      }
    }
    
    provider "aws" {
      region = "ap-northeast-1"
    }

    ```
### backend.hcl
1. backend.hclから、profileを削除します
    ```hcl
    bucket  = "ステートファイルが格納されているバケット"
    region  = "ap-northeast-1"
    encrypt = true
    ```

### GitHub
1. 作成したファイルをcommitし、pushします
1. 成功しました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/d345de62-6d04-4adf-179e-e19ae5c80b33.png)


# 考察
今回、GitHub Actionsから、Terraformを利用してS3バケットを作成してみました。
次回は、AWSネットワークを作成します

# 参考
https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services