PowerShell / PowerShell-RFC

RFC (Request for Comments) documents for community feedback on design changes and improvements to PowerShell ecosystem
MIT License
429 stars 123 forks source link

Custom remote connections #304

Closed PaulHigin closed 2 years ago

PaulHigin commented 3 years ago
constantinhager commented 3 years ago

I like that idea.

JustinGrote commented 3 years ago

This is spot on with what we discussed several months ago, providing an abstracted interface to allow implementation of different transport protocols

Ultimately I would like to see remoting into its own subsystem, and provide an interface to replace PSRP XML messages with some other protocol (gRPC, JSON-RPC, whatever) that just require you to implement functions like connect, send, receive, error, etc., so that PowerShell can be remoted to more easily from other languages (typescript, python, etc.) without having to implement and parse all of PSRP's heavyweight XML messages, however this is a good first step.

jborean93 commented 3 years ago

One other thing that would be great to see is supporting custom transports directly with the builtin PSSessison cmdlets like New-PSSession, Invoke-Command, etc. The custom module can still provide their own cmdlets as mentioned in the RFC if they wish but being able to support 3rd party transports with the proivded cmdlets follows quite nicely with the PSProvider architecture that's present with *-Item and provides a consistent way of working.

Just spitballing the idea but I could see it implemented by adding a parameter set that accepts -Connection <RunspaceConnectionInfo[]> which would allow both builtin connections and 3rd party ones. The benefit of this approach is:

For example the RFC has the following example

PS /Users/Paul> $sessions = New-MyCustomConnection -Computer $computerList -Credential $creds -UseMFA
PS /Users/Paul> $results = Invoke-Command -Session $sessions -File ./Scripts/WeeklyManagementTasks.ps1
PS /Users/Paul> Invoke-AnalyzeResults $results
PS /Users/Paul> $sessions | Remove-PSSession

If the $computerList was quite large then building the sessions could take quite some time or use up a lot of resources to keep the PSSession active. You also need to ensure that you remove the PSSessions once you are finished with them. By having Invoke-Command support something like -Connection you are delegating the session setup, invocation, and teardown to Invoke-Command which can more efficiently fan-out and throttle the provided list for you, e.g.

PS /Users/Paul> $connections = 1..4 | ForEach-Object { [MyCustomConnectionInfo]$_ }
PS /Users/Paul> $results = Invoke-Command -Connection $connections -File ./Scripts/WeeklyManagementTasks.ps1
PS /Users/Paul> Invoke-AnalyzeResults $results
PaulHigin commented 3 years ago

I like the idea of the RunspaceConnectionInfo parameter set. Not as intuitive as a specialized cmdlet that has parameters for the specific connection/authentication, but better than having a bunch of open remote connections, which is definitely resource heavy on the client. Another idea is to add support to PSSession object that allows dynamically managed session lifetime. So Invoke-Command can (via -ThrottleLimit parameter) can open the session and then close it after command completion.

jborean93 commented 3 years ago

Yea I see them as complimentary scenarios each with their own advantages and disadvantages. Having both allows you to have a custom cmdlet that either returns a PSSession or RunspaceConnectionInfo object that can then be used by native cmdlets like Invoke-Command.

iSazonov commented 3 years ago

Ultimately I would like to see remoting into its own subsystem,

Me too. Original idea was to split monolith PowerShell Engine on subsystems so that we could remove a code from distribution if it is unneeded. Current proposal add more complicity and more confuse end users than add a value.


As end user I'd prefer a transparent remoting. Why would end users think about underlying remoting transport and learn new cmdlets for every new remoting?

We have an issue to add ComputerName parameter as common parameter to all cmdlets to implement remoting support to all cmdlets. In the case we could do Get-Uptime -ComputerName wincomp, linuxcomp, maccomp, here each remote computer could has different remoting transport. It would be great UX.

jborean93 commented 3 years ago

As end user I'd prefer a transparent remoting. Why would end users think about underlying remoting transport and learn new cmdlets for every new remoting?

