GitHub Actions×Microsoft Defender for DevOpsによるセキュアなCI/CD環境の構築

# はじめに
DevOpsにより高速な開発と安定したリリースが実現できますが、デプロイからフローが自動化されていることでサイバー攻撃のリスクが高まるため、セキュリティを考慮することがますます重要になっています。そこで、本記事では`GitHub Actions`を用いたDevOpsの実現と`Microsoft Defender for DevOps`を用いてセキュリティ対策を実施します。本記事は、以下の公式ドキュメントのクイックスタートをベースにアレンジした内容になっております。細かい設定や手順については割愛させていただくので、必要であれば適宜ドキュメントをご覧ください。

https://learn.microsoft.com/ja-jp/azure/defender-for-cloud/defender-for-devops-introduction

:::note info
DevOpsとは、開発と運用のプロセスを統合することによって、より迅速で効率的なソフトウェア開発を実現するための方法論のことを言います。
:::

# ファイルとフォルダ構成
本記事では、以下のようなシンプルな構成のWebアプリを用います。`.env`と`.gitignore`については割愛します。
```:フォルダ構成
.
├─ .github
│  └─ workflows
│     ├─ codeql.yml
│     ├─ defender-for.yml
│     └─ devops.yml
├─ conf
│  └ default.conf
├─ src
│  ├ js
│  │  └ index.js
│  └ index.html
├─ .env
├─ .gitignore
└─ Dockerfile
```
このファイルは、`CodeQL`を使用して`javascript`コードを自動的に分析するために設定されています。

:::note info
CodeQLは、静的コード解析ツールの一種で、バグ、脆弱性、セキュリティ上の問題などを自動的に検出することができます。また、プログラムの検証や脆弱性診断に利用されます。CodeQLを用いたコードスキャンを行うことで、クロスサイトスクリプティング(XSS)の検出、不正な正規表現の検出、エクスポートされた関数の脆弱性を検出、クロスオリジンリソース共有(CORS)の検出などが可能です。
:::

