Skip to content

Commit

Permalink
Merge branch 'master' into feature/cswin32
Browse files Browse the repository at this point in the history
  • Loading branch information
gerardog committed May 28, 2024
2 parents 0f97e25 + bae3da5 commit 59c76d6
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 27 deletions.
4 changes: 2 additions & 2 deletions docs/docs/gsudo-vs-sudo.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ Surprisingly, Microsoft's sudo does not leverage new OS features to enhance secu
| ------- | ------- | ------------------ |
| Executes command with elevated permissions | Yes | Yes |
| Supports output redirection (`sudo dir > file.txt`) | Yes | Yes |
| Supports input redirection (`echo md SomeFolder | sudo cmd`) | Yes | Requires v1.0.0 / Windows insider build 26080 |
| Supports input redirection (`echo md SomeFolder \| sudo cmd`) | Yes | Requires v1.0.0 / Windows insider build 26080 |
| Returns the command exit code | Yes | Requires v1.0.0 / Windows insider build 26080 |
| Preserves the current directory | Yes | [It depends](https://github.com/microsoft/sudo/issues/63) |
| Source code available | [Yes](https://github.com/gerardog/gsudo) | Not for `sudo.exe`, but [promised](https://github.com/microsoft/sudo/blob/f8f1d05/README.md#contributing) |
| Source code available | [Yes](https://github.com/gerardog/gsudo) | [Yes](https://github.com/microsoft/sudo) |

### Security Impersonation Features

Expand Down
2 changes: 1 addition & 1 deletion src/gsudo.Wrappers/gsudoModule.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ if ($gsudoAutoComplete) {
'--integrity' = $integrityOptions;
'-i' = $integrityOptions;
'cache' = @('on', 'off', 'help');
'config' = @('CacheMode', 'CacheDuration', 'LogLevel', 'NewWindow.Force', 'NewWindow.CloseBehaviour', 'Prompt', 'PipedPrompt', 'ForceAttachedConsole', 'ForcePipedConsole', 'ForceVTConsole', 'CopyEnvironmentVariables', 'CopyNetworkShares', 'PowerShellLoadProfile', 'SecurityEnforceUacIsolation', 'ExceptionList');
'config' = @('CacheMode', 'CacheDuration', 'LogLevel', 'NewWindow.Force', 'NewWindow.CloseBehaviour', 'Prompt', 'PipedPrompt', 'PathPrecedence', 'ForceAttachedConsole', 'ForcePipedConsole', 'ForceVTConsole', 'CopyEnvironmentVariables', 'CopyNetworkShares', 'PowerShellLoadProfile', 'SecurityEnforceUacIsolation', 'ExceptionList');
'cachemode' = @('Auto', 'Disabled', 'Explicit', '--reset');
'loglevel' = @('All', 'Debug', 'Info', 'Warning', 'Error', 'None', '--reset');
'NewWindow.CloseBehaviour' = @('KeepShellOpen', 'PressKeyToClose', 'OsDefault', '--reset');
Expand Down
56 changes: 56 additions & 0 deletions src/gsudo/AppSettings/PathPrecedenceSetting.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using gsudo.Helpers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace gsudo.AppSettings
{
/// <summary>
/// Reorders the PATH environment variable to prioritize gsudo's path.
/// Saving the boolean value to the registry is anecdotical, the real change is done in the environment variable.
/// </summary>
internal class PathPrecedenceSetting : RegistrySetting<bool>
{
public PathPrecedenceSetting():
base("PathPrecedence", false, bool.Parse, RegistrySettingScope.GlobalOnly,
description: "Prioritize gsudo over Microsoft Sudo in the PATH environment variable.")
{

}

public override void Save(string newValue, bool global)
{
bool bNewValue = bool.Parse(newValue);
var ourPath = Path.GetDirectoryName(ProcessFactory.FindExecutableInPath("gsudo.exe")) // shim
?? Path.GetDirectoryName(ProcessHelper.GetOwnExeName());

var system32Path = Environment.GetFolderPath(Environment.SpecialFolder.System);

var allPaths = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine).Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
// I could also do .Distinct(StringComparer.OrdinalIgnoreCase);
// ...and it works well on local, but may be out of our responsibility to fix that.

IEnumerable<string> newPath;

if (bNewValue)
newPath = new[] { ourPath }.Concat(allPaths.Where(p => !p.Equals(ourPath, StringComparison.OrdinalIgnoreCase)));
else
newPath = allPaths.Where(p => !p.Equals(ourPath, StringComparison.OrdinalIgnoreCase)).Concat(new[] { ourPath });

var finalStringPath = string.Join(";", newPath);

Logger.Instance.Log($"Updating PATH environment variable to: {finalStringPath}", LogLevel.Debug);

Environment.SetEnvironmentVariable("Path", finalStringPath, EnvironmentVariableTarget.Machine);
base.Save(newValue, global);

if (bNewValue)
Logger.Instance.Log($"\"{ourPath}\" path is now prioritized in the PATH environment variable.", LogLevel.Info);
else
Logger.Instance.Log($"\"{system32Path}\" path is now prioritized in the PATH environment variable.", LogLevel.Info);

Logger.Instance.Log("Please restart all your consoles to ensure the change makes effect.", LogLevel.Warning);
}
}
}
12 changes: 7 additions & 5 deletions src/gsudo/AppSettings/RegistrySetting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ abstract class RegistrySetting
public RegistrySettingScope Scope { get; protected set; }

public string Name { get; set; }
public string Description { get; set; }
public abstract void Save(string newValue, bool global);
public abstract void Reset(bool global);
public abstract object GetStringValue();
Expand All @@ -37,19 +38,20 @@ class RegistrySetting<T> : RegistrySetting
private T runningValue;
private bool hasValue = false;
private readonly Func<string, T> deserializer;
private readonly Func<T, string> serializer;

public RegistrySetting(string name, T defaultValue, Func<string, T> deserializer, RegistrySettingScope scope = RegistrySettingScope.Any, Func<T, string> serializer = null)
: this(name, () => defaultValue, deserializer, scope, serializer)
private readonly Func<T, string> serializer;

public RegistrySetting(string name, T defaultValue, Func<string, T> deserializer, RegistrySettingScope scope = RegistrySettingScope.Any, Func<T, string> serializer = null, string description = null)
: this(name, () => defaultValue, deserializer, scope, serializer, description)
{ }

public RegistrySetting(string name, Func<T> defaultValue, Func<string, T> deserializer, RegistrySettingScope scope = RegistrySettingScope.Any, Func<T,string> serializer = null)
public RegistrySetting(string name, Func<T> defaultValue, Func<string, T> deserializer, RegistrySettingScope scope = RegistrySettingScope.Any, Func<T,string> serializer = null, string description = null)
{
Name = name.Replace('_', '.');
this.defaultValue = defaultValue;
this.deserializer = deserializer;
this.Scope = scope;
this.serializer = serializer;
this.Description = description;
}

public T Value
Expand Down
62 changes: 45 additions & 17 deletions src/gsudo/AppSettings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,85 +18,112 @@ class Settings
public static RegistrySetting<CacheMode> CacheMode { get; set; }
= new RegistrySetting<CacheMode>(nameof(CacheMode), CredentialsCache.CacheMode.Explicit,
deserializer: ExtensionMethods.ParseEnum< CacheMode>,
scope: RegistrySettingScope.GlobalOnly);
scope: RegistrySettingScope.GlobalOnly,
description: "Defines how gsudo credentials cache works: Auto, Explicit (Manual), Disabled" );

public static RegistrySetting<TimeSpan> CacheDuration { get; }
= new RegistrySetting<TimeSpan>(nameof(CacheDuration),
defaultValue: TimeSpan.FromSeconds(300),
scope: RegistrySettingScope.GlobalOnly,
deserializer: TimeSpanParseWithInfinite,
serializer: TimeSpanWithInfiniteToString
);
serializer: TimeSpanWithInfiniteToString,
description: "Defines how long (HH:MM:SS) the credentials cache will be valid if idle. Use 'Infinite' for no expiration");

public static RegistrySetting<string> PipedPrompt { get; }
= new RegistrySetting<string>(nameof(PipedPrompt),
defaultValue: DefaultAsciiPrompt,
deserializer: (s) => s
deserializer: (s) => s,
description: "Prompt to be used when gsudo uses piped mode."
);

public static RegistrySetting<string> Prompt { get; }
= new RegistrySetting<string>(nameof(Prompt),
defaultValue: GetPromptDefaultValue,
deserializer: (s) => s);
deserializer: (s) => s,
description: "Prompt to be used when gsudo uses standard mode."
);

