Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Run As User gsudo -u username #188

Merged
merged 24 commits into from
Oct 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0257d60
feat(RunAsUser): Initial WIP to Allow to start cache for different us…
gerardog Sep 10, 2022
a8cf5b4
Merge branch master into 'Feature.RunAsUser'
gerardog Oct 3, 2022
1fed73d
stash WIP
gerardog Oct 4, 2022
81b4621
Refactor: Moved Settings and Cache to own folders.
gerardog Oct 9, 2022
433516a
stash
gerardog Oct 13, 2022
dae5481
chore: code cleanup
gerardog Oct 15, 2022
d66ac12
Fix: SingleUse service (-k) should not open several services.
gerardog Oct 16, 2022
101e271
Code cleanup
gerardog Oct 16, 2022
a1ec0ba
chore refactor
gerardog Oct 17, 2022
93e7abc
Feature(Run As User): It's working.
gerardog Oct 17, 2022
d6fbd43
fix allowedSid null handling
gerardog Oct 17, 2022
44385df
fix tests
gerardog Oct 17, 2022
5bdd3f4
fix tests
gerardog Oct 17, 2022
6884737
Feature(Run As User): Bugfixing
gerardog Oct 18, 2022
5ff05fc
Fix elevation mode logic
gerardog Oct 18, 2022
105f0d7
Chore
gerardog Oct 19, 2022
951aade
Help updated with v1.8.0 features
gerardog Oct 19, 2022
0e672dd
Fix: Nicer Error message running `gsudo !!` from PowerShell indicatin…
gerardog Oct 19, 2022
563f26b
Fix: Log command line after parsing LogLevel, and even if parsing thr…
gerardog Oct 19, 2022
ef2094b
Feature(RunAsUser): Added -Credential parameter to Invoke-gsudo
gerardog Oct 20, 2022
0fd305a
docs: Update readme with RunAsUser
gerardog Oct 20, 2022
3a49be1
docs: Update docs with RunAsUser
gerardog Oct 20, 2022
70f223f
Fix elevated prompt.
gerardog Oct 20, 2022
c58c044
Merge branch 'master' into Feature.RunAsUser
gerardog Oct 23, 2022
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
233 changes: 143 additions & 90 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/docs/usage/powershell.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ $hash = gsudo "(Get-FileHash '$file' -Algorithm $algorithm).Hash"
- Current Location is preserved for non-FileSystem providers.
- `$ErrorActionPreference` is preserved.
- If your command requires accessing a function on your `$PROFILE` add the `-LoadProfile` parameter. [See More](#loading-your-ps-profile-on-command-elevations).
- Add `-Credential` to specify a user & password to run.

Examples:

Expand Down
100 changes: 47 additions & 53 deletions docs/docs/usage/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,73 +6,67 @@ hide_title: true
---
## How to Use

```gsudo``` Opens your shell elevated in the current console.

```gsudo [options] {command} [arguments]```
Executes the specified command with elevated permissions.

Most relevant **`[options]`**:

- **`-n | --new`** Starts the command in a **new** console with elevated rights (and returns immediately).
- **`-w | --wait`** Force wait for the process to end (and return the exitcode).
- **`-s | --system`** Run As Local System account ("NT AUTHORITY\SYSTEM").
- **`-i | --integrity {v}`** Run command with a specific integrity level: `Low`, `Medium`, `MediumPlus`, `High` (default), `System`. For example, use `Low` to launch a restricted process, or use `Medium` to run without Admin rights.
- **`-d | --direct`** Execute {command} directly. Does not wrap it with your current shell (Pwsh/WSL/MinGw/Yori/etc). Assumes it is a `CMD` command (eg. an `.EXE` file).
- **`--copyns`** Reconnect current connected network shares on the elevated session. Warning! This is verbose, affects the elevated user system-wide (other processes), and can prompt for credentials interactively.
- **`--debug`** Debug mode (verbose).

```gsudo config```
Show current user-settings.
``` powershell
gsudo [options] # Elevates your current shell
gsudo [options] {command} [args] # Runs {command} with elevated permissions
gsudo cache [on | off | help] # Starts/Stops an elevated cache session. (reduced UAC popups)
gsudo status # Shows current user, cache and console status.
gsudo !! # Re-run last command as admin. (YMMV)
```

```gsudo config {key} ["value" | --reset]```
Read, write, or reset a user setting to the default value.
``` powershell
General options:
-n | --new # Starts the command in a new console (and returns immediately).
-w | --wait # When in new console, force wait for the command to end.

Security options:
-i | --integrity {v} # Specify integrity level: Untrusted, Low, Medium, MediumPlus, High (default), System
-u | --user {usr} # Run as the specified user. Asks for password. For local admins shows UAC unless '-i Medium'
-s | --system # Run as Local System account (NT AUTHORITY\SYSTEM).
--ti # Run as member of NT SERVICE\TrustedInstaller
-k # Kills all cached credentials. The next time gsudo is run a UAC popup will be appear.

Shell related options:
-d | --direct # Execute {command} directly. Bypass shell wrapper (Pwsh/Yori/etc).
--loadProfile # When elevating PowerShell commands, load user profile.

Other options:
--loglevel {val} # Set minimum log level to display: All, Debug, Info, Warning, Error, None
--debug # Enable debug mode.
--copyns # Connect network drives to the elevated user. Warning: Verbose, interactive asks for credentials
--copyev # (deprecated) Copy environment variables to the elevated process. (not needed on default console mode)

```gsudo status```
Show status information about current user, security, integrity level or other gsudo relevant data.
```

**Note:** You can use anywhere **the `sudo` alias** created by the installers.

### Examples
**Examples:**

``` powershell
# elevate the current shell in the current console window (Cmd/PowerShell/Pwsh Core/Yori/Take Command/git-bash/cygwin)
gsudo

# launch the current shell elevated in a new console window
gsudo -n

# launch in new window and wait for exit
gsudo -n -w powershell ./Do-Something.ps1
gsudo # elevates the current shell in the current console window (Supports Cmd/PowerShell/Pwsh Core/Yori/Take Command/git-bash/cygwin)
gsudo -n # launch the current shell elevated in a new console window
gsudo -n -w powershell ./Do-Something.ps1 # launch in new window and wait for exit
gsudo notepad %windir%\system32\drivers\etc\hosts # launch windows app

# launch windows app
gsudo notepad %windir%\system32\drivers\etc\hosts
sudo notepad # sudo alias built-in

# sudo alias built-in with choco/scoop/manual installers:
sudo notepad %windir%\system32\drivers\etc\hosts

# Cmd Commands:
gsudo type MySecretFile.txt
gsudo md "C:\Program Files\MyApp"

# redirect/pipe input/output/error
# redirect/pipe input/output/error example
gsudo dir | findstr /c:"bytes free" > FreeSpace.txt

# Elevate last command (sudo bang bang)
gsudo !!
gsudo config LogLevel "Error" # Configure Reduced logging
gsudo config Prompt "$P [elevated]$G " # Configure a custom Elevated Prompt
gsudo config Prompt --reset # Reset to default value

# Enable credentials cache (less UAC popups):
gsudo config CacheMode Auto
```

### Configuration

``` powershell
# See current configuration
gsudo config
# Configure Reduced logging
gsudo config LogLevel "Error"
# Configure a custom Elevated Prompt
gsudo config Prompt "$P [elevated]$G "
# Reset to default value
gsudo config Prompt --reset

# Enable credentials cache (less UAC popups):
gsudo config CacheMode Auto
``` powershell
gsudo config # Show current config settings & values.
gsudo config {key} [--global] [value] # Read or write a user setting
gsudo config {key} [--global] --reset # Reset config to default value
--global # Affects all users (overrides user settings)
```
17 changes: 9 additions & 8 deletions src/gsudo.Tests/CmdTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void Cmd_DebugTestHelper()
//[TestMethod]
public void Cmd_AdminUserTest()
{
Assert.IsTrue(ProcessHelper.IsAdministrator(), "This test suite is intended to be run as an administrator, otherwise it will not work.");
Assert.IsTrue(SecurityHelper.IsAdministrator(), "This test suite is intended to be run as an administrator, otherwise it will not work.");
}

[TestMethod]
Expand All @@ -42,10 +42,10 @@ public void Cmd_ChangeDirTest()
// TODO: Test --raw, --vt, --attached
var testDir = Environment.CurrentDirectory;
var p1 = new TestProcess(
$"\"{testDir}\\gsudo\" cmd /c cd \r\n"
$"\"{testDir}\\gsudo\" cmd /c cd \r\n" // => show current path
+ $"cd .. \r\n"
+ $"\"{testDir}\\gsudo\" cmd /c cd \r\n"
);
); ;
p1.WaitForExit();

var otherDir = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory,".."));
Expand Down Expand Up @@ -102,12 +102,12 @@ public void Cmd_CommandLineAppNoWaitTest()
public void Cmd_WindowsAppWaitTest()
{
bool stillWaiting = false;
var p = new TestProcess("gsudo \"c:\\Program Files (x86)\\Windows NT\\Accessories\\wordpad.exe\"");
var p = new TestProcess("gsudo -w \"c:\\Program Files (x86)\\Windows NT\\Accessories\\wordpad.exe\"");
try
{
p.WaitForExit(3000);
}
finally
catch
{
stillWaiting = true;
}
Expand All @@ -120,16 +120,17 @@ public void Cmd_WindowsAppWaitTest()
[TestMethod]
public void Cmd_WindowsAppNoWaitTest()
{
var p = new TestProcess("gsudo notepad");
var p = new TestProcess("gsudo \"c:\\Program Files (x86)\\Windows NT\\Accessories\\wordpad.exe\"");
try
{
p.WaitForExit();
}
finally
{
Process.Start("gsudo", "taskkill.exe /FI \"WINDOWTITLE eq Untitled - Notepad\" ").WaitForExit();
p.WaitForExit();
Process.Start("gsudo", "taskkill.exe /IM Wordpad.exe").WaitForExit();
}

p.WaitForExit();
}

[TestMethod]
Expand Down
22 changes: 20 additions & 2 deletions src/gsudo.Tests/PowershellTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using gsudo.Commands;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace gsudo.Tests
Expand All @@ -11,7 +12,24 @@ public class PowerShellCoreTests : PowerShellTests

public PowerShellCoreTests()
{
PS_FILENAME = "pwsh.exe";
PS_FILENAME = "pwsh.exe";
}
}

[TestClass]
public class PowerShellCoreAttachedTests : PowerShellTests
{
[ClassInitialize]
public static new void ClassInitialize(TestContext context)
{
TestShared.StartCacheSession();
new ConfigCommand() { key = "ForceAttachedConsole", value = new string[] { "true" } }.Execute();
}

[ClassCleanup]
public static new void ClassCleanup()
{
new ConfigCommand() { key = "ForceAttachedConsole", value = new string[] { "--reset" } }.Execute();
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/gsudo.Tests/TestProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ class TestProcess
public uint ProcessId { get; set; }
public int ExitCode;

static int TestNumber = 1;
private readonly string _testId = Random.Shared.Next(1,999999).ToString() ;// DateTime.Now.ToString("yyyyMMddHHmmssff");
string _sIn => $"in{_testId}";
string _sOut => $"out{_testId}";
Expand Down Expand Up @@ -41,7 +40,7 @@ public TestProcess(string inputScript, string shell = "cmd /k")

File.WriteAllText($"{_sIn}", inputScript + "\r\nExit /b %errorlevel%\r\n");

_process = ProcessFactory.StartDetached(_batchFile, arguments, Environment.CurrentDirectory, false);
_process = ProcessFactory.StartDetached(_batchFile, arguments, Environment.CurrentDirectory, false);
_testProcessHandle = new SafeProcessHandle(_process.Handle, false);

ProcessId = (uint) _process.Id;
Expand Down
120 changes: 66 additions & 54 deletions src/gsudo.Wrappers/Invoke-gsudo.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,28 @@ Serializes a scriptblock and executes it in an elevated powershell.
The ScriptBlock runs in a different process, so it can´t read/write variables from the invoking scope.
If you reference a variable in a scriptblock using the `$using:variableName` it will be replaced with it´s serialized value.
The elevated command can accept input from the pipeline with $Input. It will be serialized, so size matters.
The script result is serialized, sent back to the non-elevated instance, and returned.
Optionally you can check for "$LastExitCode -eq 999" to find out if gsudo failed to elevate (UAC popup cancelled)
The command result is serialized, sent back to the non-elevated instance, deserealized and returned.

Optionally you can check for "$LastExitCode -eq 999" to find out if gsudo failed to elevate (for example, UAC popup cancelled)

.PARAMETER ScriptBlock
Specifies a ScriptBlock that will be run in an elevated PowerShell instance. '
e.g. { Get-Process Notepad }

.PARAMETER ArgumentList
An list of elements that will be accesible inside the script as: $args
An list of elements that will be accesible inside the script as: $args[0] ... $args[n]

.PARAMETER LoadProfile
Load the user profile in the elevated powershell instance. (regardless of `gsudo config PowerShellLoadProfile`)

.PARAMETER NoElevate
A test mode where the command is executed out-of-scope but without real elevation: The serialization/marshalling is still done.
.PARAMETER NoProfile
Do not load the user profile in the elevated powershell instance. (regardless of `gsudo config PowerShellLoadProfile`)

.INPUTS
You can pipe any object to Invoke-Gsudo. It will be serialized and available in the userScript as $Input.

.OUTPUTS
Whatever the scriptblock returns. Use explicit "return" in your scriptblock.
Whatever the scriptblock returns.

.EXAMPLE
PS> Get-Process notepad | Invoke-gsudo { Stop-Process }
Expand Down Expand Up @@ -63,10 +67,9 @@ param
[switch]
$NoProfile = $false,

#test mode
[Parameter()]
[switch]
$NoElevate = $false
[System.Management.Automation.PSCredential]
$Credential
)

# Replaces $using:variableName with the serialized value of $variableName.
Expand Down Expand Up @@ -137,57 +140,66 @@ if ($Debug) {
Write-Debug "Full Script to run on the isolated instance: { $remoteCmd }"
}

if($NoElevate) {
# We could invoke using Invoke-Command:
# $result = $InputObject | Invoke-Command (Deserialize-Scriptblock $remoteCmd) -ArgumentList $ArgumentList
# Or run in a Job to ensure same variable isolation:
$pwsh = ("""$([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName)""") # Get same running powershell EXE.

if ($host.Name -notmatch 'consolehost') { # Workaround for PowerShell ISE, or PS hosted inside other process
if ($PSVersionTable.PSVersion.Major -le 5)
{ $pwsh = "powershell.exe" }
else
{ $pwsh = "pwsh.exe" }
}

$windowTitle = $host.ui.RawUI.WindowTitle;

$dbg = if ($debug) {"--debug "} else {" "}

$job = Start-Job -ScriptBlock (Deserialize-Scriptblock $remoteCmd) -errorAction $errorAction | Wait-Job;
$result = Receive-Job $job -errorAction $errorAction
if ($LoadProfile -and (-not $NoProfile -or (gsudo.exe --loglevel None config PowerShellLoadProfile).Split(" = ")[1] -like "*true*")) {
$sNoProfile = ""
} else {
$pwsh = ("""$([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName)""") # Get same running powershell EXE.

if ($host.Name -notmatch 'consolehost') { # Workaround for PowerShell ISE, or PS hosted inside other process
if ($PSVersionTable.PSVersion.Major -le 5)
{ $pwsh = "powershell.exe" }
else
{ $pwsh = "pwsh.exe" }
}

$windowTitle = $host.ui.RawUI.WindowTitle;
$sNoProfile = "-NoProfile "
}

$dbg = if ($debug) {"--debug "} else {" "}
if ($credential) {
$currentSid = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).User.Value;
$user = "-u $($credential.UserName) "

if ($LoadProfile -or ((gsudo.exe --loglevel None config Powershellloadprofile).Split(" = ")[1] -like "*true*" -and -not $NoProfile)) {
$sNoProfile = ""
} else {
$sNoProfile = "-NoProfile "
}
# At the time of writing this, there is no way (considered secure) to send the password to gsudo. So instead of sending the password, lets start a credentials cache instance.
Start-Process "gsudo.exe" -Args "$dbg -u $($credential.UserName) gsudoservice $PID $CurrentSid All 00:05:00" -credential $Credential -LoadUserProfile -WorkingDirectory "$env:windir" *> $null
# This may fail with `The specified drive root "C:\Users\gerar\AppData\Local\Temp\" either does not exist, or it is not a folder.` https://github.com/PowerShell/PowerShell/issues/18333

$arguments = "-d --LogLevel Error $dbg$pwsh -nologo $sNoProfile-NonInteractive -OutputFormat Xml -InputFormat Text -encodedCommand IAAoACQAaQBuAHAAdQB0ACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAKQAgAHwAIABpAGUAeAAgAA==".Split(" ")

# Must Read: https://stackoverflow.com/questions/68136128/how-do-i-call-the-powershell-cli-robustly-with-respect-to-character-encoding-i?noredirect=1&lq=1
$result = $remoteCmd | & gsudo.exe $arguments *>&1

$host.ui.RawUI.WindowTitle = $windowTitle;
#$p.WaitForExit();
Start-Sleep -Seconds 1
} else {
$user = "";
}