```yml:.github/workflows/codeql.yml
# ワークフローの名前を"CodeQL"と指定しています。
name: "CodeQL"

# トリガーとなるイベントを設定します。
# この例では、pushとpull_requestの両方がmasterブランチに対して実行されます。
# さらに、定期的に実行されるようにscheduleが設定されており、cron式で毎週土曜日の9:16に実行されます。
on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]
  schedule:
    - cron: '16 9 * * 6'

# ジョブのリストを定義します。
jobs:
  # analyzeというジョブが定義されています。
  analyze:

    # ジョブの名前を設定します。
    name: Analyze

    # ジョブが実行される仮想マシンのオペレーティングシステムを指定します。
    # Ubuntuを使用しています。
    runs-on: ubuntu-latest

    #　アクションが使用するGitHubリソースへのアクセス許可を設定します。
    permissions:
      actions: read
      contents: read
      security-events: write

    # 並列ジョブの設定を定義します。
    strategy:
      fail-fast: false

      # 異なる言語で同時に分析するために、CodeQLが使用する言語のリストを定義します。
      matrix:
        language: [ 'javascript' ]

    # このジョブで実行される一連のステップを定義します。
    steps:

      # リポジトリをチェックアウトします。
    - name: Checkout repository
      uses: actions/checkout@v3

      # CodeQLを使用するために必要な環境を設定します。
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v2
      with:
        languages: ${{ matrix.language }}

      # コンパイルされた言語の自動ビルドを試みます。
    - name: Autobuild
      uses: github/codeql-action/autobuild@v2

      # CodeQLを使用してコードを分析します。
    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v2
      with:
        category: "/language:${{matrix.language}}"
```
このファイルは、`Microsoft Defender for DevOps`を使用してGitHubリポジトリのセキュリティスキャンを自動化するためのワークフローです。このワークフローは、GitHubリポジトリのセキュリティスキャンを自動化し、`Microsoft Defender for DevOps`によって生成された結果をSecurityタブにアップロードすることが目的です。
```yml:.github/workflows/defender-for-devops.yml
# ワークフローの名前を指定しています。
name: "Microsoft Defender For Devops"

# ワークフローのトリガーを定義しています。
# この場合、masterブランチにプッシュまたはプルリクエストが作成された場合
# または毎週日曜日の22:00にスケジュールされた実行がトリガーとなります。
on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]
  schedule:
    - cron: '22 0 * * 0'

# 実行されるジョブの定義を開始します。
jobs:

  # MSDOという名前のジョブが定義されています。
  MSDO:

    # ジョブが実行されるランナーのOSを定義します。
    # Windows最新版を指定しています。
    runs-on: windows-latest

    # ジョブのステップのリストを開始します。
    # これらは上から下に順番に実行されます。
    steps:

      # 各ステップで使用するアクションを指定しています。
      # リポジトリをチェックアウトしています。
    - uses: actions/checkout@v3

    # NET Coreランタイムをセットアップしています。
    - uses: actions/setup-dotnet@v3
      with:
        dotnet-version: |
          5.0.x
          6.0.x

      # Microsoft Security DevOpsアクションを実行しています。
    - name: Run Microsoft Security DevOps
      uses: microsoft/security-devops-action@v1.6.0
      id: msdo

      # セキュリティスキャン結果をアップロードしています。
    - name: Upload results to Security tab
      uses: github/codeql-action/upload-sarif@v2
      with:
        sarif_file: ${{ steps.msdo.outputs.sarifFile }}
```
このファイルは`GitHub Actions`のワークフローを定義しています。この特定のワークフローは、`Linux`コンテナのビルド、プッシュ、デプロイを行います。
```yml:.github/workflows/devops.yml
# このワークフローは、リポジトリにプッシュがあった場合に実行されます。
on: [push]

# このワークフローの名前は Linux_Container_Workflow です。
name: Linux_Container_Workflow

# このセクションは、このワークフローが含むジョブを定義します。
jobs:

    # ジョブの名前です。
    build-and-deploy:

        # ubuntu-latest 環境でジョブを実行するように指定しています。
        runs-on: ubuntu-latest

        # ジョブで実行される手順を定義するブロックです。
        steps:

          # リポジトリをチェックアウトします。
        - name: 'Checkout GitHub Action'
          uses: actions/checkout@main
          
          # Azure CLI を使用してログインし、Azureリソースにアクセスします。
        - name: 'Login via Azure CLI'
          uses: azure/login@v1
          with:
            creds: ${{ secrets.AZURE_CREDENTIALS }}
        
          # Dockerイメージをビルドし、Azure Container Registryにプッシュします。
        - name: 'Build and push image'
          uses: azure/docker-login@v1
          with:
            login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
            username: ${{ secrets.REGISTRY_USERNAME }}
            password: ${{ secrets.REGISTRY_PASSWORD }}
          
          # run コマンドを使用して、シェルコマンドを実行します。
          # ここではDockerコマンドを使用してイメージをビルドし、プッシュします。
        - run: |
            docker build . -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/sampleapp:${{ github.sha }}
            docker push ${{ secrets.REGISTRY_LOGIN_SERVER }}/sampleapp:${{ github.sha }}

          # ACIにアプリをデプロイします。
        - name: 'Deploy to Azure Container Instances'
          uses: 'azure/aci-deploy@v1'
          with:

            # デプロイするAzureリソースグループを指定します。
            resource-group: ${{ secrets.RESOURCE_GROUP }}

            # デプロイされるコンテナーのDNS名を指定します。
            dns-name-label: ${{ secrets.RESOURCE_GROUP }}${{ github.run_number }}

            # ACRからイメージを取得して、ACIにデプロイします。
            image: ${{ secrets.REGISTRY_LOGIN_SERVER }}/sampleapp:${{ github.sha }}

            # Azure Container Registryにログインするためのログインサーバーを指定します。
            registry-login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}

            # Azure Container Registryにログインするためのユーザー名を指定します。
            registry-username: ${{ secrets.REGISTRY_USERNAME }}

            # Azure Container Registryにログインするためのパスワードを指定します。
            registry-password: ${{ secrets.REGISTRY_PASSWORD }}

            # ACIの名前を指定します。
            name: aci-sampleapp

            # ACIが展開される場所を指定します。
            location: 'west us'
```
`default.conf`ファイルは、`nginx`の設定ファイルです。このファイルで`listen`するポート番号、サーバー名、ドキュメントルートなどの設定を記述します。
```conf:conf/default.conf
server {
        # HTTPリクエストの受信に使用されるWebサーバーを設定しています。
        # listenにより、nginxが受信するポート番号を設定しています。
        # HTTPリクエストを受信するポート80とを設定しています。
        listen 80;
        # HTTPSリクエストを受信するポート443を設定しています。
        listen 443;
        
        # server_nameによりサーバー名の設定を行っています。
        server_name localhost;

        # ドキュメントルートの設定
        root /usr/share/nginx/html;

        # デフォルトのインデックスファイルの設定
        index index.html index.html;

        # リクエストの処理ルールの設定
        # locationディレクティブは、リクエストの処理ルールを設定するためのディレクティブです。
        # ここでは、リクエストされたファイルが存在しない場合には、/src/index.htmlファイルを返すように設定しています。
        location / {
                try_files $uri $uri/ /src/index.html$query_string;
        }

        # エラーページの設定
        error_page   500 502 503 504  /50x.html;
                 location = /50x.html {
                   root   /usr/share/nginx/html;
        }
}
```
`Hello World`を表示するシンプルな`html`ファイルです。脆弱性を持つ`index.js`を読み込んでいます。
```html:src/index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>サンプルアプリ</title>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <div>Hello World</div>
  </body>
  <!-- 脆弱性を持つindex.jsのインポート -->
  <script src="js/index.js"></script>
</html>
```
`CodeQL`を用いた脆弱性チェックを行うために`index.js`では、意図的にシークレットキーと`http`のURLを記述しています。
```javascript:src/js/index.js
function dummy_func() {
  // 意図的な脆弱性(シークレットキーはソースコード内に記述すべきでない)
  // ダミーシークレットキー
  var DUMMY_SECRET_KEY = "sk_test_51HFbZTKgBlpEUSwRBlkbXZPCt2pfHRAs5DHZB5jbMmnnsVGD8hazbBAAVp8UeEqnC6wZZ9IY2rsXllwzxonWQKZL00nmvWRYUy"
  // 意図的な脆弱性(httpsにするべきである)
  var url = "http://umayadia-apisample.azurewebsites.net/api/persons/Shakespeare" 
}
```
この`Dockerfile`は`nginx`のWebサーバーをベースとしたDockerイメージを構築するためのファイルです。
```Dockerfile:Dockerfile
# ベースとなるイメージとして、最新のNginxイメージを指定しています。
FROM nginx:latest

# ローカルの./conf/default.confファイルを、Dockerイメージ内の/etc/nginx/conf.d/default.confにコピーします。
# ここでは、nginxの設定ファイルをDockerイメージ内に取り込んでいます。
ADD ./conf/default.conf /etc/nginx/conf.d/default.conf

# ローカルの./srcフォルダを、Dockerイメージ内の/usr/share/nginx/htmlフォルダにコピーします。
# ここでは、nginxが提供するWebコンテンツのルートディレクトリをDockerイメージ内に取り込んでいます。
ADD ./src /usr/share/nginx/html

# nginxを起動するためのコマンドを指定します。
# ここでは、単にstart nginxという文字列を表示するだけのコマンドを実行します。
# この行は、Dockerイメージのビルド時に実行されます。
RUN echo "start nginx"
```

