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

WIP - Add k8s manifest secret masking #1369

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.IO;
using Calamari.Common.FeatureToggles;
using Calamari.Common.Plumbing.FileSystem;
using Calamari.Common.Plumbing.Logging;
using Calamari.Common.Plumbing.ServiceMessages;
using Calamari.Common.Plumbing.Variables;
using Calamari.Kubernetes;
using Calamari.Kubernetes.Commands;
using Calamari.Testing.Helpers;
using Calamari.Tests.Fixtures.Integration.FileSystem;
using Calamari.Tests.Helpers;
using FluentAssertions;
using NUnit.Framework;

namespace Calamari.Tests.KubernetesFixtures.Commands
{
[TestFixture]
public class ReportKubernetesManifestCommandFixture
{
[TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))]
[TestCase( OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)]
public void DoAThing(string enabledFeatureToggle)
{
// Arrange
var fs = new TestCalamariPhysicalFileSystem();
var log = new InMemoryLog();
var variables = new CalamariVariables();
variables.Set(KnownVariables.EnabledFeatureToggles, enabledFeatureToggle);

//write the text to a temporary file
var tempFileStream = fs.CreateTemporaryFile("yaml", out var filePath);
using (var writer = new StreamWriter(tempFileStream))
{
writer.Write(@"apiVersion: v1
kind: Secret
metadata:
name: dotfile-secret
namespace: ""my-cool-namespace""
data:
.secret-file: dmFsdWUtMg0KDQo=
another-secret: ""this-is-not-a-base64-value""");
}

var expectedYaml = @"apiVersion: v1
kind: Secret
metadata:
name: dotfile-secret
namespace: ""my-cool-namespace""
data:
.secret-file: <redacted-HTIOeIP7rD4Wa4OGFOrZDOgzs/Ns7RxxQUSMW5AM9zM=>
another-secret: ""<redacted-LjxWWuTodgQ0Z95zOPbBWpkk9icLpHtGBa9sm2Z/U4k=>""
".ReplaceLineEndings();

var command = CreateCommand(log,fs, variables);

// Act
var result = command.Execute(new []{ $"-path={filePath}"});

// Assert
var expected = ServiceMessage.Create(SpecialVariables.ServiceMessageNames.ManifestApplied.Name, ("ns", "my-cool-namespace"), ("manifest", expectedYaml));
log.ServiceMessages.Should().BeEquivalentTo(new List<ServiceMessage> { expected });

result.Should().Be(0);
}

ReportKubernetesManifestCommand CreateCommand(ILog log, ICalamariFileSystem fs, IVariables variables)
{
var manifestReporter = new ManifestReporter(variables, fs, log);
return new ReportKubernetesManifestCommand(log, fs, manifestReporter);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using System.IO;
using System.Linq;
using Calamari.Kubernetes;
using Calamari.Tests.Helpers;
using FluentAssertions;
using NUnit.Framework;
using YamlDotNet.RepresentationModel;

namespace Calamari.Tests.KubernetesFixtures
{
[TestFixture]
public class ManifestDataMaskerTests
{
[TestCaseSource(nameof(MaskSensitiveDataTestData))]
public void MaskSensitiveData_ShouldMaskExpectedTasks(string sourceYaml, string expectedYaml)
{
//Arrange
YamlMappingNode node;
using (var reader = new StringReader(sourceYaml))
{
var stream = new YamlStream();
stream.Load(reader);

var doc = stream.Documents.First();
node = (YamlMappingNode)doc.RootNode;
}

expectedYaml = expectedYaml.ReplaceLineEndings();

//Act
ManifestDataMasker.MaskSensitiveData(node);

//Assert
using (var writer = new StringWriter())
{
var writeStream = new YamlStream(new YamlDocument(node));
writeStream.Save(writer);

var outputYaml = writer.ToString()
//The yaml stream adds a document separator (...) to the end of the yaml (even for a single document), so strip it as we don't care for the test assertion
.TrimEnd('\r', '\n', '.')
.ReplaceLineEndings();

outputYaml.Should().Be(expectedYaml);
}
}


public static object[] MaskSensitiveDataTestData = {
new TestCaseData(@"apiVersion: v1
kind: Namespace
metadata:
name: my-cool-namespace",
@"apiVersion: v1
kind: Namespace
metadata:
name: my-cool-namespace"
)
.SetName("Non-secret manifest has no masking applied"),
new TestCaseData(@"apiVersion: v1
kind: ConfigMap
metadata:
name: game-demo
data:
player_initial_lives: ""3""
ui_properties_file_name: ""user-interface.properties""",
@"apiVersion: v1
kind: ConfigMap
metadata:
name: game-demo
data:
player_initial_lives: ""3""
ui_properties_file_name: ""user-interface.properties"""
)
.SetName("ConfigMap manifest has no masking applied"),
new TestCaseData(@"apiVersion: v1
kind: Secret
metadata:
name: dotfile-secret
data:
.secret-file: dmFsdWUtMg0KDQo=
another-secret: ""this-is-not-a-base64-value""",
@"apiVersion: v1
kind: Secret
metadata:
name: dotfile-secret
data:
.secret-file: <redacted-HTIOeIP7rD4Wa4OGFOrZDOgzs/Ns7RxxQUSMW5AM9zM=>
another-secret: ""<redacted-LjxWWuTodgQ0Z95zOPbBWpkk9icLpHtGBa9sm2Z/U4k=>"""
)
.SetName("Secret manifest has data values masked")
};
}
}
77 changes: 56 additions & 21 deletions source/Calamari.Tests/KubernetesFixtures/ManifestReporterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,22 @@ namespace Calamari.Tests.KubernetesFixtures
[TestFixture]
public class ManifestReporterTests
{
[Test]
public void GivenDisabledFeatureToggle_ShouldNotPostServiceMessage()
{
var memoryLog = new InMemoryLog();
var variables = new CalamariVariables();

var yaml = @"foo: bar";
using (CreateFile(yaml, out var filePath))
{
var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog);

mr.ReportManifestApplied(filePath);

memoryLog.ServiceMessages.Should().BeEmpty();
}
}
// [Test]
// public void GivenDisabledFeatureToggle_ShouldNotPostServiceMessage()
// {
// var memoryLog = new InMemoryLog();
// var variables = new CalamariVariables();
//
// var yaml = @"foo: bar";
// using (CreateFile(yaml, out var filePath))
// {
// var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog);
//
// mr.ReportManifestApplied(filePath, "default");
//
// memoryLog.ServiceMessages.Should().BeEmpty();
// }
// }

[TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))]
[TestCase( OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)]
Expand All @@ -55,7 +55,7 @@ public void GivenValidYaml_ShouldPostSingleServiceMessage(string enabledFeatureT
{
var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog);

mr.ReportManifestApplied(filePath);
mr.ReportManifestApplied(filePath, "default");

var expected = ServiceMessage.Create(SpecialVariables.ServiceMessageNames.ManifestApplied.Name, ("ns", "default"), ("manifest", yaml));
memoryLog.ServiceMessages.Should().BeEquivalentTo(new List<ServiceMessage> { expected });
Expand All @@ -75,7 +75,7 @@ public void GivenInValidManifest_ShouldNotPostServiceMessage(string enabledFeatu
{
var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog);

mr.ReportManifestApplied(filePath);
mr.ReportManifestApplied(filePath, "default");

memoryLog.ServiceMessages.Should().BeEmpty();
}
Expand All @@ -97,7 +97,7 @@ public void GivenNamespaceInManifest_ShouldReportManifestNamespace(string enable
variables.Set(SpecialVariables.Namespace, variableNs);
var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog);

mr.ReportManifestApplied(filePath);
mr.ReportManifestApplied(filePath, "default");

memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair<string, string>("ns", "XXX"));
}
Expand All @@ -117,7 +117,7 @@ public void GivenNamespaceNotInManifest_ShouldReportVariableNamespace(string ena
variables.Set(SpecialVariables.Namespace, variableNs);
var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog);

mr.ReportManifestApplied(filePath);
mr.ReportManifestApplied(filePath, variableNs);

memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair<string, string>("ns", variableNs));
}
Expand All @@ -135,12 +135,47 @@ public void GiveNoNamespaces_ShouldDefaultNamespace(string enabledFeatureToggle)
{
var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog);

mr.ReportManifestApplied(filePath);
mr.ReportManifestApplied(filePath, "default");

memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair<string, string>("ns", "default"));
}
}

[TestCase(nameof(FeatureToggle.KubernetesLiveObjectStatusFeatureToggle))]
[TestCase( OctopusFeatureToggles.KnownSlugs.KubernetesObjectManifestInspection)]
public void SecretManifest_ShouldRedactDataValues(string enabledFeatureToggle)
{
var memoryLog = new InMemoryLog();
var variables = new CalamariVariables();
variables.Set(KnownVariables.EnabledFeatureToggles, enabledFeatureToggle);

const string yaml = @"apiVersion: v1
kind: Secret
metadata:
name: dotfile-secret
data:
.secret-file: dmFsdWUtMg0KDQo=
another-secret: ""this-is-not-a-base64-value""";
var expectedYaml = @"apiVersion: v1
kind: Secret
metadata:
name: dotfile-secret
data:
.secret-file: <redacted-HTIOeIP7rD4Wa4OGFOrZDOgzs/Ns7RxxQUSMW5AM9zM=>
another-secret: ""<redacted-LjxWWuTodgQ0Z95zOPbBWpkk9icLpHtGBa9sm2Z/U4k=>""
".ReplaceLineEndings();

using (CreateFile(yaml, out var filePath))
{
var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog);

mr.ReportManifestApplied(filePath, "default");

var expected = ServiceMessage.Create(SpecialVariables.ServiceMessageNames.ManifestApplied.Name, ("ns", "default"), ("manifest", expectedYaml));
memoryLog.ServiceMessages.Should().BeEquivalentTo(new List<ServiceMessage> { expected });
}
}

static IDisposable CreateFile(string yaml, out string filePath)
{
var tempDir = TemporaryDirectory.Create();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
using System.Linq;
using System.Threading.Tasks;
using Calamari.Common.Commands;
using Calamari.Common.FeatureToggles;
using Calamari.Common.Plumbing.FileSystem;
using Calamari.Common.Plumbing.Logging;
using Calamari.Common.Plumbing.Variables;
using Calamari.Kubernetes.Integration;
using Calamari.Kubernetes.ResourceStatus.Resources;
using Octopus.CoreUtilities.Extensions;
Expand Down Expand Up @@ -51,14 +53,14 @@ protected override async Task<IEnumerable<ResourceIdentifier>> ApplyAndGetResour
for (int i = 0; i < globDirectories.Count(); i++)
{
var directory = globDirectories[i];
log.Info($"Applying Batch #{i+1} for YAML matching '{directory.Glob}'");
log.Info($"Applying Batch #{i + 1} for YAML matching '{directory.Glob}'");
var res = ApplyBatchAndReturnResourceIdentifiers(deployment, directory).ToArray();

if (appliedResourcesCallback != null)
{
await appliedResourcesCallback(res);
}

resourcesIdentifiers.UnionWith(res);
}

Expand All @@ -73,24 +75,30 @@ IEnumerable<ResourceIdentifier> ApplyBatchAndReturnResourceIdentifiers(RunningDe
log.Warn($"No files found matching '{globDirectory.Glob}'");
return Array.Empty<ResourceIdentifier>();
}

ReportEachManifestBeingApplied(globDirectory, files);

string[] executeArgs = {"apply", "-f", $@"""{globDirectory.Directory}""", "--recursive", "-o", "json"};
ReportEachManifestBeingApplied(deployment.Variables, globDirectory, files);

string[] executeArgs = { "apply", "-f", $@"""{globDirectory.Directory}""", "--recursive", "-o", "json" };
executeArgs = executeArgs.AddOptionsForServerSideApply(deployment.Variables, log);
var result = kubectl.ExecuteCommandAndReturnOutput(executeArgs);

return ProcessKubectlCommandOutput(deployment, result, globDirectory.Directory);
}

void ReportEachManifestBeingApplied(GlobDirectory globDirectory, string[] files)
void ReportEachManifestBeingApplied(IVariables variables, GlobDirectory globDirectory, string[] files)
{
if (!FeatureToggle.KubernetesLiveObjectStatusFeatureToggle.IsEnabled(variables) && !OctopusFeatureToggles.KubernetesObjectManifestInspectionFeatureToggle.IsEnabled(variables))
return;

var directoryWithTrailingSlash = globDirectory.Directory + Path.DirectorySeparatorChar;
foreach (var file in files)
{
var fullFilePath = fileSystem.GetRelativePath(directoryWithTrailingSlash, file);
log.Verbose($"Matched file: {fullFilePath}");
manifestReporter.ReportManifestApplied(file);

var implicitNamespace = variables.Get(SpecialVariables.Namespace) ?? "default";

manifestReporter.ReportManifestApplied(file, implicitNamespace);
}
}
}
Expand Down
Loading