🐈‍⬛

GitHubで管理されているClaudeの設定ファイルが更新されたら、自動でIntune経由で配布する方法

に公開

はじめに

全社的に「Claude Codeを使っていこう!」という施策(参考:こちらの記事)が進む中、Windows 端末への管理設定(managed-settings.json)の配布が課題になりました。

なぜこの設定を配布することになったのかは、Kajinariさんが発信しているClaudeを組織で安全に使うために NOT A HOTELで実施している4つのレイヤー を参考にしてください!

立ちはだかった壁

Windowsへの配布には越えるべき壁がいくつかありました。

  1. Intune上ではバージョンがわからない
  2. Claudeの設定ファイルが更新された後、Intuneから再配布されるのか不明だった
  3. Intuneスクリプト上でセキュアにクレデンシャルを取り扱いたかった

具体的に何が問題で、どう解決したのかですが、

  1. Intune上ではバージョンがわからない問題

    Intuneでは一度スクリプトをアップロードすると中身が確認できません。いつの時点のファイルなのかわからずタイトルなどで推測するか、配布された側の端末でなんとか探し出すしかありません。(今回のように設定ファイルを配布する場合は探せる可能性がある)

  2. Claudeの設定ファイルが更新された後、再実行されるのか不明だった問題

    Intuneで設定ファイルを配布した場合、その後更新のたびにアップデートが端末側に再配布されるのかが不明でした。しかし、プラットフォームスクリプトという機能であれば、再起動のたびに新しいスクリプトや変更がないか確認してくれることがわかりました。

    Intune 管理拡張機能は、再起動のたびに新しいスクリプトや変更がないか確認します。

    参考:https://learn.microsoft.com/en-us/intune/device-management/tools/run-powershell-scripts-windows

    確認される仕組みは確認できたが、そもそも“スクリプト自体”をGitHub更新に合わせて自動更新したいという要件もあります。そこで「GitHubにある設定ファイルの更新を検知して、自動でIntuneに反映できないか?」と考え、Graph API 経由での更新を検討し始めました。

  3. Intuneスクリプト上でセキュアにクレデンシャルを取り扱いたかった

    次に問題になったのが、Intune のプラットフォームスクリプトを自動更新するための認証(=Graph API を安全に呼び出す方法)です。

    最初に考えたのは、端末側のPowerShellからGitHubのプライベートリポジトリをHTTPで直接取得する方式でした。しかしこの方式だと、GitHub PATなどの秘密情報をスクリプトに埋め込む必要があり、端末に秘密情報が配布されてしまうリスクがあります。

    そこで採用したのが、GitHub ActionsとEntra IDのWorkload Identity連携(OIDC)です。これにより長期間有効なシークレットを持たずに GitHub Actions からトークンを取得でき、Microsoft Graph API 経由で Intune のスクリプトを更新できるようになりました。

    私自身、普段の業務の中でなかなかGitHubを触ることがないので、「Entra ID にアプリを登録すれば、GitHub リポジトリの Secrets を使って連携ができる」ということを知りませんでした。AIもなかなか提案してくれなかったので、もっとこの情報が広まってほしい…!

    以下、解決策で詳しく説明します。

このブログでは、設定方法についてのみ言及していますので、Claude Code の managed-settings.json の仕組みや各 OS のファイルパスについては、公式ドキュメントを参照してください。

解決策:GitHub Actions × Workload Identity で自動化する

GitHub の設定ファイル更新(push)をトリガーに、GitHub Actions から Microsoft Graph API 経由で Intune のプラットフォームスクリプトを更新する方式にしました。

全体の流れは以下の通りです。

  1. GitHub リポジトリの設定ファイルを更新して main ブランチに push
  2. GitHub Actions が起動し、設定ファイルの内容を埋め込んだ PowerShell スクリプトを生成
  3. Microsoft Graph API 経由で Intune のプラットフォームスクリプトを上書き更新
  4. Intune がスクリプトの変更を検知して端末に再配布
  5. 端末でスクリプトが実行され、WSL 上に設定ファイルが配置される