# 製品一覧
本記事で扱う製品一覧になります。今回は`GitHub`を用いてDevOpsの構築をしていますが、`Azure DevOps`を用いても構築可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/f4fbb124-d429-8235-b47b-e8428ae45536.png)

## Microsoft Defender for DevOpsとは
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/cd2dd66a-70b3-7d2f-b46e-7fa0b8c005e9.png)
`Microsoft Defender for DevOps`は、Azure DevOpsおよびGitHubなどのCI/CDプラットフォームに統合されたセキュリティ製品で、コード解析や脆弱性診断、悪意のあるアクティビティの検出などを提供します。ビルド、デプロイ、テストなどのアクティビティを実行するときに、セキュリティとコンプライアンスをサポートすることができ、脆弱性の検出、シークレット管理、脅威検知、コンプライアンス監視、コンテナセキュリティ、アプリケーションの保護など、さまざまな機能を提供します。また、AIおよび機械学習技術を利用して、異常なアクティビティや攻撃を自動的に検知することもできます。

# アーキテクチャ
今回構築するアーキテクチャになります。ソースコードを`GitHub`にpushをすると、事前に定義されていた`GitHub Actions`のワークフローがトリガーされ、`javascript`の脆弱性チェック及び、自動ビルド&自動デプロイが行われます。脆弱性は`CodeQL`にチェックされ、コードスキャンやシークレットスキャンが行われます。`GitHub`と`Microsoft Defender for DevOps`は連携されていることから、脆弱性が見つかれば、`GitHub`と`Azure portal`の両方から確認をすることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/0472eda6-54ce-9e3a-cae4-ea1b030220f4.png)

