Skip to content

Commit

Permalink
Moved the Azure auth options to a separate class and share this betwe…
Browse files Browse the repository at this point in the history
…en AzureKeyVaultCommand and TrustedSigningCommand.
  • Loading branch information
dlemstra committed Jun 15, 2024
1 parent d33da27 commit 39b9d20
Show file tree
Hide file tree
Showing 53 changed files with 543 additions and 973 deletions.
57 changes: 57 additions & 0 deletions src/Sign.Cli/AzureAuthOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE.txt file in the project root for more information.

using System.CommandLine;
using System.CommandLine.Invocation;
using Azure.Core;
using Azure.Identity;
using Sign.Core;

namespace Sign.Cli
{
internal sealed class AzureAuthOptions
{
internal Option<bool> ManagedIdentityOption { get; } = new(["-azm", "--azure-managed-identity"], getDefaultValue: () => false, Resources.ManagedIdentityOptionDescription);
internal Option<string?> TenantIdOption { get; } = new(["-azt", "--azure-tenant-id"], Resources.TenantIdOptionDescription);
internal Option<string?> ClientIdOption { get; } = new(["-azi", "--azure-client-id"], Resources.ClientIdOptionDescription);
internal Option<string?> ClientSecretOption { get; } = new(["-azs", "--azure-client-secret"], Resources.ClientSecretOptionDescription);

internal void AddOptionsToCommand(Command command)
{
command.AddOption(ManagedIdentityOption);
command.AddOption(TenantIdOption);
command.AddOption(ClientIdOption);
command.AddOption(ClientSecretOption);
}

internal TokenCredential? CreateTokenCredential(InvocationContext context)
{
bool useManagedIdentity = context.ParseResult.GetValueForOption(ManagedIdentityOption);

if (useManagedIdentity)
{
return new DefaultAzureCredential();
}

string? tenantId = context.ParseResult.GetValueForOption(TenantIdOption);
string? clientId = context.ParseResult.GetValueForOption(ClientIdOption);
string? secret = context.ParseResult.GetValueForOption(ClientSecretOption);

if (string.IsNullOrEmpty(tenantId) ||
string.IsNullOrEmpty(clientId) ||
string.IsNullOrEmpty(secret))
{
context.Console.Error.WriteFormattedLine(
Resources.InvalidClientSecretCredential,
TenantIdOption,
ClientIdOption,
ClientSecretOption);
context.ExitCode = ExitCode.NoInputsFound;
return null;
}

return new ClientSecretCredential(tenantId, clientId, secret);
}
}
}
55 changes: 10 additions & 45 deletions src/Sign.Cli/AzureKeyVaultCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,16 @@
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using Azure.Core;
using Azure.Identity;
using Sign.Core;
using Sign.SignatureProviders.KeyVault;

