Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 116 additions & 21 deletions 1-Draft/RFCXXXX-RFC-Invoke-DscResource.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ RFC: RFCXXXX
Author: Gael Colas
Status: Draft
SupersededBy: N/A
Version: 0.8
Version: 0.9
Area: Microsoft.PowerShell.DesiredStateConfiguration
---

Expand Down Expand Up @@ -45,7 +45,7 @@ Specifically, we already know some features that **will not be supported** in th

#### Syntax

As we don't plan on changing the usage, plus the functions is currently not available outside of PowerShell, and there is currently no command for PowerShell 7+, there is no need to change the Syntax found in Windows PowerShell 5.1.
As we don't plan on changing the usage much, plus the functions is currently not available outside of PowerShell, and there is currently no command for PowerShell 7+, so there is no need to change the Syntax found in Windows PowerShell 5.1.

The increment in PowerShell [MAJOR](https://semver.org/#spec-item-8)'s version field is enough to indicate the change of public API (as per [Semantic Versioning](https://semver.org/)).

Expand All @@ -58,7 +58,7 @@ Invoke-DscResource [-Name] <string> [-Method] <string> -ModuleName <ModuleSpecif
We aim at enabling existing scripts using `Invoke-DscResource`, written for Windows PowerShell 5.1, to "just work" in PowerShell 7+, but **in the current user context** when the **PsDscRunAsCredential** DSC common property is **not** supplied.

```PowerShell
Invoke-DscResource -Name xFile -ModuleName @{ModuleName='PSDscResources';ModuleVersion='2.12.0.0'} -Method 'Set' -Properties @{
Invoke-DscResource -Name Script -ModuleName @{ModuleName='PSDscResources';ModuleVersion='2.12.0.0'} -Method 'Set' -Property @{
GetScript = '<# My Get ScriptBlock #>'
SetScript = '<# My Set ScriptBlock #>'
TestScript = '<# My Test ScriptBlock #>'
Expand All @@ -67,16 +67,29 @@ Invoke-DscResource -Name xFile -ModuleName @{ModuleName='PSDscResources';ModuleV

This should run in the current session state where the command is invoked.

#### PsDscRunAsCredential: New Process as different user
#### PsDscRunAsCredential: throw exception if supplied

It's the user's responsibility to either leverage [PsDscRunAsCredential](https://docs.microsoft.com/en-us/powershell/dsc/configurations/runasuser) to execute the resource in a user context that has the required privilege, or wrapping the call in a user context that runs as system (such as using [Invoke-CommandAs](https://www.powershellgallery.com/packages/Invoke-CommandAs) by Mark Kellerman).
After more discussions (with @Jaykul & @TravisEz13) and thinking this through,
it is **not** right to build the support for PsDscRunAsCredential in this command
(more on why later in this RFC).

`PsDscRunAsCredential` is a recommended practice to implement [least-privilege Administrative Models](https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/implementing-least-privilege-administrative-models).
It's the user's responsibility to wrap the call in the required user context
(such as using [Invoke-CommandAs](https://www.powershellgallery.com/packages/Invoke-CommandAs)
by Mark Kellerman).

Since we're now bypassing the LCM, we need to provide the feature in the `Invoke-DscResource`.
Although `PsDscRunAsCredential` is a recommended practice for DSC in WMF 5.1 to
implement [least-privilege Administrative Models](https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/implementing-least-privilege-administrative-models),
it will **not** be supported as part of this implementation of `Invoke-DscResource`.

The Parameter will be extracted from the `-Properties` argument, and used to invoke the Resource in a process running as that user.
It will be equivalent as (from within the `Invoke-DscResource` command point of view):
Instead, we'll aim at providing an example function in a script published

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of removing a very important feature and replacing it with a simple link to unsupported third party code, you could do what every other .NET cross-platform library does and throw a "PlatformNotSupported" exception if you try to use that feature on a Mac or Linux machine. Or just ignore those parameters on that platform. It makes no sense to cripple your entire existing user base to support a tiny portion of your potential future user base.

Alternatively, you could make an "Invoke-DscResourceAs" Cmdlet that wraps the new "Invoke-DscResource" Cmdlet and does all that RunAs stuff for you. Saying that "it's up to the agent to implement this" will lead to a different implementation for every agent, which means that resources that work in one agent may not work in another.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could do what every other .NET cross-platform library does and throw a "PlatformNotSupported" exception

Technically we could, but I don't think that's a good approach for something that has to be supported for as long as PS7 is.

you could make an "Invoke-DscResourceAs" Cmdlet that wraps the new "Invoke-DscResource" Cmdlet and does all that RunAs stuff for you

yeah, that's exactly what I have in mind, when I say:

[...] and instead will try to publish a wrapper on the PS Gallery

But I'm also considering that:

  1. Making this part of PS7+ is a bad choice, I think it should probably be packaged in a separate and independent module/script that is easier to improve, replace, or deprecate and get rid of eventually. Also, we can make that module/script to fail to load if not in windows ($IsWinodws -> throw PlatformNotSupported).
  2. There is several implementations possible, even when only considering Windows. Something like PSThread Job could provide enough isolation for using impersonation within a thread. Having something developed independently & in the open, with third parties & vendors contributing to it, would make it evolve quicker, and avoid the multiple implementations. That is still out of scope for this RFC though (but could potentially be another one).
  3. I don't want this to be slowing down or blocking the core functionality as per the MVP this RFC is attempting to address.

Ideally, PowerShell would have something like PSThreadJob built-in, with cross platform support for runAs, then it would make sense to have Invoke-DscResource to leverage that.

Copy link

@thedevopsmachine thedevopsmachine Aug 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I don't think is being taken into consideration is "why do people want to use RunAs?" You use PSDscRunAsCredential because you need to use Kerberos to authenticate to an external service/resource, e.g. a SMB file share, Active Directory, a Windows Cluster node, a SQL Server etc.

On Windows, Kerberos is this magical thing that just happens when you log in as a user.

On Linux/Mac, Kerberos is implemented via keytab files. The root user can access any of these files, so all it needs to do is the run kinit using the RunAs user's keytab file. Using PSJobs doesn't actually solve your problem since you'd need to do the kinit stuff anyway.

But before we go further down this path, let's ask the question, when does a Linux machine ever actually need to use Kerberos? Take a look at all of the DSC resources that require Kerb. Windows Clusters, Storage Spaces Direct, SQL Server (only with Windows auth), Sharepoint etc. Notice what they have in common? They're all Windows-specific resources. What's your use case for RunAs on Linux? Is meeting that use case so important that it justifies ignoring all your other use cases?

Moving it to a separate module that doesn't ship with Windows is a pain because it means that I have to download things onto my machine in order to execute DSC. Since I can't use DSC for this, I'm now writing build scripts again, which is what DSC is designed to eliminate. I also have another module to deploy and manage across my entire fleet. I either need internet access to public PSGallery, a private PS gallery, or a open file share from which I can download it if I don't have internet access. I don't understand how introducing so much extra pain for your customers so that they can do what they already do today is "the right choice". Seems like a big step backwards to me.

"I don't think that's a good approach for something that has to be supported for as long as PS7 is." - you don't have to release everything on day 1. You can use windows update etc. to deploy new functionality, on Day 1 you can support your biggest use case (Windows) and on Day x you can deploy new code that will support Linux and Mac. So just because PS7 needs to be supported for a long time, it doesn't mean that we can't add features and fix bugs after the initial release.

on the PowerShell Gallery to proxy this command, so that it handles `PsDscRunAsCredential`
to invoke the resource in a different user context, using PowerShell Jobs.

If the key `PsDscRunAsCredential` is to be found amongst the keys of the
`-Properties` parameter, then `Invoke-DscResource` will throw an exception,
unless the `Invoke-DscResource` is invoked with the (new) switch parameter `-IgnorePsDscRunAsCredential`,
in which case it will invoke the DSC Resource method after stripping
the `PsDscRunAsCredential` key/value pair from the properties.

So calling:

Expand All @@ -90,32 +103,114 @@ Invoke-DscResource -Name Script -ModuleName @{ModuleName='PSDscResources';Module

```

What will be executed will be equivalent (in terms of scope) to:

```PowerShell
Start-Job -Credential $Credential -ScriptBlock {
Invoke-DscResource -Name Script -ModuleName @{ModuleName='PSDscResources';ModuleVersion='2.12.0.0'} -Method 'Set' -Properties @{
GetScript = '<# My Get ScriptBlock #>'
SetScript = '<# My Set ScriptBlock #>'
TestScript = '<# My Test ScriptBlock #>'
} -Verbose
}
```
Will **throw an exception**, because of the `PsDscRunAsCredential` key.

> NOTES: We're avoiding to take dependencies on other technologies that would require extra configurations or permissions (such as remoting), or would not be available on other OSes.

#### Independent and isolated execution

`Invoke-DscResource` in PowerShell 7+ will not be aware of other instances being executed, and as such it will be possible to execute several instances in parallel when isolated in their own runspaces, or run in parallel with the LCM.
`Invoke-DscResource` in PowerShell 7+ will not be aware of other instances being executed, and as such it will be possible to execute several instances in parallel when isolated in their own runspace, or run in parallel with the LCM.

This means that it enables concurrent execution, but also risks conflict if two conflicting resources are run simultaneously.

It is up to the user to sequence the execution safely, or to create appropriate resources.

#### Output & Types

##### Get

The `GET` function invoked via the LCM (WMF 5.1) returns a CIM representation of a hashtable,
and will be a `hashtable` for `Invoke-DscResource` in PS7+.

##### Test

The `TEST` function in WMF 5.1 returns a CIM object, with a `[bool]` NoteProperty
`InDesiredState` that has the boolean result of the test.

```PowerShell
InDesiredState
--------------
True
```

In PS7+, `Invoke-DscResource -Method Test ...` will return an object (not CIM)
that has the same `InDesiredState` Property.

##### Set

The `SET` function in WMF 5.1 returns a CIM object, with a `[bool]` NoteProperty
`RebootRequired`, corresponding whether the `$global:DSCMachineStatus` has been
set to 1.

```PowerShell
RebootRequired
--------------
False
```

In PS7+, `Invoke-DscResource -Method Set ...` will return an object (not CIM) that
has the same `RebootRequired` Property, based on whether `$global:DSCMachineStatus`
has been set to 1.

# Out of Scope for initial work & other notes

We're aware that some extra work or feature could be solved at the same time, but we're trying to have the MVP (minimum viable product) out as soon as possible, to help addressing the points raised in the [Motivation](#Motivation) section.

## DSC Resource Parameters & Supported Types

As the `Invoke-DscResource` does not need to serialize and deserialize parameters
using CIM (via MOF objects), it is not limiting the types it can accept for the
DSC Resource functions (Get, Set, Test).

In simple words, a resource could in theory accept an `[hashtable]` as a parameter
type, instead of a `[Microsoft.Management.Infrastructure.CimInstance[]]`.

The downside here is that it's not backward compatible, for this reason, we
can't recommend any other type to be used for now.

Also, not all types are easily serializable and deserializable, so be careful
when the Configuration Data has to be passed to the resource invocation over
the network.

## PsDscRunAsCredential Support not built-in

Here's why we've decided to not handle the `PsDscRunAsCredential` from the `Invoke-DscResource`
advanced function, and instead will try to publish a wrapper on the PS Gallery.

To be truly cross platform and support invoking a resource's method under another
credential, Linux/Unix OSes creates another process under that user. In Windows
it is also possible to Impersonate a user, using `Win32.AdvApi32` for instance.

In WMF 5.1, the LCM is in charge of this. But in PowerShell 7+, the only cross platform
way to do this is by using PowerShell Jobs, which are relatively slow, heavy, and

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not true. PowerShell is written in .NET, and .NET has a class that can identify what platform you're running on. Then you can do an if/else check, if Windows then use existing runas code for Windows, if Linux then throw PlatformNotSupported exception (or just strip the parameter). Just because Linux/Mac doesn't support a feature, doesn't mean you should remove it. Just remove it for the platforms that don't support it.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PlatformNotSupported on Linux/MacOS is not what I consider "truly cross platform".
For the core functionality I think it's important to stick with things that behaves the same way in all OSes (be it Jobs for both or not supporting at all in the core command).
We can extend functionalities on a per-os basis by extending (wrapping) functionalities as we suggested.
I agree that using Jobs for both is a bad idea in terms of performance on Windows (i.e. could be much better with impersonation), and it poses the problem of consistency (when you run as current user without job, or as another user), that's why I don't want it built-in either.

a bit more tricky to troubleshoot, as they need a way to serialize and deserialize
objects.

In PowerShell, executing commands as job returns what we informally call "dead objects":
the object after it was serialized and then deserialized, pertaining most of its
properties but not an "live" object that still has its methods available.

Here, we could have `Invoke-DscResource` to always use Jobs, but that would severely
impact performance, and would make troubleshooting difficult.

We could, as initially thought, handle both case: directly as the current user when
`PsDscRunAsCredential` is not specified, and as Job when specified, but it would
then have two different behavior, depending on the `-Properties` value (and not
the command's actual signature), obfuscating the potential issue to the user.

In the end, this is very much an Agent feature, and each agent author may want to
(and already do) support this themselves, so it's unnecessary code and complexity
for them.

For those reasons, and with the possibility that an elegant or standardized solution
emerges from the community, we believe it's best to leave it outside the scope of
the `Invoke-DscResource` MVP.

In order to enable existing users to support the same behavior than the LCM,
we will try to provide, separately, a function that wraps around this `Invoke-DscResource`
and creates a job when `PsRunAsCredential` is specified, but this won't be part
of the `PSDesiredStateConfiguration` module work.

## Invoke-DscResource will not clear the Builtin Provider Cache

The Built-in provider cache, located in `$env:ProgramData\Microsoft\Windows\PowerShell\Configuration\BuiltinProvCache`, is currently cleared by the LCM (in WMF 5.1).
Expand Down