From df70458ff7e5abe10582e2f29d3dcf66cabed2a4 Mon Sep 17 00:00:00 2001 From: Dirk-jan Mollema Date: Thu, 21 May 2026 14:25:36 +0200 Subject: [PATCH] roadrecon: support more policy controls in UI and policies plugin --- .../usersdialog/usersdialog.component.html | 73 ++++++++++++++++++- .../frontend/src/app/appmain/utils.service.ts | 9 +++ .../roadtools/roadrecon/plugins/policies.py | 45 +++++++----- .../roadrecon/plugins/policyanalysis.py | 3 +- 4 files changed, 107 insertions(+), 23 deletions(-) diff --git a/roadrecon/frontend/src/app/appmain/users/usersdialog/usersdialog.component.html b/roadrecon/frontend/src/app/appmain/users/usersdialog/usersdialog.component.html index d3159a5..9238df1 100644 --- a/roadrecon/frontend/src/app/appmain/users/usersdialog/usersdialog.component.html +++ b/roadrecon/frontend/src/app/appmain/users/usersdialog/usersdialog.component.html @@ -549,6 +549,7 @@
Policy processing for guest users is currently not completely accurate. Policies scoped to guest types are not processed.
+
This table shows Conditional Access policies scoped to the current user. It only shows enabled policies, policies in report only mode or disabled policies are skipped.
@@ -661,19 +662,83 @@ +
+ + + {{risk}} + +
+ +
+ + + {{risk}} + +
+
- - {{control.Control ? control.Control.join(' or ') : ''}}{{control.AuthStrengthIds ? 'Auth strength: ' + control.AuthStrengthIds.join(', ') : ''}} - + + + {{control.Control.join(' or ')}} + + + + Auth strength: {{utils.translateAuthStrength(guid)}} + + + + +
+ +
+ + + + + Microsoft Managed risk remediation + + + {{control}} + +
- {{control}} + + + + + + SignInFrequency (Unknown setting) + + + SignInFrequency (Every time) + + + SignInFrequency (Every {{row.policyDetail.SignInFrequencyTimeSpan}}) + + + SignInFrequency (Unknown SIF type {{row.policyDetail.SignInFrequencyType}}) + + + + + + PersistentBrowserSession: {{row.policyDetail.PersistentBrowserSessionMode}} + + + + + {{control}} + + +
diff --git a/roadrecon/frontend/src/app/appmain/utils.service.ts b/roadrecon/frontend/src/app/appmain/utils.service.ts index 9dd68cf..57b9b6c 100644 --- a/roadrecon/frontend/src/app/appmain/utils.service.ts +++ b/roadrecon/frontend/src/app/appmain/utils.service.ts @@ -335,4 +335,13 @@ export class UtilitiesService { return dateString; } } + + public translateAuthStrength(guid: string): string { + const builtIn: { [key: string]: string } = { + '00000000-0000-0000-0000-000000000002': 'Multi-factor authentication', + '00000000-0000-0000-0000-000000000003': 'Passwordless MFA', + '00000000-0000-0000-0000-000000000004': 'Phishing-resistant MFA' + }; + return builtIn[guid] ?? `Unknown auth strength policy: ${guid} (probably custom)`; + } } diff --git a/roadrecon/roadtools/roadrecon/plugins/policies.py b/roadrecon/roadtools/roadrecon/plugins/policies.py index 2c0e9ab..9bba2af 100644 --- a/roadrecon/roadtools/roadrecon/plugins/policies.py +++ b/roadrecon/roadtools/roadrecon/plugins/policies.py @@ -337,36 +337,28 @@ class AccessPoliciesPlugin(): return ot def _parse_signinrisks(self, cond): - try: - srcond = cond['SignInRisks'] - except KeyError: - return '' - - ot = 'Including: ' - for icrit in srcond['Include']: - ot += ', '.join([escape(crit) for crit in icrit['SignInRisks']]) - - if 'Exclude' in srcond: - ot += '\n
Excluding: ' - for icrit in srcond['Exclude']: - ot += ', '.join([escape(crit) for crit in icrit['SignInRisks']]) - - return ot + return self._parse_risks(cond, 'SignInRisks') def _parse_agentrisks(self, cond): + return self._parse_risks(cond, 'AgentIdRisks') + + def _parse_userrisks(self, cond): + return self._parse_risks(cond, 'UserRisks') + + def _parse_risks(self, cond, riskskey): try: - srcond = cond['AgentIdRisks'] + srcond = cond[riskskey] except KeyError: return '' ot = 'Including: ' for icrit in srcond['Include']: - ot += ', '.join([escape(crit) for crit in icrit['AgentIdRisks']]) + ot += ', '.join([escape(crit) for crit in icrit[riskskey]]) if 'Exclude' in srcond: ot += '\n
Excluding: ' for icrit in srcond['Exclude']: - ot += ', '.join([escape(crit) for crit in icrit['AgentIdRisks']]) + ot += ', '.join([escape(crit) for crit in icrit[riskskey]]) return ot @@ -540,6 +532,17 @@ class AccessPoliciesPlugin(): ucond.append(condition) return ', '.join(ucond) + def _parse_extensiblecontrols(self, cond): + if not 'ExtensibleControls' in cond: + return '' + ucond = [] + for control in cond['ExtensibleControls']: + if control == 'MicrosoftManagedRemediation': + ucond.append('Microsoft Managed risk remediation') + else: + ucond.append(f'Unknown extensible control: {control}') + return ', '.join(ucond) + def _parse_compressed_cidr(self,detail): if not 'CompressedCidrIpRanges' in detail: return '' @@ -598,8 +601,10 @@ class AccessPoliciesPlugin(): out['locations'] = self.parse_wrapper(self._parse_locations, conditions, policy) out['clients'] = self.parse_wrapper(self._parse_clients, conditions, policy) out['signinrisks'] = self.parse_wrapper(self._parse_signinrisks, conditions, policy) + out['userrisks'] = self.parse_wrapper(self._parse_userrisks, conditions, policy) out['agentrisks'] = self.parse_wrapper(self._parse_agentrisks, conditions, policy) out['sessioncontrols'] = self._parse_sessioncontrols(detail) + out['extensiblecontrols'] = self._parse_extensiblecontrols(detail) out['devices'] = self.parse_wrapper(self._parse_devices, conditions, policy) try: @@ -683,12 +688,16 @@ class AccessPoliciesPlugin(): table += ''.format(out['locations']) if out['signinrisks'] != '': table += ''.format(out['signinrisks']) + if out['userrisks'] != '': + table += ''.format(out['userrisks']) if out['agentrisks'] != '': table += ''.format(out['agentrisks']) if out['authflows'] != '': table += ''.format(out['authflows']) if out['controls'] != '': table += ''.format(out['controls']) + if out['extensiblecontrols'] != '': + table += ''.format(out['extensiblecontrols']) if out['sessioncontrols'] != '': table += ''.format(out['sessioncontrols']) table += '' diff --git a/roadrecon/roadtools/roadrecon/plugins/policyanalysis.py b/roadrecon/roadtools/roadrecon/plugins/policyanalysis.py index 298f558..64795fd 100644 --- a/roadrecon/roadtools/roadrecon/plugins/policyanalysis.py +++ b/roadrecon/roadtools/roadrecon/plugins/policyanalysis.py @@ -131,7 +131,8 @@ class PoliciesPlugin(): 'Roles': self._get_role, 'ServicePrincipals': self._get_serviceprincipal, 'ServicePrincipalFilterRule': self._get_serviceprincipalrule, - 'GuestsOrExternalUsers': self._translate_guestsexternal + 'GuestsOrExternalUsers': self._translate_guestsexternal, + 'AgenticServicePrincipals': self._get_serviceprincipal } inscope_uids = [] for ctype, clist in crit.items():
At locations{0}
Sign-in risks{0}
User risks{0}
Agent risks{0}
Authentication flows{0}
Controls{0}
Extensible controls{0}
Session controls{0}