Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bugfix] GR13 V3 SignIn Logs logic with consideration of retention period and misc. error handling update #315

Merged
merged 11 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified psmodules/Monitor-BreakGlassAccount.zip
Binary file not shown.
2 changes: 1 addition & 1 deletion setup/IaC/modules/automationaccount.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ resource guardrailsAC 'Microsoft.Automation/automationAccounts@2021-06-22' = if
properties: {
contentLink: {
uri: '${ModuleBaseURL}/Monitor-BreakGlassAccount.zip'
version: '1.0.0'
version: '1.0.1'
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion setup/modules.json
Original file line number Diff line number Diff line change
Expand Up @@ -1195,7 +1195,7 @@
"Status": "Enabled",
"Required": "True",
"Profiles": [2, 3, 4, 5, 6],
"Script": "Test-BreakGlassAccounts -ControlName $msgTable.CtrName13 -ItemName $msgTable.bgAccountTesting -FirstBreakGlassUPN $vars.FirstBreakGlassUPN -SecondBreakGlassUPN $vars.SecondBreakGlassUPN -MsgTable $msgTable -ReportTime $ReportTime -itsgcode $vars.itsgcode -CloudUsageProfiles $cloudUsageProfilesString -ModuleProfiles $ModuleProfilesString",
"Script": "Test-BreakGlassAccounts -ControlName $msgTable.CtrName13 -ItemName $msgTable.bgAccountTesting -FirstBreakGlassUPN $vars.FirstBreakGlassUPN -SecondBreakGlassUPN $vars.SecondBreakGlassUPN -LAWResourceId $vars.LAWResourceId -MsgTable $msgTable -ReportTime $ReportTime -itsgcode $vars.itsgcode -CloudUsageProfiles $cloudUsageProfilesString -ModuleProfiles $ModuleProfilesString",
"secrets": [
{
"Name": "FirstBreakGlassUPN",
Expand All @@ -1206,6 +1206,12 @@
"Value": "BGA2"
}
],
"variables": [
{
"Name": "LAWResourceId",
"Value": "SecurityLAWResourceId"
}
],
"localVariables": [
{
"Name": "itsgcode",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
RootModule = 'Monitor-BreakGlassAccount'

# Version number of this module.
ModuleVersion = '1.0.0'
ModuleVersion = '1.0.1'

# Supported PSEditions
# CompatiblePSEditions = @()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,72 +17,76 @@ The solution will ensures that Break Glass accounts remain active and secure by
function Test-BreakGlassAccounts {

param (
[string] $ControlName,
[string] $ItemName,
[string] $FirstBreakGlassUPN,
[string] $SecondBreakGlassUPN,
[Parameter(Mandatory=$true)]
[string] $LAWResourceId,
[hashtable] $msgTable,
[string] $itsgcode,
[string] $ControlName,
[string] $ItemName,
[Parameter(Mandatory=$true)]
[string]
$ReportTime,
[string]
$CloudUsageProfiles = "3", # Passed as a string
[string] $ReportTime,
[string] $CloudUsageProfiles = "3", # Passed as a string
[string] $ModuleProfiles, # Passed as a string
[switch] $EnableMultiCloudProfiles # New feature flag, default to false
)

[bool] $IsCompliant = $false
$commentsArray = @()
[PSCustomObject] $ErrorList = New-Object System.Collections.ArrayList

[String] $FirstBreakGlassUPNUrl = $("/users/" + $FirstBreakGlassUPN + "?$" + "select=userPrincipalName,id,userType")
[String] $SecondBreakGlassUPNUrl = $("/users/" + $SecondBreakGlassUPN + "?$" + "select=userPrincipalName,id,userType")

$bgCountConfig = 0
if ($FirstBreakGlassUPN -ne ""){$bgCountConfig += 1}
if ($SecondBreakGlassUPN -ne ""){$bgCountConfig += 1}

# Validate two BG accounts exist
if($FirstBreakGlassUPN -eq "" -or $SecondBreakGlassUPN -eq ""){
# Validate at least one unique BG accounts exist in config.json
if($FirstBreakGlassUPN -eq "" -and $SecondBreakGlassUPN -eq ""){
$IsCompliant = $false
$PsObject = [PSCustomObject]@{
ComplianceStatus = $IsCompliant
ControlName = $ControlName
ItemName = $ItemName
Comments = $msgTable.isNotCompliant + " " + $msgTable.bgAccountNotExist
ReportTime = $ReportTime
itsgcode = $itsgcode
itsgcode = $itsgcode
}
}
elseif(($FirstBreakGlassUPN -ne "" -or $SecondBreakGlassUPN -ne "") -and $FirstBreakGlassUPN -eq $SecondBreakGlassUPN){
$IsCompliant = $false
$PsObject = [PSCustomObject]@{
ComplianceStatus = $IsCompliant
ControlName = $ControlName
ItemName = $ItemName
Comments = $msgTable.isNotCompliant + " " + $msgTable.bgAccountNotExist
ReportTime = $ReportTime
itsgcode = $itsgcode
}
elseif (($bgCountConfig -eq 2) -and $FirstBreakGlassUPN -eq $SecondBreakGlassUPN){
$IsCompliant = $false
$PsObject = [PSCustomObject]@{
ComplianceStatus = $IsCompliant
ControlName = $ControlName
ItemName = $ItemName
Comments = $msgTable.isNotCompliant + " " + $msgTable.bgAccountNotExist
ReportTime = $ReportTime
itsgcode = $itsgcode
}
}
else{
# Validate listed BG accounts as members
# Step 1: Validate listed BG accounts as members
$FirstBreakGlassAcct = [PSCustomObject]@{
UserPrincipalName = $FirstBreakGlassUPN
apiUrl = $FirstBreakGlassUPNUrl
ComplianceStatus = $false
existStatus = $false
}
$SecondBreakGlassAcct = [PSCustomObject]@{
UserPrincipalName = $SecondBreakGlassUPN
apiUrl = $SecondBreakGlassUPNUrl
ComplianceStatus = $false
existStatus = $false
}

# get 1st break glass account
try {
$urlPath = $FirstBreakGlassAcct.apiUrl
$response = Invoke-GraphQuery -urlPath $urlPath -ErrorAction Stop

$data = $response.Content

if ($data.userType -eq "Member") {
$FirstBreakGlassAcct.ComplianceStatus = $true
if ($null -ne $data) {
$FirstBreakGlassAcct.existStatus = $true
}
}
catch {
Expand All @@ -97,83 +101,147 @@ function Test-BreakGlassAccounts {

$data = $response.Content

if ($data.userType -eq "Member") {
$SecondBreakGlassAcct.ComplianceStatus = $true
if ($null -ne $data) {
$SecondBreakGlassAcct.existStatus = $true
}
}
catch {
$ErrorList.Add("Failed to call Microsoft Graph REST API at URL '$urlPath'; returned error message: $_")
Write-Warning "Error: Failed to call Microsoft Graph REST API at URL '$urlPath'; returned error message: $_"
}

# compliance status
$IsCompliant = $FirstBreakGlassAcct.ComplianceStatus -and $SecondBreakGlassAcct.ComplianceStatus
Write-Host "step 1 validate listed BG accounts compliance status: $IsCompliant"
if ($bgCountConfig -eq 2){
$validBG = $FirstBreakGlassAcct.existStatus -and $SecondBreakGlassAcct.existStatus
}
else {
$validBG = $FirstBreakGlassAcct.existStatus -or $SecondBreakGlassAcct.existStatus
}

Write-Host "step 1 validate listed BG accounts compliance status: $validBG"
# if not compliant
if(-not $IsCompliant){
if(-not $validBG){
$PsObject = [PSCustomObject]@{
ComplianceStatus = $IsCompliant
ComplianceStatus = $validBG
ControlName = $ControlName
ItemName = $ItemName
Comments = $msgTable.isNotCompliant + " " + $msgTable.bgAccountNotExist
ReportTime = $ReportTime
itsgcode = $itsgcode
}
}
else{
# Validate BG account Sign-in activity
$IsSigninCompliant = $false
$oneYearAgo = (Get-Date).AddYears(-1)
else {
# Step 2: Validate BG account Sign-in activity
# Parse LAW Resource ID
$lawParts = $LAWResourceId -split '/'
$subscriptionId = $lawParts[2]
$resourceGroupName = $lawParts[4]
$workspaceId = $lawParts[8]

$urlPath = "/auditLogs/signIns"
# get context
try{
Select-AzSubscription -Subscription $subscriptionId -ErrorAction Stop | Out-Null
}
catch {
$ErrorList.Add("Failed to execute the 'Select-AzSubscription' command with subscription ID '$($subscription)'--`
ensure you have permissions to the subscription, the ID is correct, and that it exists in this tenant; returned `
error message: $_")
throw "Error: Failed to execute the 'Select-AzSubscription' command with subscription ID '$($subscription)'--ensure `
you have permissions to the subscription, the ID is correct, and that it exists in this tenant; returned error message: $_"
}

# Validate signIn log is enabled
try {
$response = Invoke-GraphQuery -urlPath $urlPath -ErrorAction Stop
Write-Host "step 2 validate BG account Sign-in $($response.Content.Value.Count)"
# the log name to validate
$SignInLogs = @('SignInLogs')

# check 1st break glass account signin
$firstBGdata = $response.Content.Value | Where-Object {$_.userPrincipalName -eq $FirstBreakGlassUPN}
$dataMostRecentSignInFirstBG = $firstBGdata | Sort-Object createdDateTime -Descending | Select-Object -First 1

$dataSignInFirstBG = $dataMostRecentSignInFirstBG | Select-Object id, userDisplayName, userPrincipalName, createdDateTime, userId
$firstBGisWithinLastYear = $dataSignInFirstBG.createdDateTime -ge $oneYearAgo
# Retrieve diagnostic settings to check for logs
$diagnosticSettings = get-AADDiagnosticSettings
$matchingSetting = $diagnosticSettings | Where-Object { $_.properties.workspaceId -eq $LAWResourceId } | Select-Object -First 1

Write-Host "step 2 firstBGisWithinLastYear: $firstBGisWithinLastYear"

# check 2nd break glass account signin
$secondBGdata = $response.Content.Value | Where-Object {$_.userPrincipalName -eq $SecondBreakGlassUPN}
$dataMostRecentSignInSecondBG = $secondBGdata | Sort-Object createdDateTime -Descending | Select-Object -First 1

$dataSignInSecondBG = $dataMostRecentSignInSecondBG | Select-Object id, userDisplayName, userPrincipalName, createdDateTime, userId
$secondBGisWithinLastYear = $dataSignInSecondBG.createdDateTime -ge $oneYearAgo
Write-Host "step 2 secondBGisWithinLastYear: $secondBGisWithinLastYear"
if($matchingSetting){
$enabledLogs = $matchingSetting.properties.logs | Where-Object { $_.enabled -eq $true } | Select-Object -ExpandProperty category
$missingSignInLogs = $SignInLogs | Where-Object { $_ -notin $enabledLogs }
}
else{
$missingSignInLogs = $SignInLogs
}

# Check missing logs for SignInLogs, if missing/not enabled, non-compliant
if ($missingSignInLogs.Count -gt 0) {
$IsCompliant = $false
$commentsArray += $msgTable.isNotCompliant + " " + $msgTable.signInlogsNotCollected
}
}
catch {
$ErrorList.Add("Failed to call Microsoft Graph REST API at URL '$urlPath'; returned error message: $_")
Write-Warning "Error: Failed to call Microsoft Graph REST API at URL '$urlPath'; returned error message: $_"
# catch exceptions
if ($_.Exception.Message -like "*ResourceNotFound*") {
$IsCompliant = $false
$commentsArray += $msgTable.nonCompliantLaw -f $workspaceId
$ErrorList += "Log Analytics Workspace not found: $_"
}
else {
$IsCompliant = $false
$ErrorList += "Error accessing Log Analytics Workspace: $_"
}
}
}

# Retrieve the log data and check the data retention period for sign in
$kqlQuery = "SigninLogs
| where TimeGenerated > ago(365d)
| order by TimeGenerated desc"

try{
$workspace = Get-AzOperationalInsightsWorkspace -ResourceGroupName $resourceGroupName -Name $workspaceId
$queryResults = Invoke-AzOperationalInsightsQuery -WorkspaceId $workspace.CustomerId -Query $kqlQuery

$BGdata = $queryResults.Results | Where-Object {$_.UserPrincipalName -eq $FirstBreakGlassUPN -or $_.UserPrincipalName -eq $SecondBreakGlassUPN}

# check break glass account signin
$dataMostRecentSignInFirstBG = $BGdata | Where-Object {$_.UserPrincipalName -eq $FirstBreakGlassUPN} | Sort-Object TimeGenerated -Descending
$dataMostRecentSignInSecondBG = $BGdata | Where-Object {$_.UserPrincipalName -eq $SecondBreakGlassUPN} | Sort-Object createdDateTime -Descending

if ($null -ne $dataMostRecentSignInFirstBG -and $null -ne $dataMostRecentSignInSecondBG ){
$IsCompliant = $true
}

}
catch {
if ($null -eq $workspace) {
$IsCompliant = $false
$commentsArray += "Workspace not found in the specified resource group"
$ErrorList += "Workspace not found in the specified resource group: $_"
}
if($_.Exception.Message -like "*ResourceNotFound*"){

$IsSigninCompliant = $firstBGisWithinLastYear -and $secondBGisWithinLastYear
if($IsSigninCompliant){
$PsObject = [PSCustomObject]@{
ComplianceStatus = $IsCompliant
ControlName = $ControlName
ItemName = $ItemName
Comments = $msgTable.isCompliant
ReportTime = $ReportTime
itsgcode = $itsgcode
}
}
else{
$PsObject = [PSCustomObject]@{
ComplianceStatus = $IsSigninCompliant
ControlName = $ControlName
ItemName = $ItemName
Comments = $msgTable.isNotCompliant + " " + $msgTable.bgAccountLoginNotValid
ReportTime = $ReportTime
itsgcode = $itsgcode
}
# Handle errors and exceptions
$IsCompliant = $false
Write-Host "Error occurred retrieving the sign-in log data: $_"
}

}


if($IsCompliant){
$commentsArray = $msgTable.isCompliant + " " + $msgTable.bgAccountLoginValid
}
else {
$commentsArray = $msgTable.isNotCompliant + " " + $msgTable.bgAccountLoginNotValid
}

$Comments = $commentsArray -join ";"

$PsObject = [PSCustomObject]@{
ComplianceStatus = $IsCompliant
ControlName = $ControlName
ItemName = $ItemName
Comments = $Comments
ReportTime = $ReportTime
itsgcode = $itsgcode
}

}

# Conditionally add the Profile field based on the feature flag
Expand All @@ -195,7 +263,7 @@ function Test-BreakGlassAccounts {

$moduleOutput= [PSCustomObject]@{
ComplianceResults = $PsObject
Errors=$ErrorList
Errors = $ErrorList
AdditionalResults = $AdditionalResults
}
return $moduleOutput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ bgValidSignature = Valid Signatures and Approvals for Break Glass Account Proced
bgAccountTesting = Break Glass Account Testing Cadence
bgAccountNotExist = One or both of the Break Glass Account User Principal Names (UPNs) provided do not exist in the environment. Review the provided Break Glass Account UPNs for accuracy.
bgAccountLoginNotValid = Last login for the provided Break Glass Accounts is greater than a year. Ensure regular testing of the Break Glass Account procedure and login process.
bgAccountLoginValid = Last login for the provided Break Glass Accounts is within a year. Ensure regular testing of the Break Glass Account procedure and login process.

# GR-Common
procedureFileFound = Compliant. Required file has been uploaded for review by Cloud Security Compliance assessors. '{0}' found.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ bgValidSignature = Signatures et approbations valides pour la procédure de comp
bgAccountTesting = Cadence des tests des comptes de bris de verre
bgAccountNotExist = Un ou les deux noms d'utilisateur principal (UPN) du compte de bris de verre fournis n'existent pas dans l'environnement. Vérifiez l'exactitude des UPN du compte de bris de verre fournis.
bgAccountLoginNotValid = La dernière connexion aux comptes de bris de verre fournis est plus qu'un an. Assurez-vous d'effectuer des tests réguliers de la procédure du compte de bris de verre et du processus de connexion.
bgAccountLoginValid = La dernière connexion pour les comptes de bris de verre est moins d'un an. Assurez-vous d'effectuer des tests réguliers de la procédure de bris de verre et du processus de connexion.

# GR-Common
procedureFileFound = Conforme. Le fichier requis a été téléchargé pour examen par les évaluateurs de Conformité à la sécurité infonuagique. « {0} » trouvé.
Expand Down
Loading
Loading