GitHub CI/CD実践ガイド　GitHub Actionsの設計と運用

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3976844/7b1625b0-c079-9f26-19a4-f15cf6519338.png">

読書記録

## 読者プロフィール
組込み系エンジニア。
GitHubはプライベートは使っているが、正直お遊びなのでCI/CDまでは手付かず。
お仕事ではGitLabを使っていてgitlab-ci.ymlはたまに見る。ただコピペコピペでよくわかってはいない。ちなみにこの本は筆者のブログがフィードされて、CIどうこうよりも文章に拘った！というコメントが気になり買ってみた。なのでCI/CDのノウハウよりも文章のほうに興味があって読み始めたという珍しいパターンである。

著者がこの本について語っているのはこちら👇

https://zenn.dev/tmknom/articles/readable-github-cicd-book

## 雑感
確かに読みやすい。AWSのワークはアカウント取得前提であったが、取得せずに読み進めていた。しかもAWSの前提知識もなく、脱落しそうだ…と思いながら読んでいたが最後まで読み切ることができたのは文章を鬼チューニングしてくれたおかげだと思う。
内容としては具体的な動くワークフローを多数説明してくれていて実務に役立ちそう。また、絶対やっちゃダメだぞ！というポイントはくどくない程度に何度も警告してくれていて覚えられてよかった。（中間環境変数やクレデンシャルの扱い）
以下のあらすじでは割愛しているが、ワークフローという独特な構文についてワークフロー自体の自動テストを構築している説明は普通に感動した。

# あらすじ
本書は「基礎編」「実践編」「応用編」という3部構成になっている。基礎編は組み込み関数や条件分岐といった基本文法の紹介が多いが、それらを記載すると本書のコピーになってしまうので割愛する。重要と考えた点、調べた点を中心に以下に書き留める。

# GitHub Actionsとは？
GitHaub Actionsとは、GitHubでのイベント（よく使うのはプッシュやプルリクエスト、タグの作成）をトリガーにし、ビルドやテストの実行、デプロイを自動的に行う汎用ワークフローエンジンである。


