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

Add Check for Application Exclusions for Conditional Access Policies #1537

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
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
32 changes: 13 additions & 19 deletions PowerShell/ScubaGear/Rego/AADConfig.rego
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import data.utils.report.NotCheckedDeprecation
import data.utils.report.CheckedSkippedDetails
import data.utils.report.ReportDetailsBoolean
import data.utils.report.ReportDetailsString
import data.utils.key.IsEmptyContainer
import data.utils.key.Contains
import data.utils.key.FilterArray
import data.utils.key.ConvertToSetWithKey
Expand Down Expand Up @@ -48,7 +47,7 @@ LegacyAuthentication contains CAPolicy.DisplayName if {
some CAPolicy in input.conditional_access_policies

# Match all simple conditions
PolicyConditionsMatch(CAPolicy) == true
PolicyConditionsMatch(CAPolicy, true) == true
"other" in CAPolicy.Conditions.ClientAppTypes
"exchangeActiveSync" in CAPolicy.Conditions.ClientAppTypes
"block" in CAPolicy.GrantControls.BuiltInControls
Expand Down Expand Up @@ -86,7 +85,7 @@ BlockHighRisk contains CAPolicy.DisplayName if {
some CAPolicy in input.conditional_access_policies

# Match all simple conditions
PolicyConditionsMatch(CAPolicy) == true
PolicyConditionsMatch(CAPolicy, true) == true
"high" in CAPolicy.Conditions.UserRiskLevels
"block" in CAPolicy.GrantControls.BuiltInControls

Expand Down Expand Up @@ -139,7 +138,7 @@ SignInBlocked contains CAPolicy.DisplayName if {
some CAPolicy in input.conditional_access_policies

# Match all simple conditions
PolicyConditionsMatch(CAPolicy)
PolicyConditionsMatch(CAPolicy, true)
"high" in CAPolicy.Conditions.SignInRiskLevels
"block" in CAPolicy.GrantControls.BuiltInControls

Expand Down Expand Up @@ -181,10 +180,8 @@ tests contains {
PhishingResistantMFAPolicies contains CAPolicy.DisplayName if {
some CAPolicy in input.conditional_access_policies

"All" in CAPolicy.Conditions.Users.IncludeUsers
"All" in CAPolicy.Conditions.Applications.IncludeApplications
CAPolicy.State == "enabled"
count(CAPolicy.Conditions.Applications.ExcludeApplications) == 0
PolicyConditionsMatch(CAPolicy, true)


GroupExclusionsFullyExempt(CAPolicy, "MS.AAD.3.1v1") == true
UserExclusionsFullyExempt(CAPolicy, "MS.AAD.3.1v1") == true
Expand Down Expand Up @@ -219,7 +216,7 @@ NonSpecificMFAPolicies contains CAPolicy.DisplayName if {
some CAPolicy in input.conditional_access_policies

# Match all simple conditions
PolicyConditionsMatch(CAPolicy)
PolicyConditionsMatch(CAPolicy, true)
"mfa" in CAPolicy.GrantControls.BuiltInControls

# Only match policies with user and group exclusions if all exempted
Expand Down Expand Up @@ -402,19 +399,17 @@ tests contains {
PhishingResistantMFAPrivilegedRoles contains CAPolicy.DisplayName if {
some CAPolicy in input.conditional_access_policies

CAPolicy.State == "enabled"
PolicyConditionsMatch(CAPolicy, false)
PrivRolesSet := ConvertToSetWithKey(input.privileged_roles, "RoleTemplateId")

# Filter: only include policies that meet all the requirements
# Make sure all the necessary roles are included
count(PrivRolesSet - ConvertToSet(CAPolicy.Conditions.Users.IncludeRoles)) == 0

# Confirm excluded roles do not contain any of the privileged roles
# (if it does, that means you are excluding it which leaves role unprotected)
count(PrivRolesSet & ConvertToSet(CAPolicy.Conditions.Users.ExcludeRoles)) == 0
# Ted thinks the next line is not needed
# count(PrivRolesSet & ConvertToSet(CAPolicy.Conditions.Users.ExcludeRoles)) == 0

# Basic & special conditions
Contains(CAPolicy.Conditions.Applications.IncludeApplications, "All") == true
IsEmptyContainer(CAPolicy.Conditions.Applications.ExcludeApplications) == true
# Only match policies with user and group exclusions if all exempted
GroupExclusionsFullyExempt(CAPolicy, "MS.AAD.3.6v1") == true
UserExclusionsFullyExempt(CAPolicy, "MS.AAD.3.6v1") == true

Expand Down Expand Up @@ -445,7 +440,7 @@ tests contains {
ManagedDeviceAuth contains CAPolicy.DisplayName if {
some CAPolicy in input.conditional_access_policies

PolicyConditionsMatch(CAPolicy) == true
PolicyConditionsMatch(CAPolicy, true) == true

"compliantDevice" in CAPolicy.GrantControls.BuiltInControls
"domainJoinedDevice" in CAPolicy.GrantControls.BuiltInControls
Expand Down Expand Up @@ -480,9 +475,8 @@ tests contains {
RequireManagedDeviceMFA contains CAPolicy.DisplayName if {
some CAPolicy in input.conditional_access_policies

Contains(CAPolicy.Conditions.Users.IncludeUsers, "All") == true
PolicyConditionsMatch(CAPolicy, true)
Contains(CAPolicy.Conditions.Applications.IncludeUserActions, "urn:user:registersecurityinfo") == true
CAPolicy.State == "enabled"

Conditions := [
"compliantDevice" in CAPolicy.GrantControls.BuiltInControls,
Expand Down
32 changes: 25 additions & 7 deletions PowerShell/ScubaGear/Rego/Utils/AAD.rego
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package utils.aad
import rego.v1
import data.utils.report.ArraySizeStr
import data.utils.report.Description
import data.utils.key.IsEmptyContainer
import data.utils.key.Contains
import data.utils.key.Count
import data.utils.key.ConvertToSet
Expand Down Expand Up @@ -145,15 +144,34 @@ GroupExclusionsFullyExempt(Policy, PolicyID) := true if {
# General AAD Functions #
#########################

# Return true if policy matches all conditions:
# All for include users & applications,
# block for built in controls, enabled,
# & NO excluded roles.
PolicyConditionsMatch(Policy) := true if {
# Internal helper rule to handle the conditional logic for "All" users
Check_For_All_Users(Policy, CheckForAllUsers) if {
# If false is passed then do not implement the check
CheckForAllUsers == false
} {
CheckForAllUsers == true
Contains(Policy.Conditions.Users.IncludeUsers, "All") == true
}

# PolicyConditionsMatch returns true if conditional access policy matches all conditions below:
# IncludeUsers = All (you can bypass this by passing false in the CheckForAllUsers parameter)
# IncludeApplications = All
# ExcludeRoles is empty
# ExcludeApplications is empty
# Policy state = enabled

PolicyConditionsMatch(Policy, CheckForAllUsers) := true if {
# If CheckForAllUsers is true then check for "All" users
Check_For_All_Users(Policy, CheckForAllUsers)

Contains(Policy.Conditions.Applications.IncludeApplications, "All") == true
Count(Policy.Conditions.Users.ExcludeRoles) == 0
Count(Policy.Conditions.Applications.ExcludeApplications) == 0
Policy.State == "enabled"
IsEmptyContainer(Policy.Conditions.Users.ExcludeRoles) == true

# Uncomment this line of code when we want to check for external or guest users
# Object.get() protects against undefined errors
# Count(object.get(Policy, ["Conditions", "Users", "ExcludeGuestsOrExternalUsers", "GuestOrExternalUserTypes"], null)) == 0
} else := false

# Save the Allowed MFA items as a set, check if there are any MFA
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ ConditionalAccessPolicies := {
],
"ExcludeUsers": [],
"ExcludeGroups": [],
"ExcludeRoles": []
"ExcludeRoles": [],
"ExcludeGuestsOrExternalUsers": {
"ExternalTenants": {
"MembershipKind": null
},
"GuestOrExternalUserTypes": null
},
},
"UserRiskLevels": [
"high"
Expand Down
Loading