public static RegistrySetting<LogLevel> LogLevel { get; }
= new RegistrySetting<LogLevel>(nameof(LogLevel),
defaultValue: gsudo.LogLevel.Info,
deserializer: ExtensionMethods.ParseEnum<LogLevel>);
deserializer: ExtensionMethods.ParseEnum<LogLevel>,
description: "Defines the verbosity of the log. (Valid values: All, Debug, Info, Warning, Error, None)"
);

public static RegistrySetting<bool> ForcePipedConsole { get; }
= new RegistrySetting<bool>(nameof(ForcePipedConsole),
defaultValue: false,
deserializer: bool.Parse);
deserializer: bool.Parse,
description: "Forces gsudo to use legacy piped mode. Not recommended."
);

public static RegistrySetting<bool> ForceAttachedConsole { get; }
= new RegistrySetting<bool>(nameof(ForceAttachedConsole),
defaultValue: false,
deserializer: bool.Parse);
deserializer: bool.Parse,
description: "Forces gsudo to use Attached mode. Can fix some very specific problems. Same as --attached"
);

public static RegistrySetting<bool> ForceVTConsole { get; }
= new RegistrySetting<bool>(nameof(ForceVTConsole),
defaultValue: false,
deserializer: bool.Parse);
deserializer: bool.Parse,
description: "Forces gsudo to use VT mode. Experimental. Same as --vt"
);

