Private/ConvertTo-NSPConditionalAccessCidr.ps1

function ConvertTo-NSPConditionalAccessCidr {
    <#
    .SYNOPSIS
        Validates an IP range typed by an operator and returns it as CIDR, with its Graph range type and any concerns.
    .DESCRIPTION
        A bare address becomes a single-address range (/32 or /128). Throws on anything that is
        not an IPv4 or IPv6 address with an optional prefix length. Concerns are returned, not
        thrown: a private or reserved address (Entra only ever sees the public address a sign-in
        comes from), and a very broad range (IPv4 /16 or wider, IPv6 /32 or wider).
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$Range
    )

    $text = $Range.Trim()
    $address, $prefix = $text -split '/', 2
    $ip = $null
    if (-not [System.Net.IPAddress]::TryParse($address, [ref]$ip)) { throw "'$Range' is not an IP address or CIDR range (for example 203.0.113.10 or 203.0.113.0/24)." }
    $v6 = $ip.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetworkV6
    $max = if ($v6) { 128 } else { 32 }
    $length = $max
    if ($null -ne $prefix -and $prefix -ne '') {
        if (-not [int]::TryParse($prefix, [ref]$length) -or $length -lt 1 -or $length -gt $max) { throw "'$Range' has an invalid prefix length; use 1 to $max." }
    }

    $concerns = [System.Collections.Generic.List[string]]::new()
    if (-not $v6) {
        $bytes = $ip.GetAddressBytes()
        $private = $bytes[0] -eq 10 -or $bytes[0] -eq 127 -or ($bytes[0] -eq 172 -and $bytes[1] -ge 16 -and $bytes[1] -le 31) -or
            ($bytes[0] -eq 192 -and $bytes[1] -eq 168) -or ($bytes[0] -eq 169 -and $bytes[1] -eq 254) -or ($bytes[0] -eq 100 -and $bytes[1] -ge 64 -and $bytes[1] -le 127)
        if ($private) { $concerns.Add("$text is a private or reserved address. Entra sees the office's public address, not this one.") }
        if ($length -le 16) { $concerns.Add("$text is very broad ($([math]::Pow(2, 32 - $length)) addresses). Trust only the office's own public addresses.") }
    } else {
        if ($text -match '^(?i)(fe80|fc|fd|::1)') { $concerns.Add("$text is a link-local or private IPv6 address. Entra sees the public address.") }
        if ($length -le 32) { $concerns.Add("$text is a very broad IPv6 range. Trust only the office's own prefix.") }
    }

    [pscustomobject]@{
        Cidr = "$($ip.ToString())/$length"
        Type = if ($v6) { '#microsoft.graph.iPv6CidrRange' } else { '#microsoft.graph.iPv4CidrRange' }
        Concerns = @($concerns)
    }
}