-
Notifications
You must be signed in to change notification settings - Fork 280
Cert rotator #4617
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
Merged
Merged
Cert rotator #4617
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
395deba
WIP
DrewScoggins e8034ad
Add certificate handling for auth
DrewScoggins b455366
Add cert handling for upload.py
DrewScoggins 9ab2ef1
Remove certdeploy
DrewScoggins 1d83716
Remove old constants
DrewScoggins c6ff45f
Add CertHelper code and build step
DrewScoggins 5917127
Move build step for CertHelper
DrewScoggins 75e815d
Address PR feedback
DrewScoggins 363f723
Remove islinux
DrewScoggins 61f0802
Add CertRotator to CertHelper
DrewScoggins ee2dd67
WIP
DrewScoggins 0e9d285
Fix improper list() usage
DrewScoggins f610ce8
Yaml testing changes
DrewScoggins 2654a1c
Make RunCommand verbose
DrewScoggins 3d77d4d
Testing
DrewScoggins d545d7d
Add feature to RunCommand to not echo stdout
DrewScoggins b846c3e
Remove pragma and fixup tests
DrewScoggins d91b815
Update moq version
DrewScoggins d35f87b
Remove testing changes
DrewScoggins 59fb1cb
Remove a few more testing changes
DrewScoggins 7d8f191
Remove last testing change
DrewScoggins 5cb322c
Add simple logging
DrewScoggins c735705
Address pr feedback
DrewScoggins 2ce7bbb
Address PR feedback
DrewScoggins File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Runtime.CompilerServices; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
[assembly: InternalsVisibleTo("CertHelperTests")] | ||
namespace CertHelper; | ||
internal class AssemblyInfo | ||
{ | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>$(PERFLAB_TARGET_FRAMEWORKS)</TargetFramework> | ||
<!-- Supported target frameworks --> | ||
<TargetFramework Condition="'$(TargetFramework)' == ''">net9.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Azure.Core" Version="1.44.1" /> | ||
<PackageReference Include="Azure.Identity" Version="1.11.4" /> | ||
<PackageReference Include="Azure.Security.KeyVault.Certificates" Version="4.7.0" /> | ||
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.7.0" /> | ||
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
| ||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 17 | ||
VisualStudioVersion = 17.12.35514.174 d17.12 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CertHelper", "CertHelper.csproj", "{165A37BD-2E9E-4D0A-8402-BB58C29A0BF4}" | ||
EndProject | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CertRotatorTests", "..\CertHelperTests\CertRotatorTests.csproj", "{AEA0F93B-EC9B-4438-991E-A80C0C82B3D1}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{165A37BD-2E9E-4D0A-8402-BB58C29A0BF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{165A37BD-2E9E-4D0A-8402-BB58C29A0BF4}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{165A37BD-2E9E-4D0A-8402-BB58C29A0BF4}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{165A37BD-2E9E-4D0A-8402-BB58C29A0BF4}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{AEA0F93B-EC9B-4438-991E-A80C0C82B3D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{AEA0F93B-EC9B-4438-991E-A80C0C82B3D1}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{AEA0F93B-EC9B-4438-991E-A80C0C82B3D1}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{AEA0F93B-EC9B-4438-991E-A80C0C82B3D1}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
EndGlobal |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace CertHelper; | ||
public class Constants | ||
{ | ||
public static readonly string Cert1Name = "LabCert1"; | ||
public static readonly string Cert2Name = "LabCert2"; | ||
public static readonly Uri Cert1Id = new Uri("https://test.vault.azure.net/certificates/LabCert1/07a7d98bf4884e5c40e690e02b96b3b4"); | ||
DrewScoggins marked this conversation as resolved.
Show resolved
Hide resolved
|
||
public static readonly Uri Cert2Id = new Uri("https://test.vault.azure.net/certificates/LabCert2/07a7d98bf4884e5c41e690e02b96b3b4"); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Security.Cryptography.X509Certificates; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace CertHelper; | ||
public interface IX509Store | ||
{ | ||
X509Certificate2Collection Certificates { get; } | ||
string? Name { get; } | ||
StoreLocation Location { get; } | ||
X509Store GetX509Store(); | ||
} | ||
|
||
public class TestableX509Store : IX509Store | ||
{ | ||
public X509Certificate2Collection Certificates { get => store.Certificates; } | ||
|
||
public string? Name => store.Name; | ||
|
||
public StoreLocation Location => store.Location; | ||
|
||
private X509Store store; | ||
public TestableX509Store(OpenFlags flags = OpenFlags.ReadOnly) | ||
{ | ||
store = new X509Store(StoreName.My, StoreLocation.CurrentUser, flags); | ||
} | ||
|
||
public X509Store GetX509Store() | ||
{ | ||
return store; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
using Azure; | ||
using Azure.Core; | ||
using Azure.Identity; | ||
using Azure.Security.KeyVault.Certificates; | ||
using Azure.Security.KeyVault.Secrets; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Runtime.CompilerServices; | ||
using System.Security.Cryptography.X509Certificates; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace CertHelper; | ||
|
||
public class KeyVaultCert | ||
{ | ||
private readonly string _keyVaultUrl = "https://dotnetperfkeyvault.vault.azure.net/"; | ||
private readonly string _tenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47"; | ||
private readonly string _clientId = "8c4b65ef-5a73-4d5a-a298-962d4a4ef7bc"; | ||
|
||
public X509Certificate2Collection KeyVaultCertificates { get; set; } | ||
public ILocalCert LocalCerts { get; set; } | ||
private TokenCredential _credential { get; set; } | ||
private CertificateClient _certClient { get; set; } | ||
private SecretClient _secretClient { get; set; } | ||
|
||
public KeyVaultCert(TokenCredential? cred = null, CertificateClient? certClient = null, SecretClient? secretClient = null, ILocalCert? localCerts = null) | ||
{ | ||
LocalCerts = localCerts ?? new LocalCert(); | ||
_credential = cred ?? GetCertifcateCredentialAsync(_tenantId, _clientId, LocalCerts.Certificates).Result; | ||
_certClient = certClient ?? new CertificateClient(new Uri(_keyVaultUrl), _credential); | ||
_secretClient = secretClient ?? new SecretClient(new Uri(_keyVaultUrl), _credential); | ||
KeyVaultCertificates = new X509Certificate2Collection(); | ||
} | ||
|
||
public async Task LoadKeyVaultCertsAsync() | ||
{ | ||
KeyVaultCertificates.Add(await FindCertificateInKeyVaultAsync(Constants.Cert1Name)); | ||
KeyVaultCertificates.Add(await FindCertificateInKeyVaultAsync(Constants.Cert2Name)); | ||
|
||
if (KeyVaultCertificates.Where(c => c == null).Count() > 0) | ||
{ | ||
throw new Exception("One or more certificates not found"); | ||
} | ||
} | ||
|
||
private async Task<ClientCertificateCredential> GetCertifcateCredentialAsync(string tenantId, string clientId, X509Certificate2Collection certCollection) | ||
{ | ||
ClientCertificateCredential? ccc = null; | ||
Exception? exception = null; | ||
foreach (var cert in certCollection) | ||
{ | ||
try | ||
{ | ||
ccc = new ClientCertificateCredential(tenantId, clientId, cert); | ||
await ccc.GetTokenAsync(new TokenRequestContext(new string[] { "https://vault.azure.net/.default" })); | ||
break; | ||
} | ||
catch (Exception ex) | ||
{ | ||
ccc = null; | ||
exception = ex; | ||
} | ||
} | ||
if(ccc == null) | ||
{ | ||
throw new Exception("Both certificates failed to authenticate", exception); | ||
} | ||
return ccc; | ||
} | ||
|
||
private async Task<X509Certificate2> FindCertificateInKeyVaultAsync(string certName) | ||
{ | ||
var keyVaultCert = await _certClient.GetCertificateAsync(certName); | ||
if(keyVaultCert.Value == null) | ||
{ | ||
throw new Exception("Certificate not found in Key Vault"); | ||
} | ||
var secret = await _secretClient.GetSecretAsync(keyVaultCert.Value.Name, keyVaultCert.Value.SecretId.Segments.Last()); | ||
if(secret.Value == null) | ||
{ | ||
throw new Exception("Certificate secret not found in Key Vault"); | ||
} | ||
var certBytes = Convert.FromBase64String(secret.Value.Value); | ||
#if NET9_0_OR_GREATER | ||
DrewScoggins marked this conversation as resolved.
Show resolved
Hide resolved
|
||
var cert = X509CertificateLoader.LoadPkcs12(certBytes, "", X509KeyStorageFlags.Exportable); | ||
#else | ||
var cert = new X509Certificate2(certBytes, "", X509KeyStorageFlags.Exportable); | ||
#endif | ||
return cert; | ||
} | ||
|
||
public bool ShouldRotateCerts() | ||
{ | ||
var keyVaultThumbprints = new HashSet<string>(); | ||
foreach (var cert in KeyVaultCertificates) | ||
{ | ||
keyVaultThumbprints.Add(cert.Thumbprint); | ||
} | ||
foreach(var cert in LocalCerts.Certificates) | ||
{ | ||
if (!keyVaultThumbprints.Contains(cert.Thumbprint)) | ||
{ | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.