Unable to get any authorization policies to work - Azure AD Auth/IIS

Hello. I have been absolutely banging my head at this, and i cannot for the life of me find any way to resolve this.
i am trying to get authorization policies working for my dashboard. I have been using a heavily customized version of DUD Dashboard as a framework for running the dashboard.
however, i’ve basically stripped everything out of that that i can possibly think of, and no matter what i do, absolutely no authorization policy works on any page or any element whatsoever.

This includes quite literally just using:
New-UDAuthorizationPolicy -Name “Admins” -Endpoint {return $True}

And a page that is using:
New-UDPage -Name $PageName -AuthorizationPolicy “Admins” -Icon home -Endpoint {

I have the following in my logs for any element that has any policy assigned:
18:14:57 [Debug] AuthorizationService TryRunClaimsAuthorization
18:14:57 [Debug] AuthorizationService Session ID: de3fa485-48e1-4215-b079-cc2c1c7a612b
18:14:57 [Debug] AuthorizationService No valid authorization policies for session.
18:14:57 [Debug] AuthorizationService No valid roles for session.
18:14:57 [Debug] AuthorizationService Setting access and ID token.
18:14:57 [Debug] AuthorizationService Checking page Home.
18:14:57 [Debug] AuthorizationService Authorization policy result: False

The policy is very much loading according to the logs:
18:14:51 [Debug] EndpointService Register() Admins
18:14:51 [Debug] EndpointService Unregister() Admins

Though, I’m not sure why every policy shows registr, then unregister. Is this normal, or potentially related? I have scoured every post on this forum related to authorization policy issues. I’m stumped.

I am using a script that is loaded under endpoint initialization, which takes a pair of JSON files and loads them into the $Cache. These are for two functions - one contains a list of page names, and within those items an array containing a list of group names, IE “Admins”, “Users”. The second contains a list of those group names, and an item within that contains the SID of the AD group that we want to associate with this. But regardless, that is somewhat unrelated as I am literally not able to use policies that return true without any checks.

I am running on UD Dashboard 2.9.0.
Server is Windows Server 2016, running IIS 7.
I am not enabling windows auth, as this causes authentication prompts in the browser and as we are using Azure AD instead of integrated windows (my company requires that the dashboard is backed by MFA), but had read that for some reason this would be needed?

Additionally, it seems that I am getting the following error with DCOM (this is “runtimebroker”) when the dashboard is started:
Log Time: 7/1/2020 6:14:44 PM
The application-specific permission settings do not grant Local Activation permission for the COM Server application with CLSID
{D63B10C5-BB46-4990-A94F-E40B9D520160}
and APPID
{9CA88EE3-ACB7-47C8-AFC4-AB702511C276}

This seems to happen right before I get this in my debug logs:

18:14:53 [Fatal] Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor An error occurred starting the application

any ideas?

Here’s my full web.config as well:

<?xml version="1.0" encoding="utf-8"?>

<system.webServer>

<security>

  <!-- <requestFiltering removeServerHeader ="true" /> -->

</security>

<handlers>

  <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />

</handlers>

<aspNetCore processPath="C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" arguments=".\dashboard.ps1" stdoutLogEnabled="true" stdoutLogFile="C:\inetpub\wwwroot\Logs\Dashboard_MainLog" forwardWindowsAuthToken="true" />

<httpProtocol>

  <customHeaders>

    <remove name="X-Powered-By" />

  </customHeaders>

</httpProtocol>  

</system.webServer>

<system.web>

    <authorization>

        <allow users="?" />

    </authorization>

    <identity impersonate="true" />

</system.web>

Firstly, just to make sure are you running the premium version? AzureAD auth is not available in community.
When you define your auth policy are you passing it through into New-UDLoginPage and then that though into New-UDDashboard?

$AuthorizationPolicy = @()
$AuthorizationPolicy += New-UDAuthorizationPolicy -Name "Admin" -Endpoint {return $true}


$AuthMethod = New-UDAuthenticationMethod -ClientId '000000-000-0000-0000-00000' -Instance https://login.microsoftonline.com -Domain ironmansoftware.onmicrosoft.com -TenantId '00000-0000-0000-000-00000000'

$LoginPage = New-UDLoginPage -AuthenticationMethod $AuthMethod -AuthorizationPolicy $AuthorizationPolicy

And then on new-uddashboard -LoginPage $LoginPage

Thanks for the input.
Yes, we are using a licensed enterprise version. No issues with that.
Azure login works just fine, MFA has no issues. I can get claims from users just fine, and if I run the following in the admin console, I get a return of $True:

($ClaimsPrincipal.HasClaim(“groups”,$Cache:GroupClaims.$PageGroup.claim))

Note: I am using this variable to store group names (in the test case here, $PageGroup = “Admins”). It is an imported JSON object. GroupClaims.Admins.Claim returns the azure claim ID for the relevant group. All that works just fine.

As for the auth policies, it doesn’t seem clear to me about the login page. The policies that I am working with are intended to limit access to the various pages that we are putting into the dashboard. We will of course want one applied to the login as well, but that’s not what i was targeting here.

Is there some requirement that all of the policies that are created be passed to the login page for them to be accessible to the internal pages? When my endpoint script creates the policies, i can see them running when I look under the diagnostics > endpoints page, with the “return $True” as the only code. (I have it running in design mode)

Also, I noted that for items such as “new-UDPage”, the “-AuthorizationPolicy” flag accepts a string with the name of the relevant policy. New-UDLoginPage takes an object with the policy stored in it.
However, when I pass several policies to New-UDLoginPage, i don’t see them in the endpoint list. Perhaps this is the issue?

also, if passing all of the policies to the login page is required - when does that need to be done?
Can i use an andpoint script loaded with the dashoard’s endpoint initialization to make them, and then have them passed to New-UDLoginPage?

For reference, i have a page handler function in the dashboard’s startup that loads all pages in a folder automatically. Each page must have an entry in a JSON object that is also loaded, which will contain that page name and a list of group names that are allowed. New pages get a default entry of “Admins”. This is used in the cache and matched against the GroupClaims cache that I referenced above in the policy code.

Just for reference, here is the code that i am using to create the dynamic auth policies. I take each resulting policy, and store them in a custom PSObject that can be referenced in $Cache:Authpolicies

ForEach ($Page in $Cache:dud.params.pages.name) {

        #Create a name for the auth policy

        $PolicyName = $Page + "_AuthPolicy"

        #Store the auth policy enumeration code in a string so that we can insert the page name. The policy cannot use the $Page variable as it is out of scope to it.

        $PolicyCode = '

    param(

        $User

    )

    $Granted = $False

    ForEach($PageGroup in $Cache:PagePermissions.' + "'" + $Page + "'" + '.Groups){

        if($ClaimsPrincipal.HasClaim("groups",$Cache:GroupClaims.$PageGroup.claim)){

            $Granted = $True

        }

    }

    $Granted

'

        

        #Convert the policy code into a scriptblock so that we can pass it to New-UDAuthorizationPolicy 

        $PolicyScript = [Scriptblock]::Create($PolicyCode)

        #Start each new authorization policy

        $Policy = [PSCustomObject]@{Name = $PolicyName; Policy = New-UDAuthorizationPolicy -Name $PolicyName -Endpoint $PolicyScript}

        $Policies += $Policy

        

    }

    #if (!($Cache:AuthPolicies)) { $Cache:AuthPolicies = @() }

    #$Cache:AuthPolicies = $Policies

It wasn’t clear in any of the documentation that you might have to give New-UDLoginPage ALL of the policy objects or they apparently won’t be available to the pages. I tried this, and though that it might work:

I put my policy builder code into it’s own script. Here’s what that looks like if anyone’s interested:

[CmdLetBinding()]
param
(
[String] $Page
)	
#Create a name for the auth policy
$PolicyName = $Page + "_AuthPolicy"
#Store the auth policy enumeration code in a string so that we can insert the page name. The policy cannot use the $Page variable as it is out of scope to it.
$PolicyCode = '
param(
$User
)
$Granted = $False
ForEach($PageGroup in $Cache:PagePermissions.' + "'" + $Page + "'" + '.Groups){

if($ClaimsPrincipal.HasClaim("groups",$Cache:GroupClaims.$PageGroup.claim)){
    $Granted = $True
}

}
$Granted
'
    
#Convert the policy code into a scriptblock so that we can pass it to New-UDAuthorizationPolicy 
$PolicyScript = [Scriptblock]::Create($PolicyCode)
#Start each new authorization policy
return (New-UDAuthorizationPolicy -Name $PolicyName -Endpoint $PolicyScript)

I then added a function to my separate login.ps1 script, which uses this to build the policies for all of the pages:

Function Get_AuthPolicies {

   $Policies = @()

   ForEach ($Page in $Cache:dud.params.pages.name) {

      $Policy = [PSCustomObject]@{Policy = (invoke-command (Get-Command "C:\inetpub\wwwroot\authentication\PolicyBuilder.ps1" | select-Object -ExpandProperty ScriptBlock) -ArgumentList $Page)}

      $Policies += $Policy

   }

   Return $Policies

}

$AuthPolicies = (Get_AuthPolicies).Policy

I simply use this function in the New-UDLoginPage Parameter, as the function will return an array of “AuthorizationPolicy” objects:

New-UDLoginPage -AuthorizationPolicy $AuthPolicies -WelcomeText " IT Dashboard" -Logo $logo -LoadingText "Logging in..." -PageBackgroundColor "#616161" -LoginFormBackgroundColor "#FF1c1c1c" -LoginFormFontColor "#FFFFFF" @UDLoginPageParams

Well… Nevermind. i thought that it worked, but that just broke the login page until i fixed a typo on the page. Still getting the same thing even doing it like this.

If I use the code mentioned above:

$AuthorizationPolicy = @()
$AuthorizationPolicy += New-UDAuthorizationPolicy -Name "Admin" -Endpoint {return $true}


$AuthMethod = New-UDAuthenticationMethod -ClientId '000000-000-0000-0000-00000' -Instance https://login.microsoftonline.com -Domain ironmansoftware.onmicrosoft.com -TenantId '00000-0000-0000-000-00000000'

$LoginPage = New-UDLoginPage -AuthenticationMethod $AuthMethod -AuthorizationPolicy $AuthorizationPolicy

It does work, and i can assign the “Admins” policy to pages, and they correctly enumerate the policy, including if I set it to {return $False}. But i need dynamic policies as what we are trying to do with the dashboard will include a bunch of data collection/reporting that needs to be restricted in a variable manner, as well as attempting to provide functionality so that the other members of my team can drop new pages in without needing to rewrite the dashboard (essentially, here’s your template .ps1 for a new page, write your stuff inside the “New-UDPage” block and drop it in the “pages” folder"

Basically, we’re going to have this as a kind of live reporting for our environments, and multiple teams will use it, but should only be able to access the data that their department/function requires, if that makes sense. I’m getting a purchase for the full Universal package here as well shortly, but I really like the dynamic framework that I’ve built here - everything is working beautifully except for the auth policies. I think we’d end up using universal more for the automation and scheduling portions of universal automation, and probably a second dashboard for that which we’ll use for more admin-level automation tasks.

From the way I understand it, yes you need to pass the auth policies into login page for them to be available to the dashboard. You can use -passthru on the login page which is what I do (windows auth), since I dont want nor need to actually display a physical login page.

As for dynamic auth policies, yes its totally possible, I’m sorry, i dont have the time to go through all your code/setup right now. But if it helps, the way I have mine setup is like this…

  • Pages are loaded automatically by naming convention under a pages\ subfolder.
  • I have an sql table with all the ‘policies’ I have, its basically a list of User SIDs + ad group SIDs + PageId’s for each one.
  • I load all my policies into cache
  • Authorisation.ps1 - with all my UD auth policies, auth method & login page. Theres a hardcoded admin policy, the rest is a foreach loop iterating over the policies i have in cache loaded from sql.
  • Each policy has to use a [scriptblock] which i pass into the -endpoint (due to some scoping issues) but it basically gives the policy name, a list of the user sids with comparison to my $user.Identity.User.Value and a list of groups with a comparison to $user.Identity.Groups.value. If the user has a match on either, it returns $true.
  • Front end, I’ve got an admin page which displays a list of the auth policies from SQL, along with an edit page that allows me to add and remove users and groups from each one. Enable or disable them.

The only caveat to this method is that in order to make any changes, you’ve got to recycle your app pool. Something that I was looking to address further down the line when I had the time, but i’m hoping this can be done easier with UD V3 or Universal

funny enough that’s very close to exactly what I am doing.
Right now I am just using some JSON files to store the data, but SQL would work just the same. Will probably move to that once this is closer to a running setup.

I am putting my policy code into a string variable like this, so that i can pull the $Page name from the loop that is in into the policy code, as that variable won’t be in scope to the policy itself.

$PolicyCode = '

    param(

        $User

    )

    $Granted = $False

    ForEach($PageGroup in $Cache:PagePermissions.' + "'" + $Page + "'" + '.Groups){

        if($ClaimsPrincipal.HasClaim("groups",$Cache:GroupClaims.$PageGroup.claim)){

            $Granted = $True

        }

    }

    $Granted

'

That’s then converted into a scriptblock and sent to the New-UDAuthorizationPolicy:

$PolicyScript = [Scriptblock]::Create($PolicyCode)

        #Start each new authorization policy

        $Policy = [PSCustomObject]@{Policy = New-UDAuthorizationPolicy -Name $PolicyName -Endpoint $PolicyScript}

Though if you examine the code above, i am indeed generating all the policies into an array object, that i then submit to New-UDLoginPage. I DO need the login page for two reasons:

  1. I am using Azure AD MFA with this (required by our security team)
  2. I need to use the Azure Claim IDs to actually check the policy, as all of our actual groups will be managed in AD exclusively.

Each policy ends up with a name of “PageName_AuthPolicy”. Because the DUD module was already build to load the page names into the cache, i am just using that to enumerate them, which works fine.
Though, it’s not the policy code at issue here - i am sure of that. The same problem is presented if i use {return $True} in the dynamic policies.

In my pages, I define the pagename prior to calling new-udpage, then also use a function which looks up the relevant policies in my cache and passes them into -AuthorizationPolicy based on that name, as an array like @(“admin”,“teampolicy1”,“teampolicy2”) etc

How are you passing these policies to your pages?

quite simply actually.
Every page file uses the following at the top:

$PageName = "Home"

$PolicyName = $PageName + "_AuthPolicy"

New-UDPage -Name $PageName -AuthorizationPolicy $PolicyName -Icon home -Endpoint {

The policy builder script uses the same Policy Name when building the policies:

ForEach ($Page in $Cache:dud.params.pages.name) {

        #Create a name for the auth policy

        $PolicyName = $Page + "_AuthPolicy"

There is an endpoint script running that handles loading the cache. That endpoint script loads the JSON data from the files. I have a separate page handler script that handles automatically creating new entries for any new pages, placing my “Admins” group in each one by default. That page handler loads before the dashboard, and updates the JSON files automatically such that all pages will always have an entry in the files BEFORE they are loaded into the cache, if new pages are added.

The cache loading endpoint script loads:

  1. Page Groups to $Cache:PagePermissions.$PageName.Groups
  2. Group IDs in $Cache:GroupClaims.$PageGroup.claim

As you can see in the policy code, it just gets each page group from the cache. if it was “home” for instance, that would be $Cache:PagePermissions.Home.Groups. Currently, that would return the strings “Admins” and “Users” (haven’t made others yet).
Those are then checked with:

if($ClaimsPrincipal.HasClaim("groups",$Cache:GroupClaims.$PageGroup.claim))

This is the cache loading script as well:

$Schedule = New-UDEndpointSchedule -Every 60 -Second

New-UDEndpoint -ID "Permissions_Handler" -Schedule $Schedule -Endpoint {

    

    #Define the location for the dashboard groups definition file.

    $Dashboard_Groups_File = 'C:\inetpub\wwwroot\authentication\dashboard_groups.json'

    #Define the location for the page groups definition file.

    $Page_Groups_File = 'C:\inetpub\wwwroot\authentication\page_groups.json'

    #Load the Groups JSON

    $Dashboard_Groups = Get-Content $Dashboard_Groups_File | ConvertFrom-Json

    #Load the Page Groups JSON

    $Page_Groups = Get-Content $Page_Groups_File | ConvertFrom-Json

    #If we don't have a cache, create one.

    if (!($Cache:PagePermissions)) { $Cache:PagePermissions = @() }

    if (!($Cache:GroupClaims)) { $Cache:GroupClaims = @() }

    

    #Store the Dashboard Groups and Page Groups in their respective cache locations. 

    #The authorization policy endpoints must have access to this to perform enumeration correctly. 

    $Cache:GroupClaims = $Dashboard_Groups.Groups

    $Cache:PagePermissions = $Page_Groups.pages
}

That might actually help you out a bit, since i’m using an endpoint to load the group and SID definitions into the cache, i wouldn’t think that you would need to recycle the app pool to update them. At least that was the idea, where any updates to those files would be updated to the cache every 60 seconds, and since the policies check against the cache you “should” be able to update them at any time.

Yeah, thanks :slight_smile: I’ve been meaning to re-visit mine for some time now but never really got around to it as I can live with how it works. The script block I’m building when my authorization policies are built is essentially hardcoded sids at that point, but your way is better in that respect so I may have a look at switching to a cache variable - I’m not sure why I did it the way I did, maybe i thought that cache wasn’t addressable at that point. I think I once asked adam in this forum if there was a way to manipulate the auth policies after the dashboard had started and he pointed me in a direction that might be possible, I just never got around to test, unfortunately I do all this at work and my todo list is only getting longer and longer so typically if its an improvement and not a requirement it gets left for a while… If i can find the post i’ll link it here.

EDIT:
Found it: Manipulating Authorization Policies

Wanted to get an update out here.
I was actually able to get this working with a little change to the code, and how I am submitting the policies.
I am using the same policybuilder script that I referenced above, but am using my own page handler to return the names of all pages. It appears that attempting to reference the $Cache variable in my login.ps1 script was the issue there, so no policies were actually getting passed to the dashboard.

By using my pagehandler to return all page names, and then passing them to the policybuilder code, i can actually get auth policies returned that are then passed to New-UDLoginPageL

Function Get_AuthPolicies {

   #Collect all page names using the page handler script. We need the names to build the auth policies for each page

   $Pages = (invoke-command (Get-Command "C:\inetpub\wwwroot\Dashboard_Root\pagehandler.ps1" | select-Object -ExpandProperty ScriptBlock)).name

   $Policies = @()

   #Loop through each page, and send the page names to the policy builder script. this script will return a dynamic authorization policy object for each page

   ForEach ($Page in $Pages) {

      $Policy = [PSCustomObject]@{Policy = (invoke-command (Get-Command "C:\inetpub\wwwroot\authentication\PolicyBuilder.ps1" | select-Object -ExpandProperty ScriptBlock) -ArgumentList $Page)}

      $Policies += $Policy

   }

   #return the array of policy object that we've built.

   Return $Policies

}

#run the policy builder function, and store the policy objects in a variable. 

$AuthPolicies = (Get_AuthPolicies).Policy

I’ve confirmed this is working, giving me dynamic, automatic authorization policies for every page that i drop in my pages folder, allowing me to drop in new pages any time I want (using a $PageName variable at the top of each page file). These pages are loaded during dashboard start by my page hander script, which adds page group tags to my page groups file automatically, such that as new pages are added, they are added to the page group definitions with a default entry of “Admins”. These page groups are loaded into the $Cache at dashboard start and thus all it takes to add new functionality to my dashboard is to drop page .ps1 files in my pages folder, and then tag allowed groups for that page after the dashboard starts, or at any other time.

Hope some of this helps others on here!