0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Entra ID 条件付きアクセスをコードで管理する【Microsoft Graph PowerShell / Terraform IaC化ガイド】

0
Posted at

「条件付きアクセスのポリシーが何十個もあって、誰が何を変えたかわからない」——Microsoft Entra ID(旧称: Azure AD)の条件付きアクセスを手動でGUI管理していると必ずぶつかる課題です。

本記事では Microsoft Graph PowerShellTerraform(azuread provider) の両アプローチで、条件付きアクセスポリシーを IaC(Infrastructure as Code)化する手順を解説します。

注意: 本記事では現行の正式名称「Microsoft Entra ID」を使用します。「Azure AD」は非推奨名称です。


なぜ条件付きアクセスを IaC 化するのか

課題 IaC 化による解決
変更履歴が追えない Git で差分管理・コードレビュー
環境間のポリシー差異 同一コードで本番・テスト環境に適用
設定ミスのリスク -WhatIf / terraform plan で事前確認
ドキュメント化の手間 コード自体がドキュメント

アプローチ 1: Microsoft Graph PowerShell

1-1. モジュールのインストールと認証

# Microsoft Graph PowerShell SDK のインストール(管理者権限で実行)
Install-Module Microsoft.Graph.Identity.SignIns -Scope CurrentUser -Force

# 条件付きアクセス読み書きに必要なスコープで認証
Connect-MgGraph -Scopes "Policy.ReadWrite.ConditionalAccess", "Application.Read.All"

# 接続確認
Get-MgContext | Select-Object Account, TenantId, Scopes

1-2. 既存ポリシーをエクスポートする

まず現状を JSON としてエクスポートして把握します。

# すべての条件付きアクセスポリシーを取得
$policies = Get-MgIdentityConditionalAccessPolicy

# JSON でファイルに保存(IaC 化の起点として使用)
$policies | ConvertTo-Json -Depth 10 | Out-File -FilePath ".\ca-policies-export.json" -Encoding UTF8

Write-Host "エクスポート完了: $($policies.Count) ポリシー"

1-3. 新規ポリシーをコードで作成する

実装例: MFA 必須ポリシー(信頼済み場所外からのアクセス)

# ポリシーパラメーターを定義
$params = @{
  displayName = "[CA001] 信頼済み場所外からのアクセスに MFA を要求"
  state       = "enabled"    # "disabled" でレポートのみモード
  conditions  = @{
    clientAppTypes = @("browser", "mobileAppsAndDesktopClients")
    applications   = @{
      # "All" で全クラウドアプリに適用
      includeApplications = @("All")
      excludeApplications = @()
    }
    users = @{
      # 全ユーザーに適用(緊急アクセスアカウントは除外)
      includeUsers  = @("All")
      excludeUsers  = @("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")  # 緊急アクセス用アカウントID
      includeGroups = @()
    }
    locations = @{
      # 全ロケーションを対象にして信頼済みを除外
      includeLocations = @("All")
      excludeLocations = @("AllTrusted")
    }
  }
  grantControls = @{
    operator         = "OR"
    builtInControls  = @("mfa")
  }
}

# 実際に作成する前に -WhatIf で確認
# New-MgIdentityConditionalAccessPolicy -BodyParameter $params -WhatIf

# ポリシーを作成
$newPolicy = New-MgIdentityConditionalAccessPolicy -BodyParameter $params
Write-Host "作成完了: $($newPolicy.Id) / $($newPolicy.DisplayName)"

実装例: 非準拠デバイスのブロックポリシー

