Skip to content

Commit

Permalink
Created GA control
Browse files Browse the repository at this point in the history
  • Loading branch information
alalvi00 committed Dec 18, 2023
1 parent 182c8d6 commit 8c87d8b
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 0 deletions.
28 changes: 28 additions & 0 deletions setup/modules.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,34 @@
}
]
},
{
"ModuleName": "Check-GlobalAdministratorAccntsMFA",
"Control": "Guardrails1",
"ModuleType": "Builtin",
"Status": "Enabled",
"Required": "True",
"Script": "Check-GAAuthenticationMethods -StorageAccountName $vars.storageaccountname -ContainerName $vars.containerName -ResourceGroupName $ResourceGroupName -SubscriptionID $SubID -DocumentName $vars.DocumentName -ControlName $msgTable.CtrName1 -ItemName $msgTable.gaAccntsMFACheck -MsgTable $msgTable -ReportTime $ReportTime -itsgcode $vars.itsgcode",
"variables": [
{
"Name": "storageAccountName",
"Value": "StorageAccountName"
},
{
"Name": "containerName",
"Value": "ContainerName"
}
],
"localVariables": [
{
"Name": "DocumentName",
"Value": "GlobalAdministratorsUPN.txt"
},
{
"Name": "itsgcode",
"Value": "IA2(1)"
}
]
},
{
"ModuleName": "Check-DocumentExistsInStorage",
"Control": "Guardrails1",
Expand Down
9 changes: 9 additions & 0 deletions src/GuardRails-Localization/GR-ComplianceChecks-Msgs.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ bgValidLicenseAssigned = has a valid Microsoft Entra ID P2 assigned
bgAccountHasManager = BG Account {0} has a Manager
bgAccountNoManager = BG Account {0} doesn't have a Manager
bgBothHaveManager = Both BreakGlass accounts have manager
gaAccntsMFACheck = Global Administrators Accounts MFA check
# GuardRail #2
MSEntIDLicenseTypeFound = Found correct license type
Expand Down Expand Up @@ -183,5 +184,13 @@ enableMktPlace = Enable Azure Private MarketPlace as per: https://docs.microsoft
# GR-Common
procedureFileFound = File {0} found in Container.
procedureFileNotFound = Could not find document for {0}, please create and upload a file with the name '{1}' in Container '{2}' on Storage Account '{3}' to confirm you have completed the Item in the control.
procedureFileDataInvalid = The global administrator file(s) contain(s) invalid User Principal Names (UPNs). Ensure that UPNs start with a hyphen, and type each of them on a new line.
globalAdminFileFound = File {0} found in Container.
globalAdminFileNotFound = Could not find document for {0}, please create and upload a file with the name '{1}' in Container '{2}' on Storage Account '{3}' to confirm you have completed the Item in the control.
globalAdminFileEmpty = Empty file {0} found in Container.
globalAdminNotExist = Global Administrator accounts not found or declared in file {0}.
globalAdminMFAPassAndMin2Accnts = Two or more global administrator accounts have been identified, and multi-factor authentication (MFA) is enabled for all of them.
globalAdminMinAccnts = There must be at least two global administrator accounts.
globalAdminAccntsMFADisabled = Not all the global administrator accounts have multi-factor authentication (MFA) enabled
'@
156 changes: 156 additions & 0 deletions src/Guardrails-Common/GR-Common.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,150 @@ function Add-LogAnalyticsResults {
-logType $LogType `
-TimeStampField Get-Date
}

function Check-GAAuthenticationMethods {
param (
[string] $StorageAccountName,
[string] $ContainerName,
[string] $ResourceGroupName,
[string] $SubscriptionID,
[string[]] $DocumentName,
[string] $ControlName,
[string]$ItemName,
[hashtable] $msgTable,
[string]$itsgcode,
[Parameter(Mandatory = $true)]
[string]
$ReportTime
)
[PSCustomObject] $ErrorList = New-Object System.Collections.ArrayList
[bool] $IsCompliant = $false
[string] $Comments = $null

try {
Select-AzSubscription -Subscription $SubscriptionID | out-null
}
catch{
$ErrorList.Add("Failed to run 'Select-Azsubscription' with error: $_")
throw "Error: Failed to run 'Select-Azsubscription' with error: $_"
}
try {
$StorageAccount = Get-Azstorageaccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName -ErrorAction Stop
}
catch {
$ErrorList.Add("Could not find storage account '$storageAccountName' in resoruce group '$resourceGroupName' of `
subscription '$subscriptionId'; verify that the storage account exists and that you have permissions to it. Error: $_")

Write-Error "Could not find storage account '$storageAccountName' in resoruce group '$resourceGroupName' of `
subscription '$subscriptionId'; verify that the storage account exists and that you have permissions to it. Error: $_"
}

$docFileEmpty = $false
$docFileNotAvailable = $false
$mfaEnabled = $false
$globalAdminFound = $false
$globalAdminCount = 0
$mfaCounter = 0
$commentsArray = @()
$globalAdminUPNs = @()

ForEach ($docName in $DocumentName) {

$blobs = Get-AzStorageBlob -Container $ContainerName -Context $StorageAccount.Context -Blob $docName -ErrorAction SilentlyContinue

If ($null -eq $blobs) {
# a blob with the name $DocumentName was located in the specified storage account
$commentsArray += $msgTable.procedureFileNotFound -f $ItemName, $docName, $ContainerName, $StorageAccountName
}
else {
# get blob content if blob exists
$blobContent = (Get-AzStorageBlobContent -Container $ContainerName -Blob $docName -Context $StorageAccount.Context -ErrorAction SilentlyContinue).ICloudBlob.DownloadText()

if ($blobContent -eq '' -or $blobContent -eq ' ') {
docFileEmpty = $true
$commentsArray += $msgTable.globalAdminFileEmpty -f $docName
}
elseif ($blobContent -eq 'N/A' -or $blobContent -eq 'n/a') {
docFileNotAvailable = $true
$commentsArray += $msgTable.globalAdminNotExist -f $docName
}
else {
$globalAdminFound = $true
$globalAdminUPNs = $blobContent -split '-' | Where-Object { $_ -ne '' }
}
}
}

if ($globalAdminFound) {

#Clean up the data and remove any invalid email formats
$filteredUPNs = Clean-GAData -GAUPNs $globalAdminUPNs

$globalAdminCount = $filteredUPNs.Count

ForEach ($globalAdminAccount in $filteredUPNs) {
$urlPath = '/users/' + $globalAdminAccount + '/authentication/methods'

try {
$response = Invoke-GraphQuery -urlPath $urlPath -ErrorAction Stop
}
catch {
$ErrorList.Add("Failed to call Microsoft Graph REST API at URL '$urlPath'; returned error message: $_" )
Write-Error "Error: Failed to call Microsoft Graph REST API at URL '$urlPath'; returned error message: $_"
}

$data = $response.Content
$authenticationmethods = $data.value

# To check if MFA is setup for a user, we're looking for either :
# #microsoft.graph.microsoftAuthenticatorAuthenticationMethod or
# #microsoft.graph.phoneAuthenticationMethod
Write-Host $authenticationmethods

foreach ($authmeth in $authenticationmethods) {
if (($($authmeth.'@odata.type') -eq "#microsoft.graph.phoneAuthenticationMethod") -or `
($($authmeth.'@odata.type') -eq "#microsoft.graph.microsoftAuthenticatorAuthenticationMethod")) {
$mfaCounter += 1 #Need to keep track of each GA mfa in counter and compare it to count
}
}
}
}

if($mfaCounter -eq $globalAdminCount) {
$mfaEnabled = $true
}
else{
$commentsArray += $msgTable.globalAdminAccntsMFADisabled
}

if ($globalAdminCount -lt 2 -and ($docFileEmpty -and $docFileNotAvailable)) {
$commentsArray += $msgTable.globalAdminMinAccnts
}

if ($globalAdminCount -ge 2 -and $mfaEnabled) {
$commentsArray += $msgTable.globalAdminMFAPassAndMin2Accnts
$IsCompliant = $mfaEnabled
}
$Comments = $commentsArray -join ";"

$PsObject = [PSCustomObject]@{
ComplianceStatus = $IsCompliant
ControlName = $ControlName
ItemName = $ItemName
DocumentName = $DocumentName
Comments = $Comments
ReportTime = $ReportTime
itsgcode = $itsgcode
}
$moduleOutput = [PSCustomObject]@{
ComplianceResults = $PsObject
Errors = $ErrorList
AdditionalResults = $AdditionalResults
}
return $moduleOutput

}

function Check-DocumentExistsInStorage {
[Alias('Check-DocumentsExistInStorage')]
param (
Expand Down Expand Up @@ -511,6 +655,18 @@ function Hide-Email {
}
}

function Clean-GAData {
param (
[string[]] $GAUPNs
)

$FilteredUPNs = $GAUPNs | Where-Object { $_ -match '\S' -and $_ -like "*@*" } | ForEach-Object { $_ -replace '\s' }

if ($FilteredUPNs) {
return $FilteredUPNs
}
}

function Invoke-GraphQuery {
param(
# URL path (ex: /users)
Expand Down

0 comments on commit 8c87d8b

Please sign in to comment.