PowerShell for Admins PowerShell for Developers Tips and Tricks Tools Tutorials

Secure Your Powershell Session with JEA and Constrained Endpoints

Nathaniel Webb (ArtisanByteCrafter)
7 min read
Share:

Index

What is a constrained endpoint and why would I need one?

Powershell constrained endpoints are a means of interacting with powershell in a manner consistent with the principal of least privilege. In Powershell terms, this is referred to as Just-Enough-Administration, or JEA.

JEA is very well documented, so this won’t simply be repeating everything those references detail. Instead, we’ll go through a simple, real-world use-case of when and why you might need to deploy one.

Scenario:

A subset of your team needs permissions to do one single action outside the normal scope of their jobs - The ability to restart a service on a server. This particular application will not accept changes to it without a restart, so this access needs to be delegated to the team responsible for maintaining the application, rather than calling you ever 12-15 minutes throughout the day.

You might be thinking, why not just email them a link to a one-liner of code and say “Hey, run this in the terminal thingy!”

Invoke-Command -Server myserver -ScriptBlock {Get-Service myservice | Restart-Service} First, now the entire team needs PS-Remoting rights, administrator rights on the remote server (!), and a contract with HR not to replace the contents of

-ScriptBlock { } with something more sinister or destructive. Instead, we’re going to let them do just enough administration to accomplish what they need to.

Setup and Configuration

Now that we’ve established why we need a constrained endpoint, let’s use powershell to create one. For this example, we will have a custom module

mymodule.psm1 that exposes two functions:

Get-Foo - a custom function we wrote for demonstration purposes

Restart-OurCustomService - a function that explicitly calls

Restart-Service -Service OurCustomService Here is our custom module,

mymodule.psm1 :

Function Get-Foo { param( [string] $Message = "Hello World!" ) Write-Output $Message Write-EventLog -LogName 'MyPSEndpoint' -Source 'Get-Foo' -EntryType Information -EventId 2000 -Message "Get-Foo -Message '$Message' was run." } Function Restart-OurCustomService { [cmdletbinding()] param() Try { Restart-Service -Name OurCustomService -Force -ErrorAction Stop -ErrorVariable err Write-Host -ForegroundColor green "OurCustomService was restarted!" Write-EventLog -LogName 'MyPSEndpoint' -Source 'Restart-OurCustomService' -EntryType Information -EventId 2001 -Message "Successfully restarted." } Catch { Write-Host -ForegroundColor red "OurCustomService could not be restarted." Write-EventLog -LogName 'MyPSEndpoint' -Source 'Restart-OurCustomService' -EntryType Error -EventId 2002 -Message "$err" } } These are the only two commands we want our team members to be able to run.

Logging

It’s always good idea to have some type of logging, so before we even create the actual PS-Session, we’re going to create a Windows Event Log source for it:

$Sources = @( 'Get-Foo', 'Restart-OurCustomService' ) New-EventLog -LogName "MyPSEndpoint" -Source $Sources Now, we’ll be able to see what commands were run through the Event Log, as well as audit any errors thrown.

Creating the module

We’ll need to make sure our module is available on the remote computer. There are several ways to do this, but for this demo, we’ll simply create a folder for it in one of the standard module directories.

The file path will end up being:

C:\Windows\system32\WindowsPowerShell\v1.0\Modules\MyModule\mymodule.psm1 .

Creating the session configuration file

Next, we need to actually create the session endpoint. We need to ensure our users can only use the functions and cmdlets we’ve specified, so in order to do that we need to configure a few parameters.

First, is

LanguageMode , of which we’ll be using the

Restricted type. The help file for

New-PSSessionConfigurationFile explain exactly what this entails:

RestrictedLanguage: Users may run cmdlets and functions, but are not permitted to use script blocks or variables except for the following permitted variables: $PSCulture, $PSUICulture, $True, $False, and $Null. Users may use only the basic comparison operators (-eq, -gt, -lt). Assignment statements, property references, and method calls are not permitted.

Similar to language mode, we also want to set a custom ExecutionPolicy for our endpoint. For this example, since we really only need our 3 defined commands, we’ll use

RemoteSigned . For more information on various execution policies, see Microsoft’s

about_Execution_Policies documentation.

Last, we will configure a

SessionType . Another brief look at

