From c6a3232d1b3937baaa40c66e8b3e9c964a871101 Mon Sep 17 00:00:00 2001 From: Florian Heberl <49693964+floh96@users.noreply.github.com> Date: Thu, 23 May 2024 17:29:50 +0200 Subject: [PATCH 1/5] Microsoft sudo source code was published --- docs/docs/gsudo-vs-sudo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/gsudo-vs-sudo.md b/docs/docs/gsudo-vs-sudo.md index 6364c008..fab0d2e9 100644 --- a/docs/docs/gsudo-vs-sudo.md +++ b/docs/docs/gsudo-vs-sudo.md @@ -24,7 +24,7 @@ Surprisingly, Microsoft's sudo does not leverage new OS features to enhance secu | 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 From 4c6211983ac3f87c4e2fe710bdc237fd26f5fd8e Mon Sep 17 00:00:00 2001 From: Gerardo Grignoli Date: Fri, 24 May 2024 09:22:10 -0300 Subject: [PATCH 2/5] Update gsudo-vs-sudo.md From 0481effc0ebd2df7248a643531c5a6f87a6a13e5 Mon Sep 17 00:00:00 2001 From: Gerardo Grignoli Date: Fri, 24 May 2024 09:23:47 -0300 Subject: [PATCH 3/5] Update gsudo-vs-sudo.md --- docs/docs/gsudo-vs-sudo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/gsudo-vs-sudo.md b/docs/docs/gsudo-vs-sudo.md index fab0d2e9..15175169 100644 --- a/docs/docs/gsudo-vs-sudo.md +++ b/docs/docs/gsudo-vs-sudo.md @@ -21,7 +21,7 @@ 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) | [Yes](https://github.com/microsoft/sudo) | From 1f8474fc0844b29d2ad8ad49a4fdf6e79293210e Mon Sep 17 00:00:00 2001 From: Gerardo Grignoli Date: Sun, 26 May 2024 02:52:22 -0300 Subject: [PATCH 4/5] Added PathPrecedenceSetting.cs: - Defines a setting to reorder the PATH environment variable to prioritize gsudo's path Update gsudoModule.psm1: - Added 'PathPrecedence' to the 'gsudo config' autocomplete list --- src/gsudo.Wrappers/gsudoModule.psm1 | 2 +- .../AppSettings/PathPrecedenceSetting.cs | 55 +++++++++++++++++++ src/gsudo/AppSettings/Settings.cs | 5 +- 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 src/gsudo/AppSettings/PathPrecedenceSetting.cs diff --git a/src/gsudo.Wrappers/gsudoModule.psm1 b/src/gsudo.Wrappers/gsudoModule.psm1 index e12a79ec..6ec205c0 100644 --- a/src/gsudo.Wrappers/gsudoModule.psm1 +++ b/src/gsudo.Wrappers/gsudoModule.psm1 @@ -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'); diff --git a/src/gsudo/AppSettings/PathPrecedenceSetting.cs b/src/gsudo/AppSettings/PathPrecedenceSetting.cs new file mode 100644 index 00000000..d17b4f5b --- /dev/null +++ b/src/gsudo/AppSettings/PathPrecedenceSetting.cs @@ -0,0 +1,55 @@ +using gsudo.Helpers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace gsudo.AppSettings +{ + /// + /// 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. + /// + internal class PathPrecedenceSetting : RegistrySetting + { + public PathPrecedenceSetting(): + base("PathPrecedence", false, bool.Parse, RegistrySettingScope.GlobalOnly) + { + + } + + 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 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); + } + } +} diff --git a/src/gsudo/AppSettings/Settings.cs b/src/gsudo/AppSettings/Settings.cs index 0c622aff..b4f66c93 100644 --- a/src/gsudo/AppSettings/Settings.cs +++ b/src/gsudo/AppSettings/Settings.cs @@ -98,6 +98,8 @@ class Settings deserializer: ExtensionMethods.ParseEnum, scope: RegistrySettingScope.Any); + public static RegistrySetting PathOverrideSetting = new PathPrecedenceSetting(); + public static IDictionary AllKeys => new Dictionary(StringComparer.OrdinalIgnoreCase) .Add( @@ -120,7 +122,8 @@ class Settings PowerShellLoadProfile, SecurityEnforceUacIsolation, - ExceptionList + ExceptionList, + PathOverrideSetting ); internal static TimeSpan TimeSpanParseWithInfinite(string value) From 781d010e4fb90b0aabe1f69df1b1f48bce04c03a Mon Sep 17 00:00:00 2001 From: Gerardo Grignoli Date: Sun, 26 May 2024 04:21:54 -0300 Subject: [PATCH 5/5] Added descriptions to the config settings --- .../AppSettings/PathPrecedenceSetting.cs | 3 +- src/gsudo/AppSettings/RegistrySetting.cs | 12 ++-- src/gsudo/AppSettings/Settings.cs | 57 +++++++++++++------ src/gsudo/Commands/ConfigCommand.cs | 16 +++++- 4 files changed, 64 insertions(+), 24 deletions(-) diff --git a/src/gsudo/AppSettings/PathPrecedenceSetting.cs b/src/gsudo/AppSettings/PathPrecedenceSetting.cs index d17b4f5b..59f2f0d1 100644 --- a/src/gsudo/AppSettings/PathPrecedenceSetting.cs +++ b/src/gsudo/AppSettings/PathPrecedenceSetting.cs @@ -13,7 +13,8 @@ namespace gsudo.AppSettings internal class PathPrecedenceSetting : RegistrySetting { public PathPrecedenceSetting(): - base("PathPrecedence", false, bool.Parse, RegistrySettingScope.GlobalOnly) + base("PathPrecedence", false, bool.Parse, RegistrySettingScope.GlobalOnly, + description: "Prioritize gsudo over Microsoft Sudo in the PATH environment variable.") { } diff --git a/src/gsudo/AppSettings/RegistrySetting.cs b/src/gsudo/AppSettings/RegistrySetting.cs index b580e342..26af1d54 100644 --- a/src/gsudo/AppSettings/RegistrySetting.cs +++ b/src/gsudo/AppSettings/RegistrySetting.cs @@ -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(); @@ -37,19 +38,20 @@ class RegistrySetting : RegistrySetting private T runningValue; private bool hasValue = false; private readonly Func deserializer; - private readonly Func serializer; - - public RegistrySetting(string name, T defaultValue, Func deserializer, RegistrySettingScope scope = RegistrySettingScope.Any, Func serializer = null) - : this(name, () => defaultValue, deserializer, scope, serializer) + private readonly Func serializer; + + public RegistrySetting(string name, T defaultValue, Func deserializer, RegistrySettingScope scope = RegistrySettingScope.Any, Func serializer = null, string description = null) + : this(name, () => defaultValue, deserializer, scope, serializer, description) { } - public RegistrySetting(string name, Func defaultValue, Func deserializer, RegistrySettingScope scope = RegistrySettingScope.Any, Func serializer = null) + public RegistrySetting(string name, Func defaultValue, Func deserializer, RegistrySettingScope scope = RegistrySettingScope.Any, Func 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 diff --git a/src/gsudo/AppSettings/Settings.cs b/src/gsudo/AppSettings/Settings.cs index b4f66c93..e3be1cc5 100644 --- a/src/gsudo/AppSettings/Settings.cs +++ b/src/gsudo/AppSettings/Settings.cs @@ -18,85 +18,110 @@ class Settings public static RegistrySetting CacheMode { get; set; } = new RegistrySetting(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 CacheDuration { get; } = new RegistrySetting(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 PipedPrompt { get; } = new RegistrySetting(nameof(PipedPrompt), defaultValue: DefaultAsciiPrompt, - deserializer: (s) => s + deserializer: (s) => s, + description: "Prompt to be used when gsudo uses piped mode." ); public static RegistrySetting Prompt { get; } = new RegistrySetting(nameof(Prompt), defaultValue: GetPromptDefaultValue, - deserializer: (s) => s); + deserializer: (s) => s, + description: "Prompt to be used when gsudo uses standard mode." + ); public static RegistrySetting LogLevel { get; } = new RegistrySetting(nameof(LogLevel), defaultValue: gsudo.LogLevel.Info, - deserializer: ExtensionMethods.ParseEnum); + deserializer: ExtensionMethods.ParseEnum, + description: "Defines the verbosity of the log. (Valid values: All, Debug, Info, Warning, Error, None)" + ); public static RegistrySetting ForcePipedConsole { get; } = new RegistrySetting(nameof(ForcePipedConsole), defaultValue: false, - deserializer: bool.Parse); + deserializer: bool.Parse, + description: "Forces gsudo to use legacy piped mode. Not recommended." + ); public static RegistrySetting ForceAttachedConsole { get; } = new RegistrySetting(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 ForceVTConsole { get; } = new RegistrySetting(nameof(ForceVTConsole), defaultValue: false, - deserializer: bool.Parse); + deserializer: bool.Parse, + description: "Forces gsudo to use VT mode. Experimental. Same as --vt" + ); public static RegistrySetting CopyEnvironmentVariables { get; } = new RegistrySetting(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 CopyNetworkShares { get; } = new RegistrySetting(nameof(CopyNetworkShares), defaultValue: false, - deserializer: bool.Parse); + deserializer: bool.Parse, + description: "Reconnect network shares on the elevated context. Same as --CopyNs" + ); public static RegistrySetting PowerShellLoadProfile { get; } = new RegistrySetting(nameof(PowerShellLoadProfile), defaultValue: false, - bool.Parse); + bool.Parse, + description: "Loads the PowerShell profile when elevating PowerShell commands. Same as --LoadProfile" + ); public static RegistrySetting SecurityEnforceUacIsolation { get; } = new RegistrySetting(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 ExceptionList { get; } = new RegistrySetting(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 NewWindow_Force { get; } = new RegistrySetting(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 NewWindow_CloseBehaviour { get; } = new RegistrySetting(nameof(NewWindow_CloseBehaviour), defaultValue: CloseBehaviour.OsDefault, deserializer: ExtensionMethods.ParseEnum, - scope: RegistrySettingScope.Any); + scope: RegistrySettingScope.Any, + description: "When elevating in new window, let the window auto-close (OsDefault), KeepShellOpen or PressKeyToClose" + ); public static RegistrySetting PathOverrideSetting = new PathPrecedenceSetting(); diff --git a/src/gsudo/Commands/ConfigCommand.cs b/src/gsudo/Commands/ConfigCommand.cs index e154b22d..509ed3c4 100644 --- a/src/gsudo/Commands/ConfigCommand.cs +++ b/src/gsudo/Commands/ConfigCommand.cs @@ -32,11 +32,23 @@ public Task 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); }