# GitHaub Actionsの基本的な構文（とCIアクション）
本書で紹介されており、おそらく良く使われるであろうCIに関するサンプルコードは以下。
```yaml:yaml
name: Test
on:
  pull_request:                        # プルリクエストが作成されたら起動
    paths: ['go/**/*.go']              # ただしGoのファイル変更時のみ
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4      # ステップ1: チェックアウト
      - uses: actions/setup-go@v5      # ステップ2: 言語セットアップ
        with:
          go-version: '1.22'
      - run: go test go/excellent/*.go # ステップ3: テストの実行
```
|名称  |概要  |
|---|---|
|name:  |ワークフローの名前を "Test" とする |
|on:  |トリガー条件を示している。この例ではプルリクエストの作成時である。pushにすればpush時に実行される。pathsを削除すれば毎回実行される。この例では、条件として Go ファイルに変更があった場合のみ実行としている。 |
|jobs:  |testという名称のジョブを定義している。  |
|runs-on:  |ubuntu-latest を使用してジョブを実行する。  |
|steps:  |
|- uses:  |actions/checkout@v4 を使用してリポジトリをチェックアウト。 |
|- uses  |actions/setup-go@v5 を使用して指定されたバージョンは 1.22 でGoをセットアップ  |
|- run: |go test go/excellent/*.go コマンドを実行して、テストを実行する  |

この設定によってプルリクエストが作成され、Goファイルに変更が加えられた場合に自動的にテストが実行される仕組みとなっている。ランナーで使用可能な言語には制限があるため以下で確認可能とのこと。言語セットアップなどを切り替えればほかの環境にも適用可能。

ランナー詳細

https://github.com/actions/runner-images

本書で紹介されているサンプルはこちら

https://github.com/tmknom/example-github-cicd/tree/main/.github/workflows

なお、テストに関して役立ちそうなものとして、タイムアウトの設定、静的解析の実行、Concurrencyによりワークフローの多重起動抑制、マトリックスによるOSや言語バージョンの組み合わせ（マトリックス）を指定しての実行、チャット通知などがある。何れも上記のリンクにサンプルが公開されている。


# デプロイの自動化
本書ではAWS ECSのコンテナオーケストレーションシステムを構築し、デプロイ（コンテナイメージビルドしてAmazon ECRにプッシュ）をワークフローで自動化している。
クラウドプロバイダのクレデンシャルを静的ではなく一時クレデンシャルとして取得するためにOpenID Connectを用いる。これは万が一静的クレデンシャルが流出すると被害が甚大なためである。

一時クレデンシャルを得るためのOpenID Connectのプロトコルについても言及されている。GitHub OICD Providerが生成するトークンは以下のような<ヘッダー><ペイロード><デジタル署名>という構成で、JWT（JSON Web Token）と呼ばれる。重要なのはデジタル署名が付与されている点とsubクレームである。
```Json:Json
{
  "alg": "RS256",
  "typ": "JWT"
}
.
{
  "iss": "https://github.com",
  "sub": "repo:foo/bar:pull_request",
  "aud": "sts.amazonaws.com",
  "exp": 1672531199,
  "iat": 1672531199,
  "email": "john.doe@example.com"
}
.
<デジタル署名>
```

GitHub OICD Providerが生成するトークンにはGitHub OICD Providerによるデジタル署名がされている。そのためクラウドプロバイダ（AWS）はGitHub OICD Providerの公開鍵を検索し取得し、トークンがGitHub OICD Providerによって生成されたものだと確証を得ることができる。
ただし、公開鍵認証のみではクラウドプロバイダはそのトークンに紐づくGithubアカウントやリポジトリを識別できない。そこでsubクレームに着目すると、subクレームはrepo:< OWNER>/< REPO>となっておりアカウントとリポジトリが記述されているためsubクレームを検証することでデプロイ元の情報を検証できる。クラウドプロバイダの設定を事前に行っておくことでsubクレームを検証してくれるようになり、デプロイの安全性が向上する。

# GitHubActionsとセキュリティ
Githubはコラボレーションを推奨するサービスで、誰でもプルリクエストを出したり優れたアクションを公開できる。これはOSSの開発に大きく貢献しているが、一方でセキュリティ観点では課題が発生してしまう。

本書ではセキュリティについてGitHubActionsの利用にあたって発生するセキュリティ上の注意点と自分たちの開発物がセキュリティ違反（脆弱性のアプデやクレデンシャル流出）しないことを継続的に監視するという２点について述べている。

## スクリプトインジェクション（コンテキスト）
コンテキストとはジョブの実行時の情報や実行結果を保持するオブジェクトである。githubやjobといったコンテキストがあり、様々なプロパティ（ブランチ名やランナーOSの名称）を取得でき、ワークフローでできることを増やしてくれる便利なものである。

しかしコンテキストとプロパティは多様でありこれをそのままGitHubActionsに組み込むことのリスクがある。なぜなら、悪意は無くともコンテキストに特殊文字を含んでいるケースや、明確な悪意のある命令を注入（スクリプトインジェクション）される懸念があるからだ。pull_request.titleやissue.titleなどは分かり易い。

```yaml:yaml
 #アンチパターン
 - run: echo "${{ github.actor}}" # クォートしているつもりだが、クォートされない
```
本書ではさらっと終わっていて調べたのだが、bashであれば以下のようにクォートする（""で囲う）ことで環境変数を特殊文字を含め文字列として扱うことができるが、コンテキストはそうではないようだ。つまりgithub.actorが文字列＋空白＋文字列のような場合、最初の文字列がechoされるような結果となる。（エラーになるかもしれない）
```shell:bash
#!/bin/bash 

# クォートなし（エラーの原因となる）
MESSAGE=Hello, world! 
echo $MESSAGE # 出力: Hello, 

# クォートあり（正しく解釈される）
MESSAGE="Hello, world!"
echo "$MESSAGE"
```

つまりアンチパターンの状態では特殊文字含めそのまま渡されてしまうため、予期しない動作をしてしまうということである。よって対策としてはいったん環境変数に代入し、環境変数をクォートすることでコンテキストの中身の特殊文字が展開されることを抑止することである。なおShellCheckを用いることでクォート忘れを検出でき、いぶし銀の活躍をみせるとのこと。

ShellCheck

https://github.com/koalaman/ShellCheck/



```yaml:yaml
 #良い実装
 env:
   ACTOR:${{ github.actor}}
 steps:
 - run: echo "${ACTOR}" #環境変数をクォートしているので中身は展開されない
```

## GitHubActionsにおけるセキュリティ観点
GitHubActionsにおけるもっとも大きなアタックサーフェス（攻撃される可能性のある場所）は第三者が作成したアクションを用いることである。これについては様々な注意点やアドバイスと共に、具体的な制限方法が記載されている。クレデンシャルの扱いやワークフローに与える権限についても言及している。

## セキュリティの自動化戦略
dependabotを用いた依存関係のあるパッケージの自動更新の手法やパッチバージョンに限定して自動マージする方法について説明している。また依存関係の脆弱性を発見した場合にメールでアラートする機能について有効化の手順を示している。Secretlintを用いたクレデンシャルの混入や、そのほかにも複数のコマンドやツールを紹介してくれており、自動化までの道筋がわかる。