That's mostly why I proposed the -Connection option to use with Invoke-Command, etc. Building the connection info could be done with a custom cmdlet the module would provide or the class itself could be built like any normal .NET class; e.g.

$connInfo = New-WSManConnectionInfo -ConnectionUri '...' -OtherParameters

# or

$connInfo = [WSManConnectionInfo]@{ConnectionUri = '...'; ...}

Invoke-Command -Connection $connInfo, $otherConn, $etc { echo 'hi' }

Like with -ComputerName, -HostName, -Session, the -Connection parameter can be used in the same places.

We have an issue to add ComputerName parameter as common parameter to all cmdlets to implement remoting support to all cmdlets. In the case we could do Get-Uptime -ComputerName wincomp, linuxcomp, maccomp, here each remote computer could has different remoting transport. It would be great UX.

The trouble with it accepting a string is how would it determine whether to use WSMan, SSH, insert custom transport, based on just the name itself. You would have to have some way to encapsulate the provider as well as allow options the caller wishes to set for that provider into a string. You could maybe accept a hashtable but discoverability somewhat becomes harder in this scenario compared to a cmdlet that can give this "connection info". Therein lies what I think the problem with accepting a simple string, it works great where things are just simple but as soon as you start to stray from this path it's a lot more complicated.

iSazonov commented 3 years ago

The trouble with it accepting a string is how would it determine whether to use WSMan, SSH, insert custom transport, based on just the name itself.

If we want innovations we should start from UX considerations:

  1. Simple install new remoting subsystem
  2. Simple configuration
  3. Simple use

Only then should we talk about the details of implementation. Ideally users shouldn't think about what transport is used and scripts should works on any transport without script change.

jborean93 commented 3 years ago

I just don’t know how you could have a connection without the user knowing how they want to connect. How should PowerShell determine that you want to use WSMan/SSH/custom? The mere fact that something is custom would indicate the custom implementation deals with something PowerShell knows nothing about. I don’t think we can eliminate the caller choosing how to connect as the caller is the one who knows the context behind it.

For the other points you’ve mentioned I don’t see them as insurmountable. Like with any modules if someone has installed it then IMO it’a a reasonable assumption that they also know about how it can generate the connection/session object for you.

awakecoding commented 3 years ago

I just don’t know how you could have a connection without the user knowing how they want to connect. How should PowerShell determine that you want to use WSMan/SSH/custom? The mere fact that something is custom would indicate the custom implementation deals with something PowerShell knows nothing about. I don’t think we can eliminate the caller choosing how to connect as the caller is the one who knows the context behind it.

For the other points you’ve mentioned I don’t see them as insurmountable. Like with any modules if someone has installed it then IMO it’a a reasonable assumption that they also know about how it can generate the connection/session object for you.

I agree with Jordan on this one - there is no way to make custom transports fully transparent to users to the point where they do not have to explicitly specify how they want to connect. We can try and make it easier to specify, but we can't get rid of everything - we need at least a transport protocol name somewhere, like "ssh", "wsman" or "my-custom-protocol" + protocol-specific parameters. I think that if we try and make it automagic for the user, we could easily derail this entire discussion into not making it do what it should do: expose ways to override the PowerShell remoting transport with a different, custom transport.

iSazonov commented 3 years ago

I just don’t know how you could have a connection without the user knowing how they want to connect.......

In Windows PowerShell I do Get-Process -ComputerName comp and I don't do any extra work - this works OOB. As end user my responsibility is an useful work - administrative, management or business, not waste time on low level implementation details. As an end user, I don't want to know anything about connection implementation.

There are many examples of how to implement transparent extensions based on negotiations. See RDP. Or SMTP https://cr.yp.to/smtp/ehlo.html

JustinGrote commented 3 years ago

@iSazonov I think this is reasonable, but how would the "registration" work? Would we require the user to import the module ahead of time, or would there be some kind of special Register-Transport command that would add it to a config somewhere? How do we handle the order of transports (e.g. typically I use my custom gRPC transport to connect to the stated computername server but this one time I want to use WSMAN instead)? And what if my transport for instance requires a certificate to authenticate and I need to specify which one? Should I supply cmdlets to configure it? What if the user needs to make a runtime exception?