public static RegistrySetting<bool> CopyEnvironmentVariables { get; }
= new RegistrySetting<bool>(nameof(CopyEnvironmentVariables),
defaultValue: false,
deserializer: bool.Parse);
deserializer: bool.Parse,
description: "Only applies to Attached Mode. Forces copying caller's env variables to the elevated context. Same as --CopyEv"
);

public static RegistrySetting<bool> CopyNetworkShares { get; } =
new RegistrySetting<bool>(nameof(CopyNetworkShares),
defaultValue: false,
deserializer: bool.Parse);
deserializer: bool.Parse,
description: "Reconnect network shares on the elevated context. Same as --CopyNs"
);

public static RegistrySetting<bool> PowerShellLoadProfile { get; } =
new RegistrySetting<bool>(nameof(PowerShellLoadProfile),
defaultValue: false,
bool.Parse);
bool.Parse,
description: "Loads the PowerShell profile when elevating PowerShell commands. Same as --LoadProfile"
);

public static RegistrySetting<bool> SecurityEnforceUacIsolation { get; } =
new RegistrySetting<bool>(nameof(SecurityEnforceUacIsolation),
defaultValue: false,
deserializer: bool.Parse,
scope: RegistrySettingScope.GlobalOnly);
scope: RegistrySettingScope.GlobalOnly,
description: "Elevates but with the input handle closed. More secure, less convenient. To be implemented soon also as --disableInput"
);

public static RegistrySetting<string> ExceptionList { get; } =
new RegistrySetting<string>(nameof(ExceptionList),
defaultValue: "notepad.exe;powershell.exe;whoami.exe;vim.exe;nano.exe;",
deserializer: (string s)=>s,
scope: RegistrySettingScope.GlobalOnly);
scope: RegistrySettingScope.GlobalOnly,
description: "List of executables with some issues so they will be started with \"cmd /c executable\""
);

public static RegistrySetting<bool> NewWindow_Force { get; } =
new RegistrySetting<bool>(nameof(NewWindow_Force),
defaultValue: false,
deserializer: bool.Parse,
scope: RegistrySettingScope.Any);
scope: RegistrySettingScope.Any,
description: "Always elevate in new window. Same as --new");

public static RegistrySetting<CloseBehaviour> NewWindow_CloseBehaviour { get; } =
new RegistrySetting<CloseBehaviour>(nameof(NewWindow_CloseBehaviour),
defaultValue: CloseBehaviour.OsDefault,
deserializer: ExtensionMethods.ParseEnum<CloseBehaviour>,
scope: RegistrySettingScope.Any);
scope: RegistrySettingScope.Any,
description: "When elevating in new window, let the window auto-close (OsDefault), KeepShellOpen or PressKeyToClose"
);

public static RegistrySetting<bool> PathOverrideSetting = new PathPrecedenceSetting();

public static IDictionary<string, RegistrySetting> AllKeys =>
new Dictionary<string, RegistrySetting>(StringComparer.OrdinalIgnoreCase)
Expand All @@ -120,7 +147,8 @@ class Settings

PowerShellLoadProfile,
SecurityEnforceUacIsolation,
ExceptionList
ExceptionList,
PathOverrideSetting
);

internal static TimeSpan TimeSpanParseWithInfinite(string value)
Expand Down
16 changes: 14 additions & 2 deletions src/gsudo/Commands/ConfigCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,23 @@ public Task<int> Execute()

if (key == null)
{
// print all configs
// print all configs Descriptions
foreach (var k in Settings.AllKeys)
{
var scope = k.Value.HasGlobalValue() ? "(global)" :
Console.ForegroundColor = ConsoleColor.Yellow;
if (Settings.LogLevel <= LogLevel.Info)
{
Console.WriteLine($"# {k.Value.Name}: {k.Value.Description}");
}
Console.ResetColor();
}

// print all config values
foreach (var k in Settings.AllKeys)
{
var scope = k.Value.HasGlobalValue() ? "(global)" :
(k.Value.HasLocalValue() ? "(user)" : "(default)");

Console.WriteLine($"{k.Value.Name} = \"{ k.Value.GetStringValue().ToString()}\" ".PadRight(50) + scope);
}

Expand Down

0 comments on commit 59c76d6

Please sign in to comment.