## 代替可能なアーキテクチャ
DevOps構築のために、`GitHub`の代わりに`Azure DevOps`を用いることができます。また、コンテナーアプリのデプロイ先として、`Azure Container Instance`の代わりに`App Service`、`Azure Kubernetes Service`、`Azure Container Apps`でも指定することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/99ebf03e-b5b4-f6fb-1271-48f77edbdeb2.png)

### Azure DevOpsとGitHub Actionsの使い分け
`Azure DevOps`と`GitHub Actions`の違いについて簡単にまとめました。プロジェクトの規模や、他にMicrosoft製品を使用しているかによって使い分けることができそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/52040953-1ccf-943e-19fc-15e4f6aec056.png)

### Azure DevOpsとGitHub Actionsの違い
2023年3月現在、`Azure DevOps`と`GitHub Actions`でいくつかの機能の違いが確認されました。しかし、今後はこれらの違いはなくなると思われます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/be3158b4-dc86-d0c0-9ecb-1294e85fa70a.png)

### ACI/ACA/AKS/Azure App Serviceの違い
デプロイ先のサービスとして、いくつか候補があるので`ACI`、`ACA`、`AKS`、`Azure App Service`について違いを簡単にまとめました。プロジェクトの規模や、アプリの形式によって使い分けができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/6009549e-2a59-529c-64f0-3fc20a2445a7.png)

# セキュアな自動ビルドと自動デプロイ
gitにより、事前に作成&登録してあるリモートリポジトリにファイルをpushします。
```terminal
git .
git commit -m "first commit"
git push origin master
```
`GitHub`の画面から`Actions`を選択すると、自動ビルド&自動デプロイが行われていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/1cdcd378-3bfe-2ce8-1ee6-c02d66043fc5.png)
同時に`CodeQL`による`javascript`ファイルの脆弱性チェックも行われています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/4aba3e2a-57d1-f10b-8009-b84877f727fe.png)

# GitHub画面
Securityタブ>`Code scanning`でアラートの内容が表示できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/adf443a6-e3e2-50b8-7944-ba8ae61f5971.png)
アラートの内容を選択すると、ソースコードの該当箇所とアラート内容が確認できます。(表示されているurlが多少違いますが、類似した内容のアラートが表示されるはずです。)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/dc1c663a-7c59-2fe8-b05f-ba951b2ec52b.png)
シークレットキーの漏洩についてもスキャンを行い、アラートを出してくれます。(当然ですが、シークレットキーに似せた適当な文字列も公開しましたが、アラートは出されませんでした。)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/eb723d6e-55e2-64f5-2ef6-3b508114624a.png)

# Azureポータル画面
`Microsoft Defender for Cloud`>`DevOps Security`のページより、`GitHub`画面で表示されたアラートと同じアラートを確認することができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/9030e243-bd4e-f553-619f-6c600da84554.png)
`GitHub`のプロジェクトを選択することで、アラートの内容を確認することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/3853ba38-8b58-4d06-d0c8-ba735743ee80.png)


# おわりに
DevOpsのCI/CD環境の構築を始めて行ったのですが、GitHubにソースコードをpushするだけで自動的にビルドとデプロイをしてくれるのは非常に役に立つと感じました。また、`GitHub Actions`を用いた`CodeQL`による脆弱性チェックにより、`javascript`の脆弱性を簡単に見つけることができました。私自身`Azure`の勉強中でなので、何か間違いを見つけたら教えていただけると幸いです。