Remoting doesn't currently do this today with e.g. SSH transport vs WBEM transport, it always uses the WBEM transport and doesn't try to "detect" SSH if it is available as a transport, you explicitly have to call it out by using -Hostname instead of -ComputerName which is unintuitive.

awakecoding commented 3 years ago

I just don’t know how you could have a connection without the user knowing how they want to connect.......

In Windows PowerShell I do Get-Process -ComputerName comp and I don't do any extra work - this works OOB. As end user my responsibility is an useful work - administrative, management or business, not waste time on low level implementation details. As an end user, I don't want to know anything about connection implementation.

There are many examples of how to implement transparent extensions based on negotiations. See RDP. Or SMTP https://cr.yp.to/smtp/ehlo.html

I think what you're really asking for is a way to make custom transports usable for PowerShell implicit remoting, where you only specify the target computer name and it automatically handles everything for you. There are a number of challenges to solve that may not be appropriate for all custom transports, the first one being that you need to detect which custom transport to use, and the second is that credentials need to be implicit as well. While this is relatively easy with Integrated Windows Authentication in a Windows domain, it's not very doable for most other transport types.

As for RDP-style negotiation, this requires... a negotiation protocol. The goal of custom transports is not replace the transport entirely, not to provide a new protocol on top of which we should tunnel a custom protocol. RDP can tunnel virtual channels protocols that the core protocol doesn't know much about beyond a virtual channel name, but that virtual channel exchange is still done over RDP. That won't fly if we want to swap the entire transport layer by something else.

awakecoding commented 3 years ago

@iSazonov I think this is reasonable, but how would the "registration" work? Would we require the user to import the module ahead of time, or would there be some kind of special Register-Transport command that would add it to a config somewhere? How do we handle the order of transports (e.g. typically I use my custom gRPC transport to connect to the stated computername server but this one time I want to use WSMAN instead)? And what if my transport for instance requires a certificate to authenticate and I need to specify which one? Should I supply cmdlets to configure it? What if the user needs to make a runtime exception?

Remoting doesn't currently do this today with e.g. SSH transport vs WBEM transport, it always uses the WBEM transport and doesn't try to "detect" SSH if it is available as a transport, you explicitly have to call it out by using -Hostname instead of -ComputerName which is unintuitive.

I do have a suggestion that could make implicit remoting a lot more ergonomic: accept a protocol scheme in the target hostname, like ssh://computer, wsman://computer, wayk://computer. This would still require credentials to be available in the current context for implicit remoting, but it would be a relatively simple way to select a remoting protocol.

As for registration, we may not even truly need it: the protocol could become part of an expected function name like this:

ssh:// -> New-SshPSSession wsman:// -> New-WSManPSsession wayk:// -> New-WaykPSession

If we accept all valid protocol schemes, we should support characters like +/-/. but it can still be mapped automatically to a valid function name:

jet+tcp:// -> New-JetTcpPSSession rdp-tcp:// -> New-RdpTcpPSSession

I'm just using a bunch of custom protocol schemes from my own products for reference here, but you get the idea. All you'd need is to declare a New-PSSession function in the current context and it could get picked up automatically based on the URL scheme of the target.

JustinGrote commented 3 years ago

The goal of custom transports is not replace the transport entirely, not to provide a new protocol on top of which we should tunnel a custom protocol.

Just to clarify, I think you mean this: The goal of custom transports is to replace the transport entirely, not simply to provide a new protocol on top of which we should tunnel a custom protocol.

jborean93 commented 3 years ago

In Windows PowerShell I do Get-Process -ComputerName comp and I don't do any extra work - this works OOB