Get-Help New-PSSessionConfigurationFile shows:

RestrictedRemoteServer: Includes only the following proxy functions:

> Exit-PSSession > ,

> Get-Command > ,

> Get-FormatData > ,

> Get-Help > ,

> Measure-Object > ,

> Out-Default > , and

> Select-Object > . Use the parameters of this cmdlet to add modules, functions, scripts, and other features to the session.

Our code to create our session should now look like this:

$sessionparams = @{ 'Path' = "$env:windir\system32\WindowsPowerShell\v1.0\MyPSEndpoint.pssc" 'LanguageMode' = 'RestrictedLanguage' 'ExecutionPolicy' = 'RemoteSigned' 'SessionType' = 'RestrictedRemoteServer' 'ModulesToImport' = @('MyModule') } New-PSSessionConfigurationFile @sessionparams The last thing necessary to begin using our constrained endpoint is to register it with Powershell:

$registerparams = @{ 'Name' = 'MyPSEndpoint' 'Path' = "$env:windir\system32\WindowsPowerShell\v1.0\MyPSEndpoint.pssc" 'ShowSecurityDescriptorUI' = $True } Register-PSSessionConfiguration @registerparams A very important screen should now appear. This is the SecurityDescriptorUI, which will allow us to delegate permissions for who can access our endpoint.

NOTE: You won’t be able to set the SecurityDescriptorUI over a remote PSSession, so be sure to use the console to do this part. If you mess this up, you can reset it from a console:

> Get-PSSessionConfiguration -Name MyPSEndpoint | SetPSSessionConfiguration -ShowSecurityDescriptorUI >

Assign permissions as needed, and then verify your configuration has appropriate permissions:

PS C:\Users\nate> Get-PSSessionConfiguration -Name MyPSEndpoint Name : MyPSEndpoint PSVersion : 5.1 StartupScript : RunAsUser : Permission : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed Without any additional configuration, commands run through the endpoint will execute as the logged in user. For this example, we need to specify other credentials on the server to execute our commands so our users do not need admin rights themselves.

$RunAsCred = (Get-Credential) Set-PSSessionConfiguration -Name MyPSEndpoint -RunAsCredential $RunAsCred ## Using our endpoint {.wp-block-heading}

Now we’re ready to connect and use our endpoint. As a user with permissions delegated via the Security Descriptor above, run the following:

New-PSSession -ComputerName 'remoteserver' -ConfigurationName 'MyPSEndpoint' | Enter-PSSession If all goes well, we should be greeted with a remote session PS prompt, as denoted by the

[remoteserver] PS> in front of the console prompt.

Now we can start to explore what we can can’t do! (As long as we configured our session correctly)

Familiar commands like ‘Get-ChildItem’ won’t work, and will result in an ‘unknown cmdlet’ error. In fact there’s literally nothing we can run except the commands in our module, and a few pre-defined commands necessary for the session to function. We can list our options with

Get-Command[wincore2019demo]: PS> Get-Command CommandType Name Version Source