$blockNonCompliant = @{
  displayName = "[CA002] 非準拠デバイスからのアクセスをブロック"
  state       = "enabledForReportingButNotEnforcing"  # まず監査モードで開始
  conditions  = @{
    clientAppTypes = @("all")
    applications   = @{
      includeApplications = @("All")
    }
    users = @{
      includeUsers = @("All")
      excludeUsers = @("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
    }
    platforms = @{
      includePlatforms = @("all")
      # iOS/Android はモバイル MDM ポリシーで別途管理
      excludePlatforms = @("iOS", "android")
    }
  }
  grantControls = @{
    operator        = "OR"
    builtInControls = @("compliantDevice")
  }
}

New-MgIdentityConditionalAccessPolicy -BodyParameter $blockNonCompliant

1-4. 既存ポリシーを更新する

# ポリシー ID を取得
$policyId = (Get-MgIdentityConditionalAccessPolicy -Filter "displayName eq '[CA001] 信頼済み場所外からのアクセスに MFA を要求'").Id

# 状態を変更(disabled → enabled)
$updateParams = @{
  state = "enabled"
}

Update-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $policyId -BodyParameter $updateParams
Write-Host "更新完了: $policyId"

1-5. ポリシーを一括で適用するスクリプト

複数ポリシーを JSON ファイルから読み込んで一括適用します。

# policies/ フォルダ内の JSON ファイルをすべて適用
function Apply-CAPoliciesFromJson {
  param(
    [string]$PolicyDir = ".\policies"
  )

  $jsonFiles = Get-ChildItem -Path $PolicyDir -Filter "*.json"
  Write-Host "$($jsonFiles.Count) 件のポリシーファイルを処理します"

  foreach ($file in $jsonFiles) {
    $policy = Get-Content $file.FullName | ConvertFrom-Json -Depth 10

    # ID が含まれていれば更新、なければ新規作成
    if ($policy.id) {
      try {
        Update-MgIdentityConditionalAccessPolicy `
          -ConditionalAccessPolicyId $policy.id `
          -BodyParameter $policy
        Write-Host "[UPDATE] $($policy.displayName)"
      } catch {
        Write-Warning "更新失敗 ($($file.Name)): $_"
      }
    } else {
      try {
        $created = New-MgIdentityConditionalAccessPolicy -BodyParameter $policy
        Write-Host "[CREATE] $($created.DisplayName) (ID: $($created.Id))"
      } catch {
        Write-Warning "作成失敗 ($($file.Name)): $_"
      }
    }
  }
}

Apply-CAPoliciesFromJson -PolicyDir ".\ca-policies"

アプローチ 2: Terraform(azuread provider)

2-1. プロバイダー設定

# versions.tf
terraform {
  required_providers {
    azuread = {
      source  = "hashicorp/azuread"
      version = "~> 3.0"  # 2026年6月時点の最新メジャーバージョン
    }
  }
  required_version = ">= 1.6"
}

provider "azuread" {
  # 環境変数 ARM_TENANT_ID / ARM_CLIENT_ID / ARM_CLIENT_SECRET を参照
  # または Workload Identity Federation を使用(推奨)
}

2-2. 条件付きアクセスポリシーを定義する

# conditional_access.tf

# --- データソース: 緊急アクセスグループ(除外対象)---
data "azuread_group" "emergency_access" {
  display_name     = "EmergencyAccess-BreakGlass"
  security_enabled = true
}

# --- ポリシー 1: MFA 必須 ---
resource "azuread_conditional_access_policy" "require_mfa_outside_trusted" {
  display_name = "[CA001] 信頼済み場所外からのアクセスに MFA を要求"
  state        = "enabled"

  conditions {
    client_app_types = ["browser", "mobileAppsAndDesktopClients"]

    applications {
      included_applications = ["All"]
    }

    users {
      included_users  = ["All"]
      excluded_groups = [data.azuread_group.emergency_access.object_id]
    }

    locations {
      included_locations = ["All"]
      excluded_locations = ["AllTrusted"]
    }
  }

  grant_controls {
    operator          = "OR"
    built_in_controls = ["mfa"]
  }
}

# --- ポリシー 2: レガシー認証ブロック ---
resource "azuread_conditional_access_policy" "block_legacy_auth" {
  display_name = "[CA003] レガシー認証プロトコルをブロック"
  state        = "enabled"

  conditions {
    # レガシー認証クライアントのみを対象
    client_app_types = ["exchangeActiveSync", "other"]

    applications {
      included_applications = ["All"]
    }

    users {
      included_users  = ["All"]
      excluded_groups = [data.azuread_group.emergency_access.object_id]
    }

    locations {
      included_locations = ["All"]
    }
  }

  grant_controls {
    operator          = "OR"
    built_in_controls = ["block"]
  }
}

# --- ポリシー 3: サインイン頻度制御(セッションコントロール)---
resource "azuread_conditional_access_policy" "sign_in_frequency" {
  display_name = "[CA004] サインイン頻度を 8 時間に制限"
  state        = "enabled"

  conditions {
    client_app_types = ["browser"]

    applications {
      included_applications = ["All"]
    }

    users {
      included_users = ["All"]
    }

    locations {
      included_locations = ["All"]
    }
  }

  # grant_controls なし(session_controls のみ)
  session_controls {
    sign_in_frequency                 = 8
    sign_in_frequency_period          = "hours"
    sign_in_frequency_authentication_type = "primaryAndSecondaryAuthentication"
    sign_in_frequency_interval        = "timeBased"
  }
}

2-3. Terraform の運用フロー

# 初期化
terraform init

# 変更内容を事前確認(破壊的変更がないか必ず確認)
terraform plan -out=tfplan

# 適用
terraform apply tfplan

# 現状の Entra ID からステートをインポート(既存ポリシーを管理下に置く)
terraform import azuread_conditional_access_policy.require_mfa_outside_trusted "<ポリシーID>"

2-4. CI/CD パイプライン(GitHub Actions)

# .github/workflows/conditional-access.yml
name: Deploy Conditional Access Policies

on:
  push:
    branches: [main]
    paths:
      - 'infra/entra/**'
  pull_request:
    paths:
      - 'infra/entra/**'

jobs:
  terraform:
    runs-on: ubuntu-latest
    permissions:
      id-token: write   # OIDC 認証に必要
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: "~1.8"

      # Workload Identity Federation で認証(シークレット不要)
      - name: Azure Login
        uses: azure/login@v2
        with:
          client-id:       ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id:       ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Terraform Init
        run: terraform init
        working-directory: ./infra/entra

      - name: Terraform Plan
        run: terraform plan -no-color
        working-directory: ./infra/entra

      - name: Terraform Apply (main ブランチのみ)
        if: github.ref == 'refs/heads/main'
        run: terraform apply -auto-approve
        working-directory: ./infra/entra

ポリシー設計のベストプラクティス

命名規則

[CA{番号:3桁}] {対象}_{アクション}_{条件}
例:
[CA001] 全ユーザー_MFA要求_信頼外
[CA010] 管理者_MFA要求_常時
[CA020] 全ユーザー_ブロック_レガシー認証
[CA030] デバイス_MFA要求_非準拠

緊急アクセスアカウントの除外

Break-Glass アカウントと緊急アクセスグループはすべてのポリシーから除外します。条件付きアクセスでロックアウトされた場合の復旧手段として必須です。

段階的な有効化

1. state = "enabledForReportingButNotEnforcing"(監査モード)
2. サインインログで影響範囲を確認(7〜14日間)
3. state = "enabled" に変更して本番適用

まとめ

観点 Graph PowerShell Terraform
学習コスト 低(PowerShell スキルで対応可) 中(HCL の学習が必要)
ドリフト検出 手動スクリプト terraform plan で自動検出
CI/CD 統合 可能 ネイティブサポート
既存環境インポート スクリプト自作 terraform import

小規模なら PowerShell スクリプト+Git 管理、中規模以上なら Terraform が効果的です。どちらの場合も 変更前の -WhatIf / plan 確認が最重要です。

情シス365 では、Entra ID の条件付きアクセス設計・IaC 化を含む Microsoft 365 セキュリティ運用代行も承っています。「ポリシーが増えすぎて管理できない」という場合もお声がけください。


参考リンク


著者紹介

亀田 英佑(株式会社BTNコンサルティング 代表取締役)

中小企業向け IT アウトソーシングサービス「情シス365」を運営。Microsoft 365 のセキュリティ設計・Entra ID 運用・Intune 展開を含む IT 運用全般を代行しています。

無料相談はこちら: https://meetings-na2.hubspot.com/e-kameta

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?