「条件付きアクセスのポリシーが何十個もあって、誰が何を変えたかわからない」——Microsoft Entra ID(旧称: Azure AD)の条件付きアクセスを手動でGUI管理していると必ずぶつかる課題です。
本記事では Microsoft Graph PowerShell と Terraform(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 セキュリティ運用代行も承っています。「ポリシーが増えすぎて管理できない」という場合もお声がけください。
参考リンク
- New-MgIdentityConditionalAccessPolicy - Microsoft Learn
- Create conditionalAccessPolicy - Microsoft Graph v1.0
- azuread_conditional_access_policy - Terraform Registry
著者紹介
亀田 英佑(株式会社BTNコンサルティング 代表取締役)
中小企業向け IT アウトソーシングサービス「情シス365」を運営。Microsoft 365 のセキュリティ設計・Entra ID 運用・Intune 展開を含む IT 運用全般を代行しています。