認証にはEntra IDのWorkload Identity連携(OIDCフェデレーション)を使います。

これにより、クライアントシークレットすら不要になります。

参考:Workload Identity Federation

構成図

セットアップ手順

1. Entra ID でアプリを登録する

まず Entra ID でアプリの登録を行います。

  1. https://entra.microsoft.comアプリの登録新規登録
  2. API のアクセス許可で Microsoft Graph のアプリケーションのアクセス許可から DeviceManagementScripts.ReadWrite.All を追加
  3. テナントの管理者の同意を付与

2. Workload Identity 連携を設定する

アプリ登録の 証明書とシークレットフェデレーション資格情報 からOIDC連携を設定します。

  • シナリオ:GitHub Actions でデプロイする Azure リソース
  • 組織:GitHub の Organization 名
  • リポジトリ:対象のリポジトリ名
  • エンティティの種類:ブランチ
  • ブランチ名:main

エンティティを「ブランチ」にすることで、mainからのpushのみトークン取得可能になります。

featureブランチのPRからは認証できないため、意図しない更新を防げます。

3. GitHub リポジトリの Secrets を登録する

リポジトリの Settings → Secrets and variables → Actions に以下を登録します。

Secret 名
AZURE_TENANT_ID Entra ID のテナント ID
AZURE_CLIENT_ID アプリ登録のクライアント ID
INTUNE_SCRIPT_ID 更新対象のプラットフォームスクリプトの ID

Workload Identity連携のおかげで、クライアントシークレットの登録は不要です。

INTUNE_SCRIPT_IDは、Intune設定画面のURLのpolicyId以下の値です。

4. GitHub Actions ワークフローを作成する

.github/workflows/intune-managed-settings.yml を作成します。

yaml内の path/to/managed-settings.json(2箇所)を、リポジトリ内の実際のファイルパスに置き換えてください。

Secretsは手順 3 で登録済みなので、それ以外の変更は不要です。

name: Deploy settings to Intune

on:
  push:
    branches:
      - main
    paths:
      - 'path/to/managed-settings.json'
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Azure Login (Workload Identity)
        uses: azure/login@v2
        with:
          client-id: $ secrets.AZURE_CLIENT_ID 
          tenant-id: $ secrets.AZURE_TENANT_ID 
          allow-no-subscriptions: true

      - name: Deploy script to Intune
        run: |
          set -euo pipefail

          # 設定ファイルを Base64 エンコード
          SETTINGS_BASE64=$(base64 -w 0 < "path/to/managed-settings.json")

          # PowerShell スクリプトを組み立て
          read -r -d '' SCRIPT_CONTENT << PSTEMPLATE || true
          \$ErrorActionPreference = "Stop"

          function Write-Log {
              param([string]\$Message)
              Write-Output "[\$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] \$Message"
          }

          try {
              Write-Log "Decoding and writing embedded settings.json"

              \$base64 = "${SETTINGS_BASE64}"
              \$bytes = [System.Convert]::FromBase64String(\$base64)
              \$content = [System.Text.Encoding]::UTF8.GetString(\$bytes)

              \$tempFile = "\$env:TEMP\managed-settings.json"
              [System.IO.File]::WriteAllText(\$tempFile, \$content, (New-Object System.Text.UTF8Encoding \$false))

              Write-Log "Deploying to WSL"
              wsl -d Ubuntu -u root -- mkdir -p /etc/claude-code
              wsl -d Ubuntu -u root -- cp "/mnt/c/Users/\$env:USERNAME/AppData/Local/Temp/managed-settings.json" /etc/claude-code/managed-settings.json

              Write-Log "Done"
          }
          catch {
              Write-Log "Error: \$_"
              exit 1
          }
          PSTEMPLATE

          # スクリプト全体を Base64 エンコード
          SCRIPT_BASE64=$(echo "$SCRIPT_CONTENT" | base64 -w 0)

          # アクセストークンを取得
          ACCESS_TOKEN=$(az account get-access-token \
            --resource https://graph.microsoft.com \
            --query accessToken -o tsv)

          # Intune のスクリプトを更新
          HTTP_STATUS=$(curl -s -o /tmp/response.json -w "%{http_code}" -X PATCH \
            "https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts/$ secrets.INTUNE_SCRIPT_ID " \
            -H "Authorization: Bearer $ACCESS_TOKEN" \
            -H "Content-Type: application/json" \
            -d "{\"scriptContent\": \"$SCRIPT_BASE64\"}")

          if [ "$HTTP_STATUS" -eq 200 ] || [ "$HTTP_STATUS" -eq 204 ]; then
            echo "Script updated successfully (HTTP $HTTP_STATUS)"
          else
            echo "Failed to update script (HTTP $HTTP_STATUS)"
            cat /tmp/response.json
            exit 1
          fi

