-
-
Notifications
You must be signed in to change notification settings - Fork 21
Prevent secret leaks from environment table wrapping #3561
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
base: main
Are you sure you want to change the base?
Changes from all commits
41d8e53
1bfd9a1
dae57ee
ee3a050
f6a0d77
e2da7b4
5a1cc07
664638c
217ee09
2561a60
521873d
0a96590
94f2f9e
7d2df16
52b3e11
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,11 @@ | ||
| using System.Collections; | ||
| using Microsoft.Extensions.Logging; | ||
| using Microsoft.Extensions.Options; | ||
| using ModularPipelines.Constants; | ||
| using ModularPipelines.Enums; | ||
| using ModularPipelines.Helpers; | ||
| using ModularPipelines.Models; | ||
| using ModularPipelines.Options; | ||
| using Spectre.Console; | ||
|
|
||
| namespace ModularPipelines.Engine.Executors; | ||
|
|
@@ -18,8 +21,41 @@ internal class PipelineInitializer( | |
| IPipelineFileWriter pipelineFileWriter, | ||
| ILogger<PipelineInitializer> logger, | ||
| IConsoleWriter consoleWriter, | ||
| ISecretObfuscator secretObfuscator) : IPipelineInitializer | ||
| ISecretObfuscator secretObfuscator, | ||
| IOptions<SecretMaskingOptions> secretMaskingOptions) : IPipelineInitializer | ||
| { | ||
| // Two outer borders, one separator, and one space of padding on each side of both columns. | ||
| private const int TableDecorationWidth = 7; | ||
|
|
||
| private static readonly string[] SensitiveEnvironmentVariableNameParts = | ||
| [ | ||
| "TOKEN", | ||
| "SECRET", | ||
| "PASSWORD", | ||
| "PASSPHRASE", | ||
| "KEY", | ||
| "PWD", | ||
|
thomhurst marked this conversation as resolved.
|
||
| "CREDENTIAL", | ||
| "AUTH", | ||
|
thomhurst marked this conversation as resolved.
thomhurst marked this conversation as resolved.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When trace logging runs in an X11 desktop or SSH X-forwarding session, the standard Useful? React with 👍 / 👎. |
||
| "CONNECTION_STRING", | ||
| "CONNECTIONSTRING", | ||
| "CONNSTR", | ||
|
thomhurst marked this conversation as resolved.
|
||
| ]; | ||
|
thomhurst marked this conversation as resolved.
thomhurst marked this conversation as resolved.
|
||
|
|
||
| private static readonly string[] SensitiveEnvironmentVariableNames = | ||
|
thomhurst marked this conversation as resolved.
|
||
| [ | ||
| "AzureWebJobsStorage", | ||
| "ALL_PROXY", | ||
| "DATABASE_URL", | ||
|
thomhurst marked this conversation as resolved.
|
||
| "HTTP_PROXY", | ||
| "HTTPS_PROXY", | ||
| "MONGODB_URI", | ||
| "PIP_EXTRA_INDEX_URL", | ||
| "PIP_INDEX_URL", | ||
| "REDIS_URL", | ||
|
thomhurst marked this conversation as resolved.
|
||
| "VSS_NUGET_EXTERNAL_FEED_ENDPOINTS", | ||
| ]; | ||
|
thomhurst marked this conversation as resolved.
|
||
|
|
||
| private static readonly Action<ILogger, BuildSystem, string, Exception?> LogDetectedBuildSystem = | ||
| LoggerMessage.Define<BuildSystem, string>( | ||
| LogLevel.Information, | ||
|
|
@@ -43,6 +79,7 @@ internal class PipelineInitializer( | |
| private readonly ILogger<PipelineInitializer> _logger = logger; | ||
| private readonly IConsoleWriter _consoleWriter = consoleWriter; | ||
| private readonly ISecretObfuscator _secretObfuscator = secretObfuscator; | ||
| private readonly IOptions<SecretMaskingOptions> _secretMaskingOptions = secretMaskingOptions; | ||
| private OrganizedModules? _organizedModules; | ||
|
|
||
| public async Task<OrganizedModules> Initialize(CancellationToken cancellationToken = default) | ||
|
|
@@ -52,8 +89,26 @@ public async Task<OrganizedModules> Initialize(CancellationToken cancellationTok | |
|
|
||
| internal static Table CreateEnvironmentVariablesTable( | ||
| IDictionary variables, | ||
| Func<string, string> obfuscate) | ||
| Func<string, string> obfuscate, | ||
| string maskValue = LoggingConstants.SecretMask, | ||
| int? consoleWidth = null) | ||
| { | ||
| var effectiveMaskValue = string.IsNullOrWhiteSpace(maskValue) | ||
| ? LoggingConstants.SecretMask | ||
| : maskValue; | ||
| var entries = variables | ||
| .Cast<DictionaryEntry>() | ||
| .OrderBy(entry => entry.Key?.ToString(), StringComparer.OrdinalIgnoreCase) | ||
| .ToArray(); | ||
| var maximumNameWidth = Math.Max( | ||
| GetMaximumCellWidth("Name"), | ||
| entries | ||
| .Select(entry => GetMaximumCellWidth(entry.Key?.ToString() ?? string.Empty)) | ||
| .DefaultIfEmpty() | ||
| .Max()); | ||
| var maximumValueWidth = Math.Max( | ||
| 0, | ||
| (consoleWidth ?? AnsiConsole.Console.Profile.Width) - maximumNameWidth - TableDecorationWidth); | ||
|
thomhurst marked this conversation as resolved.
|
||
| var table = new Table | ||
| { | ||
| Border = TableBorder.Rounded, | ||
|
|
@@ -64,18 +119,66 @@ internal static Table CreateEnvironmentVariablesTable( | |
| table.AddColumn(new TableColumn("[bold]Name[/]").LeftAligned()); | ||
| table.AddColumn(new TableColumn("[bold]Value[/]").LeftAligned()); | ||
|
|
||
| foreach (var environmentVariable in variables | ||
| .Cast<DictionaryEntry>() | ||
| .OrderBy(entry => entry.Key?.ToString(), StringComparer.OrdinalIgnoreCase)) | ||
| foreach (var environmentVariable in entries) | ||
| { | ||
| var name = environmentVariable.Key?.ToString() ?? string.Empty; | ||
| var value = environmentVariable.Value?.ToString() ?? string.Empty; | ||
| var obfuscatedValue = obfuscate(value); | ||
| var displayValue = IsSensitiveEnvironmentVariableName(name) | ||
| || RequiresUnsafeRendering( | ||
| value, | ||
| obfuscatedValue, | ||
| effectiveMaskValue, | ||
| maximumValueWidth) | ||
| ? effectiveMaskValue | ||
| : MakeSingleLine(obfuscatedValue); | ||
|
|
||
| table.AddRow( | ||
| Markup.Escape(environmentVariable.Key?.ToString() ?? string.Empty), | ||
| Markup.Escape(obfuscate(environmentVariable.Value?.ToString() ?? string.Empty))); | ||
| new Text(name), | ||
| new Text(displayValue).Ellipsis()); | ||
| } | ||
|
|
||
| return table; | ||
| } | ||
|
|
||
| private static bool RequiresUnsafeRendering( | ||
| string value, | ||
| string obfuscatedValue, | ||
| string maskValue, | ||
| int maximumValueWidth) | ||
| { | ||
| if (!value.Equals(obfuscatedValue, StringComparison.Ordinal)) | ||
| { | ||
| return !obfuscatedValue.Equals(maskValue, StringComparison.Ordinal); | ||
| } | ||
|
|
||
| return GetMaximumCellWidth(value) > maximumValueWidth | ||
| || value.Any(char.IsControl); | ||
| } | ||
|
|
||
| private static int GetMaximumCellWidth(string value) | ||
| { | ||
| return value.Sum(character => char.IsAscii(character) ? 1 : 2); | ||
| } | ||
|
|
||
| private static bool IsSensitiveEnvironmentVariableName(string name) | ||
| { | ||
| return !name.Equals("PWD", StringComparison.OrdinalIgnoreCase) | ||
| && !name.Equals("OLDPWD", StringComparison.OrdinalIgnoreCase) | ||
| && !name.Equals("SSH_AUTH_SOCK", StringComparison.OrdinalIgnoreCase) | ||
| && !name.StartsWith("GIT_AUTHOR_", StringComparison.OrdinalIgnoreCase) | ||
| && (SensitiveEnvironmentVariableNames.Contains(name, StringComparer.OrdinalIgnoreCase) | ||
| || SensitiveEnvironmentVariableNameParts.Any( | ||
| part => name.Contains(part, StringComparison.OrdinalIgnoreCase))); | ||
| } | ||
|
|
||
| private static string MakeSingleLine(string value) | ||
| { | ||
| return value | ||
| .Replace("\r", "\\r", StringComparison.Ordinal) | ||
| .Replace("\n", "\\n", StringComparison.Ordinal); | ||
| } | ||
|
|
||
| private async Task<OrganizedModules> InitializeInternal(CancellationToken cancellationToken) | ||
| { | ||
| _consolePrinter.PrintLogo(); | ||
|
|
@@ -114,6 +217,7 @@ private void PrintEnvironmentVariables() | |
|
|
||
| _consoleWriter.Write(CreateEnvironmentVariablesTable( | ||
| Environment.GetEnvironmentVariables(), | ||
| value => _secretObfuscator.Obfuscate(value, null))); | ||
| value => _secretObfuscator.Obfuscate(value, null), | ||
| _secretMaskingOptions.Value.MaskValue)); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.