@iSazonov that is exactly like -ComputerName or -HostName with the other cmdlets, it is hardcoded to 1 protocol and 1 protocol only (RPC in this case). If that doesn't work then the caller is out of luck just like with anything else. There is no magic going on there. In fact most of these cmdlet specific remoting protocols have been removed in PowerShell in favour of using Invoke-Command and the various PSRemoting cmdlets available.

Having some sort of fallback mechanism where PowerShell will try multiple connection is just a bit too magical for my liking. Just like with hostnames and what you want to connect to I firmly believe how you want to connect is a decision solely in the domain of the caller and not PowerShell should try and determine for you.

I do have a suggestion that could make implicit remoting a lot more ergonomic: accept a protocol scheme in the target hostname, like ssh://computer, wsman://computer, wayk://computer

I was going to suggest a similar thing like wsman:\blah, pid:\1234 and a module during autoload would register it's prefix/root but this is getting dangerously close to trying to shoehorn in potentially complex connection objects into an unstructured string (I thought this was PowerShell!). People will want to start encapsulating more connection details in this string, like port :wsman:\blah:5986, quickly turning this into something that is a lot more complex. You could maybe argue that it should accept a hashtable but ultimately you loose a lot of discoverability compared to just calling a cmdlet to give the info back. Also a hashtable is just 1 step away from [MyConnectionInfoClass]@{...} or even New-CustomConnection @hash.

awakecoding commented 3 years ago

@jborean93 I think the simplest is usually best - I think we should accept the protocol scheme, but also properly parse the entire parameter as a valid URI. This includes an optional port field, and even query parameters. For 99% of use cases where credentials are present in the current context, the only thing you will ever need is a correctly-constructed URL to indicate where to connect and how.

As for passing credentials, they obviously shouldn't be passed in the URL. The first solution would be to have them available in the current context (like SSH keys, or Integrated Windows Authentication) for cases where it is possible. Dynamic credential prompting could be done by a New-CustomPSSession function, but should ideally be avoided as it would only be compatible with interactive scenarios, and incompatible with implicit remoting.

Another option would be to pass an opaque hashmap of parameters to the New-CustomPSSession constructor, but then we need to add a new parameter in New-PSSession, and find a way to pass this new parameter in implicit remoting scenarios. This is where explicit credentials could be passed.

jborean93 commented 3 years ago

IMO if you willing to write a string with the protocol in there why not just call the cmdlet directly that gives you the object anyway. Take completion with New-CustomConnection <tab> is a hell of a lot better than "custom+<tab>" (latter is non-existent). Cmdlets also have documentation and can be identified in the modules export details for even more discoverability.

No need to add messy string parsing logic that may or may not work with across all future custom transport types that complicates already complicated code. No need to force module authors to provide some sort of registration implementation during module load complicating an already complicated process for what I argue is only really nice in the demoware scenarios.

JustinGrote commented 3 years ago

I'm with @jborean93, and I think you should be able to have some sort of Set-PSTransportOption command that will let you specify the order of transports to attempt, otherwise as transports are loaded as modules they get inserted into the top of the order of methods to try, and a transport should have some way to gracefully report that it is either not running or failed to connect before the next transport method is attempted, ideally something that is suppressable because noone wants to see a red wall of text every time they try an implicit command unless all connection methods fail (at which point the exceptions for all could be rolled into an innerexception for TransportConnectivityFailed or whatever)

A URL scheme has bad discoverability compared to something like a PSSessionTransportOption object specifically for Transports, however allowing a URL scheme to be passed as a string as a shortcut that just parses it into a PSSessionTransportOption object would be fine I think, to help allow for those one-off adjustments you may need to do like specify a different port or username.

For configuration/preauth/etc. of transports, I think it should be left up to transport authors to implement similar to SecretManagement, but ideally some sort of .sshconfig style configuration where you can have per-host overrides would become a de-facto standard if not a prescribed standard.

awakecoding commented 3 years ago