permissionsid-token: writeを指定しないとトークン取得に失敗するので注意してください。

スクリプトが更新されると、Intune上で自動的に「配布済み→未配布」に変わり、再配布されます。

ハマったポイント

実際に構築する中でいくつかハマったポイントがありました。

Graph API のエンドポイントが beta のみ

deviceManagementScripts は 2026 年 5 月時点で Graph API の beta 版にのみ存在します。v1.0 にはまだ含まれていないため、エンドポイントは https://graph.microsoft.com/beta/... を使う必要があります。

beta API は破壊的変更のリスクがある点を認識した上で使用してください。

日本語を含む設定ファイルが文字化けする

これが一番厄介でした。設定ファイルに日本語(例えば利用ガイドのメッセージ)が含まれている場合、PowerShell スクリプトに直接 JSON を埋め込むと文字化けします。

原因は、Intune がスクリプトを端末に配布する際にエンコーディングが変換されることです。

対策として、JSON を Base64 エンコードした状態でスクリプトに埋め込み、PowerShell 側でデコードする方式を採用しました。

# Base64 文字列をデコード
$base64 = "eyJrZXkiOiAi5pel5pys6Kqe44KS5ZCr44KA5YCkIn0="
$bytes = [System.Convert]::FromBase64String($base64)
$content = [System.Text.Encoding]::UTF8.GetString($bytes)

Intune 上ではスクリプトの中身が確認できない

Intuneの管理センターでは、登録済みスクリプトの内容を表示する機能がありません。

更新後の確認方法は主に2つです。

  • Graph APIで取得scriptContentをBase64デコードして中身を確認
  • 端末で直接確認cat /etc/claude-code/managed-settings.json

端末側はスクリプトの再実行後に反映されます。

すぐに反映させたい場合は Restart-Service IntuneManagementExtension でチェックインを強制できます。(再起動した場合とどちらが早いのかは疑問)

セキュリティ上のポイント

  • 端末に秘密鍵が存在しない:認証は GitHub Actions と Entra ID の間で完結するため、端末側にはトークンやシークレットが一切配布されない
  • Workload Identity 連携による一時的なトークン:GitHub Actions が取得するトークンはジョブ実行中のみ有効で、長期間有効なシークレットが存在しない
  • 監査ログ:GitHub Actions の実行ログと Graph API の監査ログの両方で、いつ誰がスクリプトを更新したか追跡できる

まとめ

GitHub Actions と Workload Identity 連携を使うことで、Intune のプラットフォームスクリプトを Git ベースで管理・自動デプロイできるようになりました。

導入のメリットをまとめると以下の通りです。

  • Intune上ではバージョンがわからない

→GitHub連携で今適用されているバージョンを確認できるようになった

  • Claudeの設定ファイルが更新された後、Intuneから再配布されるのか不明だった

→スクリプトは変更時に自動的に再実行できた

  • Intuneスクリプト上でセキュアにクレデンシャルを取り扱いたかった

→Workload Identity 連携で秘密鍵を埋め込む必要がなくなった

Graph API が beta 版である点は注意が必要ですが、Intune のスクリプト管理を IaC(Infrastructure as Code)的に運用したい方の参考になれば幸いです!

NOT A HOTEL

Discussion