d3e0769799
- Restructure launchers: Start-IntuneToolkit.ps1 moves to repo root; Start-HeadlessIntune.ps1 moves to Scripts/; TUI helper moves to Scripts/Private/ - Add AGENTS.md with project architecture, entry points, and security notes - Add CIS M365 baseline assets (CISM365-v7, M365-CIS-Rapid) and reporting scripts - Add Python reporting utilities (Export-SettingsReport, Export-AssignmentReport, Export-ObjectInventoryReport) and CA wizard helpers - Update Deploy-IntuneBaseline.ps1 with Merge conflict resolution, ReportPath, and optimized group loading - Update Initialize-IntuneAuth.ps1 with -RotateSecret and configurable secret expiry - Update Extensions for Settings Catalog definition auto-export - Update README with v4.1.0, new entry points and script catalog - Bump VERSION to 4.1.0 - Harden .gitignore against .DS_Store, __pycache__, .venv-pdf/, local exports, Settings.json and IntuneManagement.log
683 lines
24 KiB
PowerShell
683 lines
24 KiB
PowerShell
#requires -Version 7.0
|
||
<#
|
||
.SYNOPSIS
|
||
Generates a Conditional Access baseline YAML manifest from high-level security requirements.
|
||
|
||
.DESCRIPTION
|
||
Creates a CIS M365-compatible baseline YAML file covering Conditional Access policies.
|
||
The output can be reviewed and then deployed with Deploy-CISM365Baseline.ps1.
|
||
|
||
Policy names follow the structured naming convention:
|
||
<INDEX>-<TARGET>-<APP/RESOURCE>-<CONTROL>-<SCOPE>
|
||
|
||
Index ranges:
|
||
CA0xx – User policies
|
||
CA1xx – Guest policies
|
||
CA2xx – Application policies
|
||
CA3xx – Admin policies
|
||
CA4xx – Threat policies
|
||
|
||
Example: CA001-AllUsers-AllApps-BlockLegacyAuth-Prod
|
||
|
||
.PARAMETER RequireTrustedLocations
|
||
Enforce that users can only sign in from trusted named locations.
|
||
- None: No location restriction policy
|
||
- AllUsers: All users must be on trusted locations
|
||
- Admins: Only administrative roles must be on trusted locations
|
||
- All: Both AllUsers and Admins policies
|
||
|
||
.PARAMETER AdminDeviceCompliance
|
||
Device requirements for administrative roles.
|
||
- None: No device policy for admins
|
||
- Required: Admins must use compliant or hybrid-joined devices
|
||
- RequiredWithMFA: Admins must use compliant/hybrid-joined devices AND MFA
|
||
|
||
.PARAMETER GuestMFA
|
||
Require MFA for guest and external users.
|
||
|
||
.PARAMETER SessionTimeoutHours
|
||
Require re-authentication after N hours. 0 disables session timeout policies.
|
||
|
||
.PARAMETER DisablePersistentBrowser
|
||
Prevent persistent browser sessions (users must re-auth when browser restarts).
|
||
|
||
.PARAMETER TrustedLocationsExemptFromReauth
|
||
When SessionTimeoutHours is set, do not require re-authentication from trusted locations.
|
||
This creates an exclusion so users on trusted networks are not nagged.
|
||
|
||
.PARAMETER RequireMFAForAllUsers
|
||
Require MFA for all member users.
|
||
|
||
.PARAMETER BlockLegacyAuth
|
||
Block all legacy authentication protocols (Exchange ActiveSync, basic auth, etc.).
|
||
|
||
.PARAMETER BlockHighRiskSignIns
|
||
Block sign-ins with medium or high risk level (requires Entra ID P2).
|
||
|
||
.PARAMETER RequireMFAForAdminPortals
|
||
Require MFA when accessing Microsoft admin portals (Azure, M365, Exchange, etc.).
|
||
|
||
.PARAMETER RequireMFAForAdmins
|
||
Require MFA for all administrative roles across all applications.
|
||
|
||
.PARAMETER RequirePhishingResistantMFAForAdmins
|
||
Require phishing-resistant MFA (FIDO2, certificate) for administrative roles.
|
||
|
||
.PARAMETER BlockDeviceCodeFlow
|
||
Block sign-ins using the device code authentication flow.
|
||
|
||
.PARAMETER RequireManagedDeviceForAllUsers
|
||
Require all users to use compliant or hybrid-joined devices.
|
||
|
||
.PARAMETER OutputPath
|
||
Path where the generated YAML baseline will be written.
|
||
|
||
.PARAMETER Scope
|
||
Deployment stage suffix applied to every policy name.
|
||
- Test, Pilot1, Pilot2, Pilot3, Prod
|
||
|
||
.PARAMETER UseDescriptiveNames
|
||
Use human-readable descriptive names instead of the structured naming convention.
|
||
|
||
.PARAMETER Prefix
|
||
Optional prefix applied before the INDEX (e.g. "ACME-" produces ACME-CA001-...).
|
||
|
||
.PARAMETER BreakGlassGroup
|
||
Name of the break-glass group to auto-exclude from every CA policy.
|
||
|
||
.PARAMETER ReportOnly
|
||
Default all generated policies to report-only mode (recommended for initial rollout).
|
||
|
||
.EXAMPLE
|
||
# Minimal baseline: MFA for all + block legacy auth
|
||
./Scripts/New-ConditionalAccessBaseline.ps1 `
|
||
-RequireMFAForAllUsers `
|
||
-BlockLegacyAuth `
|
||
-OutputPath ./Baselines/MyCA.yaml
|
||
|
||
.EXAMPLE
|
||
# Full security baseline with structured names scoped to production
|
||
./Scripts/New-ConditionalAccessBaseline.ps1 `
|
||
-RequireTrustedLocations AllUsers `
|
||
-AdminDeviceCompliance RequiredWithMFA `
|
||
-GuestMFA `
|
||
-SessionTimeoutHours 8 `
|
||
-DisablePersistentBrowser `
|
||
-TrustedLocationsExemptFromReauth `
|
||
-BlockLegacyAuth `
|
||
-BlockHighRiskSignIns `
|
||
-OutputPath ./Baselines/SecureTenant-CA.yaml `
|
||
-Scope Prod
|
||
|
||
.EXAMPLE
|
||
# Pilot rollout with descriptive names instead of structured convention
|
||
./Scripts/New-ConditionalAccessBaseline.ps1 `
|
||
-RequireMFAForAllUsers `
|
||
-BlockLegacyAuth `
|
||
-OutputPath ./Baselines/Pilot-CA.yaml `
|
||
-Scope Pilot1 `
|
||
-UseDescriptiveNames
|
||
#>
|
||
[CmdletBinding()]
|
||
param(
|
||
[Parameter()]
|
||
[ValidateSet('None','AllUsers','Admins','All')]
|
||
[string]$RequireTrustedLocations = 'None',
|
||
|
||
[Parameter()]
|
||
[ValidateSet('None','Required','RequiredWithMFA')]
|
||
[string]$AdminDeviceCompliance = 'None',
|
||
|
||
[Parameter()]
|
||
[switch]$GuestMFA,
|
||
|
||
[Parameter()]
|
||
[ValidateRange(0,24)]
|
||
[int]$SessionTimeoutHours = 0,
|
||
|
||
[Parameter()]
|
||
[switch]$DisablePersistentBrowser,
|
||
|
||
[Parameter()]
|
||
[switch]$TrustedLocationsExemptFromReauth,
|
||
|
||
[Parameter()]
|
||
[switch]$RequireMFAForAllUsers,
|
||
|
||
[Parameter()]
|
||
[switch]$BlockLegacyAuth,
|
||
|
||
[Parameter()]
|
||
[switch]$BlockHighRiskSignIns,
|
||
|
||
[Parameter()]
|
||
[switch]$RequireMFAForAdminPortals,
|
||
|
||
[Parameter()]
|
||
[switch]$RequireMFAForAdmins,
|
||
|
||
[Parameter()]
|
||
[switch]$RequirePhishingResistantMFAForAdmins,
|
||
|
||
[Parameter()]
|
||
[switch]$BlockDeviceCodeFlow,
|
||
|
||
[Parameter()]
|
||
[switch]$RequireManagedDeviceForAllUsers,
|
||
|
||
[Parameter(Mandatory = $true)]
|
||
[string]$OutputPath,
|
||
|
||
[Parameter()]
|
||
[ValidateSet('Test','Pilot1','Pilot2','Pilot3','Prod')]
|
||
[string]$Scope = 'Prod',
|
||
|
||
[Parameter()]
|
||
[switch]$UseDescriptiveNames,
|
||
|
||
[Parameter()]
|
||
[string]$Prefix = '',
|
||
|
||
[Parameter()]
|
||
[string]$BreakGlassGroup = 'CIS-BreakGlass',
|
||
|
||
[Parameter()]
|
||
[switch]$ReportOnly
|
||
)
|
||
|
||
$ErrorActionPreference = 'Stop'
|
||
|
||
# =====================================================================
|
||
# Naming convention engine
|
||
# =====================================================================
|
||
# Format: CA<area><scope><seq2digit>-<TARGET>-<APP/RESOURCE>-<CONTROL>
|
||
# Area: 0=Threat/Tenant, 1=User, 2=Admin, 3=Guest, 4=Application
|
||
# Scope: 0=Test, 1=Pilot1, 2=Pilot2, 3=Pilot3, 9=Prod
|
||
# Seq: auto-increment per area
|
||
# =====================================================================
|
||
$script:AreaDigitMap = @{
|
||
'User' = '1'
|
||
'Guest' = '3'
|
||
'Application' = '4'
|
||
'Admin' = '2'
|
||
'Threat' = '0'
|
||
}
|
||
$script:ScopeDigitMap = @{
|
||
'Test' = '0'
|
||
'Pilot1' = '1'
|
||
'Pilot2' = '2'
|
||
'Pilot3' = '3'
|
||
'Prod' = '9'
|
||
}
|
||
$script:NextSeq = @{
|
||
'0' = 1
|
||
'1' = 1
|
||
'2' = 1
|
||
'3' = 1
|
||
'4' = 1
|
||
}
|
||
|
||
function Get-StructuredPolicyName {
|
||
param(
|
||
[Parameter(Mandatory)]
|
||
[ValidateSet('User','Guest','Application','Admin','Threat')]
|
||
[string]$Category,
|
||
|
||
[Parameter(Mandatory)]
|
||
[string]$Target,
|
||
|
||
[Parameter(Mandatory)]
|
||
[string]$AppResource,
|
||
|
||
[Parameter(Mandatory)]
|
||
[string]$Control
|
||
)
|
||
$area = $script:AreaDigitMap[$Category]
|
||
$scope = $script:ScopeDigitMap[$Scope]
|
||
$seq = $script:NextSeq[$area]++
|
||
$idx = "$area$scope$($seq.ToString('D2'))"
|
||
$name = "CA$idx-${Target}-${AppResource}-${Control}"
|
||
if ($Prefix) { $name = "$Prefix$name" }
|
||
return $name
|
||
}
|
||
|
||
function Get-DescriptivePolicyName {
|
||
param([string]$Name)
|
||
if ($Prefix) { return "$Prefix$Name" }
|
||
return $Name
|
||
}
|
||
|
||
function Get-DefaultState {
|
||
if ($ReportOnly) { return 'enabledForReportingButNotEnforced' }
|
||
return 'enabled'
|
||
}
|
||
|
||
# =====================================================================
|
||
# Shared data
|
||
# =====================================================================
|
||
$script:AdminRoles = @(
|
||
'Global Administrator',
|
||
'Privileged Role Administrator',
|
||
'Security Administrator',
|
||
'Exchange Administrator',
|
||
'SharePoint Administrator',
|
||
'Conditional Access Administrator',
|
||
'Application Administrator',
|
||
'Cloud Application Administrator',
|
||
'User Administrator',
|
||
'Helpdesk Administrator',
|
||
'Billing Administrator',
|
||
'Authentication Administrator',
|
||
'Password Administrator'
|
||
)
|
||
|
||
$script:AdminPortalAppIds = @(
|
||
'797f4846-ba00-4fd7-ba43-dac1f8f63013', # Azure Management
|
||
'c44b4083-3bb0-49c1-b47d-974e53cbdf3c', # Azure AD PowerShell
|
||
'1b730954-1685-4b74-9bfd-dac224a7b894', # Microsoft Graph PowerShell
|
||
'00000003-0000-0ff1-ce00-000000000000', # Office 365 Exchange Online
|
||
'00000003-0000-0000-c000-000000000000', # Microsoft Graph
|
||
'de8bc8b5-d9f9-48b1-a8ad-b748da725064', # Microsoft Intune
|
||
'00000002-0000-0ff1-ce00-000000000000', # Office 365 SharePoint Online
|
||
'66a88757-258c-4c72-893c-3e8bed4d6899' # Microsoft365DSC
|
||
)
|
||
|
||
# =====================================================================
|
||
# Policy builders
|
||
# =====================================================================
|
||
|
||
function New-PolicyBlockLegacyAuth {
|
||
$policy = @{
|
||
name = if ($UseDescriptiveNames) { Get-DescriptivePolicyName 'Block-Legacy-Authentication' } else { Get-StructuredPolicyName -Category Threat -Target AllUsers -AppResource AllApps -Control BlockLegacyAuth }
|
||
description = 'Block all legacy authentication protocols (EAS, basic auth, IMAP, POP, etc.)'
|
||
state = Get-DefaultState
|
||
conditions = @{
|
||
applications = @{ includeApplications = @('All') }
|
||
users = @{ includeUsers = @('All') }
|
||
clientAppTypes = @('exchangeActiveSync', 'other')
|
||
}
|
||
grantControls = @{
|
||
builtInControls = @('block')
|
||
operator = 'OR'
|
||
}
|
||
}
|
||
return $policy
|
||
}
|
||
|
||
function New-PolicyRequireMFAAllUsers {
|
||
$policy = @{
|
||
name = if ($UseDescriptiveNames) { Get-DescriptivePolicyName 'Require-MFA-All-Users' } else { Get-StructuredPolicyName -Category User -Target AllUsers -AppResource AllApps -Control RequireMFA }
|
||
description = 'Require multi-factor authentication for all users'
|
||
state = Get-DefaultState
|
||
conditions = @{
|
||
applications = @{ includeApplications = @('All') }
|
||
users = @{ includeUsers = @('All') }
|
||
}
|
||
grantControls = @{
|
||
builtInControls = @('mfa')
|
||
operator = 'OR'
|
||
}
|
||
}
|
||
return $policy
|
||
}
|
||
|
||
function New-PolicyRequireMFAAdmins {
|
||
$policy = @{
|
||
name = if ($UseDescriptiveNames) { Get-DescriptivePolicyName 'Require-MFA-Admins' } else { Get-StructuredPolicyName -Category Admin -Target Admins -AppResource AllApps -Control RequireMFA }
|
||
description = 'Require multi-factor authentication for all administrative roles'
|
||
state = Get-DefaultState
|
||
conditions = @{
|
||
applications = @{ includeApplications = @('All') }
|
||
users = @{ includeRoles = $script:AdminRoles }
|
||
}
|
||
grantControls = @{
|
||
builtInControls = @('mfa')
|
||
operator = 'OR'
|
||
}
|
||
}
|
||
return $policy
|
||
}
|
||
|
||
function New-PolicyRequireMFAAdminPortals {
|
||
$policy = @{
|
||
name = if ($UseDescriptiveNames) { Get-DescriptivePolicyName 'Require-MFA-Admin-Portals' } else { Get-StructuredPolicyName -Category Application -Target AllUsers -AppResource AdminPortals -Control RequireMFA }
|
||
description = 'Require MFA when accessing Microsoft admin portals'
|
||
state = Get-DefaultState
|
||
conditions = @{
|
||
applications = @{ includeApplications = $script:AdminPortalAppIds }
|
||
users = @{ includeUsers = @('All') }
|
||
}
|
||
grantControls = @{
|
||
builtInControls = @('mfa')
|
||
operator = 'OR'
|
||
}
|
||
}
|
||
return $policy
|
||
}
|
||
|
||
function New-PolicyTrustedLocations {
|
||
param([switch]$ForAdmins)
|
||
if ($ForAdmins) {
|
||
$name = if ($UseDescriptiveNames) { Get-DescriptivePolicyName 'Trusted-Locations-Only-Admins' } else { Get-StructuredPolicyName -Category Admin -Target Admins -AppResource AllApps -Control BlockUntrustedLocations }
|
||
$desc = 'Administrators can only sign in from trusted named locations'
|
||
$userDef = @{ includeRoles = $script:AdminRoles }
|
||
} else {
|
||
$name = if ($UseDescriptiveNames) { Get-DescriptivePolicyName 'Trusted-Locations-Only-All-Users' } else { Get-StructuredPolicyName -Category User -Target AllUsers -AppResource AllApps -Control BlockUntrustedLocations }
|
||
$desc = 'All users can only sign in from trusted named locations'
|
||
$userDef = @{ includeUsers = @('All') }
|
||
}
|
||
$policy = @{
|
||
name = $name
|
||
description = $desc
|
||
state = Get-DefaultState
|
||
conditions = @{
|
||
applications = @{ includeApplications = @('All') }
|
||
users = $userDef
|
||
locations = @{
|
||
includeLocations = @('All')
|
||
excludeLocations = @('AllTrusted')
|
||
}
|
||
}
|
||
grantControls = @{
|
||
builtInControls = @('block')
|
||
operator = 'OR'
|
||
}
|
||
}
|
||
return $policy
|
||
}
|
||
|
||
function New-PolicyAdminDeviceCompliance {
|
||
param([switch]$WithMFA)
|
||
$controls = @('compliantDevice', 'domainJoinedDevice')
|
||
$operator = 'OR'
|
||
$desc = 'Administrators must use compliant or hybrid-joined devices'
|
||
|
||
if ($WithMFA) {
|
||
$controls = @('compliantDevice', 'domainJoinedDevice', 'mfa')
|
||
$operator = 'AND'
|
||
$desc = 'Administrators must use compliant/hybrid-joined devices AND MFA'
|
||
$name = if ($UseDescriptiveNames) { Get-DescriptivePolicyName 'Require-Compliant-Device-and-MFA-Admins' } else { Get-StructuredPolicyName -Category Admin -Target Admins -AppResource AllApps -Control RequireCompliantDeviceAndMFA }
|
||
} else {
|
||
$name = if ($UseDescriptiveNames) { Get-DescriptivePolicyName 'Require-Compliant-Device-Admins' } else { Get-StructuredPolicyName -Category Admin -Target Admins -AppResource AllApps -Control RequireCompliantDevice }
|
||
}
|
||
|
||
$policy = @{
|
||
name = $name
|
||
description = $desc
|
||
state = Get-DefaultState
|
||
conditions = @{
|
||
applications = @{ includeApplications = @('All') }
|
||
users = @{ includeRoles = $script:AdminRoles }
|
||
}
|
||
grantControls = @{
|
||
builtInControls = $controls
|
||
operator = $operator
|
||
}
|
||
}
|
||
return $policy
|
||
}
|
||
|
||
function New-PolicyGuestMFA {
|
||
$policy = @{
|
||
name = if ($UseDescriptiveNames) { Get-DescriptivePolicyName 'Require-MFA-Guests' } else { Get-StructuredPolicyName -Category Guest -Target Guests -AppResource AllApps -Control RequireMFA }
|
||
description = 'Require multi-factor authentication for guest and external users'
|
||
state = Get-DefaultState
|
||
conditions = @{
|
||
applications = @{ includeApplications = @('All') }
|
||
users = @{
|
||
includeGuestsOrExternalUsers = @{
|
||
guestTypes = @('internalGuest', 'b2bCollaborationGuest', 'b2bCollaborationMember', 'b2bDirectConnectUser')
|
||
externalTenants = @{ membershipKind = 'all' }
|
||
}
|
||
}
|
||
}
|
||
grantControls = @{
|
||
builtInControls = @('mfa')
|
||
operator = 'OR'
|
||
}
|
||
}
|
||
return $policy
|
||
}
|
||
|
||
function New-PolicySessionControls {
|
||
param(
|
||
[int]$TimeoutHours = 0,
|
||
[switch]$DisablePersistent,
|
||
[switch]$ExemptTrustedLocations
|
||
)
|
||
$sessionControls = @{}
|
||
$parts = [System.Collections.Generic.List[string]]::new()
|
||
|
||
if ($TimeoutHours -gt 0) {
|
||
$sessionControls['signInFrequency'] = @{
|
||
value = $TimeoutHours
|
||
type = 'hours'
|
||
isEnabled = $true
|
||
}
|
||
$parts.Add("re-authenticate every $TimeoutHours hours")
|
||
}
|
||
|
||
if ($DisablePersistent) {
|
||
$sessionControls['persistentBrowser'] = @{
|
||
mode = 'never'
|
||
isEnabled = $true
|
||
}
|
||
$parts.Add('no persistent browser sessions')
|
||
}
|
||
|
||
$desc = 'Session controls: ' + ($parts -join '; ')
|
||
if ($ExemptTrustedLocations) {
|
||
$desc += ' (exempt when on trusted locations)'
|
||
}
|
||
|
||
$controlTag = if ($TimeoutHours -gt 0 -and $DisablePersistent) {
|
||
'SessionControls'
|
||
} elseif ($TimeoutHours -gt 0) {
|
||
'SignInFrequency'
|
||
} else {
|
||
'NoPersistentBrowser'
|
||
}
|
||
|
||
$name = if ($UseDescriptiveNames) {
|
||
if ($TimeoutHours -gt 0 -and $DisablePersistent) {
|
||
Get-DescriptivePolicyName 'Session-Timeout-and-No-Persistent-Browser'
|
||
} elseif ($TimeoutHours -gt 0) {
|
||
Get-DescriptivePolicyName "Session-Timeout-${TimeoutHours}h"
|
||
} else {
|
||
Get-DescriptivePolicyName 'No-Persistent-Browser'
|
||
}
|
||
} else {
|
||
Get-StructuredPolicyName -Category User -Target AllUsers -AppResource AllApps -Control $controlTag
|
||
}
|
||
|
||
$conditions = @{
|
||
applications = @{ includeApplications = @('All') }
|
||
users = @{ includeUsers = @('All') }
|
||
}
|
||
|
||
if ($ExemptTrustedLocations) {
|
||
$conditions['locations'] = @{
|
||
excludeLocations = @('AllTrusted')
|
||
}
|
||
}
|
||
|
||
$policy = @{
|
||
name = $name
|
||
description = $desc
|
||
state = Get-DefaultState
|
||
conditions = $conditions
|
||
grantControls = @{
|
||
builtInControls = @('mfa')
|
||
operator = 'OR'
|
||
}
|
||
}
|
||
|
||
if ($sessionControls.Count -gt 0) {
|
||
$policy['sessionControls'] = $sessionControls
|
||
}
|
||
|
||
return $policy
|
||
}
|
||
|
||
function New-PolicyBlockHighRisk {
|
||
$policy = @{
|
||
name = if ($UseDescriptiveNames) { Get-DescriptivePolicyName 'Block-High-Risk-SignIns' } else { Get-StructuredPolicyName -Category Threat -Target AllUsers -AppResource AllApps -Control BlockHighRisk }
|
||
description = 'Block sign-ins with medium or high risk score (requires Entra ID P2)'
|
||
state = Get-DefaultState
|
||
conditions = @{
|
||
applications = @{ includeApplications = @('All') }
|
||
users = @{ includeUsers = @('All') }
|
||
signInRiskLevels = @('medium', 'high')
|
||
}
|
||
grantControls = @{
|
||
builtInControls = @('block')
|
||
operator = 'OR'
|
||
}
|
||
}
|
||
return $policy
|
||
}
|
||
|
||
function New-PolicyPhishingResistantMFAAdmins {
|
||
$policy = @{
|
||
name = if ($UseDescriptiveNames) { Get-DescriptivePolicyName 'Require-PhishingResistant-MFA-Admins' } else { Get-StructuredPolicyName -Category Admin -Target Admins -AppResource AllApps -Control RequirePhishingResistantMFA }
|
||
description = 'Require phishing-resistant MFA (FIDO2, certificate) for administrative roles'
|
||
state = Get-DefaultState
|
||
conditions = @{
|
||
applications = @{ includeApplications = @('All') }
|
||
users = @{ includeRoles = $script:AdminRoles }
|
||
}
|
||
grantControls = @{
|
||
builtInControls = @('authenticationStrength')
|
||
authenticationStrength = @{ id = '00000000-0000-0000-0000-000000000004' }
|
||
operator = 'OR'
|
||
}
|
||
}
|
||
return $policy
|
||
}
|
||
|
||
function New-PolicyBlockDeviceCodeFlow {
|
||
$policy = @{
|
||
name = if ($UseDescriptiveNames) { Get-DescriptivePolicyName 'Block-Device-Code-Flow' } else { Get-StructuredPolicyName -Category Threat -Target AllUsers -AppResource AllApps -Control BlockDeviceCodeFlow }
|
||
description = 'Block sign-ins using the device code authentication flow'
|
||
state = Get-DefaultState
|
||
conditions = @{
|
||
applications = @{ includeApplications = @('All') }
|
||
users = @{ includeUsers = @('All') }
|
||
authenticationFlows = @{
|
||
deviceCodeFlow = @{ isEnabled = $true }
|
||
}
|
||
}
|
||
grantControls = @{
|
||
builtInControls = @('block')
|
||
operator = 'OR'
|
||
}
|
||
}
|
||
return $policy
|
||
}
|
||
|
||
function New-PolicyRequireManagedDeviceAllUsers {
|
||
$policy = @{
|
||
name = if ($UseDescriptiveNames) { Get-DescriptivePolicyName 'Require-Managed-Device-All-Users' } else { Get-StructuredPolicyName -Category User -Target AllUsers -AppResource AllApps -Control RequireCompliantDevice }
|
||
description = 'Require all users to use compliant or hybrid-joined devices'
|
||
state = Get-DefaultState
|
||
conditions = @{
|
||
applications = @{ includeApplications = @('All') }
|
||
users = @{ includeUsers = @('All') }
|
||
}
|
||
grantControls = @{
|
||
builtInControls = @('compliantDevice', 'domainJoinedDevice')
|
||
operator = 'OR'
|
||
}
|
||
}
|
||
return $policy
|
||
}
|
||
|
||
# =====================================================================
|
||
# Build the policy list based on parameters
|
||
# =====================================================================
|
||
$policies = [System.Collections.Generic.List[hashtable]]::new()
|
||
|
||
if ($BlockLegacyAuth) { $policies.Add((New-PolicyBlockLegacyAuth)) }
|
||
if ($RequireMFAForAllUsers) { $policies.Add((New-PolicyRequireMFAAllUsers)) }
|
||
if ($RequireMFAForAdmins) { $policies.Add((New-PolicyRequireMFAAdmins)) }
|
||
if ($RequireMFAForAdminPortals) { $policies.Add((New-PolicyRequireMFAAdminPortals)) }
|
||
if ($BlockHighRiskSignIns) { $policies.Add((New-PolicyBlockHighRisk)) }
|
||
if ($BlockDeviceCodeFlow) { $policies.Add((New-PolicyBlockDeviceCodeFlow)) }
|
||
if ($RequirePhishingResistantMFAForAdmins) { $policies.Add((New-PolicyPhishingResistantMFAAdmins)) }
|
||
if ($RequireManagedDeviceForAllUsers) { $policies.Add((New-PolicyRequireManagedDeviceAllUsers)) }
|
||
|
||
switch ($RequireTrustedLocations) {
|
||
'AllUsers' { $policies.Add((New-PolicyTrustedLocations)) }
|
||
'Admins' { $policies.Add((New-PolicyTrustedLocations -ForAdmins)) }
|
||
'All' { $policies.Add((New-PolicyTrustedLocations)); $policies.Add((New-PolicyTrustedLocations -ForAdmins)) }
|
||
}
|
||
|
||
switch ($AdminDeviceCompliance) {
|
||
'Required' { $policies.Add((New-PolicyAdminDeviceCompliance)) }
|
||
'RequiredWithMFA' { $policies.Add((New-PolicyAdminDeviceCompliance -WithMFA)) }
|
||
}
|
||
|
||
if ($GuestMFA) { $policies.Add((New-PolicyGuestMFA)) }
|
||
|
||
if ($SessionTimeoutHours -gt 0 -or $DisablePersistentBrowser) {
|
||
$policies.Add((New-PolicySessionControls `
|
||
-TimeoutHours $SessionTimeoutHours `
|
||
-DisablePersistent:$DisablePersistentBrowser `
|
||
-ExemptTrustedLocations:$TrustedLocationsExemptFromReauth))
|
||
}
|
||
|
||
if ($policies.Count -eq 0) {
|
||
throw "No policies requested. Specify at least one requirement parameter (e.g. -RequireMFAForAllUsers, -BlockLegacyAuth, etc.)."
|
||
}
|
||
|
||
# =====================================================================
|
||
# Serialize to YAML (requires powershell-yaml)
|
||
# =====================================================================
|
||
|
||
function Test-YamlModule {
|
||
return [bool](Get-Module -ListAvailable -Name powershell-yaml)
|
||
}
|
||
|
||
if (-not (Test-YamlModule)) {
|
||
Write-Host "powershell-yaml module is required but not installed." -ForegroundColor Yellow
|
||
$confirm = Read-Host "Install powershell-yaml from PSGallery now? [Y/n]"
|
||
if ($confirm -match "^\s*n") {
|
||
throw "powershell-yaml is required. Install it with: Install-Module powershell-yaml -Scope CurrentUser -Force"
|
||
}
|
||
Install-Module powershell-yaml -Scope CurrentUser -Force
|
||
}
|
||
Import-Module powershell-yaml -Force
|
||
|
||
# Build the root document
|
||
$yamlRoot = [ordered]@{
|
||
baseline = [ordered]@{
|
||
name = 'Generated-ConditionalAccess-Baseline'
|
||
conflictResolution = 'Skip'
|
||
whatIf = $false
|
||
tenantConfig = [ordered]@{
|
||
conditionalAccess = [ordered]@{
|
||
reportOnly = $true
|
||
breakGlassGroup = $BreakGlassGroup
|
||
policies = $policies
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
$yamlText = ConvertTo-Yaml -Data $yamlRoot
|
||
|
||
# Ensure output directory exists
|
||
$outDir = Split-Path -Parent $OutputPath
|
||
if ($outDir -and -not (Test-Path $outDir)) {
|
||
New-Item -ItemType Directory -Path $outDir -Force | Out-Null
|
||
}
|
||
|
||
$yamlText | Set-Content -Path $OutputPath -Encoding UTF8 -Force
|
||
|
||
Write-Host "Generated Conditional Access baseline with $($policies.Count) policies." -ForegroundColor Green
|
||
Write-Host "Output written to: $(Resolve-Path $OutputPath)" -ForegroundColor Green
|
||
Write-Host ""
|
||
Write-Host "Review the file, then deploy with:" -ForegroundColor Cyan
|
||
Write-Host " ./Scripts/Deploy-CISM365Baseline.ps1 -BaselinePath '$OutputPath' -Mode Assess" -ForegroundColor Yellow
|
||
Write-Host " ./Scripts/Deploy-CISM365Baseline.ps1 -BaselinePath '$OutputPath' -Mode Deploy -Apply" -ForegroundColor Yellow
|