2

I have been tasked to delegate a number of everyday tasks in our domain to a group of technicians which does not have Domain Admins membership. One of these tasks is the creation of new domain-based Dfs roots (Server 2008 R2 Enterprise DCs). And this is where I am stuck.

Teh c0de

This is basically just trying to create a domain-based Dfs root, using an arbitrary (first-in-list) Domain Controller as the first Namespace Server:

$DfsnRootName="test"
$DCList = Get-ADDomainController -Filter * | ForEach-Object { ,$_.HostName } 
New-DfsnRoot -Path "\\domain.contoso.com\$DfsnRootName" -TargetPath "\\$($DCList[0])\$dfsnRootName" `
             -Description "Dfs-Root für $DfsnRootName" -EnableAccessBasedEnumeration $true -Type DomainV2
$DCList | ForEach-Object {
    New-DfsnRootTarget -Path "\\domain.contoso.com\$DfsnRootName" `
                       -TargetPath "\\$_\$dfsnRootName" -State Online
}

Teh err0r

The code above is throwing an exception mentioning missing access to a CIM resource. The path ROOT\Microsoft\Windows\DFSN\MSFT_DFSNamespace given in CategoryInfo looks like a WMI path:

New-DfsnRoot : Access to a CIM resource was not available to the client.
At line:1 char:1
+ New-DfsnRoot -Path "\\domain.contoso.com\$DfsnRootName" -TargetPath "\\$($DCList ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : PermissionDenied: (MSFT_DFSNamespace:ROOT\Microsoft\...FT_DFSNamespace) [New-DfsnRoot], CimException
    + FullyQualifiedErrorId : MI RESULT 2,New-DfsnRoot


PS Z:\> $Error[0].CategoryInfo


Category   : PermissionDenied
Activity   : New-DfsnRoot
Reason     : CimException
TargetName : MSFT_DFSNamespace
TargetType : ROOT\Microsoft\Windows\DFSN\MSFT_DFSNamespace

Teh helpless res0luti0n attempts:

I have "Delegated Management permissions" via the Dfs console for the entire domain: delegate management permissions screenshot

which is effectively just adding a "full control" ACE for the added principal to the CN=Dfs-Configuration,CN=System AD container.

And as I was getting an error indicating missing permissions on the Service Control Manager using the "Add Dfs root" Wizard in dfsmgmt.msc, I used sc sdset scmanager to manipulate the SDDL string adding the respective group with "KA" (key access) permissions, analogous to the BUILTIN\Administrators ACE which exists by default.

This way, I can use the "Add Dfs root" wizard to walk through all the steps, but the creation of the root itself is still failing - "The namespace server \ADSRV0\Test cannot be added. Access is denied"

W00t?

