はじめに
DevOpsにより高速な開発と安定したリリースが実現できますが、デプロイからフローが自動化されていることでサイバー攻撃のリスクが高まるため、セキュリティを考慮することがますます重要になっています。そこで、本記事ではGitHub Actions
を用いたDevOpsの実現とMicrosoft Defender for DevOps
を用いてセキュリティ対策を実施します。本記事は、以下の公式ドキュメントのクイックスタートをベースにアレンジした内容になっております。細かい設定や手順については割愛させていただくので、必要であれば適宜ドキュメントをご覧ください。
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
コードを自動的に分析するために設定されています。
CodeQLは、静的コード解析ツールの一種で、バグ、脆弱性、セキュリティ上の問題などを自動的に検出することができます。また、プログラムの検証や脆弱性診断に利用されます。CodeQLを用いたコードスキャンを行うことで、クロスサイトスクリプティング(XSS)の検出、不正な正規表現の検出、エクスポートされた関数の脆弱性を検出、クロスオリジンリソース共有(CORS)の検出などが可能です。
# ワークフローの名前を"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タブにアップロードすることが目的です。
# ワークフローの名前を指定しています。
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
コンテナのビルド、プッシュ、デプロイを行います。
# このワークフローは、リポジトリにプッシュがあった場合に実行されます。
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
するポート番号、サーバー名、ドキュメントルートなどの設定を記述します。
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
を読み込んでいます。
<!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を記述しています。
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イメージを構築するためのファイルです。
# ベースとなるイメージとして、最新の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
を用いても構築可能です。
Microsoft Defender for DevOpsとは
Microsoft Defender for DevOps
は、Azure DevOpsおよびGitHubなどのCI/CDプラットフォームに統合されたセキュリティ製品で、コード解析や脆弱性診断、悪意のあるアクティビティの検出などを提供します。ビルド、デプロイ、テストなどのアクティビティを実行するときに、セキュリティとコンプライアンスをサポートすることができ、脆弱性の検出、シークレット管理、脅威検知、コンプライアンス監視、コンテナセキュリティ、アプリケーションの保護など、さまざまな機能を提供します。また、AIおよび機械学習技術を利用して、異常なアクティビティや攻撃を自動的に検知することもできます。
アーキテクチャ
今回構築するアーキテクチャになります。ソースコードをGitHub
にpushをすると、事前に定義されていたGitHub Actions
のワークフローがトリガーされ、javascript
の脆弱性チェック及び、自動ビルド&自動デプロイが行われます。脆弱性はCodeQL
にチェックされ、コードスキャンやシークレットスキャンが行われます。GitHub
とMicrosoft Defender for DevOps
は連携されていることから、脆弱性が見つかれば、GitHub
とAzure portal
の両方から確認をすることができます。
代替可能なアーキテクチャ
DevOps構築のために、GitHub
の代わりにAzure DevOps
を用いることができます。また、コンテナーアプリのデプロイ先として、Azure Container Instance
の代わりにApp Service
、Azure Kubernetes Service
、Azure Container Apps
でも指定することができます。
Azure DevOpsとGitHub Actionsの使い分け
Azure DevOps
とGitHub Actions
の違いについて簡単にまとめました。プロジェクトの規模や、他にMicrosoft製品を使用しているかによって使い分けることができそうです。
Azure DevOpsとGitHub Actionsの違い
2023年3月現在、Azure DevOps
とGitHub Actions
でいくつかの機能の違いが確認されました。しかし、今後はこれらの違いはなくなると思われます。
ACI/ACA/AKS/Azure App Serviceの違い
デプロイ先のサービスとして、いくつか候補があるのでACI
、ACA
、AKS
、Azure App Service
について違いを簡単にまとめました。プロジェクトの規模や、アプリの形式によって使い分けができます。
セキュアな自動ビルドと自動デプロイ
gitにより、事前に作成&登録してあるリモートリポジトリにファイルをpushします。
git .
git commit -m "first commit"
git push origin master
GitHub
の画面からActions
を選択すると、自動ビルド&自動デプロイが行われていることが確認できます。
同時にCodeQL
によるjavascript
ファイルの脆弱性チェックも行われています。
GitHub画面
Securityタブ>Code scanning
でアラートの内容が表示できます。
アラートの内容を選択すると、ソースコードの該当箇所とアラート内容が確認できます。(表示されているurlが多少違いますが、類似した内容のアラートが表示されるはずです。)
シークレットキーの漏洩についてもスキャンを行い、アラートを出してくれます。(当然ですが、シークレットキーに似せた適当な文字列も公開しましたが、アラートは出されませんでした。)
Azureポータル画面
Microsoft Defender for Cloud
>DevOps Security
のページより、GitHub
画面で表示されたアラートと同じアラートを確認することができました。
GitHub
のプロジェクトを選択することで、アラートの内容を確認することができます。
おわりに
DevOpsのCI/CD環境の構築を始めて行ったのですが、GitHubにソースコードをpushするだけで自動的にビルドとデプロイをしてくれるのは非常に役に立つと感じました。また、GitHub Actions
を用いたCodeQL
による脆弱性チェックにより、javascript
の脆弱性を簡単に見つけることができました。私自身Azure
の勉強中でなので、何か間違いを見つけたら教えていただけると幸いです。