Remove Member Of

Hi Team. 

I am just looking for suggestions on overcoming a minor issue. 

Let's say I have the following. 

UserA and UserA is a member of GroupA

We have a secure T0 area in our AD, and we have not allowed anyone in GroupA to change the accounts that are in T0. However, because UserA can add members to groups outside of T0, they also seem to be able to add T0 accounts to the groups. 

Is there a way to deny Add on Member of for accounts in a certain OU if the person making the change is in GroupA

Thanks in advance 

Parents
  • Hi  

    You grant the ability to add or remove members (users, groups, computers etc) objects by apply permissions against the group you want to allow someone to change the members of, by providing them with the ability to write the members property. The members they can add to that group are made up of all object classes that they have the ability to list and are valid objects to be added to a group of that type.

    You don't say if you have different admin accounts for managing T0 objects vs other tier objects?

    If you do, and your only granting List and Read permissions to the objects each of those Tier objects should be able to manage, you should be good. If however you are granting permissions to List and Read all user, group and computer objects, in addition to the write member of the T0 group objects, they can add anyone to the T0 group.

    Therefore if you're not using Tier Admin accounts (adm_cmcfarlane, adm_cmfarlane_T0 etc), then you might want to look into that. - This would be my preferred option, but would mean thinking your Delegation of Control Model

    Adding Deny permissions is possible, however I tend to reserve deny for very particular use cases, as they can paint you into a corner down the line. I'd prefer not to grant the users permissions in the first place, opposed to blocking it. In this instance you'd need a deny group which you can add to all users would should be able to write members for the T0 groups. - This is my least preferred option.

    Other options would also include using Workflow's to block the change, if the request crosses the tier boundary. However not that you'd need to write a script, which looks at the group membership change, and works out if any of the objects (1 or more) are crossing that boundary, the block the request (or remove the cross boundary add/remove from the request and silently succeed, even thought the request hasn't completed as was requested as programmatically its removed some objects). There is an example I'll try and dig out which blocked the creation of a universal group via workflow, similar logic would be used, but re-focused on group membership. This does work, but your still granting access to add to the group, your just resolving the issue in code, instead of denying permissions - This would be my second option, but would mean you'd need to write a script module, to check if there are cross boundary changes, the manage them as you need.

  • Thanks, Stu. Yes, we have a separate account used to manage T0 assets. 

    The issue is not T0 accounts being able to add a T0 user to a group but a non-t0 admin account being able to add a T0 asset to a security group. The non t0 admin cant do anything to the t0 asset at all but it can add them to security groups still which is what I am trying to stop. 

  • We ended up using a policy script to block crossing tiers via group membership, here's our script:

    function onInit($context) {
        $param = $Context.AddParameter("Tier")
        $param.Required = $true
        $param.DefaultValue = "3"
    }
    
    function onPreModify($Request) {
        if ($Request.Class -ne "group") { return }
        if ($null -eq $Request.Get("member")) { return }
        $Tier = $context.Parameter("Tier")
        $allowedOus = @{
            "Tier0" = @(
                "OU=Tier 0,OU=Admin,DC=somewhere,DC=local"
            )
            "Tier1" = @(
                "OU=Tier 1,OU=Admin,DC=somewhere,DC=local"
            )
            "Tier2" = @(
                "OU=Tier 2,OU=Admin,DC=somewhere,DC=local"
            )
            "Tier3" = @(
                "OU=Tier 3,OU=Admin,DC=somewhere,DC=local"
            )
        }
    
        for ($i = 0; $i -lt $Request.PropertyCount; $i++) {
            $item = $Request.Item($i)
            if ($item.Name -eq "member") {
                if ($item.ControlCode -eq $Constants.ADS_PROPERTY_APPEND) {
                    foreach ($dn In $item.Values) {
                        $memberAllowed = switch ($allowedOus["Tier" + $Tier]) {
                            { $dn -like "*$($_)" } { $true; break }
                        }
                        if ($memberAllowed -ne $true) {
                            throw (
                                "Administrative Policy:",
                                [System.Environment]::NewLine,
                                "$($dn) is not a member of tier $($Tier)"
                            )
                            return
                        }
                    }
                }
                break
            }
        }
    
    }

    There is a policy for each tier that all use the same script and use the parameter to define which tier to check.

  • Thanks Jody. I will check this out. 

  • Hi Jody. 

    Can you please explain the script briefly and explain how you implemented it? 

    We have multiple OU's, which we would class as T1 but only a single OU that is T0

  • The script looks for changes to groups and in particular the member attribute of the group.

    If there are changes to the member attribute then it checks the OU of the member and compares it to the list of allowed OUs for that tier.  If the member isn't part of one of the allowed OU's, the change will be rejected.

    You can have one or many allowed OU's per tier.  Add the OU paths to the relevant tier in the $allowedTiers hash table, here's an example: 

     

        $allowedOus = @{
            "Tier0" = @(
                "OU=Tier 0,OU=Admin,DC=somewhere,DC=local"
            )
            "Tier1" = @(
                "OU=Tier 1,OU=Admin,DC=somewhere,DC=local",
                "OU=another,OU=location,DC=somewhere,DC=local",
                "OU=one,OU=more,DC=somewhere,DC=local"
            )
            "Tier2" = @(
                "OU=Tier 2,OU=Admin,DC=somewhere,DC=local"
            )
            "Tier3" = @(
                "OU=Tier 3,OU=Admin,DC=somewhere,DC=local"
            )
        }

    To use the script we create a provisioning policy per tier, we called it "Group Members must be in Tier X", you'll need one policy per tier. 

    The policy will use the Script Execution Policy to run the script.  On the parameters tab , select onInit as the function to declare parameters, then set Tier to the necessary value.  For some reason I cannot paste a screen shot, otherwise I would include one here.

    Link to this to the relevant OU's for your groups.

    You might be able to use a Workflow instead of policies, I haven't looked at that though.

    I hope that helps!

Reply
  • The script looks for changes to groups and in particular the member attribute of the group.

    If there are changes to the member attribute then it checks the OU of the member and compares it to the list of allowed OUs for that tier.  If the member isn't part of one of the allowed OU's, the change will be rejected.

    You can have one or many allowed OU's per tier.  Add the OU paths to the relevant tier in the $allowedTiers hash table, here's an example: 

     

        $allowedOus = @{
            "Tier0" = @(
                "OU=Tier 0,OU=Admin,DC=somewhere,DC=local"
            )
            "Tier1" = @(
                "OU=Tier 1,OU=Admin,DC=somewhere,DC=local",
                "OU=another,OU=location,DC=somewhere,DC=local",
                "OU=one,OU=more,DC=somewhere,DC=local"
            )
            "Tier2" = @(
                "OU=Tier 2,OU=Admin,DC=somewhere,DC=local"
            )
            "Tier3" = @(
                "OU=Tier 3,OU=Admin,DC=somewhere,DC=local"
            )
        }

    To use the script we create a provisioning policy per tier, we called it "Group Members must be in Tier X", you'll need one policy per tier. 

    The policy will use the Script Execution Policy to run the script.  On the parameters tab , select onInit as the function to declare parameters, then set Tier to the necessary value.  For some reason I cannot paste a screen shot, otherwise I would include one here.

    Link to this to the relevant OU's for your groups.

    You might be able to use a Workflow instead of policies, I haven't looked at that though.

    I hope that helps!

Children
  • Hi. 

    I thought i would take another look at doing this outside of Active Roles first and then move it back inside. 

    I have knocked together the script below which works outside of Active Roles but inside it does not seem to fire when used with a Workflow. 

    I have created a Managed Unit with an account, and I am then searching to see if that user is a member of the MU and if it is, then throw an error

    As I say outside of ARS this works. But inside, nothing. Any ideas? 

    function onPreModify($Request) {
    
    $ARServer = "ARSERVERNAME-HERE"
    Connect-QADService $ARServer -Proxy
    
    
    #Obtain the SamAccountName of user account being changed. 
    $Username = $DirObj.get("samaccountname")
    
    $ManagedUnit = "CN=Test,CN=Managed Units,CN=Configuration"
    
    # Import the Active Roles Management Shell module
    Import-Module ActiveRolesManagementShell
    
    # Function to check if a user is a member of a specific Managed Unit
    function Is_UserInManagedUnit {
    
        # Check if the user is a member of the Managed Unit
        $isMember = Get-QADUser -SearchRoot $ManagedUnit | Where-Object { $_.SamAccountName -eq $Username }
    
        if ($isMember) {
            return $true
        }
        else {
            return $false
        }
    }
    
    # Main logic
    if (Is_UserInManagedUnit -User $Username -MU $ManagedUnit) {
    
        throw "$Username is a T0 asset. Access Denied"
        #Write-Host "User $Username is a member of the Managed Unit $ManagedUnit. Cannot add to any security groups."
    
    }
    else {
                Write-Host "$Username non T0 User. Access Approved"
        
    }
    
    }

  • Craig,

    A few notes on this script module:

    On line 1, your function name (onPreModify) is one of the reserved names for Event Handlers used in a Policy Script. If you intend to use this in a Workflow, then you should be using a custom function name to ensure that there is no confusion and to ensure that this script doesn't wreak havoc with your policies if it happens to be accidentally included in a policy.

    Line 3 and 4 are not needed. When running a script module within Active Roles, when creating the PowerShell runspace, the Active Roles Administration Service will execute 'Connect-QADService -Proxy -Service localhost'

    Line 13 is not needed. Active Roles Administration Service will execute 'Import-Module ActiveRolesManagementShell' when creating the PowerShell runspace.

    Line 19 is not very efficient. You're returning the entire Managed Unit and then looping through each object using the Where-Object. This will work, but if the Managed Unit grows significantly, performance will be terrible. Instead, I'd use the -LdapFilter parameter with something like '(samAccountName=$Username)' to perform a more efficient search.

    Other than these items, the logic of the script seems to be good. The biggest thing that you're missing would be variable checks. After your variable assignment you're assuming that everything is fine and you are proceeding to use the variable. Scripting best-practice would be to not assume anything. If you need to retrieve or construct a variable, make sure that is has a value before attempting to use it.

  • Thanks, Terrance. I have tidied it up a little based on your suggestions. I will come back to the -LdapFilter issue. 

    It is almost like this script, when run through ARS is not picking up

    $Username = $DirObj.get("samaccountname") 

    If I hard code the username name in the script using $Username = "TestUser" then it works and I see the throw message.  

    function T0Asset($Request) {
    
    #Obtain the SamAccountName of user account being changed. 
    $Username = $DirObj.get("samaccountname")
    $ManagedUnit = "CN=Test,CN=Managed Units,CN=Configuration"
    
    # Function to check if a user is a member of a specific Managed Unit
    function Is_UserInManagedUnit {
    
        # Check if the user is a member of the Managed Unit
        $isMember = Get-QADUser -SearchRoot $ManagedUnit | Where-Object { $_.SamAccountName -eq $Username }
    
        if ($isMember) {
            Write-Host "true"
            return $true
        }
        else {
            Write-Host "false"
            return $false
        }
    }
    
    # Main logic
    if (Is_UserInManagedUnit -User $Username) {
    
        throw "$Username is a T0 asset. Access Denied"
        #Write-Host "User $Username is a member of the Managed Unit $ManagedUnit. Cannot add to any security groups."
    
    }
    else {
                Write-Host "$Username non T0 User. Access Approved"
        
    }
    
    }

  • You're trying this in a Change Workflow, correct? And not an Automation Workflow?

  • Hi. Yes, that is correct in a change workflow. 

  • Then, the $Request and $DirObj objects should exist.

    Test that and confirm. Write them to a file and make sure that they have what you need:

    $Request | Out-file C:\Temp\Request.txt

    $Request | Get-Member |Out-file C:\Temp\Request.txt -append

    $DirObj | Out-file C:\Temp\DirObj.txt

    $DirObj | Get-Member | Out-file C:\Temp\DirObj.txt -append

    You'll likely need to dig into several attributes of the objects in order to see and confirm the attributes and methods.

     

  • Thanks. I now have the output from the commands. While I can see the security group the account is being added to, its not display what username from the dirobj

    I assume this should be in the output? 

  • No, most of the real attributes won't be in the $DirObj object. It's essentially a pointer to the directory object which you can use as a springboard to get what you need.

    If it has any attributes and it has the necessary methods, then it should work.

    I'll run a sanity check in my lab with a small script and I'll confirm expected functionality.

    Can you provide a screenshot of the Workflow so that I can see how you are calling the script?

  • I did also think/try to grab the username from the workflow saved object properties and then try to grab that via 

    $Username = $workflow.SavedObjectProperties("T0").get("SamAccountName")

    Same issue through, its not firing 

  • Ooooh.

    Okay, I see where you may be having some issue.

    The $Request object is created with an object that is related to the Active Directory operation being performed. This operation is "Add member to a group". In this context, the $Request object and $DirObj will have references to the group, not to the added member.

    I think that this could be implemented without a script at all, honestly. I configured a Change Workflow in my lab to prevent adding users to a group if those users were present in a Managed Unit, and it seems to work properly.

    https://imgur.com/a/BVbA2qW