From 08cf2a875100fdafa2be90812c7bb0db460b17d1 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 12 Sep 2024 16:51:10 -0700 Subject: [PATCH] Set `SendCertificateChain` option in KeyVaultReader to enable SN+I authentication (#10179) * Set `SendCertificateChain` option in KeyVaultReader to enable SN+I authentication * Add unit test for Sendx5c * Add tests * Fix test * Add a mock SecretClient and internal constructor --- src/NuGet.Services.KeyVault/KeyVaultReader.cs | 30 +++++++++++++- .../Properties/AssemblyInfo.cs | 10 +++++ .../KeyVaultReaderFacts.cs | 40 +++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 src/NuGet.Services.KeyVault/Properties/AssemblyInfo.cs create mode 100644 tests/NuGet.Services.KeyVault.Tests/KeyVaultReaderFacts.cs diff --git a/src/NuGet.Services.KeyVault/KeyVaultReader.cs b/src/NuGet.Services.KeyVault/KeyVaultReader.cs index 5214f8c6f8..f6302bc190 100644 --- a/src/NuGet.Services.KeyVault/KeyVaultReader.cs +++ b/src/NuGet.Services.KeyVault/KeyVaultReader.cs @@ -13,14 +13,23 @@ namespace NuGet.Services.KeyVault { /// /// Reads secretes from KeyVault. - /// Authentication with KeyVault is done using either a managed identity or a certificate in location:LocalMachine store name:My + /// Authentication with KeyVault is done using either a managed identity or a certificate in location:LocalMachine store name:My /// public class KeyVaultReader : ISecretReader { private readonly KeyVaultConfiguration _configuration; private readonly Lazy _keyVaultClient; - protected SecretClient KeyVaultClient => _keyVaultClient.Value; + internal bool _testMode; + internal bool _isUsingSendx5c; + + internal KeyVaultReader(SecretClient secretClient, KeyVaultConfiguration configuration, bool testMode = false) + { + _configuration = configuration; + _keyVaultClient = new Lazy(() => secretClient); + _testMode = testMode; + InitializeClient(); + } public KeyVaultReader(KeyVaultConfiguration configuration) { @@ -97,10 +106,27 @@ private SecretClient InitializeClient() credential = new ManagedIdentityCredential(_configuration.ClientId); } } + else if (_configuration.SendX5c) + { + var clientCredentialOptions = new ClientCertificateCredentialOptions + { + SendCertificateChain = true + }; + + credential = new ClientCertificateCredential(_configuration.TenantId, _configuration.ClientId, _configuration.Certificate, clientCredentialOptions); + + // If we are in unit testing mode, we dont actually create a SecretClient + if (_testMode) + { + _isUsingSendx5c = true; + return _keyVaultClient.Value; + } + } else { credential = new ClientCertificateCredential(_configuration.TenantId, _configuration.ClientId, _configuration.Certificate); } + return new SecretClient(GetKeyVaultUri(_configuration), credential); } diff --git a/src/NuGet.Services.KeyVault/Properties/AssemblyInfo.cs b/src/NuGet.Services.KeyVault/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..cb1d0059e8 --- /dev/null +++ b/src/NuGet.Services.KeyVault/Properties/AssemblyInfo.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; + +#if SIGNED_BUILD +[assembly: InternalsVisibleTo("NuGet.Services.KeyVault.Tests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +#else +[assembly: InternalsVisibleTo("NuGet.Services.KeyVault.Tests")] +#endif \ No newline at end of file diff --git a/tests/NuGet.Services.KeyVault.Tests/KeyVaultReaderFacts.cs b/tests/NuGet.Services.KeyVault.Tests/KeyVaultReaderFacts.cs new file mode 100644 index 0000000000..ff4398555a --- /dev/null +++ b/tests/NuGet.Services.KeyVault.Tests/KeyVaultReaderFacts.cs @@ -0,0 +1,40 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using Azure.Security.KeyVault.Secrets; +using Moq; +using Xunit; + +namespace NuGet.Services.KeyVault.Tests +{ + public class KeyVaultReaderFacts + { + [Fact] + public void VerifyKeyvaultReaderSendX5c() + { + // Arrange + const string vaultName = "vaultName"; + const string tenantId = "tenantId"; + const string clientId = "clientId"; + + X509Certificate2 certificate = new X509Certificate2(); + KeyVaultConfiguration keyVaultConfiguration = new KeyVaultConfiguration(vaultName, tenantId, clientId, certificate, sendX5c:true); + + var mockSecretClient = new Mock(); + + // Act + var keyvaultReader = new KeyVaultReader(mockSecretClient.Object, keyVaultConfiguration, testMode: true); + + // Assert + + // The KeyVaultReader constructor is internal which accepts a SecretClient object, KeyVaultConfiguration object and a boolean testMode parameter + // The KeyVaultConfiguration object has the sendX5c property which is set to true + // The KeyVaultReader object has an internal boolean _isUsingSendx5c which is set to true if the sendX5c property is set to true + // The KeyVaultReader shot-circuits when the testMode is set to true instead of calling Azure KeyVault + Assert.True(keyvaultReader._isUsingSendx5c); + } + } +} \ No newline at end of file