namespace Sign.Cli
{
internal sealed class AzureKeyVaultCommand : Command
{
private readonly CodeCommand _codeCommand;

internal Option<string> CertificateOption { get; } = new(["-kvc", "--azure-key-vault-certificate"], AzureKeyVaultResources.CertificateOptionDescription);
internal Option<string?> ClientIdOption { get; } = new(["-kvi", "--azure-key-vault-client-id"], AzureKeyVaultResources.ClientIdOptionDescription);
internal Option<string?> ClientSecretOption { get; } = new(["-kvs", "--azure-key-vault-client-secret"], AzureKeyVaultResources.ClientSecretOptionDescription);
internal Option<bool> ManagedIdentityOption { get; } = new(["-kvm", "--azure-key-vault-managed-identity"], getDefaultValue: () => false, AzureKeyVaultResources.ManagedIdentityOptionDescription);
internal Option<string?> TenantIdOption { get; } = new(["-kvt", "--azure-key-vault-tenant-id"], AzureKeyVaultResources.TenantIdOptionDescription);
internal Option<Uri> UrlOption { get; } = new(["-kvu", "--azure-key-vault-url"], AzureKeyVaultResources.UrlOptionDescription);
internal Option<string> CertificateOption { get; } = new(["-kvc", "--azure-key-vault-certificate"], AzureKeyVaultResources.CertificateOptionDescription);
internal AzureAuthOptions AzureAuthOptions { get; } = new();

internal Argument<string?> FileArgument { get; } = new("file(s)", Resources.FilesArgumentDescription);

Expand All @@ -31,19 +25,12 @@ internal AzureKeyVaultCommand(CodeCommand codeCommand, IServiceProviderFactory s
ArgumentNullException.ThrowIfNull(codeCommand, nameof(codeCommand));
ArgumentNullException.ThrowIfNull(serviceProviderFactory, nameof(serviceProviderFactory));

_codeCommand = codeCommand;

CertificateOption.IsRequired = true;
UrlOption.IsRequired = true;

ManagedIdentityOption.SetDefaultValue(false);

AddOption(UrlOption);
AddOption(TenantIdOption);
AddOption(ClientIdOption);
AddOption(ClientSecretOption);
AddOption(CertificateOption);
AddOption(ManagedIdentityOption);
AzureAuthOptions.AddOptionsToCommand(this);

AddArgument(FileArgument);

Expand All @@ -67,41 +54,19 @@ internal AzureKeyVaultCommand(CodeCommand codeCommand, IServiceProviderFactory s
return;
}

TokenCredential? credential = AzureAuthOptions.CreateTokenCredential(context);
if (credential is null)
{
return;
}

// Some of the options are required and that is why we can safely use
// the null-forgiving operator (!) to simplify the code.
Uri url = context.ParseResult.GetValueForOption(UrlOption)!;
string? tenantId = context.ParseResult.GetValueForOption(TenantIdOption);
string? clientId = context.ParseResult.GetValueForOption(ClientIdOption);
string? secret = context.ParseResult.GetValueForOption(ClientSecretOption);
string certificateId = context.ParseResult.GetValueForOption(CertificateOption)!;
bool useManagedIdentity = context.ParseResult.GetValueForOption(ManagedIdentityOption);

TokenCredential? credential = null;

if (useManagedIdentity)
{
credential = new DefaultAzureCredential();
}
else
{
if (string.IsNullOrEmpty(tenantId) ||
string.IsNullOrEmpty(clientId) ||
string.IsNullOrEmpty(secret))
{
context.Console.Error.WriteFormattedLine(
AzureKeyVaultResources.InvalidClientSecretCredential,
TenantIdOption,
ClientIdOption,
ClientSecretOption);
context.ExitCode = ExitCode.NoInputsFound;
return;
}

credential = new ClientSecretCredential(tenantId, clientId, secret);
}

KeyVaultServiceProvider keyVaultServiceProvider = new(credential, url, certificateId);
await _codeCommand.HandleAsync(context, serviceProviderFactory, keyVaultServiceProvider, fileArgument);
await codeCommand.HandleAsync(context, serviceProviderFactory, keyVaultServiceProvider, fileArgument);
});
}
}
Expand Down
45 changes: 0 additions & 45 deletions src/Sign.Cli/AzureKeyVaultResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 0 additions & 16 deletions src/Sign.Cli/AzureKeyVaultResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -123,25 +123,9 @@
<data name="ClickOnceExtensionNotSupported" xml:space="preserve">
<value>ClickOnce signing via the legacy .clickonce ZIP workaround is no longer supported. See documentation.</value>
</data>
<data name="ClientIdOptionDescription" xml:space="preserve">
<value>Client ID to authenticate to Azure Key Vault.</value>
</data>
<data name="ClientSecretOptionDescription" xml:space="preserve">
<value>Client secret to authenticate to Azure Key Vault.</value>
</data>
<data name="CommandDescription" xml:space="preserve">
<value>Use Azure Key Vault.</value>
</data>
<data name="InvalidClientSecretCredential" xml:space="preserve">
<value>If not using a managed identity, all of these options are required: {0}, {1}, and {2}.</value>
<comment>{NumberedPlaceholder="{0}", "{1}", "{2}"} are option names and should not be localized.</comment>
</data>
<data name="ManagedIdentityOptionDescription" xml:space="preserve">
<value>Managed identity to authenticate to Azure Key Vault.</value>
</data>
<data name="TenantIdOptionDescription" xml:space="preserve">
<value>Tenant ID to authenticate to Azure Key Vault.</value>
</data>
<data name="UrlOptionDescription" xml:space="preserve">
<value>URL to an Azure Key Vault.</value>
</data>
Expand Down
45 changes: 45 additions & 0 deletions src/Sign.Cli/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions src/Sign.Cli/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@
<data name="CertificateStoreCommandDescription" xml:space="preserve">
<value>Use Windows Certificate Store or a local certificate file.</value>
</data>
<data name="ClientIdOptionDescription" xml:space="preserve">
<value>Client ID to authenticate to Azure Key Vault.</value>
</data>
<data name="ClientSecretOptionDescription" xml:space="preserve">
<value>Client secret to authenticate to Azure Key Vault.</value>
</data>
<data name="CodeCommandDescription" xml:space="preserve">
<value>Sign binaries and containers.</value>
</data>
Expand Down Expand Up @@ -157,6 +163,10 @@
<value>Invalid value for {0}. The value must be the certificate's fingerprint (in hexadecimal).</value>
<comment>{NumberedPlaceholder="{0}"} is the option name (e.g.: --certificate-fingerprint) and should not be localized.</comment>
</data>
<data name="InvalidClientSecretCredential" xml:space="preserve">
<value>If not using a managed identity, all of these options are required: {0}, {1}, and {2}.</value>
<comment>{NumberedPlaceholder="{0}", "{1}", "{2}"} are option names and should not be localized.</comment>
</data>
<data name="InvalidDigestValue" xml:space="preserve">
<value>Invalid value for {0}. The value must be 'sha256', 'sha384', or 'sha512'.</value>
<comment>{Locked="sha256", "sha384", "sha512"} are cryptographic hash algorithm names and should not be localized. {NumberedPlaceholder="{0}"} is an option name (e.g.: --file-digest) and should not be localized.</comment>
Expand All @@ -172,6 +182,9 @@
<value>Invalid value for {0}. The value must be an absolute HTTP or HTTPS URL.</value>
<comment>{NumberedPlaceholder="{0}"} is an option name (e.g.: --timestamp-url) and should not be localized.</comment>
</data>
<data name="ManagedIdentityOptionDescription" xml:space="preserve">
<value>Managed identity to authenticate to Azure Key Vault.</value>
</data>
<data name="MaxConcurrencyOptionDescription" xml:space="preserve">
<value>Maximum concurrency.</value>
</data>
Expand All @@ -195,6 +208,9 @@
<value>Some files do not exist. Try using a different {0} value or a fully qualified file path.</value>
<comment>{NumberedPlaceholder="{0}"} is an option name (e.g.: --base-directory) and should not be localized.</comment>
</data>
<data name="TenantIdOptionDescription" xml:space="preserve">
<value>Tenant ID to authenticate to Azure Key Vault.</value>
</data>
<data name="TimestampDigestOptionDescription" xml:space="preserve">
<value>Digest algorithm for the RFC 3161 timestamp server. Allowed values are sha256, sha384, and sha512.</value>
<comment>{Locked="RFC 3161"} is an Internet standard (https://www.rfc-editor.org/info/rfc3161), and {Locked="sha256", "sha384", "sha512"} are cryptographic hash algorithm names should not be localized.</comment>
Expand Down
Loading

0 comments on commit 39b9d20

Please sign in to comment.