release: v4.1.0 — restructure entry points, add CIS baselines, reporting tools and fzf hints
- 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
This commit is contained in:
@@ -0,0 +1,682 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user