Function Clear-Host Function Exit-PSSession Function Get-Command Function Get-Foo 0.0 MyModule Function Get-FormatData Function Get-Help Function Measure-Object Function Out-Default Function Restart-OurCustomService 0.0 MyModule Function Select-Object `Our two commands are present from our module, and that’s essentially it. The other functions are pre-defined by the session type

RestrictedRemoteServer , and are needed for the endpoint to function correctly.

We can run

Get-Foo :

[wincore2019demo]: PS>Get-Foo -Message "I love Powershell!" I love Powershell! PS> We can run

Restart-OurCustomService :

PS> Restart-OurCustomService OurCustomService could not be restarted. PS> We can see the results of our interactions in the event log. My demo VM has no service “OurCustomService” so it displayed a friendly error to the console, and logged the verbose error to the event log.

TIP: In Restricted Language Mode, we do not have access to the global variable $error. However, by utilizing advanced functions, we can specify -ErrorVariable to still be able to write the error to the event log, even if we don’t present this information to our users in the console. This can be seen in the Restart-OurCustomService function, and in the screenshot below.

Imgur

At this point we’ve gone over creating a Powershell JEA Endpoint using restricted language and an available custom module for restarting a service. There is a massive amount more you can do with this, but I hope this real-world demonstration has made JEA just a little bit less intimidating and easy to use!

Related Articles

Oct 18, 2016

Pitfalls of the Pipeline

Pipelining is an important concept in PowerShell. Though the idea did not originate with PowerShell (you can find it used decades earlier in Unix, for example), PowerShell does provide the unique advantage of being able to pipeline not just text, but first-class .NET objects.
Pipelining has several advantages:

  • It helps to conserve memory resources. Say you want to modify text in a huge file. Without a pipeline you might read the huge file into memory, modify the appropriate lines, and write the file back out to disk. If it is large enough you might not even have enough memory to read the whole thing.
  • It can substantially improve actual performance. Commands in a pipeline are run concurrently-even if you have only a single processor, because when one process blocks, for example, while reading a large chunk of your file, then another process in the pipeline can do a unit of work in the meantime.
  • It can have a significant effect on your end-user experience, enhancing the perceived performance dramatically. If your end-user executes a sequence of commands that takes 60 seconds, then until 60 seconds has elapsed he/she would see nothing without pipelining, whereas output could start appearing almost immediately with pipelining.

PowerShell provides a variety of techniques for using pipelining but it is all to easy to do it wrong, so you think you are pipelining but in fact you are not. In my article Ins and Outs of the PowerShell Pipeline, I discuss the most common things that can trip you up with implementing pipelining and how to avoid them.

May 22, 2016

Practical PowerShell Unit-Testing

By the time you are using PowerShell to automate an increasing amount of your system administration, database maintenance, or application-lifecycle work, you will likely come to the realization that PowerShell is indeed a first-class programming language and, as such, you need to treat it as such. That is, you need to do development in PowerShell just as you would with other languages, and in particular to increase robustness and decrease maintenance cost with unit tests and–dare I say–test-driven development (TDD). I put together several articles on getting started with unit tests and TDD in PowerShell using Pester, the leading test framework for PowerShell. This series introduces you to Pester and provides what I like to call “tips from the trenches” on using it most effectively, along with a gentle prodding towards a TDD style.
Part 1: Getting Started with the Pester Framework
Starting with the ubiquitous “Hello, World”, this introduces Pester, showing how to execute tests, how to start writing tests, and the anatomy of a test.
Part 2: Mock Objects and Parameterized Test Cases
To be able to create true unit tests, you need to be able to isolate your functions and modules to be able to focus on the component under test; mocks provide great support for doing that. Another topic of “power” unit tests is making them parameterizable, i.e. being able to run several scenarios through a single test simply by providing different inputs.
Part 3: Validating Data and Call History
The final part of this series provides a “how-to” for several other key parts of Pester: how to validate data, how to determine if something was called appropriately, and how to address a particular challenge with Pester, validating arrays. I’ve included a library for array validation to supplement Pester.
For a more general treatment of unit tests, I refer you to Roy Osherove’s canonical text on the subject, The Art of Unit Testing.
… you wanted to know about Unit Testing in .NET | Coding in .NET

Apr 29, 2016

Documenting your PowerShell API–solved!

Long has it been known how to easily document your PowerShell source code simply by embedding properly formatted documentation comments right along side your code, making maintenance relatively painless…

Sample Doc-Comments for PowerShell source
But if you advanced to writing your PowerShell cmdlets in C#, you have largely been on your own, either hand-crafting MAML files or using targeted MAML editors far removed from your source code. But not anymore. With the advent of Chris Lambrou’s open-source XmlDoc2CmdletDoc, the world has been righted upon its axis once more: it allows instrumenting your C# source with doc-comments just like any other C# source:
csharp doc-comment sample
All of the above provides fuel for Get-Help, i.e. providing help one cmdlet at a time. But we are a civilized people; we also need a web-based version of our full custom PowerShell API. That is, a hierarchical and indexed set of Get-Help pages for all the cmdlets in our module. For this task, my own open-source effort, DocTreeGenerator, nicely fills the gap, requiring very little beyond the doc-comments described above to do the complete job.
I have written extensively on using both XmlDoc2CmdletDoc and DocTreeGenerator, and just this week, released a one-page wallchart that shows how all the pieces work together:
doc wallchart thumbnail
Here’s the link to get you started on this fun journey:
Unified Approach to Generating Documentation for PowerShell Cmdlets