フォーム読み込み中
当社では一部の社内システムをAzure上に構築して運用しています。セキュリティポリシーに準拠するため、すべての環境に接続元IPアドレスによるアクセス制御を適用しています。
これらの社内システムの開発・運用には、DevOpsを実現する上で重要な要素であるCI/CD(Continuous Integration / Continuous Delivery、継続的インテグレーション/継続的デリバリー)を導入しています。
まず、GitHubなどのソースコードリポジトリへのcommit/pushやPull Requestの作成などをトリガとして、GitHub ActionsやCircleCIのようなワークフローツールによって、CI/CDワークフローを起動します。
CI/CDワークフローは、ビルドやテストを実施し、これらがパスした際に、所定の環境へのデプロイなどを行います。
このとき、デプロイ先の環境が接続元IPアドレスによるアクセス制限を実施している場合、通常はデプロイ先のホワイトリストにCI/CDワークフローを実行中のランナーのIPアドレスが含まれていないため、デプロイに失敗します。
この記事では、Azure側で接続元IPアドレスによるアクセス制限を実施している場合に、GitHub Actionsのランナーから対象のAzure環境に接続してデプロイなどを可能にする方法を記載します。
Azure Blob Storageは、 静的Webサイトホスティング を実施する場合や、何らかの中間ファイルを読み書きする場合などに、GitHub Actionsから接続する必要性があります。
Azure Virtual Machines(以降、Azure仮想マシン)は、ビルド成果物を仮想マシン上でホスティングする場合や、 SonarQube のような開発ツールをインストールして使用している場合などに、GitHub Actionsから接続する必要性があります。
Azure Functionsは、FaaS(Function as a Service)の一種でサーバレスでアプリケーションを実行できるプラットフォームです。
GitHub上で管理しているアプリケーションのコードを、Azure Functionsにデプロイする場合や、Azure Functions上で動作しているAPIを実行する場合などに、GitHub Actionsから接続する必要性があります。
GitHub Actionsのランナーが使用するパブリックIPアドレスの一覧は、GitHubが提供する API を実行することで取得することが可能です。
ご参考:GitHubのIPアドレスについて - GitHub Docs
このAPIで公開されているIPアドレスをホワイトリストに追加することで、接続元IPアドレスによるアクセス制限を回避することが可能です。
しかし、これらのIPアドレスは変わる可能性があるため、この一覧をホワイトリストに登録する場合は、GitHub側のIPアドレスの変更に追従できるように、ホワイトリストを随時メンテナンスするための仕組みづくりが必要になります。
また、他のGitHubユーザも同一のIPアドレスを使用するため、悪意のあるユーザによる不正侵入を許してしまう可能性があり、この方法は推奨できません。
以上を踏まえ、GitHub Actionsが実行される度にランナーのパブリックIPアドレスを取得し、都度ホワイトリストを更新する方法がベストプラクティスと言えます。
この記事でAzureの操作を行う場合は、原則としてBashのCloud Shellを使用します。
AzureポータルのWebUIでも同様の操作が可能ですが、この記事ではその方法については解説しません。
まず、Azureポータルにログインし、画面右上の Cloud Shell アイコンをクリックします。
しばらく待機すると、画面下部にプロンプト領域が展開します。
プロンプト領域の左上のプルダウンで、PowerShell が選択されている場合は、 Bash を選択します。
Azure上にサービスプリンシパルを作成します。
これは、GitHub ActionsのランナーからAzure上のリソース操作を行う時に利用します。
Azure Cloud Shellで以下のコードを入力し実行します。
なお、このとき SP_NAME は、任意のサービスプリンシパル名に変更します。
#----- 設定ここから ----- # サービスプリンシパル名 SP_NAME=github-actions-test-app #----- 設定ここまで ----- # サービスプリンシパルを作成 (出力されるJSONファイルは保存しておく) az ad sp create-for-rbac \ --name $SP_NAME \ --years $((9999 - $(date "+%Y"))) \ --sdk-auth | tee AZURE_CREDENTIALS_$SP_NAME.json
上記コマンドにおいて、yearsで計算処理を実施しています。
これは、Azure側で生成される認証情報が、最大で西暦9999年までの有効期限を設定できる仕様になっているため、西暦9999年から現在の西暦年を減算して、最大の有効期限を設定しています。
このオプション指定を省略した場合、認証情報の有効期限は1年間となり、1年後には本記事で紹介する仕組みが機能しなくなります。
作成したサービスプリンシパルに対して、GitHub Actionsから操作したい対象リソースへのアクセス権限を付与します。
この操作は、リソースによって操作方法が若干異なります。
Azure Cloud Shellで以下のコードを入力し実行します。
なお、SP_NAME はサービスプリンシパル名に、RG_NAME はストレージアカウントが所属するリソースグループ名に、SA_NAME はストレージアカウント名に変更します。
#----- 設定ここから ----- # サービスプリンシパル名 SP_NAME=github-actions-test-app # リソースグループ名 RG_NAME=rg-gha-test # ストレージアカウント名 SA_NAME=stgithubactionstest #----- 設定ここまで ----- # リソースグループのIDを取得 groupId=$(az group show \ --name $RG_NAME \ --query id \ --output tsv) # ストレージアカウントのIDを取得 storageId=$(az storage account show \ --name $SA_NAME \ --query id \ --output tsv) # サービスプリンシパルのオブジェクトIDを取得 clientId=$(az ad sp list \ --display-name $SP_NAME \ --query "[].{id:id}" \ --output tsv) # サービスプリンシパルにBlobストレージへの読み書き権限を付与 az role assignment create \ --assignee $clientId \ --scope $storageId \ --role "Storage Blob Data Contributor"
Azure Cloud Shellで以下のコードを入力し実行します。
なお、SP_NAME はサービスプリンシパル名に、RG_NAME はAzure仮想マシンが所属するリソースグループ名に変更します。
#----- 設定ここから ----- # サービスプリンシパル名 SP_NAME=github-actions-test-app # リソースグループ名 RG_NAME=rg-test-vm #----- 設定ここまで ----- # サービスプリンシパルのオブジェクトIDを取得 clientId=$(az ad sp list \ --display-name $SP_NAME \ --query "[].{id:id}" \ --output tsv) # 対象のリソースグループにネットワーク共同作成者ロール割り当てる az role assignment create \ --assignee $clientId \ --role "Network Contributor" \ --resource-group $RG_NAME
Azure Cloud Shellで以下のコードを入力し実行します。
なお、SP_NAME はサービスプリンシパル名に、RG_NAME はAzure Functionsが所属するリソースグループ名に、 FUNC_NAME はAzure Functionsのリソース名に変更します。
#----- 設定ここから ----- # サービスプリンシパル名 SP_NAME=github-actions-test-app # リソースグループ名 RG_NAME=rg-test-vm # Functionsのリソース名 FUNC_NAME=gha-test-func #----- 設定ここまで ----- # サービスプリンシパルのオブジェクトIDを取得 clientId=$(az ad sp list \ --display-name $SP_NAME \ --query "[].{id:id}" \ --output tsv) # FunctionsのIDを取得 functionId=$(az functionapp show \ --resource-group $RG_NAME \ --name $FUNC_NAME \ --query id --output tsv) # サービスプリンシパルにFunctionsへの共同作成者ロールを付与 az role assignment create \ --assignee $clientId \ --scope $functionId \ --role "functions Contributor"
サービスプリンシパルを作成した際に生成されたjsonオブジェクトを、GitHubリポジトリのSecretsに登録します。
GitHubリポジトリのSecretsに登録することで、認証情報を保護しつつ、GitHub Actionsで安全に呼び出して使用することができます。
ブラウザでGitHubリポジトリを開き、 Settings を開きます。
Settings が表示されない場合は、権限不足の可能性があるため、リポジトリの管理者に問い合わせが必要です。
リポジトリの設定画面で、 Security > Secrets > Actions と辿り、New repository sycret ボタンをクリックします。
シークレットの設定画面で、以下のように入力し、Add secret ボタンをクリックします。
Name | AZURE_CREDENTIALS |
---|---|
Value | 事前準備2で保存しておいたjsonファイルの内容を貼り付け |
接続先のAzureリソースに応じて必要な情報を、同様の手順でGitHubリポジトリのSecretsに登録します。
Name | Value |
---|---|
AZURE_SA_NAME | アクセスしたいストレージアカウント名 |
Name | Value |
---|---|
AZURE_RG_NAME | アクセスしたいAzure仮想マシンが含まれるリソースグループ名 |
AZURE_NSG_NAME | アクセスしたい仮想マシンのネットワークインターフェース (NIC) 、または仮想ネットワークのサブネットにアタッチされているネットワーク セキュリティ グループ (NSG) 名 |
Name | Value |
---|---|
AZURE_RG_NAME | アクセスしたいAzure Functionsが含まれるリソースグループ名 |
AZURE_FUNCTIONAPP_NAME | アクセスしたいAzure Functionsのリソース名 |
GitHub Actionsを使用するには、リポジトリ直下に .github/workflows というディレクトリを作成し、このディレクトリ配下にワークフローを定義した yaml 形式のファイルを格納します。
GitHub Actionsは、yamlファイルに定義された条件に合致した場合に、対応するワークフローが自動で実行されます。
GitHub Actionsから、接続元IPアドレスによるアクセス制限を実施しているAzureリソースにアクセスする場合、以下のような処理を含むワークフローを作成します。
上記の1〜2の処理は、Azureリソースの種類に関わらず共通です。
一方で、3〜6の処理については、Azureリソースの種類によって異なるため、詳細は記事の後半でリソースの種類ごとに解説します。
実際のワークフローファイルは以下のようになります。
name: CI/CD Workflow (master) on: push: branches: - master jobs: CD: runs-on: ubuntu-latest steps: # チェックアウト - name: Checkout uses: actions/checkout@master # 1. CI/CDワークフローを実行中のGitHub ActionランナーのIPアドレスを取得 - name: Queries the GitHub actions runner's public IP address id: ip uses: haythem/[email protected] # 2. 事前準備で作成したサービスプリンシパルを使用してAzureにログイン - name: Login to Azure uses: Azure/login@v1 with: creds: ${{ secrets.AZURE_CREDENTIALS }} # 3. Azure上の対象リソースのホワイトリストに、GitHub ActionランナーのIPアドレスを登録 - name: Add the public IP to the whitelist # TODO: リソースの種類に応じた処理を定義 # 4. ホワイトリストが反映されるまで待機 - name: Sleep for 10 seconds # TODO: リソースの種類に応じた待機時間を設定する # 5. Azure上の対象リソースに対する操作を実施(デプロイなど) - name: Do something # TODO: 対象リソースに対する任意処理を定義 # 6. Azure上の対象リソースのホワイトリストから、GitHub ActionランナーのIPアドレスを削除 - name: Remove the public IP from the whitelist if: always() # TODO: リソースの種類に応じた処理を定義
このワークフローの例では、 on.push.branches に master を指定しているため、masterブランチへのpushをトリガとして、以降に定義されている CD という名前のジョブが実行されます。
IPアドレスの取得には haythem/public-ip というアクションを指定しています。
このアクションでは、GitHub ActionsのランナーのパブリックIPを取得することができます。
このステップには ip というIDを定義しているので、後続のステップで ${{ steps.ip.outputs.ipv4 }} のように呼び出すことで、取得したパブリックIPを変数として使用することができます。
また、上記の例では、ホワイトリストへの変更後に10秒間のスリープ処理を加えています。
これは、ホワイトリストへの変更の反映に、Azureリソースの種類によって時間がかかることを考慮しています。
待機すべき時間はAzureリソースの種類によって異なるため、別途リソースの種類ごとに解説します。
もう一点特筆すべき点として、6の「Azure上の対象リソースのホワイトリストから、GitHub ActionランナーのIPアドレスを削除」する処理があります。
このステップでは if: always() という定義を追加しています。
この定義を追加することで、前段のデプロイなどの処理が失敗した場合であっても、必ずホワイトリストからパブリックIPが削除されます。
もし、この定義が含まれない場合、悪意のあるユーザによる不正侵入を許してしまう可能性があります。
GitHub ActionsのCI/CDワークフローからAzure Blob Storageにアクセスするには、ワークフローの3〜6を以下のように定義します。
# 3. Azure上の対象リソースのホワイトリストに、GitHub ActionランナーのIPアドレスを登録 - name: Add the public IP to the whitelist uses: azure/CLI@v1 with: azcliversion: 2.30.0 inlineScript: | az storage account network-rule add \ --account-name ${{ secrets.AZURE_SA_NAME }} \ --ip-address ${{ steps.ip.outputs.ipv4 }} # 4. ホワイトリストが反映されるまで待機 - name: Sleep for 10 seconds run: sleep 10s shell: bash # 5. Azure上の対象リソースに対する操作を実施(デプロイなど) - name: Deploy to Azure Static WebSite hosting on Azure Blob storage uses: azure/CLI@v1 with: azcliversion: 2.30.0 inlineScript: | az storage blob upload-batch \ --source artifact \ --destination \$web \ --account-name ${{ secrets.AZURE_SA_NAME }} # 6. Azure上の対象リソースのホワイトリストから、GitHub ActionランナーのIPアドレスを削除 - name: Remove the public IP from the whitelist if: always() uses: azure/CLI@v1 with: azcliversion: 2.30.0 inlineScript: | az storage account network-rule remove \ --account-name ${{ secrets.AZURE_SA_NAME }} \ --ip-address ${{ steps.ip.outputs.ipv4 }}
この例では、成果物をAzure Blob Storageの静的Webサイトホスティングにデプロイする処理を想定しています。(ビルド処理などは省略)
Azure Storageのホワイトリストの操作は az storage account network-rule コマンドを使用します。
Azure Storageのホワイトリスト変更後の反映には数秒かかるため、10秒間のスリープ処理を加えています。
GitHub ActionsのCI/CDワークフローからAzure仮想マシンにアクセスするには、ワークフローの3〜6を以下のように定義します。
# 3. Azure上の対象リソースのホワイトリストに、GitHub ActionランナーのIPアドレスを登録 - name: Add the public IP to the whitelist uses: azure/CLI@v1 with: azcliversion: 2.30.0 inlineScript: | az network nsg rule create \ --resource-group ${{ secrets.AZURE_RG_NAME }} \ --nsg-name ${{ secrets.AZURE_NSG_NAME }} \ --name AllowGitHubActions \ --priority 4000 \ --source-address-prefixes ${{ steps.ip.outputs.ipv4 }} \ --source-port-ranges '*' \ --destination-address-prefixes '*' \ --destination-port-ranges 9000 \ --access Allow \ --protocol Tcp \ --description "Allow from GitHub Actions to port 9000." # 4. ホワイトリストが反映されるまで待機 - name: Sleep for 60 seconds run: sleep 60s shell: bash # 5. Azure上の対象リソースに対する操作を実施(デプロイなど) - name: Run SonarQube uses: sonarsource/sonarqube-scan-action@master # 6. Azure上の対象リソースのホワイトリストから、GitHub ActionランナーのIPアドレスを削除 - name: Remove the public IP from the whitelist if: always() uses: azure/CLI@v1 with: azcliversion: 2.30.0 inlineScript: | az network nsg rule delete \ --resource-group ${{ secrets.AZURE_RG_NAME }} \ --nsg-name ${{ secrets.AZURE_NSG_NAME }} \ --name AllowGitHubActions
この例では、SonarQubeを用いてソースコードの静的解析を実施し、Azure仮想マシン上のSonarQubeサーバに結果を送信する処理を想定しています。
Azure仮想マシンのホワイトリストの操作は、仮想マシンのネットワークインターフェース (NIC) 、または仮想ネットワークのサブネットにアタッチする ネットワーク セキュリティ グループ (NSG) の 受信セキュリティ規則 によって実施します。
NSGの受信セキュリティ規則は az network nsg rule コマンドを使用して変更可能です。
なお、この例では優先度4000 (--priority 4000)で、9000番ポート(--destination-port-ranges 9000)に対するアクセスを許可する設定をしています。これらの設定は要件に応じて変更する必要があります。
また、Azure仮想マシンのホワイトリスト変更後の反映には十数秒〜二十数秒かかるため、60秒間のスリープ処理を加えています。
GitHub ActionsのCI/CDワークフローからAzure仮想マシンにアクセスするには、ワークフローの3〜6を以下のように定義します。
# 3. Azure上の対象リソースのホワイトリストに、GitHub ActionランナーのIPアドレスを登録 - name: Add the public IP to the whitelist uses: azure/CLI@v1 with: azcliversion: 2.30.0 inlineScript: | az functionapp config access-restriction add \ --resource-group ${{ secrets.AZURE_RG_NAME }} \ --name ${{ secrets.AZURE_FUNCTIONAPP_NAME }} \ --rule-name "GitHub Actions" \ --action Allow \ --ip-address ${{ steps.ip.outputs.ipv4 }} \ --priority 999 # 4. ホワイトリストが反映されるまで待機 - name: Sleep for 10 seconds run: sleep 10s shell: bash # 5. Azure上の対象リソースに対する操作を実施(デプロイなど) - name: Run Azure Functions Action uses: Azure/functions-action@v1 id: fa with: app-name: ${{ secrets.AZURE_FUNCTIONAPP_NAME }} slot-name: "production" package: ${{ secrets.AZURE_FUNCTIONAPP_PACKAGE_PATH }} publish-profile: ${{ secrets.AzureAppService_PublishProfile }} # 6. Azure上の対象リソースのホワイトリストから、GitHub ActionランナーのIPアドレスを削除 - name: Remove the public IP from the whitelist if: always() uses: azure/CLI@v1 with: azcliversion: 2.30.0 inlineScript: | az functionapp config access-restriction remove \ --resource-group ${{ secrets.AZURE_RG_NAME }} \ --name ${{ secrets.AZURE_FUNCTIONAPP_NAME }} \ --rule-name "GitHub Actions"
この例では、成果物をAzure Functionsにデプロイする処理を想定しています。(ビルド処理などは省略)
デプロイにあたって、別途GitHub ActionsのSecretsへの発行プロファイル (Publish profile)の追加が必要となります。Microsoft社の公式ドキュメント等を参考に設定を行ってください。
Azure Functionsのホワイトリストの操作は az functionapp config access-restriction コマンドを使用します。
Azure Functionsのホワイトリスト変更後の反映には数秒かかるため、10秒間のスリープ処理を加えています。
この記事では、Azure側で接続元IPアドレスによるアクセス制限を実施している場合に、GitHub Actionsのランナーから対象のAzure環境に接続してデプロイなどを可能にする方法について解説しました。
今回は、Azure Blob Storage、Azure Virtual Machines、Azure Functionsを例として解説しましたが、基本的な考え方は他のAzureサービスや、Azure以外のサービスにおいても同様に活用することができます。
この記事が皆さまの開発の一助となれば幸いです。
条件に該当するページがございません