cisagov / ScubaGear

Automation to assess the state of your M365 tenant against CISA's baselines
https://www.cisa.gov/resources-tools/services/secure-cloud-business-applications-scuba-project
Creative Commons Zero v1.0 Universal
1.6k stars 215 forks source link

Fix error when tenant has no licenses #1145

Open Jeff-Jerousek opened 3 months ago

Jeff-Jerousek commented 3 months ago

๐Ÿ—ฃ Description

Running on a new tenant with no licenses in use Get-MgBetaSubscribedSku will return with a body of [].

๐Ÿ’ญ Motivation and context

$LicenseInfo was not verifying $SubscribedSku had data before creating the variable that will be used in the report, resulting in a JSON primitive error because the JSON is "license_information": ,

๐Ÿงช Testing

โœ… Pre-approval checklist

โœ… Pre-merge checklist

โœ… Post-merge checklist

schrolla commented 3 months ago

May conflict with other MS Graph related updates and needs to be reviewed against them before merging.

tkol2022 commented 1 month ago

@Jeff-Jerousek I wanted to thank you for this submission and your code. It is very helpful and we have been aware of the problem internally but haven't gotten the chance to address it yet. There is a previous issue #979 related to this although that issue doesn't really contain all the important information about the underlying topic, which is, what should ScubaGear do when it cannot locate any subscribed SKUs and/or service plans? Look at my newer comments for more info.

tkol2022 commented 1 month ago

@Jeff-Jerousek @gdasher @schrolla I prototyped the solution provided in Grant's comment which was as follows:

$LicenseInfo = ConvertTo-Json -Depth 3 @($SubscribedSku | Select-Object -Property Sku*, ConsumedUnits, PrepaidUnits)

I simulated the condition where $SubscribedSku could be in either of the states below: State 1: $SubscribedSku = @()

State 2: $SubscribedSku = $null

This solves the problem of the licensing report crashing. Instead the AAD provider executes and the user sees an empty licensing table the report. No errors are observed at the command line.

image

However if $SubscribedSku is either null or empty, the AAD provider will produce error messages in section 7 of the report. This is because of the logic directly following the initialization of the $LicenseInfo variable and specifically the else statement.

image

image

I think this means that we need to address the broader question of how do we handle the scenario when ScubaGear cannot locate any subscribed SKUs and/or service plans instead of just producing errors in section 7 of the report?

At a high level, I think we can still execute the AAD provider for the policies in section 7 (there are a few of them) that do not depend on the AAD_PREMIUM_P2 license. Below are some suggested code changes that I prototyped which will do that. My code includes your update to the way the $LicenseInfo is created. Take a look and if you think this is feasible I will talk to Addam on the best approach to get this change incorporated.

$LicenseInfo = ConvertTo-Json -Depth 3 @($SubscribedSku | Select-Object -Property Sku*, ConsumedUnits, PrepaidUnits)

# I commented the outer "if/else" statement below that was examining $ServicePlans.

# if ($ServicePlans) {
    # ********** The next three lines are NEW code.
    $RequiredServicePlan = $null
    if ($ServicePlans) {
        $RequiredServicePlan = $ServicePlans | Where-Object -Property ServicePlanName -eq -Value "AAD_PREMIUM_P2"
    }

    if ($RequiredServicePlan) {
        # If the tenant has the premium license then we want to also include PIM Eligible role assignments - otherwise we don't to avoid an API error
        $PrivilegedUsers = $Tracker.TryCommand("Get-PrivilegedUser", @{"TenantHasPremiumLicense"=$true; "M365Environment"=$M365Environment})
    }
    else{
        $PrivilegedUsers = $Tracker.TryCommand("Get-PrivilegedUser", @{"TenantHasPremiumLicense"=$false; "M365Environment"=$M365Environment})
    }
    $PrivilegedUsers = $PrivilegedUsers | ConvertTo-Json
    $PrivilegedUsers = if ($null -eq $PrivilegedUsers) {"{}"} else {$PrivilegedUsers}

    if ($RequiredServicePlan){
        # If the tenant has the premium license then we want to also include PIM Eligible role assignments - otherwise we don't to avoid an API error
        $PrivilegedRoles = $Tracker.TryCommand("Get-PrivilegedRole", @{"TenantHasPremiumLicense"=$true; "M365Environment"=$M365Environment})
    }
    else {
        $PrivilegedRoles = $Tracker.TryCommand("Get-PrivilegedRole", @{"TenantHasPremiumLicense"=$false; "M365Environment"=$M365Environment})
    }
    $PrivilegedRoles = ConvertTo-Json -Depth 10 @($PrivilegedRoles) # Depth required to get policy rule object details
# }
# else {
#     Write-Warning "Omitting calls to Get-PrivilegedRole and Get-PrivilegedUser."
#     $PrivilegedUsers = ConvertTo-Json @()
#     $PrivilegedRoles = ConvertTo-Json @()
#     $Tracker.AddUnSuccessfulCommand("Get-PrivilegedRole")
#     $Tracker.AddUnSuccessfulCommand("Get-PrivilegedUser")
# }

So if these changes are made, the AAD provider will still run the Get-PrivilegedRole and Get-PrivilegedUser and ensure that the first three policies in section 7 are evaluated instead of producing an error message in the report. All policies in the baseline (including the ones in section 2) that depend on the AAD_PREMIUM_P2 license will behave in the same manner that they behave today when we don't find that license, which is to produce a Fail in the report along with the message "NOTE: Your tenant does not have a Microsoft Entra ID P2 license, which is required for this feature".

image