Skip to content

Commit

Permalink
[New control] GR2 V3 Automated Role Reviews: Role Assignments for Use…
Browse files Browse the repository at this point in the history
…rs and Global Administrators (M) (#260)

* test

* update

* update ItemName

* update flow

* update

* update

* cleanup

* update

* update logic

* update

* required permission

* error handling update

* update FR messages
  • Loading branch information
dutt0 authored Nov 29, 2024
1 parent eacfea9 commit 034004c
Show file tree
Hide file tree
Showing 9 changed files with 331 additions and 5 deletions.
Binary file added psmodules/Check-UserRoleReviews.zip
Binary file not shown.
14 changes: 11 additions & 3 deletions setup/IaC/modules/automationaccount.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,7 @@ resource guardrailsAC 'Microsoft.Automation/automationAccounts@2021-06-22' = if
}
}
}

resource module33 'modules' = if (newDeployment || updatePSModules) {
resource module33 'modules' = if (newDeployment || updatePSModules) {
name: 'Check-OnlineAttackCountermeasures'
properties: {
contentLink: {
Expand Down Expand Up @@ -455,7 +454,16 @@ resource guardrailsAC 'Microsoft.Automation/automationAccounts@2021-06-22' = if
}
}

resource variable1 'variables' = if (newDeployment || updateCoreResources) {
resource module45 'modules' = if (newDeployment || updatePSModules) {
name: 'Check-UserRoleReviews'
properties: {
contentLink: {
uri: '${ModuleBaseURL}/Check-UserRoleReviews.zip'
version: '1.0.0'
}
}
}
resource variable1 'variables' = if (newDeployment || updateCoreResources) {
name: 'KeyvaultName'
properties: {
isEncrypted: true
Expand Down
15 changes: 15 additions & 0 deletions setup/modules.json
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,21 @@
}
]
},
{
"ModuleName": "Check-UserRoleReviews",
"Control": "Guardrails2",
"ModuleType": "Builtin",
"Status": "Enabled",
"Required": "True",
"Profiles": [1, 2, 3, 4, 5, 6],
"Script": "Check-UserRoleReviews -ControlName $msgTable.CtrName2 -ItemName $msgTable.automatedRoleForUsers -MsgTable $msgTable -ReportTime $ReportTime -itsgcode $vars.itsgcode -CloudUsageProfiles $cloudUsageProfilesString -ModuleProfiles $ModuleProfilesString",
"localVariables": [
{
"Name": "itsgcode",
"Value": "IA2(1)"
}
]
},
{
"ModuleName": "Check-CloudConsoleAccess",
"Control": "Guardrails3",
Expand Down
134 changes: 134 additions & 0 deletions src/GUARDRAIL 2 MANAGE ACCESS/Audit/Check-UserRoleReviews.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#
# Module manifest for module 'Check-UserRoleReviews'
#
# Generated by: Cloud Security Compliance Team
#
# Contact Information for module : cloudsecuritycompliance-conformiteinfonuagiquesecurise@ssc-spc.gc.ca
#
# Generated on: 2024-10-09
#

@{

# Script module or binary module file associated with this manifest.
RootModule = 'Check-UserRoleReviews'

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

# Supported PSEditions
# CompatiblePSEditions = @()

# ID used to uniquely identify this module
GUID = 'ac9cd941-f27d-40b7-9af0-4e5af9a9534b'

# Author of this module
Author = 'Cloud Security Compliance'

# Company or vendor of this module
CompanyName = 'Shared Services Canada'

# Copyright statement for this module
Copyright = ''

# Description of the functionality provided by this module
# Description = ''

# Minimum version of the PowerShell engine required by this module
# PowerShellVersion = ''

# Name of the PowerShell host required by this module
# PowerShellHostName = ''

# Minimum version of the PowerShell host required by this module
# PowerShellHostVersion = ''

# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = ''

# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# ClrVersion = ''

# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''

# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()

# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()

# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()

# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()

# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()

# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()

# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = '*'

# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = '*'

# Variables to export from this module
VariablesToExport = '*'

# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = '*'

# DSC resources to export from this module
# DscResourcesToExport = @()

# List of all modules packaged with this module
# ModuleList = @()

# List of all files packaged with this module
# FileList = @()

# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{

PSData = @{

# Tags applied to this module. These help with module discovery in online galleries.
Tags = 'GOC 30 days Guardrails'

# A URL to the license for this module.
# LicenseUri = ''

# A URL to the main website for this project.
# ProjectUri = ''

# A URL to an icon representing this module.
# IconUri = ''

# ReleaseNotes of this module
# ReleaseNotes = ''

# Prerelease string of this module
# Prerelease = ''

# Flag to indicate whether the module requires explicit user acceptance for install/update/save
# RequireLicenseAcceptance = $false

# External dependent modules of this module
# ExternalModuleDependencies = @()

} # End of PSData hashtable

} # End of PrivateData hashtable

# HelpInfo URI of this module
# HelpInfoURI = ''

# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''

}

160 changes: 160 additions & 0 deletions src/GUARDRAIL 2 MANAGE ACCESS/Audit/Check-UserRoleReviews.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
function Check-UserRoleReviews {
param (
[Parameter(Mandatory=$true)]
[string] $ControlName,
[Parameter(Mandatory=$true)]
[string] $ItemName,
[Parameter(Mandatory=$true)]
[string] $itsgcode,
[Parameter(Mandatory=$true)]
[hashtable] $msgTable,
[Parameter(Mandatory=$true)]
[string] $ReportTime,
[string]
$CloudUsageProfiles = "3", # Passed as a string
[string] $ModuleProfiles, # Passed as a string
[switch]
$EnableMultiCloudProfiles # New feature flag, default to false
)

[PSCustomObject] $ErrorList = New-Object System.Collections.ArrayList
[bool] $IsCompliant = $false
[string] $Comments = $null

$accessReviewList = @()

# list all acces reviews in identity governance
$urlPath = "/identityGovernance/accessReviews/definitions"

try {
$response = Invoke-GraphQuery -urlPath $urlPath -ErrorAction Stop
# portal
$data = $response.Content
# # localExecution
# $data = $response

if ($null -ne $data -and $null -ne $data.value) {
$accessReviewsAll = $data.value
$accessReviewsSorted = $accessReviewsAll | Sort-Object -Property displayName, createdDateTime -Descending

# Check if any policies exist
if ($accessReviewsSorted.Count -eq 0) {
$commentsArray = $msgTable.isNotCompliant + " " + $msgTable.noAutomatedAccessReview
}
else{
Write-Host "Tenant has been onboarded to automated MS Access Reviews and has at least one access review."
# Get the most recent review for each ReviewName
$accessReviewHistory = $accessReviewsSorted | Group-Object -Property displayName | ForEach-Object {
$_.Group | Select-Object -First 1
}
Write-Host "Number of most recent access review per displayName: $($accessReviewHistory.count)"
foreach($review in $accessReviewHistory){
# get the query for each instance
$queryURI = $review.'[email protected]'
Write-Host "Query: $queryURI "

$cleanQueryURI = $queryURI -replace '\$metadata#', '' -replace '\(' , '/' -replace '\)', '' -replace "'", ''
$cleanQueryURI = $cleanQueryURI -replace 'https://graph.microsoft.com/v1.0', ''
try{
$queryResponse = Invoke-GraphQuery -urlPath $cleanQueryURI -ErrorAction Stop
# portal
$queryResponseData = $queryResponse.Content
# # localExecution
# $queryResponseData = $queryResponse

if($queryResponseData.'@odata.count' -ne 0){
if ($null -ne $queryResponseData -and $null -ne $queryResponseData.value) {
$queryData = $queryResponseData.value
Write-Host "Number of queryData: $($queryData.count)"
# query data status can be 'Completed','InProgress', 'Applied', 'NotStarted'
$queryDataSorted = $queryData | Where-Object { $_.status -ne 'NotStarted'} | Sort-Object -Property startDateTime, endDateTime -Descending
$mostRecentQueryData = $queryDataSorted[0]

$accessReviewInfo = [PSCustomObject]@{
AccessReviewName = $review.displayName
AccessReviewInstanceId = $review.id
DescriptionForAdmins = $review.descriptionForAdmins
DescriptionForReviewers = $review.descriptionForReviewers
startDateTimeMostRecentAccessReview = $mostRecentQueryData.startDateTime
endDateTimeMostRecentAccessReview = $mostRecentQueryData.endDateTime
AccessReviewStatus = $mostRecentQueryData.status
}

$accessReviewList += $accessReviewInfo
}
}
else{
Write-Host "Query response data count is: $($queryResponseData.'@odata.count')"
}
}
catch {
$errorMsg = "Failed to call Microsoft Graph REST API at URL '$cleanQueryURI'; returned error message: $_"
$ErrorList.Add($errorMsg)
Write-Error "Error: $errorMsg"
}
}

# check if the access review is within the last year
$oneYearAgo = (Get-Date).AddYears(-1)
$accessReviewList | ForEach-Object {
$review = $_
$reviewAge = [datetime]::Parse($review.endDateTimeMostRecentAccessReview)
$isWithinLastOneYear = if ($reviewAge -ge $oneYearAgo){$true} else {$false}
$review | Add-Member -MemberType NoteProperty -Name "isWithinLastOneYear" -Value $isWithinLastOneYear
}

# validation: any of the access review is within last one year
$anyReviewWithinOneYear = $accessReviewList | Where-Object { $_.isWithinLastOneYear -eq $true }
if ($anyReviewWithinOneYear.Count -ge 1){
$IsCompliant = $true
$commentsArray = $msgTable.isCompliant + " " + $msgTable.hasScheduledAccessReview
}
else{
$commentsArray = $msgTable.isNotCompliant + " " + $msgTable.noScheduledAccessReview
}
}
}
else{
Write-Host "Graph query response data is null: $data"
}
}
catch {
$errorMsg = "Failed to call Microsoft Graph REST API at URL '$urlPath'; returned error message: $_"
$ErrorList.Add($errorMsg)
Write-Error "Error: $errorMsg"
}

$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
if ($EnableMultiCloudProfiles) {
$evalResult = Get-EvaluationProfile -CloudUsageProfiles $CloudUsageProfiles -ModuleProfiles $ModuleProfiles
if (!$evalResult.ShouldEvaluate) {
if ($evalResult.Profile -gt 0) {
$PsObject.ComplianceStatus = "Not Applicable"
$PsObject | Add-Member -MemberType NoteProperty -Name "Profile" -Value $evalResult.Profile
$PsObject.Comments = "Not evaluated - Profile $($evalResult.Profile) not present in CloudUsageProfiles"
} else {
$ErrorList.Add("Error occurred while evaluating profile configuration")
}
} else {
$PsObject | Add-Member -MemberType NoteProperty -Name "Profile" -Value $evalResult.Profile
}
}

$moduleOutput= [PSCustomObject]@{
ComplianceResults = $PsObject
Errors = $ErrorList
AdditionalResults = $AdditionalResults
}
return $moduleOutput
}
4 changes: 4 additions & 0 deletions src/GuardRails-Localization/GR-ComplianceChecks-Msgs.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ nonCompliantC2= Configure the conditional access policy to prevent sign-in's fro
nonCompliantC1C2 = Configure the conditional access policies outlined in the remediation guidance.
compliantC1C2 = Both conditional access policies have been configured.
automatedRoleForUsers = Role Assignments for Users and Global Administrators
noAutomatedAccessReview = Tenant has not been onboarded to automated MS Access Reviews.
noScheduledAccessReview = Tenant has no scheduled access review.
hasScheduledAccessReview = Tenant has at least one scheduled access review.
# GuardRail #3
consoleAccessConditionalPolicy = Conditional Access Policy for Cloud Console Access.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ nonCompliantC2= Configurez la politique d'accès conditionnel pour empêcher les
nonCompliantC1C2 = Configurez les politiques d'accès conditionnel décrites dans les conseils de remédiation.
compliantC1C2 = Les deux politiques d'accès conditionnel ont été configurées.
automatedRoleForUsers = Attributions de rôles pour les utilisateurs et les administrateurs généraux
noAutomatedAccessReview = Le locataire n'a pas été intégré aux révisions automatisés de « MS Access Reviews ».
noScheduledAccessReview = Le locataire n'a pas de révision d'accès planifiée.
hasScheduledAccessReview = Le locataire a au moins une révision d'accès planifiée.
# GuardRail #3
noCompliantPoliciesfound=Aucune stratégie conforme n'a été trouvée. Les politiques doivent avoir un emplacement unique et cet emplacement doit être réservé au Canada.
allPoliciesAreCompliant=Toutes les politiques sont conformes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Function Deploy-GSACoreResources {
#region Assign permissions>
$graphAppId = "00000003-0000-0000-c000-000000000000"
$graphAppSP = Get-AzADServicePrincipal -ApplicationId $graphAppId
$appRoleIds = @("Organization.Read.All", "User.Read.All", "UserAuthenticationMethod.Read.All", "Policy.Read.All","Directory.Read.All","AuditLog.Read.All")
$appRoleIds = @("Organization.Read.All", "User.Read.All", "UserAuthenticationMethod.Read.All", "Policy.Read.All","Directory.Read.All","AuditLog.Read.All","AccessReview.Read.All")

foreach ($approleidName in $appRoleIds) {
Write-Verbose "`tAdding permission to $approleidName"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Core components deployments will make the following changes to your environment:
- Create a Log Analyics workspace, Automation Account, Storage Account, Key Vault, and Workbook resources in the new resource group
- Grant the Automation Account Managed Service Identity (MSI) permissions to the Key Vault, Storage Account, and Log Analytics workspace in the new resource group
- Grant the Automation Account Managed Service Identity (MSI) reader rights to your Azure tenant at the root Management Group scope
- Grant the Automation Account MSI the following roles in Azure AD: "Organization.Read.All", "User.Read.All", "UserAuthenticationMethod.Read.All", "Policy.Read.All", "Directory.Read.All"`n
- Grant the Automation Account MSI the following roles in Azure AD: "Organization.Read.All", "User.Read.All", "UserAuthenticationMethod.Read.All", "Policy.Read.All", "Directory.Read.All", "AccessReview.Read.All"`n
"@
}
If ($components -contains 'CentralizedCustomerReportingSupport') {
Expand Down

0 comments on commit 034004c

Please sign in to comment.