Skip to content

Commit

Permalink
Sanitize Docker environment vars in logs (#1163)
Browse files Browse the repository at this point in the history
  • Loading branch information
grvillic authored Jun 7, 2024
1 parent e1b4ada commit 341b036
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 4 deletions.
20 changes: 20 additions & 0 deletions src/Microsoft.ComponentDetection.Common/DockerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,23 @@ public async Task<bool> TryPullImageAsync(string image, CancellationToken cancel
}
}

internal void SanitizeEnvironmentVariables(ImageInspectResponse inspectResponse)
{
var envVariables = inspectResponse?.Config?.Env;
if (envVariables == null || !envVariables.Any())
{
return;
}

var sanitizedVarList = new List<string>();
foreach (var variable in inspectResponse.Config.Env)
{
sanitizedVarList.Add(variable.RemoveSensitiveInformation());
}

inspectResponse.Config.Env = sanitizedVarList;
}

public async Task<ContainerDetails> InspectImageAsync(string image, CancellationToken cancellationToken = default)
{
using var record = new DockerServiceInspectImageTelemetryRecord
Expand All @@ -119,6 +136,9 @@ public async Task<ContainerDetails> InspectImageAsync(string image, Cancellation
try
{
var imageInspectResponse = await Client.Images.InspectImageAsync(image, cancellationToken);

this.SanitizeEnvironmentVariables(imageInspectResponse);

record.ImageInspectResponse = JsonSerializer.Serialize(imageInspectResponse);

var baseImageRef = string.Empty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ namespace Microsoft.ComponentDetection.Common;

public static class StringUtilities
{
private static readonly Regex SensitiveInfoRegex = new Regex(@"(?<=https://)(.+)(?=@)", RegexOptions.Compiled, TimeSpan.FromSeconds(5));
private static readonly Regex SensitiveInfoRegex = new Regex(@"(?<=https://)(.+)(?=@)", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(5));
public const string SensitivePlaceholder = "******";

/// <summary>
/// Utility method to remove sensitive information from a string, currently focused on removing on the credentials placed within URL which can be part of CLI commands.
Expand All @@ -21,7 +22,7 @@ public static string RemoveSensitiveInformation(this string inputString)

try
{
return SensitiveInfoRegex.Replace(inputString, "******");
return SensitiveInfoRegex.Replace(inputString, SensitivePlaceholder);
}
catch (Exception)
{
Expand Down
139 changes: 139 additions & 0 deletions test/Microsoft.ComponentDetection.Common.Tests/DockerServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace Microsoft.ComponentDetection.Common.Tests;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Docker.DotNet.Models;
using FluentAssertions;
using Microsoft.ComponentDetection.TestsUtilities;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -80,4 +81,142 @@ public async Task DockerService_CanCreateAndRunImageAsync()
stdout.Should().StartWith("\nHello from Docker!");
stderr.Should().BeEmpty();
}

[TestMethod]
public void DockerService_SanitizeEnvironmentVariables()
{
var responseInput = new ImageInspectResponse
{
Config = new Config
{
Env = new List<string>
{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"MARATHON_APP_RESOURCE_CPU=1",
"REGION=local",
"PIP_INDEX_URL=https://user:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@someregistry.localhost.com",
},
},
};

var expected = new ImageInspectResponse
{
Config = new Config
{
Env = new List<string>
{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"MARATHON_APP_RESOURCE_CPU=1",
"REGION=local",
$"PIP_INDEX_URL=https://{StringUtilities.SensitivePlaceholder}@someregistry.localhost.com",
},
},
};

this.dockerService.SanitizeEnvironmentVariables(responseInput);
responseInput.Should().BeEquivalentTo(expected);

responseInput = new ImageInspectResponse
{
Config = new Config
{
Env = new List<string>
{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"MARATHON_APP_RESOURCE_CPU=1",
"REGION=local",
},
},
};

expected = new ImageInspectResponse
{
Config = new Config
{
Env = new List<string>
{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"MARATHON_APP_RESOURCE_CPU=1",
"REGION=local",
},
},
};

this.dockerService.SanitizeEnvironmentVariables(responseInput);
responseInput.Should().BeEquivalentTo(expected);

responseInput = new ImageInspectResponse
{
Config = new Config
{
Env = new List<string>
{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"MARATHON_APP_RESOURCE_CPU=1",
"REGION=local",
"PIP_INDEX_URL=https://someregistry.localhost.com",
},
},
};

expected = new ImageInspectResponse
{
Config = new Config
{
Env = new List<string>
{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"MARATHON_APP_RESOURCE_CPU=1",
"REGION=local",
"PIP_INDEX_URL=https://someregistry.localhost.com",
},
},
};

this.dockerService.SanitizeEnvironmentVariables(responseInput);
responseInput.Should().BeEquivalentTo(expected);
}

[TestMethod]
public void DockerService_SanitizeEnvironmentVariables_DoesNotThrow()
{
var responseInput = new ImageInspectResponse
{
Config = new Config
{
Env = null,
},
};

var action = () => this.dockerService.SanitizeEnvironmentVariables(responseInput);
action.Should().NotThrow();
responseInput.Should().BeEquivalentTo(responseInput);

responseInput = new ImageInspectResponse
{
Config = null,
};

action = () => this.dockerService.SanitizeEnvironmentVariables(responseInput);
action.Should().NotThrow();
responseInput.Should().BeEquivalentTo(responseInput);

responseInput = null;

action = () => this.dockerService.SanitizeEnvironmentVariables(responseInput);
action.Should().NotThrow();
responseInput.Should().BeNull();

responseInput = new ImageInspectResponse
{
Config = new Config
{
Env = new List<string>(),
},
};

action = () => this.dockerService.SanitizeEnvironmentVariables(responseInput);
action.Should().NotThrow();
responseInput.Should().BeEquivalentTo(responseInput);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<ItemGroup Label="Package References">
<PackageReference Include="coverlet.collector" PrivateAssets="all" />
<PackageReference Include="Docker.DotNet" />
<PackageReference Include="FluentAssertions.Analyzers" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ public class StringUtilitiesTests
[DataRow(null, null)]
[DataRow(" ", " ")]
[DataRow(" https:// ", " https:// ")]
[DataRow("https://username:password@domain.me", "https://******@domain.me")]
[DataRow("https://username:password@domain.me", $"https://{StringUtilities.SensitivePlaceholder}@domain.me")]
[DataRow("HTTPS://username:password@domain.me", $"HTTPS://{StringUtilities.SensitivePlaceholder}@domain.me")]
[DataRow("https://domain.me", "https://domain.me")]
[DataRow("https://@domain.me", "https://@domain.me")]
[DataRow(
"install -r requirements.txt --dry-run --ignore-installed --quiet --report file.zvn --index-url https://user:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@someregistry.localhost.com",
"install -r requirements.txt --dry-run --ignore-installed --quiet --report file.zvn --index-url https://******@someregistry.localhost.com")]
$"install -r requirements.txt --dry-run --ignore-installed --quiet --report file.zvn --index-url https://{StringUtilities.SensitivePlaceholder}@someregistry.localhost.com")]
public void RemoveSensitiveInformation_ReturnsAsExpected(string input, string expected)
{
var actual = StringUtilities.RemoveSensitiveInformation(input);
Expand Down

0 comments on commit 341b036

Please sign in to comment.