ForEach ($item in $result)
{
if (
$item.psobject.Properties['Exception'] -and
($item.Exception.SerializedRemoteException.WasThrownFromThrowStatement -or
$item.Exception.WasThrownFromThrowStatement)
)
$arguments = "-d --LogLevel Error $user$dbg$pwsh $sNoProfile -nologo -NonInteractive -OutputFormat Xml -InputFormat Text -encodedCommand IAAoACQAaQBuAHAAdQB0ACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAKQAgAHwAIABpAGUAeAAgAA==".Split(" ")
# Must Read: https://stackoverflow.com/questions/68136128/how-do-i-call-the-powershell-cli-robustly-with-respect-to-character-encoding-i?noredirect=1&lq=1

$result = $remoteCmd | & gsudo.exe $arguments *>&1

$host.ui.RawUI.WindowTitle = $windowTitle;

& {
Set-StrictMode -Off #within this scope

ForEach ($item in $result)
{
throw $item
}
if ($item -is [System.Management.Automation.ErrorRecord])
{
Write-Error $item
}
else
{
Write-Output $item;
if (
$item.psobject.Properties['Exception'] -and
($item.Exception.SerializedRemoteException.WasThrownFromThrowStatement -or
$item.Exception.WasThrownFromThrowStatement)
)
{
throw $item
}
if ($item -is [System.Management.Automation.ErrorRecord])
{
Write-Error $item
}
else
{
Write-Output $item;
}
}
}
}
Loading