the-wabbit
  • 40,319
  • 13
  • 105
  • 169
  • Are your DFS-N servers going to be on Domain Controllers? is there any reason you have to create a PowerShell script for this task? will the delegated users be using the script or can they use the DFS Management tool? – Michael Brown Mar 10 '17 at 13:15
  • 2
    Would you consider creating a JEA endpoint? (JustEnoughAdministration.). You set permissions of who can access it (your techs), what they can do (specific commands, scripts, modules...), and who that runs as (could do as domain admin, so you don't have to get fancy.). Not too hard to set up, just need to carefully consider what to expose. PS5:https://msdn.microsoft.com/en-us/library/dn896648.aspx – Matthew Wetmore Mar 11 '17 at 16:50
  • @MatthewWetmore absolutely. Thanks for the pointer, it certainly looks promising. – the-wabbit Mar 14 '17 at 13:54
  • An alternative to JEA that can be configured with a GUI and is PowerShell and WMF version independent would be System Frontier: http://systemfrontier.com/powershell – Jay Adams Apr 06 '17 at 20:07

3 Answers3

2

Just Enough Administration (JEA) endpoints are well suited to your task. Designing a JEA endpoint requires three main decisions:

  1. Who can call the JEA endpoint?
  2. What can the caller do?
  3. Who will the call run as?

The PowerShell endpoint in PS 5.1 isn't technically a JEA endpoint, but the mechanism is essentially the same.

Get-PSSessionConfiguration
Name : microsoft.powershell PSVersion : 5.1
StartupScript:
RunAsUser :
Permission : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed

From this, the permission groups define who is allowed to call. There is no limitation on the PowerShell endpoint of what can be done, so you have full language capabilities. And lastly, with RunAsUser being blank - the code runs impersonated as the user or credentials provided.

With that basis, look at the help for Register-PSSessionConfiguration, and an overview of JEA.

For bonus points, consider using Group Managed Service Accounts (GMSA) as part of your solution, particularly as the caller.

In your case: You can restrict the endpoint to being called by your limited privilege users.

Then define specific cmdlets you want them to use. These can be built in, or exposed from custom modules you specify. Keep these specific to your task, to avoid elevation of privilege attacks with complicated arguments or too many low level commands exposed that can take advantage of being run as an elevated user.

You could use a RunAsUser of a Domain Administrator, or another account with sufficient privileges.

Matthew Wetmore
  • 1,631
  • 12
  • 20
  • 1
    We have done exactly that - a Powershell Session Configuration running in the default scope of LocalSystem on the DC and exposed the module functions necessary to create Dfs namespaces. Works as expected, with the only problem of Import-PSSession [being broken for PS 5.1 clients](https://serverfault.com/questions/901796/powershell-5-1-import-pssession-bug). – the-wabbit Mar 15 '18 at 14:26
  • Did you re-configure the default endpoint, or create a new configuration name? Generally we create new configurations, then use `-ConfigurationName` on `Invoke-Command` or when creating a `New-PSSession`. – Matthew Wetmore Mar 16 '18 at 15:24
  • I created a new restricted configuration. I also should mention that we ran into the problem of long-ish Kerberos tokens causing WSMan errors: https://support.microsoft.com/en-us/help/970875 which could be remedied by increasing MaxFieldLength/MaxRequestBytes to 65534. This one has been a bit tedious to debug as the error message returned by New-PSSession is not particularly helpful and we had to get a network trace of the HTTP exchange to get an idea about what was wrong. – the-wabbit Mar 19 '18 at 08:10
1

To create a domain-wide DFS namespace an account doing so should have a Local Administrator privilege on a namespace server, that is ADSRV0 in your case. Your last error message suggests to me that it isn't so in your case..

Peter Zhabin
  • 2,276
  • 8
  • 10
  • "Local Administrator" on a DC is basically a Domain Admin. I am looking for a way to create Dfs roots *without* having to assign domain admin privileges to people executing this task. I am aware that the MS documentation is suggesting you would need local administrator rights on the namespace server, it just does not fit the use case here. – the-wabbit Mar 10 '17 at 09:20
  • Well, when you create DFS Root, in essence you create a share on a namespace server. To my knowledge there's no way to achieve this without being a local admin. You can circumvent other machinery around DFS Management, but the core issue will still stand. – Peter Zhabin Mar 11 '17 at 10:52
  • Technically, you *can* [delegate the creation of shares](https://blogs.msdn.microsoft.com/aaron_margosis/2005/04/17/how-to-allow-users-to-manage-file-and-print-shares-without-granting-other-advanced-privileges/). Also, as far as I can see, the privilege to create shares is at least not sufficient to create Dfs roots. I have granted "Server Operators" membership to the users in question, so they are able to create shares on DCs by default, yet they cannot add Dfs roots – the-wabbit Mar 14 '17 at 13:53
0

Just to add some implementation detail to Matthew Wetmore's sparking idea here, we've created a function setting up the Dfs namespace according to our requirements, exposed it in a Powershell module LT-DFSManagement and created a Powershell Session Configuration running in a "virtual account" (which is getting local administrator privileges) on a Domain Controller restricted to calling this very function and accessible to the members of a technicians security group.

We are using dfsutil in the code as it was written to run on a Server 2008 R2 machine, where the Dfsn-cmdlets are not available. Please be aware that the code is using some global variables which will not be defined in your environment by default, yet are vital for correct code execution.

Function Add-DfsNamespace() {
<#
.SYNOPSIS
Creates a new Dfs namespace
#>
    Param(
        # Name of the namespace to create
        [string]$Name,
        # name of the security group to delegate management permissions to
        [string]$DelegationTo = "ACL-$Name-Dfs-management",
        # list of the servers to set up as namespace servers
        $NamespaceServerList = $global:DomainControllersList
    )

    $ErrorActionPreference = "Stop"

    Try { 
        $DelegationIdentity = (Get-ADGroup $DelegationTo -Server $global:THKDomainControllerToUse).SID
    } Catch {
        Throw "Unable to set up delegation permissions for $DelegationTo: $_"
    }
    $NamespaceServerList | ForEach-Object {
        Try {
            $NamespaceServer = $_
            Write-Debug "Create a directory for the namespace: \\$NamespaceServer\c$\DfsRoots\$Name"
            New-Item -Type Directory "\\$NamespaceServer\c$\DfsRoots\$Name" | Out-Null
        } Catch {
            Write-Warning "Creation of \\$NamespaceServer\c$\DfsRoots\$Name failed: $_"
        }

        # Create a new share through WMI
        $share = [wmiclass]"\\$_\root\CimV2:Win32_Share" 
        Write-Debug 'WMI call to create the share `"$Name`" on $_' 
        $result=$share.Create( "c:\DfsRoots\$Name", $Name, 0) 
        Switch ($result.returnValue) {
           0 { Write-Debug "\\$_\$Name share created successfully" }
           22 { Write-Warning "Share \\$_\$Name already present" }
           default { Throw "Share creation failed, return value of Win32_Share.Create: $result.returnValue" }
        }
    }
    Write-Verbose "Creating Domain Dfs namespace $Name on namespace servers $($NamespaceServerList -join "; ")"
    dfsutil root addDom "\\$($NamespaceServerList[0])\$Name"
    $NamespaceServerList | ForEach-Object {
        If($_ -ne $NamespaceServerList[0]) {
            dfsutil target add "\\$_\$Name"
        }
    }
    Write-Debug "Enabling Access-Based Enumeration"
    dfsutil property abde enable "\\$global:sADDomainFQDN\$Name"
    Write-Debug "Granting delegation rights for Namespace $Name to $DelegatedTo"
    $adsiDfsNamespace = $null
    $adsiDfsNamespace = [adsi]"LDAP://$global:THKDomainControllerToUse/CN=$Name,CN=Dfs-Configuration,CN=System,$sADDomainDN" 
    # Query ADSI for the ACL of the namespace AD object until we get a response
    $retries = 0
    Do {
        $namespaceACL = $adsiDfsNamespace.PSBase.ObjectSecurity
        If ($namespaceACL) {
            # here we've got the ACL, loop will end hereafter
        } Else {
            Write-Debug "Waiting another round ($retries/$ADSImaxRetries) as ADSI has not yet provided a security descriptor for $($adsiDfsNamespace)"
            Start-Sleep 1
        }
        $retries+=1
    } Until (($retries -gt $ADSImaxRetries) -or ($null -ne $namespaceACL))

    Write-Debug "Creating a new ACE for $DelegationTo ($DelegationIdentity)"
    # Construct a new Full Control ACE for $DelegationIdentity 
    $newAce =
        New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
            $DelegationIdentity,
            [System.DirectoryServices.ActiveDirectoryRights]::GenericAll,
            [System.Security.AccessControl.AccessControlType]::Allow,
            [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All
        )

    Write-Verbose "Updating the ACL of the Namespace $Name"
    $namespaceAcl.AddAccessRule($newAce)
    $adsiDfsNamespace.PSBase.CommitChanges()
}

The PSSessionConfiguration file defines the security group this session configuration will be available to:

@{
    'Author' = 'the-wabbit';
    'RunAsVirtualAccount' =  $true;
'GUID' = 'cfe0ea5f-9d19-406d-90aa-d26df4bc840f';
    'TranscriptDirectory' = 'C:\ProgramData\JEAConfiguration\Transcripts';
    'SchemaVersion' = '2.0.0.0';
    'RoleDefinitions' = @{
                            'DOMAIN\ACL-DFS-Technicians-AD-JEA-Remoting' = @{
                                                                                'RoleCapabilities' = 'AD-JEA-DFS' }
                        };
    'SessionType' = 'RestrictedRemoteServer' }

and the AD-JEA-DFS role capability file restricts the endpoint to the Add-DfsNamespace function only:

@{
GUID = 'b17b282d-a656-41c8-a7d4-cc6a1fbc17e4'
Author = 'the-wabbit'
CompanyName = 'Looney Tunes'
Copyright = '(c) 2017 the-wabbit. All rights reserved.'
ModulesToImport = 'LT-DFSManagement'
VisibleFunctions='Add-DfsNamespace'
}

A final registration of the configuration puts things in place:

Register-PSSessionConfiguration -Name "DFS" -Path "C:\ProgramData\JEAConfiguration\DFS.pssc"
the-wabbit
  • 40,319
  • 13
  • 105
  • 169