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

Add Audit Logging To SecreteManager.exe #4183

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 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
15 changes: 8 additions & 7 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@
<PackageVersion Include="Microsoft.DotNet.SwaggerGenerator.MSBuild" Version="$(MicrosoftDotNetSwaggerGeneratorMSBuildVersion)" />
<PackageVersion Include="Microsoft.DotNet.VersionTools" Version="$(MicrosoftDotNetVersionToolsVersion)" />
<PackageVersion Include="Microsoft.DotNet.Web.Authentication" Version="$(MicrosoftDotNetWebAuthenticationVersion)" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recognize that these updates are safe because they all implement netstandard2.0, but I'm wondering what motivates the bump in this PR?

<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.ApplicationInsights" Version="2.21.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.0" />
<PackageVersion Include="Microsoft.Identity.Client" Version="4.62.0-preview" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.0.0" />
<PackageVersion Include="Microsoft.NET.Sdk.Functions" Version="4.1.1" />
Expand All @@ -81,6 +81,7 @@
<PackageVersion Include="NUnit" Version="4.2.2" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageVersion Include="Octokit" Version="13.0.1" />
<PackageVersion Include="OpenTelemetry.Audit.Geneva" Version="2.2.3" />
<PackageVersion Include="ServiceFabricMocks" Version="$(ServiceFabricMocksVersion)" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta1.21308.1" />
<PackageVersion Include="System.Data.SqlClient" Version="4.8.6" />
Expand Down
3 changes: 2 additions & 1 deletion NuGet.config
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="AzureGenevaMonitoring" value="https://msblox.pkgs.visualstudio.com/_packaging/AzureGenevaMonitoring/nuget/v3/index.json" />
<add key="dotnet-public" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What permissions are required to access this feed? Will there be issues with CI and individual users getting access?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is currently an issue with this feed and it blocked the PR build from completing. I have been discussing it with Matt Mitchell to see how it can be resolved. As you observed this feed requires external permissions that are not granted so the package restore is failing.

The basic issue is that the Audit team says we have to use the version of the package that comes from 'this' feed and not the public version of the package.

  • I need to investigate if the 'public' feed version of the package has the same class and methods defined. If so, we can do something where the 'internal feed' is different than the feed used in the 'public' processes.
  • I also have open questions to the Audit team to verify if the package from 'this' feed CAN be exposed publicly. If so we may be able to just manually move target versions into one of the feeds we manage directly.

Matt said these two options have been used in the past to deal with other packages that have similar issues.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good good, Matt should have good ideas here.

Just to say it explicitly, do not merge this PR until there's a good solution for public CI and for local dev.

<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
<add key="dotnet-tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;