I'm with @jborean93, and I think you should be able to have some sort of Set-PSTransportOption command that will let you specify the order of transports to attempt, otherwise as transports are loaded as modules they get inserted into the top of the order of methods to try, and a transport should have some way to gracefully report that it is either not running or failed to connect before the next transport method is attempted, ideally something that is suppressable because noone wants to see a red wall of text every time they try an implicit command unless all connection methods fail (at which point the exceptions for all could be rolled into an innerexception for TransportConnectivityFailed or whatever)

For configuration/preauth/etc. of transports, I think it should be left up to transport authors to implement similar to SecretManagement, but ideally some sort of .sshconfig style configuration where you can have per-host overrides would become a de-facto standard if not a prescribed standard.

A list of transports to attempt in order can very easily cause extremely poor UX when the first one on the list doesn't work. How long should it try connecting using transport X before giving up? 30 seconds? 10 seconds? 1 second? I would advocate for an approach where you end up with a single protocol to try, not a list.

JustinGrote commented 3 years ago

@awakecoding Yeah I'm aware of that but that's what the set-pstransportoption would be for, if you want to force a particular protocol all the time. By default for this RFP no transport modules will be loaded so it will always still just be WSMAN, and you basically have to explicitly import a module to use that transport, so you are by essence specifying that anyways. Timeout would be left up to the transport author to configure but the encouragement should require a pretty aggressive default, allowing it to be relaxed if necessary for higher latency connections.

iSazonov commented 3 years ago

Guys, I wonder why you are discussing implementation details. The starting point is the UX and the experience we already have with the implementation of an alternative SSH remoting.

The main drawback of the current implementation is that we have to change scripts and applications to make them work on a different transport. No one will buy those applications or those transports if it leads to a continuous increase in operating costs.

That's why I say that the ideal UX is to be transparent - easy to create, easy to deploy, easy to configure, easy to use. Best workflow: install new transport with a traditional deploy system, apply a config with GPO/etc., run any cmdlet/script remotely and transparently like Get-Uptime -ComputerName comp.


As for implementation. We live last 5 years without "portable" remoting (I mean WSMan is not supported on Unix, SSH is not on all Windows - bad UX). So de-facto it is not too critical :-). And so we could start experimenting in next (non-LTS) version and try to decouple remoting to a subsystem. This will give us an understanding of how we could build in new transports.

awakecoding commented 3 years ago

@iSazonov I don't understand why we shouldn't be discussing possible implementation details - this, after all, is a request for comments, and we definitely need to go beyond "we want custom PowerShell remoting transports" without discussing the how at least on the surface.

We all have specific pain points we want to make sure are going to be considered in the design. While I consider PowerShell implicit remoting to be secondary for my use cases, it appears to be your primary point of concern. I think we can find a fair common ground that allows explicit custom transport methods to be used implicitly with minimal changes to scripts. If you want it to be transparent, then we have no choice but to discuss how it could be implemented transparently.

As a side note, I am happy with the direction the discussions are taking. There seems to be consensus on creating a PSSession object that PowerShell can consume without knowing the custom transport implementation underneath. As for the rest, it is really discussion on how to connect the dots on top of that and make it all ergonomic for every day usage and our combined use cases (explicit vs implicit, etc).

JustinGrote commented 3 years ago

Guys, I wonder why you are discussing implementation details. The starting point is the UX and the experience we already have with the implementation of an alternative SSH remoting.

Because the devil is in the details :). I agree the UX is where it starts, and having implicit remoting "just work" by just specifying a computername most of the time and have Powershell "figure it out" by attempting custom transports in order is the ideal solution, and in most cases the defaults will be fine, but in the case where the defaults are not fine (you need to specify a port/certificate/whatever to connect), how that is addressed is still part of the UX and thus why it is being discussed here.

PaulHigin commented 3 years ago

Changes to PowerShell remoting user experience is outside the scope of this RFC. But it is something that can certainly be proposed in a separate RFC. This RFC is limited to proposing a way to support other types of remoting connections via an external module.

I am happy to discuss implementation details when they relate to the feasibility or usefulness of the proposal.

SteveL-MSFT commented 2 years ago

@PowerShell/powershell-committee reviewed this and we are accepting this for an experimental feature in 7.3