Just enough admin and constrained endpoints. Part 1 Understanding endpoints.
Before we can dive into Just Enough Admin and constrained end points, I think we need fill in some of the background on endpoints and where they fit in PowerShell remoting
When you use PowerShell remoting, the local computer sees a session, which is connected to an endpoint on a remote computer. Originally, PowerShell installations did not enable inbound sessions but this has changed with newer versions. If the Windows Remote Management service (also known as WSMAN) is enabled, it will listen on port 5985; you can check with
NetStat -a | where {$_ -Match 5985}
If WSMAN is not listening you can use the Enable-PSRemoting
cmdlet to enable it.
With PS remoting enabled you can try to connect. If you run
$s = New-PSSession -ComputerName localhost
from a Non-elevated PowerShell session, you will get an access denied error but from an elevated session it should run successfully. The reason for this is explained later. When then command is successful, $s will look something like this:
Id Name ComputerName State ConfigurationName Availability
-- ---- ------------ ----- ----------------- ------------
2 Session2 localhost Opened Microsoft.PowerShell Available
We will see the importance of ConfigurationName later as well. The Enter-PSSession
cmdlet switches the shell from talking to the local session to talking to a remote one; running
Enter-PSSession $s
will change the prompt to something like this
[localhost]: PS C:\Users\James\Documents>
showing where the remote session is connected: Exit-PSSession returns to the original (local) session; you can enter and exit the session at will, or create a temporary session on demand, by running
Enter-PsSession -ComputerName LocalHost
The Get-PsSession
cmdlet shows a list of sessions and will show that there is no session left open after exiting an “on-demand” session. As well as interacting with a session you can use Invoke-command to run commands in the session, for example
Invoke-Command -Session $s -ScriptBlock {Get-Process -id $pid}
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id SI ProcessName PSComputerName
------- ------ ----- ----- ----- ------ -- -- ----------- --------------
547 26 61116 77632 ...45 0.86 5788 0 wsmprovhost localhost
At first sight this looks like a normal process object, but it has an additional property, “PSComputerName”. In fact, a remote process is represented as a different type of object. Commands in remote sessions might return objects which are not recognised on the local computer. So the object is serialized – converted to a textual representation – sent between sessions, and de-serialized back into a custom object. There are two important things to note about this:
- De-serialized objects don’t have Methods or Script Properties. Script properties often will need access to something on the originating machine – so PowerShell tries to convert them to Note Properties. A method can only be invoked in the session where the object was created – not a session which was sent a copy of the object’s data.
- The object type changes. The
.getType()
method will returnPsObject
, and thePSTypeNames
property says the object is aDeserialized.System.Diagnostics.Process
; PowerShell uses PSTypenames to decide how to format an object and will use rules defined for type X to format a Deserialized.X object.
However, testing the object type with-is [x]
will return false, and a function which requires a parameter to be of type X will not accept a Deserialized.X. In practice this works as a safety-net, if you want a function to be able to accept remote objects you should detect that they are remote objects and direct commands to the correct machine.
Invoke-Command
allows commands which don’t support a -ComputerName
parameter (or equivalent) to be targeted at a different machine, and also allows commands which aren’t available on the local computer to be used remotely. PowerShell provides two additional commands to make the process of using remote modules easier. Import-PSSession
creates a temporary module which contains proxies for all the cmdlets and functions in the remote session that don’t already exist locally, this means that instead of having to write:
Invoke-Command -Session $s -ScriptBlock {Get-ADUser}
the Get-ADUser
command works much as it would with a locally installed Active Directory module. Using Invoke-Command
will return a Deserialized AD user object and the local copy of PowerShell will fall back to default formatting rules to display it; but when the module is created it includes a format XML file describing how to format additional objects.
Import-PSSession
adds commands to a single session using a temporary module: its partner Export-PSSessio
n saves a module that can be imported as required – running commands from such a module sets up the remote session and gives the impression that the commands are running locally.
What about the configuration name and the need to logon as Admin?
WSMAN has multiple end points which sessions can connect to, the command Get-PSSessionConfiguration
lists them – by default the commands which work with PS Sessions connect to the end point named microsoft.powershell
, but the session can connect to other endpoints depending on the tasks to be carried out.
Get-PSSessionConfiguration
shows that by default for the microsoft.powershell
endpoint has StartUpScript and RunAsUser properties which are blank and a permission property of
NT AUTHORITY\INTERACTIVE AccessAllowed,
BUILTIN\Administrators AccessAllowed,
BUILTIN\Remote Management Users AccessAllowed
This explains why we need to be an administrator (or in the group “Remote Management Users”) to connect. It is possible to modify the permissions with
Set-PSSessionConfiguration -Name "microsoft.powershell" -ShowSecurityDescriptorUI
There is one special case: the switch -EnableNetworkAccess
allows the current session’s credentials to connect through to a session on the same machine.
When Start-up script and Run-As User are not set, the session looks like any other PowerShell session and runs as the user who connected – you can see the user name by running whoami
or checking the $PSSenderInfo
automatic variable in the remote session.
Setting the Run-As User allows the session to run with more privileges than are granted to the connecting user: to prevent this user running amok, the end point is Constrained – in simpler terms we put limits what can be done in that session. Typically, we don’t want the user to have access to every command available on the remote computer, and we may want to limit the parameters which can be used with those that are allowed. The start-up script does the following to setup the constrained environment:
- Loads modules
- Defines proxy functions to wrap commands and modify their functionality
- Hides cmdlets, aliases and functions from the user.
- Defines which scripts and external executables may be run
- Sets the PowerShell language mode, to further limit the commands which can be run in a session, and prevent new ones being defined.
If the endpoint is to work with Active Directory, for example, it might hide Remove-ADGroupMember
(or import only selected commands from the AD module); it might use a proxy function for Add-ADGroupMember
so that only certain groups can be manipulated. The DNS Server module might be present on the remote computer but the Import-Module
cmdlet is hidden so there is no way to load it.
Hiding or restricting commands doesn’t
stop people doing the things that their access rights allow. An administrator can use the default endpoint (or start a remote desktop session) and use the unconstrained set of tools. The goal is to give out fewer admin rights and give people Just Enough Admin to carry out a clearly defined set of tasks: so the endpoint has a privileged account (even a full administrator account) but other, less privileged accounts are allowed to connect run the constrained commands that it provides.
Register-PSSessionConfiguration
sets up a new endpoint can and Set-PSSessionConfiguration
modifies an existing one ; the same parameters work with both -for example
The -ShowSecurityDescriptorUI
switch pops up a permissions dialog box – to set permissions non-interactively it is possible to use -SecurityDescriptorSddl
and specify the information using SDDL but writing SDDL is a skill in itself.
With the end point defined the next part is to create the endpoint script, and I’ll cover that in part 2