Expand All @@ -7,9 +10,29 @@ public sealed class WrappedTokenProvider : ITokenCredentialProvider
{
private readonly TokenCredential _tokenCredential;

/// <inheritdoc/>
public string ApplicationId { get; internal set; }
/// <inheritdoc/>
public string TenantId { get; internal set; }

public WrappedTokenProvider(TokenCredential tokenCredential)
{
_tokenCredential = tokenCredential;
SetCredentialIdentityValues();
}

/// <inheritdoc/>
internal void SetCredentialIdentityValues()
{
// Get a token from the crendential provider
var tokenRequestContext = new TokenRequestContext(new[] { "https://management.azure.com/.default" });
var token = _tokenCredential.GetToken(tokenRequestContext, CancellationToken.None);

// Decode the JWT to get user identity information
var handler = new JwtSecurityTokenHandler();
var jsonToken = handler.ReadToken(token.Token) as JwtSecurityToken;
ApplicationId = jsonToken?.Claims?.FirstOrDefault(claim => claim.Type == "oid")?.Value ?? "Claim Oid Not Found";
TenantId = jsonToken?.Claims?.FirstOrDefault(claim => claim.Type == "tenant_id")?.Value ?? "Claim tenant_id Not Found";
}

public Task<TokenCredential> GetCredentialAsync() => Task.FromResult(_tokenCredential);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
using Microsoft.DncEng.CommandLineLib;
using Microsoft.DncEng.SecretManager.Commands;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.DncEng.CommandLineLib;

namespace Microsoft.DncEng.SecretManager;

[Command("info")]
class InfoCommand : Command
class InfoCommand : ProjectBaseCommand
{
private readonly IConsole _console;

public InfoCommand(IConsole console)
public InfoCommand(GlobalCommand globalCommand, IConsole console): base(globalCommand)
{
_console = console;
}

public override Task RunAsync(CancellationToken cancellationToken)
{
// Provides a curtisy warning message if the ServiceTreeId option is set to a empty guid
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Provides a curtisy warning message if the ServiceTreeId option is set to a empty guid
// Provides a courtesy warning message if the ServiceTreeId option is set to a empty guid

ValidateServiceTreeIdOption();

var exeName = Process.GetCurrentProcess().ProcessName;
var version = Assembly.GetEntryAssembly()
?.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@

using Microsoft.DncEng.CommandLineLib;
using Mono.Options;
using System;

namespace Microsoft.DncEng.SecretManager.Commands
{
/// <summary>
/// This class is used to extend the CommandLineLib.GlobalCommand class to add a global options spacific to this project
/// </summary>
public class ProjectBaseCommand : GlobalCommand
{

/// <summary>
/// Indictes if the global option for 'quiet' is set
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Indictes if the global option for 'quiet' is set
/// Indicates if the global option for 'quiet' is set

/// </summary>
public bool Quiet { get { return Verbosity == VerbosityLevel.Quiet; } }

/// <summary>
/// Check for local environment values to indicate you are running for Azure DevOps
/// SYSTEM_COLLECTIONURI is a default environment variable in Azure DevOps
/// </summary>
private bool RunningInAzureDevOps = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SYSTEM_COLLECTIONURI"));

/// <summary>
/// Provides the ServiceTreeId set with global options
/// The ID is a goid and is set to Guid.Empty if not set
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The ID is a goid and is set to Guid.Empty if not set
/// The ID is a guid and is set to Guid.Empty if not set

/// </summary>
public Guid ServiceTreeId = Guid.Empty;

/// <summary>
/// Base constructor for the ProjectBaseCommand class
/// </summary>
public ProjectBaseCommand(GlobalCommand globalCommand)
{
Verbosity = globalCommand.Verbosity;
Help = globalCommand.Help;
}

/// <summary>
/// Overides the GetOptions method from the base class to add a cusotm option for the ServiceTreeId
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Overides the GetOptions method from the base class to add a cusotm option for the ServiceTreeId
/// Overrides the GetOptions method from the base class to add a custom option for the ServiceTreeId

/// </summary>
public override OptionSet GetOptions()
{
return new OptionSet()
{
{"servicetreeid=", "The service tree ID (must be a valid GUID id from aka.ms/servicetree)", id =>
{
if (Guid.TryParse(id, out var guid))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since someone took the time to specify a service tree ID here, I would just error out if it's not a valid GUID.

{
ServiceTreeId = guid;
}
// If running in Azure DevOps use VSO tagging in the console output to the warning message will be handled by the Azure DevOps build system
else if (RunningInAzureDevOps)
{
WriteWarningMessage($"##vso[task.logissue type=warning]Failed to parse a valid Guid value from ServiceTreeId value '{id}'! Security Audit logging will be suppressed!");
}
// Else write a general warning messgae to console
else
{
WriteWarningMessage($"Failed to parse a valid Guid value from ServiceTreeId value '{id}'! Security Audit logging will be suppressed!");
}
}
}
};
}

/// <summary>
/// Provides a non-volitie warning message if the ServiceTreeId option is set to a empty guid value and argments have been parsed
internal void ValidateServiceTreeIdOption()
{
if (!Quiet && ServiceTreeId == Guid.Empty)
{
// If running in Azure DevOps use VSO tagging in the console output to the warning message will be handled by the Azure DevOps build system
if (RunningInAzureDevOps)
{
WriteWarningMessage("##vso[task.logissue type=warning]ServiceTreeId is set to an Empty Guid! Security Audit logging will be suppressed!");
}
// Else write a general warning messgae to console
else
{
WriteWarningMessage("ServiceTreeId is set to an Empty Guid! Security Audit logging will be suppressed!");
}
}
}

internal void WriteWarningMessage(string message)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(message);
Console.ResetColor();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
using ConsoleTables;
using Microsoft.DncEng.CommandLineLib;
using Microsoft.DncEng.SecretManager.StorageTypes;
using Microsoft.VisualStudio.Services.Common;
using Mono.Options;
using Command = Microsoft.DncEng.CommandLineLib.Command;

namespace Microsoft.DncEng.SecretManager.Commands;

[Command("synchronize")]
public class SynchronizeCommand : Command
public class SynchronizeCommand : ProjectBaseCommand
{
private readonly StorageLocationTypeRegistry _storageLocationTypeRegistry;
private readonly SecretTypeRegistry _secretTypeRegistry;
Expand All @@ -24,7 +25,7 @@ public class SynchronizeCommand : Command
private bool _verifyOnly = false;
private readonly List<string> _forcedSecrets = new();

public SynchronizeCommand(StorageLocationTypeRegistry storageLocationTypeRegistry, SecretTypeRegistry secretTypeRegistry, ISystemClock clock, IConsole console)
public SynchronizeCommand(GlobalCommand globalCommand, StorageLocationTypeRegistry storageLocationTypeRegistry, SecretTypeRegistry secretTypeRegistry, ISystemClock clock, IConsole console) : base(globalCommand)
{
_storageLocationTypeRegistry = storageLocationTypeRegistry;
_secretTypeRegistry = secretTypeRegistry;
Expand All @@ -42,19 +43,22 @@ public override List<string> HandlePositionalArguments(List<string> args)

public override OptionSet GetOptions()
{
return new OptionSet
return base.GetOptions().AddRange(new OptionSet()
{
{"f|force", "Force rotate all secrets", f => _force = !string.IsNullOrEmpty(f)},
{"force-secret=", "Force rotate the specified secret", _forcedSecrets.Add},
{"skip-untracked", "Skip untracked secrets", f => _skipUntracked = !string.IsNullOrEmpty(f)},
{"verify-only", "Does not rotate any secrets, instead, produces an error for every secret that needs to be rotated.", v => _verifyOnly = !string.IsNullOrEmpty(v)},
};
});
}

public override async Task RunAsync(CancellationToken cancellationToken)
{
try
{
// Provides a curtisy warning message if the ServiceTreeId option is set to a empty guid
ValidateServiceTreeIdOption();

Console.OutputEncoding = System.Text.Encoding.UTF8;
_console.WriteLine($"🔁 Synchronizing secrets contained in {_manifestFile}");
if (_force || _forcedSecrets.Any())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,25 @@
using Azure.Core;
using Microsoft.DncEng.CommandLineLib;

namespace Microsoft.DncEng.SecretManager;
namespace Microsoft.DncEng.SecretManager.Commands;

[Command("test")]
class TestCommand : Command
class TestCommand : ProjectBaseCommand
{
private readonly IConsole _console;
private readonly ITokenCredentialProvider _tokenProvider;

public TestCommand(IConsole console, ITokenCredentialProvider tokenProvider)
public TestCommand(GlobalCommand globalCommand, IConsole console, ITokenCredentialProvider tokenProvider) : base(globalCommand)
{
_console = console;
_tokenProvider = tokenProvider;
}

public override async Task RunAsync(CancellationToken cancellationToken)
{
// Provides a curtisy warning message if the ServiceTreeId option is set to a empty guid
ValidateServiceTreeIdOption();

var creds = await _tokenProvider.GetCredentialAsync();

var token = await creds.GetTokenAsync(new TokenRequestContext(new[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Threading.Tasks;
using Microsoft.DncEng.CommandLineLib;
using Microsoft.DncEng.SecretManager.StorageTypes;
using Microsoft.VisualStudio.Services.Common;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's bringing this in?

using Mono.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
Expand All @@ -14,15 +15,15 @@
namespace Microsoft.DncEng.SecretManager.Commands;

[Command("validate-all")]
public class ValidateAllCommand : Command
public class ValidateAllCommand : ProjectBaseCommand
{
private readonly IConsole _console;
private readonly SettingsFileValidator _settingsFileValidator;
private readonly StorageLocationTypeRegistry _storageLocationTypeRegistry;
private readonly List<string> _manifestFiles = new List<string>();
private string _basePath;

public ValidateAllCommand(IConsole console, SettingsFileValidator settingsFileValidator, StorageLocationTypeRegistry storageLocationTypeRegistry)
public ValidateAllCommand(GlobalCommand globalCommand, IConsole console, SettingsFileValidator settingsFileValidator, StorageLocationTypeRegistry storageLocationTypeRegistry) : base(globalCommand)
{
_console = console;
_settingsFileValidator = settingsFileValidator;
Expand All @@ -31,11 +32,11 @@ public ValidateAllCommand(IConsole console, SettingsFileValidator settingsFileVa

public override OptionSet GetOptions()
{
return new OptionSet
return base.GetOptions().AddRange(new OptionSet()
{
{"m|manifest-file=", "A secret manifest file. Can be specified more than once.", m => _manifestFiles.Add(m)},
{"b|base-path=", "The base path to search for settings files.", b => _basePath = b},
};
});
}

public override bool AreRequiredOptionsSet()
Expand All @@ -45,6 +46,9 @@ public override bool AreRequiredOptionsSet()

public override async Task RunAsync(CancellationToken cancellationToken)
{
// Provides a curtisy warning message if the ServiceTreeId option is set to a empty guid
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Provides a curtisy warning message if the ServiceTreeId option is set to a empty guid
// Provides a courtesy warning message if the ServiceTreeId option is set to an empty guid

ValidateServiceTreeIdOption();

bool haveErrors = false;
var manifestFiles = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (string manifestFile in _manifestFiles)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.DncEng.CommandLineLib;
using Microsoft.VisualStudio.Services.Common;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Necessary?

using Mono.Options;
using Command = Microsoft.DncEng.CommandLineLib.Command;

namespace Microsoft.DncEng.SecretManager.Commands;

[Command("validate")]
public class ValidateCommand : Command
public class ValidateCommand : ProjectBaseCommand
{
private readonly SettingsFileValidator _settingsFileValidator;
private string _manifestFile;
private string _baseSettingsFile;
private string _envSettingsFile;

public ValidateCommand(SettingsFileValidator settingsFileValidator)
public ValidateCommand(GlobalCommand globalCommand, SettingsFileValidator settingsFileValidator) : base(globalCommand)
{
_settingsFileValidator = settingsFileValidator;
}

public override OptionSet GetOptions()
{
return new OptionSet
return base.GetOptions().AddRange(new OptionSet()
{
{"m|manifest-file=", "The secret manifest file", f => _manifestFile = f},
{"e|env-settings-file=", "The environment settings file to validate", f => _envSettingsFile = f},
{"b|base-settings-file=", "The base settings file to validate", f => _baseSettingsFile = f},
};
});
}

public override bool AreRequiredOptionsSet()
Expand All @@ -38,6 +39,8 @@ public override bool AreRequiredOptionsSet()

public override async Task RunAsync(CancellationToken cancellationToken)
{
// Provides a curtisy warning message if the ServiceTreeId option is set to a empty guid
ValidateServiceTreeIdOption();
bool foundError = !await _settingsFileValidator.ValidateFileAsync(_envSettingsFile, _baseSettingsFile, _manifestFile, cancellationToken);

if (foundError)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,15 @@ namespace Microsoft.DncEng.SecretManager;

public interface ITokenCredentialProvider
{
/// <summary>
/// The applicatoin ID for the credential provider.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The applicatoin ID for the credential provider.
/// The application ID for the credential provider.

/// </summary>
public string ApplicationId { get; }

/// <summary>
/// The tenant ID that provided the token from the credential provider.
/// </summary>
public string TenantId { get; }

public Task<TokenCredential> GetCredentialAsync();
}
Loading
Loading